Fix Windows native rebuild EPERM devex (#2159)
This commit is contained in:
parent
6fe67c6739
commit
4d2a435bb0
|
|
@ -20,10 +20,12 @@
|
|||
*/
|
||||
|
||||
import { rebuild } from '@electron/rebuild'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { execFileSync, spawnSync } from 'node:child_process'
|
||||
import { createRequire } from 'node:module'
|
||||
import { existsSync, globSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const projectDir = process.cwd()
|
||||
const electronVersion = JSON.parse(
|
||||
readFileSync(resolve(projectDir, 'node_modules/electron/package.json'), 'utf8')
|
||||
|
|
@ -41,6 +43,23 @@ if (ignoreModules.length > 0) {
|
|||
// ABI regardless of the package manager's store layout.
|
||||
const NATIVE_MODULES = ['better-sqlite3', 'node-pty', 'cpu-features']
|
||||
const onlyModules = NATIVE_MODULES.filter((m) => !ignoreModules.includes(m))
|
||||
const forceRebuild = process.env.ORCA_FORCE_NATIVE_REBUILD === '1'
|
||||
|
||||
if (!forceRebuild) {
|
||||
// Why: Windows cannot unlink a loaded .node DLL, so avoid @electron/rebuild
|
||||
// when the current install already works with Electron's ABI.
|
||||
const probe = probeElectronNativeModules(onlyModules)
|
||||
if (probe.ok) {
|
||||
console.log('[rebuild] Native modules already load in Electron; skipping rebuild.')
|
||||
process.exit(0)
|
||||
}
|
||||
console.log('[rebuild] Native modules do not load in Electron; rebuilding.')
|
||||
if (probe.stderr.trim()) {
|
||||
console.log(probe.stderr.trim())
|
||||
}
|
||||
} else {
|
||||
console.log('[rebuild] ORCA_FORCE_NATIVE_REBUILD=1 set; forcing native rebuild.')
|
||||
}
|
||||
|
||||
// Why: cpu-features ships without `buildcheck.gypi`; its own `install` script
|
||||
// generates it by running `node buildcheck.js > buildcheck.gypi` before
|
||||
|
|
@ -49,10 +68,9 @@ const onlyModules = NATIVE_MODULES.filter((m) => !ignoreModules.includes(m))
|
|||
// store prune, or a prior failed run) node-gyp aborts with
|
||||
// "buildcheck.gypi not found". Regenerate it here before rebuilding.
|
||||
if (!ignoreModules.includes('cpu-features')) {
|
||||
const cpuFeatureDirs = globSync(
|
||||
'node_modules/.pnpm/cpu-features@*/node_modules/cpu-features',
|
||||
{ cwd: projectDir },
|
||||
)
|
||||
const cpuFeatureDirs = globSync('node_modules/.pnpm/cpu-features@*/node_modules/cpu-features', {
|
||||
cwd: projectDir
|
||||
})
|
||||
for (const relDir of cpuFeatureDirs) {
|
||||
const dir = resolve(projectDir, relDir)
|
||||
const gypiPath = resolve(dir, 'buildcheck.gypi')
|
||||
|
|
@ -62,15 +80,12 @@ if (!ignoreModules.includes('cpu-features')) {
|
|||
try {
|
||||
const out = execFileSync(process.execPath, ['buildcheck.js'], {
|
||||
cwd: dir,
|
||||
encoding: 'utf8',
|
||||
encoding: 'utf8'
|
||||
})
|
||||
writeFileSync(gypiPath, out)
|
||||
console.log(`[rebuild] Generated ${relDir}/buildcheck.gypi`)
|
||||
} catch (/** @type {any} */ err) {
|
||||
console.error(
|
||||
`[rebuild] Failed to generate ${relDir}/buildcheck.gypi:`,
|
||||
err?.message ?? err,
|
||||
)
|
||||
console.error(`[rebuild] Failed to generate ${relDir}/buildcheck.gypi:`, err?.message ?? err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -87,9 +102,121 @@ try {
|
|||
// (e.g., system Node instead of Electron's embedded Node). This is
|
||||
// common after pnpm install, which compiles native modules for system
|
||||
// Node before postinstall runs this script.
|
||||
force: true,
|
||||
force: true
|
||||
})
|
||||
} catch (/** @type {any} */ err) {
|
||||
console.error('[rebuild] Native module rebuild failed:', err?.message ?? err)
|
||||
if (isWindowsNativeLockError(err)) {
|
||||
console.error(
|
||||
'[rebuild] A Windows process appears to be using a native .node file. ' +
|
||||
'Close running Orca/Electron/dev processes for this worktree, then rerun `pnpm install` ' +
|
||||
'or `pnpm run rebuild:electron`.'
|
||||
)
|
||||
if (isPostinstall() && process.env.ORCA_STRICT_NATIVE_REBUILD !== '1') {
|
||||
console.error(
|
||||
'[rebuild] Continuing postinstall because the failure is a Windows file lock. ' +
|
||||
'The next dev/start command will re-check native modules.'
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function probeElectronNativeModules(moduleNames) {
|
||||
let electronExecutable
|
||||
try {
|
||||
electronExecutable = require('electron')
|
||||
} catch (error) {
|
||||
return { ok: false, status: null, stderr: formatError(error) }
|
||||
}
|
||||
|
||||
const probeSource = `
|
||||
const { createRequire } = require('node:module')
|
||||
const { release } = require('node:os')
|
||||
const { resolve } = require('node:path')
|
||||
const projectRequire = createRequire(resolve(process.cwd(), 'package.json'))
|
||||
const moduleNames = ${JSON.stringify(moduleNames)}
|
||||
const failures = []
|
||||
|
||||
for (const moduleName of moduleNames) {
|
||||
try {
|
||||
loadNativeModule(moduleName)
|
||||
} catch (error) {
|
||||
failures.push(moduleName + ': ' + formatError(error))
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error(failures.join('\\n'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function loadNativeModule(moduleName) {
|
||||
if (moduleName === 'better-sqlite3') {
|
||||
const Database = projectRequire(moduleName)
|
||||
const db = new Database(':memory:')
|
||||
db.close()
|
||||
return
|
||||
}
|
||||
if (moduleName === 'node-pty') {
|
||||
projectRequire('node-pty')
|
||||
const { loadNativeModule } = projectRequire('node-pty/lib/utils')
|
||||
loadNativeModule(getNodePtyNativeModuleName())
|
||||
return
|
||||
}
|
||||
projectRequire(moduleName)
|
||||
}
|
||||
|
||||
function getNodePtyNativeModuleName() {
|
||||
if (process.platform !== 'win32') {
|
||||
return 'pty'
|
||||
}
|
||||
const match = /(\\d+)\\.(\\d+)\\.(\\d+)/g.exec(release())
|
||||
const buildNumber = match && match.length === 4 ? Number.parseInt(match[3], 10) : 0
|
||||
return buildNumber >= 18309 ? 'conpty' : 'pty'
|
||||
}
|
||||
|
||||
function formatError(error) {
|
||||
return error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
`
|
||||
|
||||
const result = spawnSync(electronExecutable, ['-e', probeSource], {
|
||||
cwd: projectDir,
|
||||
env: {
|
||||
...process.env,
|
||||
ELECTRON_RUN_AS_NODE: '1'
|
||||
},
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
})
|
||||
|
||||
return {
|
||||
ok: result.status === 0,
|
||||
status: result.status,
|
||||
stderr: [result.stderr, result.stdout, result.error ? formatError(result.error) : '']
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
function isWindowsNativeLockError(error) {
|
||||
if (process.platform !== 'win32') {
|
||||
return false
|
||||
}
|
||||
const text = [error?.message, error?.stack, error?.stdout, error?.stderr]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
return /(?:EPERM|operation not permitted)[\s\S]*(?:unlink|\.node|conpty\.node|pty\.node)/i.test(
|
||||
text
|
||||
)
|
||||
}
|
||||
|
||||
function isPostinstall() {
|
||||
return process.env.npm_lifecycle_event === 'postinstall'
|
||||
}
|
||||
|
||||
function formatError(error) {
|
||||
return error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue