Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packaging/electron/scripts/prepare-postgres-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
commonDirectory,
inspectDistribution,
materializeSymlinks,
removeUnshippedFiles,
} from "./prepare-postgres-runtime";

void test("packaged PostgreSQL paths retain their build-time relative layout", () => {
Expand Down Expand Up @@ -104,3 +105,24 @@ esac
await fs.promises.rm(temp, { recursive: true, force: true });
}
});

void test("static archives and PL/Python are left out of the packaged runtime", async () => {
const temp = await fs.promises.mkdtemp(
path.join(os.tmpdir(), "vision-postgres-plpython-"),
);
const pkglib = path.join(temp, "lib", "postgresql");
const extension = path.join(temp, "share", "postgresql", "extension");
try {
await fs.promises.mkdir(pkglib, { recursive: true });
await fs.promises.mkdir(extension, { recursive: true });
for (const name of ["plpython3.dylib", "libpgport.a", "pg_trgm.dylib"])
await fs.promises.writeFile(path.join(pkglib, name), "");
for (const name of ["jsonb_plpython3u.control", "pg_trgm.control"])
await fs.promises.writeFile(path.join(extension, name), "");
removeUnshippedFiles(temp);
assert.deepEqual(await fs.promises.readdir(pkglib), ["pg_trgm.dylib"]);
assert.deepEqual(await fs.promises.readdir(extension), ["pg_trgm.control"]);
} finally {
await fs.promises.rm(temp, { recursive: true, force: true });
}
});
29 changes: 29 additions & 0 deletions packaging/electron/scripts/prepare-postgres-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,16 @@ export function assertNoSymlinks(root: string): void {
visit(root);
}

// Static archives are link-time only. PL/Python links the python.org
// framework (Postgres.app), which Vision neither uses nor ships; dropping it
// keeps the relinker from trying to vendor a Python runtime.
export function removeUnshippedFiles(root: string): void {
for (const filePath of walkFiles(root)) {
const name = path.basename(filePath);
if (name.endsWith(".a") || /plpython/i.test(name)) fs.rmSync(filePath);
}
}

function walkFiles(root: string): string[] {
const files: string[] = [];
function visit(current: string): void {
Expand Down Expand Up @@ -244,6 +254,23 @@ function dynamicLibraryId(filePath: string): string | undefined {
.find(Boolean);
}

// Postgres.app ships universal binaries. otool prints one block per slice,
// which the parsers above do not expect, and Vision ships one architecture.
function thinToBuildArchitecture(filePath: string): void {
const architectures = run("/usr/bin/lipo", ["-archs", filePath]).split(/\s+/);
if (architectures.length < 2) return;
const architecture = process.arch === "arm64" ? "arm64" : "x86_64";
if (!architectures.includes(architecture)) {
throw new Error(
`PostgreSQL file has no ${architecture} slice: ${path.basename(filePath)}`,
);
}
const thinned = `${filePath}.thin`;
run("/usr/bin/lipo", [filePath, "-thin", architecture, "-output", thinned]);
fs.chmodSync(thinned, fs.statSync(filePath).mode & 0o777);
fs.renameSync(thinned, filePath);
}

function systemLibrary(dependency: string): boolean {
return (
dependency.startsWith("/usr/lib/") ||
Expand Down Expand Up @@ -349,6 +376,7 @@ function relinkDistribution(
) {
if (visited.has(filePath)) continue;
visited.add(filePath);
thinToBuildArchitecture(filePath);
const libraryId = dynamicLibraryId(filePath);
for (const dependency of linkedLibraries(filePath)) {
if (dependency === libraryId || systemLibrary(dependency)) continue;
Expand Down Expand Up @@ -482,6 +510,7 @@ export function preparePostgresRuntime({
copyDirectory(source, targetDirectory);
materializeSymlinks(root);
assertNoSymlinks(root);
removeUnshippedFiles(root);
normalizePermissions(root);

const mappings = [...uniqueDirectories].map(([source, destination_]) => ({
Expand Down