fix: enhance cross-compilation process by replacing native binaries post-rebuild
All checks were successful
Release Electron App / release-desktop (push) Successful in 10m47s
All checks were successful
Release Electron App / release-desktop (push) Successful in 10m47s
This commit is contained in:
140
forge.config.ts
140
forge.config.ts
@@ -68,73 +68,7 @@ const config: ForgeConfig = {
|
|||||||
env: { ...process.env, npm_config_nodedir: '' },
|
env: { ...process.env, npm_config_nodedir: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
// When cross-compiling, native addons (better-sqlite3) are built
|
|
||||||
// for the build machine (Linux). Use @electron/rebuild to get the
|
|
||||||
// correct binaries for the target platform.
|
|
||||||
const targetKey = `${platform}-${arch}`;
|
const targetKey = `${platform}-${arch}`;
|
||||||
const buildKey = `${process.platform}-${process.arch}`;
|
|
||||||
if (targetKey !== buildKey) {
|
|
||||||
console.log(`[forge] Cross-compiling: ${buildKey} → ${targetKey}`);
|
|
||||||
const electronVersion = JSON.parse(
|
|
||||||
fs.readFileSync(path.resolve(__dirname, 'node_modules', 'electron', 'package.json'), 'utf-8'),
|
|
||||||
).version;
|
|
||||||
|
|
||||||
// Rebuild native modules for target platform using prebuild-install.
|
|
||||||
// prebuild-install supports cross-platform via --platform/--arch,
|
|
||||||
// unlike @electron/rebuild which only supports --arch.
|
|
||||||
// This is FATAL — a Linux .node in a Windows package is always broken.
|
|
||||||
const nativeModules = ['better-sqlite3'];
|
|
||||||
for (const mod of nativeModules) {
|
|
||||||
const modDir = path.join(buildPath, 'node_modules', mod);
|
|
||||||
if (!fs.existsSync(modDir)) continue;
|
|
||||||
|
|
||||||
// Clean stale build artifacts — a leftover build/Release/*.node
|
|
||||||
// for the host platform must not survive.
|
|
||||||
const buildRelease = path.join(modDir, 'build', 'Release');
|
|
||||||
if (fs.existsSync(buildRelease)) {
|
|
||||||
fs.rmSync(buildRelease, { recursive: true, force: true });
|
|
||||||
console.log(`[forge] Cleaned stale build/Release for ${mod}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[forge] Downloading ${mod} prebuilt for ${targetKey} (Electron ${electronVersion})...`);
|
|
||||||
execSync(
|
|
||||||
`npx --yes prebuild-install -r electron -t ${electronVersion} ` +
|
|
||||||
`--platform ${platform} --arch ${arch} --tag-prefix v --verbose`,
|
|
||||||
{ cwd: modDir, stdio: 'inherit' },
|
|
||||||
);
|
|
||||||
|
|
||||||
// prebuild-install writes the binary to build/Release/<name>.node.
|
|
||||||
// Verify at least one .node file exists there and is NOT an ELF
|
|
||||||
// (Linux) binary — a Linux .node in a Windows package always crashes.
|
|
||||||
const releaseDir = path.join(modDir, 'build', 'Release');
|
|
||||||
if (!fs.existsSync(releaseDir)) {
|
|
||||||
throw new Error(
|
|
||||||
`[forge] FATAL: build/Release/ not found for ${mod} after prebuild-install. ` +
|
|
||||||
`The native binary was not downloaded.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const nodeFiles = fs.readdirSync(releaseDir).filter((f) => f.endsWith('.node'));
|
|
||||||
if (nodeFiles.length === 0) {
|
|
||||||
throw new Error(
|
|
||||||
`[forge] FATAL: No .node files in build/Release/ for ${mod} after prebuild-install.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (const f of nodeFiles) {
|
|
||||||
const buf = Buffer.alloc(4);
|
|
||||||
const fd = fs.openSync(path.join(releaseDir, f), 'r');
|
|
||||||
fs.readSync(fd, buf, 0, 4, 0);
|
|
||||||
fs.closeSync(fd);
|
|
||||||
// ELF magic: 0x7f 'E' 'L' 'F'
|
|
||||||
if (buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46) {
|
|
||||||
throw new Error(
|
|
||||||
`[forge] FATAL: ${mod} build/Release/${f} is an ELF binary! ` +
|
|
||||||
`Cross-compilation failed — refusing to package a Linux .node for Windows.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
console.log(`[forge] Verified ${f} is not ELF ✓`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// vectordb uses platform-specific optional deps (@lancedb/vectordb-<platform>-<arch>-*).
|
// vectordb uses platform-specific optional deps (@lancedb/vectordb-<platform>-<arch>-*).
|
||||||
// npm install on Linux only pulls the Linux variant. Force-install the target's.
|
// npm install on Linux only pulls the Linux variant. Force-install the target's.
|
||||||
@@ -224,6 +158,80 @@ const config: ForgeConfig = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ── Post-rebuild: fix native binaries for cross-compilation ──────
|
||||||
|
// Forge runs @electron/rebuild AFTER packageAfterCopy, which
|
||||||
|
// recompiles native addons for the BUILD platform (Linux).
|
||||||
|
// packageAfterPrune runs AFTER rebuild+prune, so we can safely
|
||||||
|
// replace the Linux .node files with the correct target prebuilts.
|
||||||
|
packageAfterPrune: async (_forgeConfig, buildPath, _electronVersion, platform, arch) => {
|
||||||
|
const targetKey = `${platform}-${arch}`;
|
||||||
|
const buildKey = `${process.platform}-${process.arch}`;
|
||||||
|
if (targetKey === buildKey) return; // native build — nothing to fix
|
||||||
|
|
||||||
|
console.log(`[forge:afterPrune] Cross-compile fixup: ${buildKey} → ${targetKey}`);
|
||||||
|
const electronVersion = JSON.parse(
|
||||||
|
fs.readFileSync(path.resolve(__dirname, 'node_modules', 'electron', 'package.json'), 'utf-8'),
|
||||||
|
).version;
|
||||||
|
|
||||||
|
// Replace native addons that @electron/rebuild compiled for the host.
|
||||||
|
const nativeModules = ['better-sqlite3'];
|
||||||
|
for (const mod of nativeModules) {
|
||||||
|
const modDir = path.join(buildPath, 'node_modules', mod);
|
||||||
|
if (!fs.existsSync(modDir)) continue;
|
||||||
|
|
||||||
|
// Remove the host-platform binary left by @electron/rebuild
|
||||||
|
const buildRelease = path.join(modDir, 'build', 'Release');
|
||||||
|
if (fs.existsSync(buildRelease)) {
|
||||||
|
fs.rmSync(buildRelease, { recursive: true, force: true });
|
||||||
|
console.log(`[forge:afterPrune] Cleaned host-platform build/Release for ${mod}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download the correct prebuilt for the TARGET platform
|
||||||
|
console.log(`[forge:afterPrune] Downloading ${mod} prebuilt for ${targetKey} (Electron ${electronVersion})...`);
|
||||||
|
execSync(
|
||||||
|
`npx --yes prebuild-install -r electron -t ${electronVersion} ` +
|
||||||
|
`--platform ${platform} --arch ${arch} --tag-prefix v --verbose`,
|
||||||
|
{ cwd: modDir, stdio: 'inherit' },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the binary exists and is for the correct platform.
|
||||||
|
const releaseDir = path.join(modDir, 'build', 'Release');
|
||||||
|
if (!fs.existsSync(releaseDir)) {
|
||||||
|
throw new Error(
|
||||||
|
`[forge] FATAL: build/Release/ not found for ${mod} after prebuild-install. ` +
|
||||||
|
`The native binary was not downloaded.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const nodeFiles = fs.readdirSync(releaseDir).filter((f) => f.endsWith('.node'));
|
||||||
|
if (nodeFiles.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`[forge] FATAL: No .node files in build/Release/ for ${mod} after prebuild-install.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const f of nodeFiles) {
|
||||||
|
const buf = Buffer.alloc(4);
|
||||||
|
const fd = fs.openSync(path.join(releaseDir, f), 'r');
|
||||||
|
fs.readSync(fd, buf, 0, 4, 0);
|
||||||
|
fs.closeSync(fd);
|
||||||
|
// ELF magic: 0x7f 'E' 'L' 'F'
|
||||||
|
if (buf[0] === 0x7f && buf[1] === 0x45 && buf[2] === 0x4c && buf[3] === 0x46) {
|
||||||
|
throw new Error(
|
||||||
|
`[forge] FATAL: ${mod} build/Release/${f} is an ELF binary! ` +
|
||||||
|
`Cross-compilation failed — refusing to package a Linux .node for Windows.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// PE magic: 'M' 'Z' (0x4d 0x5a) — expected for win32
|
||||||
|
if (platform === 'win32' && !(buf[0] === 0x4d && buf[1] === 0x5a)) {
|
||||||
|
throw new Error(
|
||||||
|
`[forge] FATAL: ${mod} build/Release/${f} is not a PE (Windows) binary! ` +
|
||||||
|
`Magic bytes: ${buf.toString('hex')}. Refusing to package.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log(`[forge:afterPrune] Verified ${mod}/${f} — correct platform ✓`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
makers: [
|
makers: [
|
||||||
new MakerSquirrel({}),
|
new MakerSquirrel({}),
|
||||||
|
|||||||
Reference in New Issue
Block a user