FN-8499: fix macOS embedded PostgreSQL ICU loader links
Ensure embedded PostgreSQL repairs its macOS ICU ABI loader link before startup. - Normalize the libicuuc.68 dylib compatibility symlink from packaged patch versions. - Cover missing, dangling, and valid ICU compatibility links in lifecycle tests. - Add a patch changeset for the macOS startup fix. Files changed: .changeset/fn-8499-macos-postgres-libicu.md | 7 +++++ .../__tests__/postgres/embedded-lifecycle.test.ts | 32 ++++++++++++++++++---- packages/core/src/postgres/embedded-lifecycle.ts | 17 +++++++++--- 3 files changed, 47 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-8499 Fusion-Task-Lineage: 1ca453f4-f619-452c-9112-f32ff8fb8128 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8499-macos-postgres-libicu.md
Normal file
7
.changeset/fn-8499-macos-postgres-libicu.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix macOS embedded PostgreSQL startup when bundled ICU compatibility links are missing.
|
||||
category: fix
|
||||
dev: Repair the libicuuc loader-name symlink before initdb starts.
|
||||
@@ -490,12 +490,16 @@ describe("embedded-lifecycle: macOS dylib compatibility links", () => {
|
||||
writeFileSync(join(libDir, "libzstd.1.5.7.dylib"), "");
|
||||
writeFileSync(join(libDir, "liblz4.1.10.0.dylib"), "");
|
||||
writeFileSync(join(libDir, "libz.1.3.2.dylib"), "");
|
||||
// This is the loader-name pair in the packaged Darwin payload: i18n
|
||||
// requests the ABI-specific uc name rather than the unversioned link.
|
||||
writeFileSync(join(libDir, "libicui18n.68.2.dylib"), "");
|
||||
writeFileSync(join(libDir, "libicuuc.68.2.dylib"), "");
|
||||
|
||||
const created = normalizeMacosEmbeddedPostgresDylibSymlinks(nativeRoot);
|
||||
|
||||
expect(created.map((link) => link.expected).sort()).toEqual([
|
||||
"libicui18n.dylib",
|
||||
"libicuuc.68.dylib",
|
||||
"liblz4.1.dylib",
|
||||
"libpq.5.dylib",
|
||||
"libz.1.dylib",
|
||||
@@ -503,6 +507,7 @@ describe("embedded-lifecycle: macOS dylib compatibility links", () => {
|
||||
]);
|
||||
expect(readlinkSync(join(libDir, "libpq.5.dylib"))).toBe("libpq.5.15.dylib");
|
||||
expect(readlinkSync(join(libDir, "libzstd.1.dylib"))).toBe("libzstd.1.5.7.dylib");
|
||||
expect(readlinkSync(join(libDir, "libicuuc.68.dylib"))).toBe("libicuuc.68.2.dylib");
|
||||
|
||||
// Idempotent: the second pass sees the compatibility names and creates nothing.
|
||||
expect(normalizeMacosEmbeddedPostgresDylibSymlinks(nativeRoot)).toEqual([]);
|
||||
@@ -511,20 +516,37 @@ describe("embedded-lifecycle: macOS dylib compatibility links", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("replaces stale broken compatibility-name symlinks", () => {
|
||||
it("selects the highest matching ICU patch and repairs a dangling loader-name link", () => {
|
||||
const nativeRoot = mkdtempSync(join(tmpdir(), "fusion-embedded-native-"));
|
||||
try {
|
||||
const libDir = join(nativeRoot, "lib");
|
||||
mkdirSync(libDir, { recursive: true });
|
||||
writeFileSync(join(libDir, "libpq.5.16.dylib"), "");
|
||||
symlinkSync("libpq.5.15.dylib", join(libDir, "libpq.5.dylib"));
|
||||
writeFileSync(join(libDir, "libicuuc.68.2.dylib"), "");
|
||||
writeFileSync(join(libDir, "libicuuc.68.12.dylib"), "");
|
||||
symlinkSync("libicuuc.68.1.dylib", join(libDir, "libicuuc.68.dylib"));
|
||||
|
||||
const created = normalizeMacosEmbeddedPostgresDylibSymlinks(nativeRoot);
|
||||
|
||||
expect(created).toEqual([
|
||||
{ expected: "libpq.5.dylib", target: "libpq.5.16.dylib", created: true },
|
||||
{ expected: "libicuuc.68.dylib", target: "libicuuc.68.12.dylib", created: true },
|
||||
]);
|
||||
expect(readlinkSync(join(libDir, "libpq.5.dylib"))).toBe("libpq.5.16.dylib");
|
||||
expect(readlinkSync(join(libDir, "libicuuc.68.dylib"))).toBe("libicuuc.68.12.dylib");
|
||||
} finally {
|
||||
rmSync(nativeRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves a valid ICU compatibility link and treats absent library trees as no-ops", () => {
|
||||
const nativeRoot = mkdtempSync(join(tmpdir(), "fusion-embedded-native-"));
|
||||
try {
|
||||
const libDir = join(nativeRoot, "lib");
|
||||
mkdirSync(libDir, { recursive: true });
|
||||
writeFileSync(join(libDir, "libicuuc.68.2.dylib"), "");
|
||||
symlinkSync("libicuuc.68.2.dylib", join(libDir, "libicuuc.68.dylib"));
|
||||
|
||||
expect(normalizeMacosEmbeddedPostgresDylibSymlinks(nativeRoot)).toEqual([]);
|
||||
expect(readlinkSync(join(libDir, "libicuuc.68.dylib"))).toBe("libicuuc.68.2.dylib");
|
||||
expect(normalizeMacosEmbeddedPostgresDylibSymlinks(join(nativeRoot, "missing"))).toEqual([]);
|
||||
} finally {
|
||||
rmSync(nativeRoot, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -750,12 +750,20 @@ export interface EmbeddedDylibNormalization {
|
||||
readonly created: boolean;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:PostgresEmbedded 2026-07-22-14:34:
|
||||
The bundled libicui18n.68.2.dylib records libicuuc.68.dylib as its loader
|
||||
name. macOS dyld must find that ABI-specific compatibility name before initdb
|
||||
runs, so normalize it only from the packaged libicuuc.68.<patch>.dylib payload
|
||||
rather than adding a broad unversioned ICU link or relying on system libraries.
|
||||
*/
|
||||
const MACOS_EMBEDDED_DYLIB_SYMLINKS: readonly EmbeddedDylibSymlinkSpec[] = [
|
||||
{ expected: "libpq.5.dylib", candidate: /^libpq\.5\..+\.dylib$/ },
|
||||
{ expected: "libzstd.1.dylib", candidate: /^libzstd\.1\..+\.dylib$/ },
|
||||
{ expected: "liblz4.1.dylib", candidate: /^liblz4\.1\..+\.dylib$/ },
|
||||
{ expected: "libz.1.dylib", candidate: /^libz\.1\..+\.dylib$/ },
|
||||
{ expected: "libicui18n.dylib", candidate: /^libicui18n\..+\.dylib$/ },
|
||||
{ expected: "libicuuc.68.dylib", candidate: /^libicuuc\.68\..+\.dylib$/ },
|
||||
];
|
||||
|
||||
function sortDylibCandidates(files: readonly string[], candidate: RegExp): string[] {
|
||||
@@ -768,10 +776,11 @@ function sortDylibCandidates(files: readonly string[], candidate: RegExp): strin
|
||||
* Normalize macOS embedded-postgres library names before initdb/postgres spawn.
|
||||
*
|
||||
* The @embedded-postgres/darwin-* packages can contain fully-versioned dylibs
|
||||
* (for example libpq.5.15.dylib, libzstd.1.5.7.dylib) while the bundled
|
||||
* binaries link against ABI compatibility names such as libpq.5.dylib and
|
||||
* libzstd.1.dylib via @loader_path/../lib/.... When the package postinstall
|
||||
* symlink hydration is skipped or incomplete, dyld fails before initdb can run.
|
||||
* (for example libpq.5.15.dylib, libzstd.1.5.7.dylib, and
|
||||
* libicuuc.68.2.dylib) while the bundled binaries link against ABI compatibility
|
||||
* names such as libpq.5.dylib, libzstd.1.dylib, and libicuuc.68.dylib via
|
||||
* @loader_path/../lib/.... When the package postinstall symlink hydration is
|
||||
* skipped or incomplete, dyld fails before initdb can run.
|
||||
*
|
||||
* This is intentionally local to the embedded binary package and idempotent:
|
||||
* existing compatibility names are left alone; missing compatibility names are
|
||||
|
||||
Reference in New Issue
Block a user