Files
iofficeai--aionui/scripts/afterPack.js
T
2026-07-13 13:20:22 +08:00

227 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { Arch } = require('builder-util');
const fs = require('fs');
const path = require('path');
const os = require('os');
const {
normalizeArch,
rebuildSingleModule,
verifyModuleBinary,
getModulesToRebuild,
} = require('./rebuildNativeModules');
const { verifyBundledAioncoreResources } = require('../packages/shared-scripts/src/verify-bundled-aioncore-resources');
/**
* afterPack hook for electron-builder
* Rebuilds native modules for cross-architecture builds
*/
function resolveResourcesDir(electronPlatformName, appOutDir, packager) {
if (electronPlatformName !== 'darwin') return path.join(appOutDir, 'resources');
const appName = packager?.appInfo?.productFilename || 'AionUi';
return path.join(appOutDir, `${appName}.app`, 'Contents', 'Resources');
}
function verifyBundledResources(resourcesDir, electronPlatformName, targetArch) {
const result = verifyBundledAioncoreResources({
resourcesDir,
electronPlatformName,
targetArch,
});
if (result.missing.length > 0) {
console.error(` Missing bundled resources: ${result.missing.join(', ')}`);
throw new Error(`Packaged app is missing required bundled resource(s): ${result.missing.join(', ')}`);
}
console.log(` ✓ Bundled resources verified for ${result.runtimeKey} (${result.checked.length} checks)`);
}
module.exports = async function afterPack(context) {
const { arch, electronPlatformName, appOutDir, packager } = context;
const targetArch = normalizeArch(typeof arch === 'string' ? arch : Arch[arch] || process.arch);
const buildArch = normalizeArch(os.arch());
console.log(`\n🔧 afterPack hook started`);
console.log(` Platform: ${electronPlatformName}, Build arch: ${buildArch}, Target arch: ${targetArch}`);
const isCrossCompile = buildArch !== targetArch;
const forceRebuild = process.env.FORCE_NATIVE_REBUILD === 'true';
const needsSameArchRebuild = electronPlatformName === 'win32'; // 只有 Windows 需要同架构重建以匹配 Electron ABI | Only Windows needs same-arch rebuild to match Electron ABI
// Linux 使用预编译二进制,避免 GLIBC 版本依赖 | Linux uses prebuilt binaries which are GLIBC-independent
const resourcesDir = resolveResourcesDir(electronPlatformName, appOutDir, packager);
console.log(` Checking resources directory: ${resourcesDir}`);
if (fs.existsSync(resourcesDir)) {
const resourcesContents = fs.readdirSync(resourcesDir);
console.log(` Contents: ${resourcesContents.join(', ')}`);
const unpackedDir = path.join(resourcesDir, 'app.asar.unpacked');
if (fs.existsSync(unpackedDir)) {
const unpackedContents = fs.readdirSync(unpackedDir);
console.log(` app.asar.unpacked contents: ${unpackedContents.join(', ')}`);
const nodeModulesDir = path.join(unpackedDir, 'node_modules');
if (fs.existsSync(nodeModulesDir)) {
const modulesContents = fs.readdirSync(nodeModulesDir);
console.log(` node_modules contents: ${modulesContents.slice(0, 10).join(', ')}...`);
} else {
console.warn(` ⚠️ node_modules not found in app.asar.unpacked`);
}
} else {
console.warn(` ⚠️ app.asar.unpacked not found`);
}
verifyBundledResources(resourcesDir, electronPlatformName, targetArch);
} else {
throw new Error(`resources directory not found: ${resourcesDir}`);
}
if (!isCrossCompile && !needsSameArchRebuild && !forceRebuild) {
console.log(` ✓ Same architecture, rebuild skipped (set FORCE_NATIVE_REBUILD=true to override)\n`);
return;
}
// Note: Previously there was an optimization to skip macOS cross-compilation,
// but this caused incorrect architecture binaries (arm64) to be included in x64 builds.
// Now we always rebuild native modules for cross-compilation to ensure correctness.
// The rebuild process uses prebuild-install first (fast), falling back to source compilation only when needed.
if (isCrossCompile) {
console.log(` ⚠️ Cross-compilation detected (${buildArch}${targetArch}), will rebuild native modules`);
if (electronPlatformName === 'darwin') {
console.log(` 💡 Using prebuild-install for faster cross-architecture build`);
}
} else if (needsSameArchRebuild || forceRebuild) {
console.log(` ️ Rebuilding native modules for platform requirements (force=${forceRebuild})`);
}
console.log(`\n🔧 Checking native modules (${electronPlatformName}-${targetArch})...`);
console.log(` appOutDir: ${appOutDir}`);
const electronVersion =
packager?.info?.electronVersion ??
packager?.config?.electronVersion ??
require('../package.json').devDependencies?.electron?.replace(/^\D*/, '');
const nodeModulesDir = path.join(resourcesDir, 'app.asar.unpacked', 'node_modules');
// Modules that need to be rebuilt for cross-compilation
// Use platform-specific module list (Windows skips node-pty due to cross-compilation issues)
const modulesToRebuild = getModulesToRebuild(electronPlatformName);
console.log(` Modules to rebuild: ${modulesToRebuild.join(', ')}`);
// For cross-compilation, clean up build artifacts from the wrong architecture
// This prevents node-gyp-build from loading incorrect binaries
if (isCrossCompile) {
console.log(`\n🧹 Cleaning up wrong-architecture build artifacts...`);
for (const moduleName of modulesToRebuild) {
const moduleRoot = path.join(nodeModulesDir, moduleName);
if (!fs.existsSync(moduleRoot)) continue;
// Remove build/ directory (contains wrong-arch compiled binaries)
const buildDir = path.join(moduleRoot, 'build');
if (fs.existsSync(buildDir)) {
fs.rmSync(buildDir, { recursive: true, force: true });
console.log(` ✓ Removed ${moduleName}/build/`);
}
// Remove bin/ directory (might contain wrong-arch binaries)
const binDir = path.join(moduleRoot, 'bin');
if (fs.existsSync(binDir)) {
fs.rmSync(binDir, { recursive: true, force: true });
console.log(` ✓ Removed ${moduleName}/bin/`);
}
}
// Also clean up architecture-specific packages that shouldn't be included
// Remove packages for the opposite architecture of the target
const wrongArchSuffix = targetArch === 'arm64' ? 'x64' : 'arm64';
console.log(`\n🧹 Removing ${wrongArchSuffix}-specific optional dependencies (target: ${targetArch})...`);
if (fs.existsSync(nodeModulesDir)) {
const allModules = fs.readdirSync(nodeModulesDir);
for (const module of allModules) {
const modulePath = path.join(nodeModulesDir, module);
// Handle scoped packages (e.g., @lydell, @napi-rs)
if (module.startsWith('@') && fs.existsSync(modulePath) && fs.statSync(modulePath).isDirectory()) {
const scopedPackages = fs.readdirSync(modulePath);
for (const pkg of scopedPackages) {
if (pkg.includes(`-${wrongArchSuffix}`) || pkg.includes(`-${electronPlatformName}-${wrongArchSuffix}`)) {
const pkgPath = path.join(modulePath, pkg);
if (fs.existsSync(pkgPath) && fs.statSync(pkgPath).isDirectory()) {
fs.rmSync(pkgPath, { recursive: true, force: true });
console.log(` ✓ Removed ${module}/${pkg}`);
}
}
}
}
// Handle regular packages
else if (
module.includes(`-${wrongArchSuffix}`) ||
module.includes(`-${electronPlatformName}-${wrongArchSuffix}`)
) {
if (fs.existsSync(modulePath) && fs.statSync(modulePath).isDirectory()) {
fs.rmSync(modulePath, { recursive: true, force: true });
console.log(` ✓ Removed ${module}`);
}
}
}
}
}
const failedModules = [];
for (const moduleName of modulesToRebuild) {
const moduleRoot = path.join(nodeModulesDir, moduleName);
if (!fs.existsSync(moduleRoot)) {
console.warn(` ⚠️ ${moduleName} not found, skipping`);
continue;
}
console.log(` ✓ Found ${moduleName}, rebuilding for ${targetArch}...`);
// For Windows, prefer prebuild-install first (faster and more reliable in CI)
// electron-rebuild can hang on "Searching dependency tree" in some CI environments
// prebuild-install will fall back to electron-rebuild internally if no prebuilt binary exists
const forceRebuildFromSource = false; // Always try prebuild-install first
const success = rebuildSingleModule({
moduleName,
moduleRoot,
platform: electronPlatformName,
arch: targetArch,
electronVersion,
projectRoot: path.resolve(__dirname, '..'),
buildArch: buildArch, // Pass build architecture for cross-compile detection
forceRebuild: forceRebuildFromSource, // Always try prebuild-install first, fallback to rebuild
});
if (success) {
console.log(` ✓ Rebuild completed`);
} else {
console.error(` ✗ Rebuild failed`);
failedModules.push(moduleName);
continue;
}
const verified = verifyModuleBinary(moduleRoot, moduleName);
if (verified) {
console.log(` ✓ Binary verification passed`);
} else {
console.error(` ✗ Binary verification failed`);
failedModules.push(moduleName);
}
console.log(''); // Empty line between modules
}
if (failedModules.length > 0) {
throw new Error(`Failed to rebuild modules for ${electronPlatformName}-${targetArch}: ${failedModules.join(', ')}`);
}
console.log(`✅ All native modules rebuilt successfully for ${targetArch}\n`);
};