From 6546f54d53446015f10681fffff9fe895e460232 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Tue, 14 Jul 2026 13:57:00 -0400 Subject: [PATCH] build(ssh): make copied MSVC inputs deterministic Co-authored-by: Orca --- ...lay-node-pty-windows-build-determinism.mjs | 43 ++++++++++++++++--- ...ode-pty-windows-build-determinism.test.mjs | 15 ++++++- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/config/scripts/ssh-relay-node-pty-windows-build-determinism.mjs b/config/scripts/ssh-relay-node-pty-windows-build-determinism.mjs index 199375674d7..eefbd726877 100644 --- a/config/scripts/ssh-relay-node-pty-windows-build-determinism.mjs +++ b/config/scripts/ssh-relay-node-pty-windows-build-determinism.mjs @@ -1,6 +1,30 @@ import { readFile, writeFile } from 'node:fs/promises' import { join } from 'node:path' +const COMPILER_OPTIONS = ` 'VCCLCompilerTool': { + 'AdditionalOptions': [ + '/guard:cf', + '/sdl', + '/W3', + '/w34244', + '/w34267', + '/ZH:SHA_256' + ] + }` + +const REPRODUCIBLE_COMPILER_OPTIONS = ` 'VCCLCompilerTool': { + 'AdditionalOptions': [ + '/guard:cf', + '/sdl', + '/W3', + '/w34244', + '/w34267', + '/ZH:SHA_256', + '/Brepro', + '/experimental:deterministic' + ] + }` + const LINKER_OPTIONS = ` 'VCLinkerTool': { 'AdditionalOptions': [ '/DYNAMICBASE', @@ -12,7 +36,8 @@ const REPRODUCIBLE_LINKER_OPTIONS = ` 'VCLinkerTool': { 'AdditionalOptions': [ '/DYNAMICBASE', '/guard:cf', - '/Brepro' + '/Brepro', + '/experimental:deterministic' ] }` @@ -22,10 +47,18 @@ export async function applyWindowsNodePtyBuildDeterminism({ nodePtyDirectory, tu } const bindingPath = join(nodePtyDirectory, 'binding.gyp') const source = await readFile(bindingPath, 'utf8') - if (source.split(LINKER_OPTIONS).length !== 2 || source.includes("'/Brepro'")) { - throw new Error('node-pty Windows linker settings do not match the reviewed source') + if ( + source.split(COMPILER_OPTIONS).length !== 2 || + source.split(LINKER_OPTIONS).length !== 2 || + source.includes("'/Brepro'") || + source.includes("'/experimental:deterministic'") + ) { + throw new Error('node-pty Windows build settings do not match the reviewed source') } - // Why: MSVC otherwise embeds per-build timestamps/identities in the copied artifact binaries. - await writeFile(bindingPath, source.replace(LINKER_OPTIONS, REPRODUCIBLE_LINKER_OPTIONS), 'utf8') + // Why: ARM64 compiler output changes even with a reproducible linker unless both stages opt in. + const reproducibleSource = source + .replace(COMPILER_OPTIONS, REPRODUCIBLE_COMPILER_OPTIONS) + .replace(LINKER_OPTIONS, REPRODUCIBLE_LINKER_OPTIONS) + await writeFile(bindingPath, reproducibleSource, 'utf8') return true } diff --git a/config/scripts/ssh-relay-node-pty-windows-build-determinism.test.mjs b/config/scripts/ssh-relay-node-pty-windows-build-determinism.test.mjs index 24985dc889a..85ea7dff2ad 100644 --- a/config/scripts/ssh-relay-node-pty-windows-build-determinism.test.mjs +++ b/config/scripts/ssh-relay-node-pty-windows-build-determinism.test.mjs @@ -19,13 +19,24 @@ async function copiedSource() { } describe('SSH relay Windows node-pty build determinism', () => { - it('adds deterministic MSVC linking only to the copied Windows source', async () => { + it('adds deterministic MSVC compilation and linking only to the copied Windows source', async () => { const directory = await copiedSource() + const repositorySource = await readFile(resolve('node_modules/node-pty/binding.gyp'), 'utf8') await expect( applyWindowsNodePtyBuildDeterminism({ nodePtyDirectory: directory, tuple: 'win32-arm64' }) ).resolves.toBe(true) const source = await readFile(join(directory, 'binding.gyp'), 'utf8') - expect(source.match(/'\/Brepro'/g)).toHaveLength(1) + const compilerStart = source.indexOf("'VCCLCompilerTool'") + const linkerStart = source.indexOf("'VCLinkerTool'") + const compilerOptions = source.slice(compilerStart, linkerStart) + const linkerOptions = source.slice(linkerStart) + expect(compilerOptions).toContain("'/Brepro'") + expect(compilerOptions).toContain("'/experimental:deterministic'") + expect(linkerOptions).toContain("'/Brepro'") + expect(linkerOptions).toContain("'/experimental:deterministic'") + expect(await readFile(resolve('node_modules/node-pty/binding.gyp'), 'utf8')).toBe( + repositorySource + ) }) it('leaves POSIX source untouched and rejects an unexpected Windows source shape', async () => {