mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] build: drop foreign-arch native prebuilds from the app, with a release gate to keep them out
This commit is contained in:
@@ -83,8 +83,45 @@ function stageRouterNodeModules(context) {
|
||||
console.log(`[afterPack] staged 9Router node_modules into ${routerDir}`);
|
||||
}
|
||||
|
||||
// prebuildify packages (uiohook-napi and friends) ship a .node for EVERY platform+arch they support.
|
||||
// Six of the seven are dead weight in any one build, and on macOS an x86_64 Mach-O sitting inside an
|
||||
// arm64 bundle is what makes the OS put up its Intel-deprecation dialog. node-gyp-build only ever
|
||||
// looks in prebuilds/<platform>-<arch>, so deleting the rest is invisible to the app. Runs here in
|
||||
// afterPack because code-signing comes next and seals the bundle; a later delete breaks the seal.
|
||||
function pruneForeignPrebuilds(context) {
|
||||
const { appOutDir, electronPlatformName, arch } = context;
|
||||
// Read the arch name off electron-builder's own enum rather than hardcoding its numbering.
|
||||
const archName = require('builder-util').Arch[arch];
|
||||
const root = electronPlatformName === 'darwin'
|
||||
? path.join(appOutDir, `${context.packager.appInfo.productFilename}.app`, 'Contents', 'Resources')
|
||||
: path.join(appOutDir, 'resources');
|
||||
let removed = 0;
|
||||
|
||||
(function walk(dir, depth) {
|
||||
if (depth > 12) return;
|
||||
let ents = [];
|
||||
try { ents = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
||||
for (const e of ents) {
|
||||
if (!e.isDirectory()) continue;
|
||||
const full = path.join(dir, e.name);
|
||||
if (e.name !== 'prebuilds') { walk(full, depth + 1); continue; }
|
||||
for (const tuple of fs.readdirSync(full, { withFileTypes: true })) {
|
||||
// prebuildify names dirs "<platform>-<arch>", sometimes fat: "darwin-x64+arm64".
|
||||
const [tuplePlatform, archPart] = tuple.name.split('-');
|
||||
const covers = tuplePlatform === electronPlatformName && String(archPart || '').split('+').includes(archName);
|
||||
if (covers) continue;
|
||||
fs.rmSync(path.join(full, tuple.name), { recursive: true, force: true });
|
||||
removed += 1;
|
||||
}
|
||||
}
|
||||
})(root, 0);
|
||||
|
||||
console.log(`[afterPack] pruned ${removed} foreign prebuild dir(s), kept ${electronPlatformName}-${archName}`);
|
||||
}
|
||||
|
||||
exports.default = async function afterPack(context) {
|
||||
stageRouterNodeModules(context);
|
||||
pruneForeignPrebuilds(context);
|
||||
// VMP signing runs last and unconditionally, after every file is staged, so the
|
||||
// OS code-sign that electron-builder runs next seals the VMP signature too.
|
||||
signVmp(context);
|
||||
|
||||
@@ -36,6 +36,7 @@ function main() {
|
||||
['deps fully pinned (reproducible backend builds)', 'verify-deps-pinned.js', []],
|
||||
['no build-host paths leaked into the artifact', 'verify-host-leakage.js', appArg],
|
||||
['locale paks shipped (empty --lang -> Blink null-deref crash)', 'verify-locale-paks.js', appArg],
|
||||
['native modules match the target arch (no x86_64 .node in an arm64 app)', 'verify-native-arch.js', appArg],
|
||||
['9router deps shipped (else subscription service hangs)', 'verify-router-deps.js', appArg],
|
||||
['bundled python runs (--version + import smoke)', 'verify-python-health.js', appArg],
|
||||
['MCP bundles answer initialize over stdio', 'verify-mcp-bundles.js', appArg],
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env node
|
||||
// Guards against shipping a native module built for the WRONG architecture.
|
||||
// prebuildify packages (uiohook-napi, the dictation hotkey tap) ship one .node per platform+arch
|
||||
// they support: seven dirs, of which exactly one is ours. Six are dead weight, and on macOS an
|
||||
// x86_64 Mach-O inside an arm64 bundle is what makes the OS raise its Intel-deprecation dialog at
|
||||
// the user. electron/build/after-pack.js prunes them; this asserts the prune actually happened,
|
||||
// because an afterPack hook that silently no-ops (layout changed, module moved into the asar) would
|
||||
// otherwise ship the same bundle it always did with nobody the wiser.
|
||||
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execFileSync } = require('child_process');
|
||||
const h = require('./lib/app-harness');
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = { app: null };
|
||||
for (let i = 0; i < argv.length; i++) if (argv[i] === '--app') out.app = argv[++i];
|
||||
return out;
|
||||
}
|
||||
|
||||
function findNodeFiles(root) {
|
||||
const found = [];
|
||||
(function walk(dir, depth) {
|
||||
if (depth > 14) return;
|
||||
let ents = [];
|
||||
try { ents = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
||||
for (const e of ents) {
|
||||
const full = path.join(dir, e.name);
|
||||
if (e.isDirectory()) walk(full, depth + 1);
|
||||
else if (e.isFile() && e.name.endsWith('.node')) found.push(full);
|
||||
}
|
||||
})(root, 0);
|
||||
return found;
|
||||
}
|
||||
|
||||
// `file` names every slice in a Mach-O, so a fat binary reports both and a wrong-arch one reports
|
||||
// only the wrong slice. On Windows/Linux there is no equivalent worth the dependency, so the gate
|
||||
// there just asserts no foreign prebuild DIRS survived.
|
||||
function machoArches(file) {
|
||||
try {
|
||||
return execFileSync('file', [file], { encoding: 'utf8' }).trim();
|
||||
} catch (err) {
|
||||
return `file failed: ${err && err.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const exe = h.packagedAppPath(args.app);
|
||||
const root = process.platform === 'darwin' ? exe.slice(0, exe.indexOf('.app') + 4) : path.dirname(exe);
|
||||
const want = process.arch === 'arm64' ? 'arm64' : 'x86_64';
|
||||
|
||||
const nodes = findNodeFiles(root);
|
||||
process.stdout.write(` ${nodes.length} .node file(s) under ${path.basename(root)}\n`);
|
||||
|
||||
const offenders = [];
|
||||
for (const n of nodes) {
|
||||
// A prebuilds dir for another OS is wrong no matter what `file` says about it.
|
||||
const tuple = path.basename(path.dirname(n));
|
||||
const platformOk = !tuple.includes('-') || tuple.startsWith(process.platform);
|
||||
const desc = process.platform === 'darwin' ? machoArches(n) : '';
|
||||
const archOk = process.platform !== 'darwin' || desc.includes(want);
|
||||
if (!platformOk || !archOk) offenders.push(`${path.relative(root, n)} [${tuple}] ${desc.split(':').slice(1).join(':').trim()}`);
|
||||
}
|
||||
|
||||
if (!offenders.length) {
|
||||
process.stdout.write(`PASS every bundled .node targets ${process.platform}/${want}\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
process.stderr.write(
|
||||
`FAIL packaged build ships ${offenders.length} native module(s) for the WRONG target:\n` +
|
||||
offenders.map((o) => ` ${o}\n`).join('') +
|
||||
` An x86_64 .node inside an arm64 bundle makes macOS show its Intel-deprecation\n` +
|
||||
` dialog, and every foreign prebuild is dead weight the user downloads.\n` +
|
||||
` Fix: electron/build/after-pack.js pruneForeignPrebuilds() should have deleted these.\n` +
|
||||
` If they now live inside app.asar rather than app.asar.unpacked, the prune needs to\n` +
|
||||
` run before the asar is built (beforePack) instead.\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user