From 70bc58df5eb269d7d7bad9796f8d56d6e5a8af4f Mon Sep 17 00:00:00 2001 From: Neil Date: Sat, 3 Oct 2026 02:59:01 -0700 Subject: [PATCH] Allow cold node-pty setup on Windows ARM CI Keep a ten-minute bound only for node-pty rebuilt on Windows ARM CI hosts; all other node-gyp calls retain five minutes. Cold setup in two completed ARM jobs left compilation less than a minute. --- config/scripts/ensure-native-runtime.mjs | 6 ++-- config/scripts/ensure-native-runtime.test.mjs | 15 ++++++++-- .../windows-process-tree-gyp-rebuild.mjs | 10 +++++++ .../windows-process-tree-gyp-rebuild.test.mjs | 28 +++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/config/scripts/ensure-native-runtime.mjs b/config/scripts/ensure-native-runtime.mjs index bee1b3fed9ee..a91b4f528c4f 100644 --- a/config/scripts/ensure-native-runtime.mjs +++ b/config/scripts/ensure-native-runtime.mjs @@ -9,6 +9,7 @@ import { ensureWindowsProcessTreeCommandLinePatch, inspectWindowsProcessTreeAddon, nodeGypRebuildInvocation, + nodeGypRebuildTimeoutMs, stageWindowsProcessTreeNodeAddonApiHeaders, windowsProcessTreeAddonPath } from './windows-process-tree-gyp-rebuild.mjs' @@ -405,6 +406,7 @@ function rebuildNodeRuntimeModules(moduleNames) { console.warn(`[native-runtime] Rebuilding ${moduleName} with node-gyp.`) // pnpm exec inside an installed addon cannot discover the root build tool. runNodeGyp( + moduleName, nodeGypRebuildInvocation( process.arch, moduleDir, @@ -417,7 +419,7 @@ function rebuildNodeRuntimeModules(moduleNames) { } } -function runNodeGyp({ args, cwd }) { +function runNodeGyp(moduleName, { args, cwd }) { const env = process.platform === 'linux' ? { ...process.env, CXXFLAGS: `${process.env.CXXFLAGS ?? ''} -std=gnu++2a`.trim() } @@ -428,7 +430,7 @@ function runNodeGyp({ args, cwd }) { cwd, env, stdio: 'inherit', - timeoutMs: 300_000 + timeoutMs: nodeGypRebuildTimeoutMs(moduleName) }) if (result.code !== 0) { console.error( diff --git a/config/scripts/ensure-native-runtime.test.mjs b/config/scripts/ensure-native-runtime.test.mjs index 3cd01374d1f3..68a10b5bb206 100644 --- a/config/scripts/ensure-native-runtime.test.mjs +++ b/config/scripts/ensure-native-runtime.test.mjs @@ -20,7 +20,10 @@ import { resolveCliCommand } from '../../src/shared/node-cli-command-resolution. import { removeTreeSync } from '../../src/shared/windows-transient-lock-removal.ts' import { resolvePnpmCliInvocation } from './pnpm-cli-invocation.mjs' import { copyScriptWithLocalModules } from './script-module-dependencies.mjs' -import { nodeGypRebuildInvocation } from './windows-process-tree-gyp-rebuild.mjs' +import { + nodeGypRebuildInvocation, + nodeGypRebuildTimeoutMs +} from './windows-process-tree-gyp-rebuild.mjs' const sourceScriptPath = fileURLToPath(new URL('./ensure-native-runtime.mjs', import.meta.url)) // The import walk sees `from './x.mjs'` only, so the createRequire'd CJS @@ -60,6 +63,7 @@ describe('ensure-native-runtime', () => { expect(result.stderr).toContain('node-gyp stderr complete\n') const log = readFileSync(logPath, 'utf8') expect(log).toContain(`node-gyp rebuild --arch=${process.arch}\n`) + expect(log).toContain(`node-gyp timeout=${nodeGypRebuildTimeoutMs('node-pty')}\n`) expect(log).toContain(`trackFileAccess=${process.platform === 'win32' ? 'false' : ''}\n`) expect(log).toContain(join('node_modules', 'node-pty')) if (process.platform === 'linux') { @@ -275,7 +279,14 @@ function mkTempProject() { copyScriptWithLocalModules(sourceScriptPath, join(projectDir, 'config', 'scripts')) writeFileSync( join(projectDir, 'config', 'scripts', 'script-child-process.mjs'), - `export { describeProcessFailure, runProcessSync } from ${JSON.stringify(new URL('./script-child-process.mjs', import.meta.url).href)}\n` + `import { appendFileSync } from 'node:fs' +import { describeProcessFailure, runProcessSync as run } from ${JSON.stringify(new URL('./script-child-process.mjs', import.meta.url).href)} +export { describeProcessFailure } +export function runProcessSync(options) { + appendFileSync(process.env.ORCA_NATIVE_TEST_LOG, \`node-gyp timeout=\${options.timeoutMs}\\n\`) + return run(options) +} +` ) for (const name of REQUIRED_CJS_SIBLINGS) { copyFileSync( diff --git a/config/scripts/windows-process-tree-gyp-rebuild.mjs b/config/scripts/windows-process-tree-gyp-rebuild.mjs index c64df18fa899..f782334628d0 100644 --- a/config/scripts/windows-process-tree-gyp-rebuild.mjs +++ b/config/scripts/windows-process-tree-gyp-rebuild.mjs @@ -65,6 +65,16 @@ export function nodeGypRebuildInvocation( } } +export function nodeGypRebuildTimeoutMs( + moduleName, + { platform = process.platform, arch = process.arch, ci = process.env.CI } = {} +) { + // Cold headers and toolchain discovery consumed over four minutes on Windows ARM CI. + return moduleName === 'node-pty' && platform === 'win32' && arch === 'arm64' && ci === 'true' + ? 600_000 + : 300_000 +} + /** The binary the addon actually loads. */ export function windowsProcessTreeAddonPath(packageDir = WINDOWS_PROCESS_TREE_PACKAGE_DIR) { return join(packageDir, 'build', 'Release', 'windows_process_tree.node') diff --git a/config/scripts/windows-process-tree-gyp-rebuild.test.mjs b/config/scripts/windows-process-tree-gyp-rebuild.test.mjs index 73756af16591..620deba58c4e 100644 --- a/config/scripts/windows-process-tree-gyp-rebuild.test.mjs +++ b/config/scripts/windows-process-tree-gyp-rebuild.test.mjs @@ -16,6 +16,7 @@ import { assertWindowsProcessTreeRuntimeCreationTime, inspectWindowsProcessTreeAddon, nodeGypRebuildInvocation, + nodeGypRebuildTimeoutMs, stageWindowsProcessTreeNodeAddonApiHeaders, WINDOWS_PROCESS_TREE_NODE_ADDON_API_HEADERS, WINDOWS_PROCESS_TREE_PACKAGE_DIR @@ -54,6 +55,33 @@ describe('windows-process-tree node-gyp rebuild', () => { }) }) + it('allows cold setup and compilation only for node-pty on a Windows ARM CI host', () => { + expect( + nodeGypRebuildTimeoutMs('node-pty', { platform: 'win32', arch: 'arm64', ci: 'true' }) + ).toBe(600_000) + }) + + it.each([ + { moduleName: 'node-pty', platform: 'win32', arch: 'x64', ci: 'true' }, + { moduleName: 'node-pty', platform: 'linux', arch: 'arm64', ci: 'true' }, + { moduleName: 'node-pty', platform: 'darwin', arch: 'arm64', ci: 'true' }, + { moduleName: 'node-pty', platform: 'win32', arch: 'arm64', ci: '' }, + { moduleName: 'node-pty', platform: 'win32', arch: 'arm64', ci: 'false' }, + { moduleName: 'node-pty', platform: 'win32', arch: 'arm64', ci: '1' }, + { moduleName: '@orca/windows-registry', platform: 'win32', arch: 'arm64', ci: 'true' }, + { moduleName: '@vscode/windows-process-tree', platform: 'win32', arch: 'arm64', ci: 'true' } + ])('keeps the five-minute bound for $moduleName on $platform/$arch with CI=$ci', (host) => { + expect(nodeGypRebuildTimeoutMs(host.moduleName, host)).toBe(300_000) + }) + + it('uses the execution host rather than an ARM cross-compilation target', () => { + const { args } = nodeGypRebuildInvocation('arm64', import.meta.dirname) + expect(args).toContain('--arch=arm64') + expect( + nodeGypRebuildTimeoutMs('node-pty', { platform: 'win32', arch: 'x64', ci: 'true' }) + ).toBe(300_000) + }) + it('copies node-addon-api headers into the patched include dir', () => { const packageDir = mkdtempSync(join(tmpdir(), 'orca-windows-process-tree-headers-')) try {