mirror of
https://github.com/stablyai/orca.git
synced 2026-10-09 00:02:39 +00:00
feat(orcad): build @parcel/watcher into the glibc 2.17 compat slot, so CentOS 7 runs managed orcad (#25199)
The compat target swapped in only node-pty, so it shipped the base target's upstream watcher.node, which needs GLIBCXX_3.4.20; CentOS 7 stops at 3.4.19. orcad's preflight refused it, and relay rung B lost file watching without saying so. The compat slot now compiles @parcel/watcher from the package's own sources against the pinned headers with the C++ runtime static, like node-pty. The slot gates (glibc 2.17 symbol floor, no shared libstdc++, N-API 8) and the smoke load cover it, the template stages it into the compat target, and both orcad and relay rung B pick it up from there. COMPAT_SLOT_ADDONS names a compat slot's own addons: a compat slot missing one fails --require-slots, and a compat template target that would ship any native file without a compat build fails the template build. The CentOS 7 cell expects an activated managed server again, and every launched relay cell loads its watcher directly. Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
* Build one node-pty prebuilt for the CURRENT platform/arch/libc and file it in orcad's
|
||||
* prebuilds matrix, so a deployment target needs no C/C++ toolchain.
|
||||
*
|
||||
* node-pty is the only ABI-sensitive native module orcad requires. It is also PATCHED in
|
||||
* node-pty is the only ABI-sensitive native module every slot builds; a compat slot also
|
||||
* builds the addons in COMPAT_SLOT_ADDONS (orcad-prebuild-compat-addons.mjs). node-pty is PATCHED in
|
||||
* this repo (config/patches/node-pty@1.1.0.patch), and that patch is the glibc-floor fix:
|
||||
* `.symver` pins on openpty/forkpty/pthread_sigmask plus the `--no-as-needed` ldflags that
|
||||
* keep libutil/libpthread in DT_NEEDED. An upstream prebuilt has none of it and reproduces
|
||||
@@ -40,13 +41,14 @@ import {
|
||||
highestGlibcNeed,
|
||||
isCompatSlot,
|
||||
mergeManifest,
|
||||
prebuildCompileGypi,
|
||||
readManifest,
|
||||
sha256Of,
|
||||
slotGlibcFloor,
|
||||
slotSourceFiles,
|
||||
SLOT_NAPI_VERSION
|
||||
} from './orcad-prebuild-slot-contents.mjs'
|
||||
import { compileCompatAddons } from './orcad-prebuild-compat-addons.mjs'
|
||||
import { nodeGypRebuild, stageNodeAddonApi } from './orcad-prebuild-node-gyp.mjs'
|
||||
import { ensurePinnedNodeExecutable, preparePinnedNodeDir } from './pinned-node-downloads.mjs'
|
||||
|
||||
export { readManifest }
|
||||
@@ -203,44 +205,23 @@ async function compileNodePty(sourceDir, slot) {
|
||||
)
|
||||
const ptySourcePath = join(stagedDir, 'src', 'unix', 'pty.cc')
|
||||
writeFileSync(ptySourcePath, ptySourceForLibc(readFileSync(ptySourcePath, 'utf8'), libc))
|
||||
const addonApiDir = dirname(
|
||||
require.resolve('node-addon-api/package.json', { paths: [sourceDir] })
|
||||
)
|
||||
cpSync(addonApiDir, join(stagedDir, 'node_modules', 'node-addon-api'), {
|
||||
recursive: true,
|
||||
dereference: true
|
||||
})
|
||||
stageNodeAddonApi(sourceDir, stagedDir)
|
||||
if (process.platform === 'win32') {
|
||||
require('./node-pty-job-ownership.cjs').assertNodePtySourceDeniesMsysBreakaway({
|
||||
nodePtyDir: stagedDir
|
||||
})
|
||||
}
|
||||
const compileGypi = join(workDir, 'prebuild-compile.gypi')
|
||||
writeFileSync(compileGypi, prebuildCompileGypi({ staticCxxRuntime: isCompatSlot(slot) }))
|
||||
const nodeDir = await preparePinnedNodeDir({ target: slot, workDir: join(workDir, 'nodedir') })
|
||||
|
||||
console.log(
|
||||
`[orcad-prebuilds] compiling patched node-pty for ${slot} against Node ${NODE_RUNTIME_PIN.version} headers, N-API ${SLOT_NAPI_VERSION} ...`
|
||||
)
|
||||
const { runProcessSync } = await import('./script-child-process.mjs')
|
||||
const result = runProcessSync({
|
||||
program: process.execPath,
|
||||
args: [
|
||||
join(ROOT, 'node_modules', 'node-gyp', 'bin', 'node-gyp.js'),
|
||||
'rebuild',
|
||||
`--nodedir=${nodeDir}`,
|
||||
'--',
|
||||
'-I',
|
||||
compileGypi
|
||||
],
|
||||
cwd: stagedDir,
|
||||
stdio: 'inherit',
|
||||
timeoutMs: null
|
||||
const buildDir = await nodeGypRebuild({
|
||||
stagedDir,
|
||||
workDir,
|
||||
nodeDir,
|
||||
staticCxxRuntime: isCompatSlot(slot)
|
||||
})
|
||||
if (result.code !== 0) {
|
||||
throw new Error(`[orcad-prebuilds] node-gyp rebuild failed (status ${result.code})`)
|
||||
}
|
||||
const buildDir = join(stagedDir, 'build', 'Release')
|
||||
if (process.platform === 'win32') {
|
||||
require('./node-pty-job-ownership.cjs').assertRebuiltConptyDeniesMsysBreakaway({
|
||||
nodePtyDir: stagedDir,
|
||||
@@ -248,7 +229,7 @@ async function compileNodePty(sourceDir, slot) {
|
||||
crossHost: false
|
||||
})
|
||||
}
|
||||
return buildDir
|
||||
return { buildDir, nodeDir }
|
||||
}
|
||||
|
||||
function requireSlots(slots) {
|
||||
@@ -310,16 +291,22 @@ async function build() {
|
||||
const slot = slotName()
|
||||
assertCompatSlotHost(slot, { platform: process.platform, arch: process.arch, libc: detectLibc() })
|
||||
const slotDir = join(PREBUILDS_DIR, slot)
|
||||
const buildDir = await compileNodePty(sourceDir, slot)
|
||||
const { buildDir, nodeDir } = await compileNodePty(sourceDir, slot)
|
||||
const compatAddons = isCompatSlot(slot)
|
||||
? await compileCompatAddons({ slot, workDir: join(WORK_DIR, slot), nodeDir })
|
||||
: []
|
||||
|
||||
rmSync(slotDir, { recursive: true, force: true })
|
||||
const files = {}
|
||||
for (const [relative, source] of slotSourceFiles({
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
buildDir,
|
||||
nodePtyDir: sourceDir
|
||||
})) {
|
||||
for (const [relative, source] of [
|
||||
...slotSourceFiles({
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
buildDir,
|
||||
nodePtyDir: sourceDir
|
||||
}),
|
||||
...compatAddons
|
||||
]) {
|
||||
if (!existsSync(source)) {
|
||||
throw new Error(`[orcad-prebuilds] ${slot} needs ${relative}, but ${source} is missing`)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,12 @@ import {
|
||||
pinnedNodeRuntimeAsset
|
||||
} from '../../src/shared/node-runtime-pin.ts'
|
||||
import { ORCAD_PREBUILDS_DIR } from './build-orcad-prebuilds.mjs'
|
||||
import { findSlotProblems, readManifest } from './orcad-prebuild-slot-contents.mjs'
|
||||
import {
|
||||
COMPAT_SLOT_ADDONS,
|
||||
findCompatAddonGaps,
|
||||
findSlotProblems,
|
||||
readManifest
|
||||
} from './orcad-prebuild-slot-contents.mjs'
|
||||
import { runProcessSync } from './script-child-process.mjs'
|
||||
import { verifyPackagedOrcadTemplate } from './verify-packaged-orcad-template.cjs'
|
||||
|
||||
@@ -121,8 +126,8 @@ export function requestedTemplateTargets(argv = process.argv) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A compat target (design D6 rung B) is its base target's package with the compat node-pty
|
||||
* slot and runtime marker swapped in; everything else is target-independent or libc-static.
|
||||
* A compat target (design D6 rung B) is its base target's package with the compat slot's addons
|
||||
* and runtime marker swapped in; everything else is target-independent or libc-static.
|
||||
* Omitted, not failed, when this build has no compat slot: rung B then refuses as unavailable.
|
||||
*/
|
||||
function stageCompatTarget(compat, basePackageDir) {
|
||||
@@ -136,12 +141,21 @@ function stageCompatTarget(compat, basePackageDir) {
|
||||
return null
|
||||
}
|
||||
const destination = join(outputDir, ORCAD_TEMPLATE_TARGETS_DIR, compat)
|
||||
const slotFiles = new Map(
|
||||
orcadNodePtySlotFiles(compat).map((file) => [
|
||||
const slotFiles = new Map([
|
||||
...orcadNodePtySlotFiles(compat).map((file) => [
|
||||
`${ORCAD_NODE_PTY_DIR}/build/Release/${file}`,
|
||||
join(ORCAD_PREBUILDS_DIR, compat, ...file.split('/'))
|
||||
]),
|
||||
...Object.entries(COMPAT_SLOT_ADDONS).map(([file, shipped]) => [
|
||||
shipped,
|
||||
join(ORCAD_PREBUILDS_DIR, compat, ...file.split('/'))
|
||||
])
|
||||
)
|
||||
])
|
||||
// Why fatal: the base binary would pass every check here and fail only on a compat host.
|
||||
const gaps = findCompatAddonGaps(orcadTemplateTargetFilenames(compat), slotFiles)
|
||||
if (gaps.length > 0) {
|
||||
throw new Error(`compat target ${compat} would ship base-target addons: ${gaps.join(', ')}`)
|
||||
}
|
||||
const files = {}
|
||||
for (const filename of orcadTemplateTargetFilenames(compat)) {
|
||||
const staged = join(destination, ...filename.split('/'))
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { mergeOrcadPrebuildTrees } from './merge-orcad-prebuilds.mjs'
|
||||
import { findSlotProblems, mergeManifest, sha256Of } from './orcad-prebuild-slot-contents.mjs'
|
||||
import {
|
||||
COMPAT_SLOT_ADDONS,
|
||||
findSlotProblems,
|
||||
isCompatSlot,
|
||||
mergeManifest,
|
||||
sha256Of
|
||||
} from './orcad-prebuild-slot-contents.mjs'
|
||||
|
||||
const dirs = []
|
||||
function temp() {
|
||||
@@ -20,15 +26,20 @@ afterEach(() => {
|
||||
/** One CI lane's `out/orcad-prebuilds`: a single slot plus its manifest. */
|
||||
function laneTree(slot, { version = '1.1.0', nodeHeaders = '24.21.0', bytes = slot } = {}) {
|
||||
const dir = temp()
|
||||
mkdirSync(join(dir, slot), { recursive: true })
|
||||
const binary = join(dir, slot, 'pty.node')
|
||||
writeFileSync(binary, bytes)
|
||||
const files = {}
|
||||
// A compat slot also carries its own addons.
|
||||
for (const file of ['pty.node', ...(isCompatSlot(slot) ? Object.keys(COMPAT_SLOT_ADDONS) : [])]) {
|
||||
const binary = join(dir, slot, ...file.split('/'))
|
||||
mkdirSync(dirname(binary), { recursive: true })
|
||||
writeFileSync(binary, bytes)
|
||||
files[file] = sha256Of(binary)
|
||||
}
|
||||
const manifest = mergeManifest(null, {
|
||||
slot,
|
||||
version,
|
||||
napi: 8,
|
||||
nodeHeaders,
|
||||
entry: { napi: 8, files: { 'pty.node': sha256Of(binary) } }
|
||||
entry: { napi: 8, files }
|
||||
})
|
||||
writeFileSync(join(dir, 'manifest.json'), JSON.stringify(manifest))
|
||||
return dir
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* The addons a compat slot builds beside node-pty (design D6 rung B). The default slots take
|
||||
* @parcel/watcher's upstream prebuild, which needs a newer libstdc++ than a glibc 2.17 host has,
|
||||
* so the compat slot compiles it from the package's own sources with the C++ runtime static.
|
||||
*/
|
||||
import { cpSync, mkdirSync, rmSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { NODE_RUNTIME_PIN } from '../../src/shared/node-runtime-pin.ts'
|
||||
import { nodeGypRebuild, stageNodeAddonApi } from './orcad-prebuild-node-gyp.mjs'
|
||||
import { COMPAT_SLOT_ADDONS, SLOT_NAPI_VERSION } from './orcad-prebuild-slot-contents.mjs'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
const BUILDERS = {
|
||||
'parcel-watcher/watcher.node': compileParcelWatcher
|
||||
}
|
||||
|
||||
/** `[slot-relative path, built file]` for every compat addon, compiled under `workDir`. */
|
||||
export async function compileCompatAddons({ slot, workDir, nodeDir }) {
|
||||
const built = []
|
||||
for (const relative of Object.keys(COMPAT_SLOT_ADDONS)) {
|
||||
const builder = BUILDERS[relative]
|
||||
if (!builder) {
|
||||
throw new Error(`[orcad-prebuilds] no builder for compat addon ${relative}`)
|
||||
}
|
||||
built.push([relative, await builder({ slot, workDir, nodeDir })])
|
||||
}
|
||||
return built
|
||||
}
|
||||
|
||||
async function compileParcelWatcher({ slot, workDir, nodeDir }) {
|
||||
const sourceDir = dirname(require.resolve('@parcel/watcher/package.json'))
|
||||
const addonWorkDir = join(workDir, 'parcel-watcher')
|
||||
const stagedDir = join(addonWorkDir, 'watcher')
|
||||
rmSync(addonWorkDir, { recursive: true, force: true })
|
||||
mkdirSync(stagedDir, { recursive: true })
|
||||
for (const entry of ['package.json', 'binding.gyp', 'src']) {
|
||||
cpSync(join(sourceDir, entry), join(stagedDir, entry), { recursive: true })
|
||||
}
|
||||
stageNodeAddonApi(sourceDir, stagedDir)
|
||||
console.log(
|
||||
`[orcad-prebuilds] compiling @parcel/watcher for ${slot} against Node ${NODE_RUNTIME_PIN.version} headers, N-API ${SLOT_NAPI_VERSION} ...`
|
||||
)
|
||||
const buildDir = await nodeGypRebuild({
|
||||
stagedDir,
|
||||
workDir: addonWorkDir,
|
||||
nodeDir,
|
||||
staticCxxRuntime: true
|
||||
})
|
||||
return join(buildDir, 'watcher.node')
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// node-gyp rebuild of one staged addon against the pinned Node headers, shared by every slot addon.
|
||||
import { cpSync, writeFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { dirname, join } from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { prebuildCompileGypi } from './orcad-prebuild-slot-contents.mjs'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const ROOT = join(import.meta.dirname, '..', '..')
|
||||
|
||||
/** Copies node-addon-api beside a staged addon, since scratch copies leave the pnpm tree behind. */
|
||||
export function stageNodeAddonApi(sourceDir, stagedDir) {
|
||||
const addonApiDir = dirname(
|
||||
require.resolve('node-addon-api/package.json', { paths: [sourceDir] })
|
||||
)
|
||||
cpSync(addonApiDir, join(stagedDir, 'node_modules', 'node-addon-api'), {
|
||||
recursive: true,
|
||||
dereference: true
|
||||
})
|
||||
}
|
||||
|
||||
export async function nodeGypRebuild({ stagedDir, workDir, nodeDir, staticCxxRuntime }) {
|
||||
const compileGypi = join(workDir, 'prebuild-compile.gypi')
|
||||
writeFileSync(compileGypi, prebuildCompileGypi({ staticCxxRuntime }))
|
||||
const { runProcessSync } = await import('./script-child-process.mjs')
|
||||
const result = runProcessSync({
|
||||
program: process.execPath,
|
||||
args: [
|
||||
join(ROOT, 'node_modules', 'node-gyp', 'bin', 'node-gyp.js'),
|
||||
'rebuild',
|
||||
`--nodedir=${nodeDir}`,
|
||||
'--',
|
||||
'-I',
|
||||
compileGypi
|
||||
],
|
||||
cwd: stagedDir,
|
||||
stdio: 'inherit',
|
||||
timeoutMs: null
|
||||
})
|
||||
if (result.code !== 0) {
|
||||
throw new Error(`[orcad-prebuilds] node-gyp rebuild failed (status ${result.code})`)
|
||||
}
|
||||
return join(stagedDir, 'build', 'Release')
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* What goes into one orcad node-pty prebuild slot, and the manifest that records it.
|
||||
* What goes into one orcad node-pty prebuild slot (plus a compat slot's own addons), and the
|
||||
* manifest that records it.
|
||||
*
|
||||
* The manifest is the loader's contract (src/main/orcad/node-pty-prebuilt-slot.ts): per-slot
|
||||
* N-API level, libc, the highest glibc symbol version the binaries need, and a sha256 per
|
||||
@@ -33,6 +34,23 @@ export const COMPAT_SLOTS = Object.freeze({
|
||||
'linux-x64-glibc217': Object.freeze({ platform: 'linux', arch: 'x64', libc: 'glibc' })
|
||||
})
|
||||
|
||||
/**
|
||||
* Native addons a compat slot builds beside node-pty, by slot-relative path, mapped to where an
|
||||
* orcad slot ships them. A compat target ships no native file without a compat build.
|
||||
*/
|
||||
export const COMPAT_SLOT_ADDONS = Object.freeze({
|
||||
'parcel-watcher/watcher.node': 'node_modules/@parcel/watcher/watcher.node'
|
||||
})
|
||||
|
||||
/**
|
||||
* The native files of a compat target with no compat build behind them: each would be the base
|
||||
* target's binary, built for a newer glibc and libstdc++ than the compat host has.
|
||||
* `compatSources` maps orcad slot paths to the compat slot files that fill them.
|
||||
*/
|
||||
export function findCompatAddonGaps(targetFilenames, compatSources) {
|
||||
return targetFilenames.filter((file) => file.endsWith('.node') && !compatSources.has(file))
|
||||
}
|
||||
|
||||
export function isCompatSlot(slot) {
|
||||
return Object.hasOwn(COMPAT_SLOTS, slot)
|
||||
}
|
||||
@@ -217,6 +235,13 @@ export function findSlotProblems(manifest, prebuildsDir, requiredSlots) {
|
||||
problems.push(`${slot}: not built`)
|
||||
continue
|
||||
}
|
||||
if (isCompatSlot(slot)) {
|
||||
for (const addon of Object.keys(COMPAT_SLOT_ADDONS)) {
|
||||
if (!Object.hasOwn(entry.files ?? {}, addon)) {
|
||||
problems.push(`${slot}/${addon}: not built`)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [file, expected] of Object.entries(entry.files ?? {})) {
|
||||
const path = join(prebuildsDir, slot, ...file.split('/'))
|
||||
if (!existsSync(path)) {
|
||||
|
||||
@@ -5,7 +5,9 @@ import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
assertCompatSlotHost,
|
||||
COMPAT_SLOT_ADDONS,
|
||||
COMPAT_SLOTS,
|
||||
findCompatAddonGaps,
|
||||
findPostBaselineNodeApiNames,
|
||||
findSharedCxxRuntimeNeeds,
|
||||
findSlotProblems,
|
||||
@@ -19,7 +21,10 @@ import {
|
||||
SLOT_NAPI_VERSION,
|
||||
windowsConptyRuntimeDir
|
||||
} from './orcad-prebuild-slot-contents.mjs'
|
||||
import { ORCAD_ADDON_NAPI_VERSION } from '../../src/shared/orcad-artifacts.ts'
|
||||
import {
|
||||
ORCAD_ADDON_NAPI_VERSION,
|
||||
orcadTemplateTargetFilenames
|
||||
} from '../../src/shared/orcad-artifacts.ts'
|
||||
|
||||
const floors = createRequire(import.meta.url)('./verify-linux-glibc-floor.cjs')
|
||||
const dirs = []
|
||||
@@ -232,4 +237,37 @@ describe('findSlotProblems', () => {
|
||||
'manifest.json is missing or not schema 2'
|
||||
])
|
||||
})
|
||||
|
||||
it('refuses a compat slot missing one of its own addons', () => {
|
||||
const dir = temp()
|
||||
const slot = 'linux-x64-glibc217'
|
||||
mkdirSync(join(dir, slot))
|
||||
writeFileSync(join(dir, slot, 'pty.node'), 'binary')
|
||||
const manifest = mergeManifest(
|
||||
null,
|
||||
next(slot, { entry: entry({ 'pty.node': sha256Of(join(dir, slot, 'pty.node')) }) })
|
||||
)
|
||||
expect(findSlotProblems(manifest, dir, [slot])).toEqual([
|
||||
`${slot}/parcel-watcher/watcher.node: not built`
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('compat addon coverage', () => {
|
||||
const nodePtySources = (target) =>
|
||||
orcadTemplateTargetFilenames(target).filter((file) => file.includes('node-pty/build/Release/'))
|
||||
|
||||
it('gives every native addon a compat target ships a compat build', () => {
|
||||
for (const compat of Object.keys(COMPAT_SLOTS)) {
|
||||
const sources = new Set([...nodePtySources(compat), ...Object.values(COMPAT_SLOT_ADDONS)])
|
||||
expect(findCompatAddonGaps(orcadTemplateTargetFilenames(compat), sources)).toEqual([])
|
||||
}
|
||||
})
|
||||
|
||||
it('names a native file that would ship as the base target build', () => {
|
||||
const target = 'linux-x64-glibc217'
|
||||
expect(
|
||||
findCompatAddonGaps(orcadTemplateTargetFilenames(target), new Set(nodePtySources(target)))
|
||||
).toEqual(['node_modules/@parcel/watcher/watcher.node'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const { join } = require('node:path')
|
||||
const { tmpdir } = require('node:os')
|
||||
|
||||
const [nodePtyDir, expectedVersion] = process.argv.slice(2)
|
||||
const [nodePtyDir, expectedVersion, ...addons] = process.argv.slice(2)
|
||||
if (!nodePtyDir || !expectedVersion) {
|
||||
throw new Error('usage: orcad-prebuild-smoke-child.cjs <node-pty dir> <expected node version>')
|
||||
}
|
||||
@@ -11,6 +11,10 @@ if (process.version !== `v${expectedVersion}`) {
|
||||
}
|
||||
|
||||
const pty = require(nodePtyDir)
|
||||
// A compat slot's own addons: loading proves their glibc and C++ runtime needs resolve here.
|
||||
for (const addon of addons) {
|
||||
require(addon)
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
// Loaded only by the non-DLL kill path; prove the shipped module still loads under this Node.
|
||||
const { loadNativeModule } = require(join(nodePtyDir, 'lib', 'utils'))
|
||||
|
||||
@@ -3,7 +3,12 @@ import { chmodSync, cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { NODE_RUNTIME_PIN } from '../../src/shared/node-runtime-pin.ts'
|
||||
import { findSlotProblems, readManifest } from './orcad-prebuild-slot-contents.mjs'
|
||||
import {
|
||||
COMPAT_SLOT_ADDONS,
|
||||
findSlotProblems,
|
||||
isCompatSlot,
|
||||
readManifest
|
||||
} from './orcad-prebuild-slot-contents.mjs'
|
||||
import { ensurePinnedNodeExecutable } from './pinned-node-downloads.mjs'
|
||||
import { runProcessSync } from './script-child-process.mjs'
|
||||
|
||||
@@ -28,6 +33,15 @@ export function stageSmokeNodePty({ slotDir, stageDir }) {
|
||||
return nodePtyDir
|
||||
}
|
||||
|
||||
/** stageSmokeNodePty copies the whole slot into build/Release, compat addons included. */
|
||||
function compatAddonPaths(slot, nodePtyDir) {
|
||||
return isCompatSlot(slot)
|
||||
? Object.keys(COMPAT_SLOT_ADDONS).map((file) =>
|
||||
join(nodePtyDir, 'build', 'Release', ...file.split('/'))
|
||||
)
|
||||
: []
|
||||
}
|
||||
|
||||
export async function runOrcadPrebuildSmoke({ slot, prebuildsDir }) {
|
||||
const problems = findSlotProblems(readManifest(prebuildsDir), prebuildsDir, [slot])
|
||||
if (problems.length > 0) {
|
||||
@@ -43,7 +57,8 @@ export async function runOrcadPrebuildSmoke({ slot, prebuildsDir }) {
|
||||
args: [
|
||||
join(import.meta.dirname, 'orcad-prebuild-smoke-child.cjs'),
|
||||
nodePtyDir,
|
||||
NODE_RUNTIME_PIN.version
|
||||
NODE_RUNTIME_PIN.version,
|
||||
...compatAddonPaths(slot, nodePtyDir)
|
||||
],
|
||||
timeoutMs: 60_000
|
||||
})
|
||||
|
||||
@@ -185,14 +185,8 @@ export const HOSTILE_HOST_CELLS: readonly HostileHostCell[] = [
|
||||
runtime: 'linux-x64-glibc217',
|
||||
refusals: [{ step: 'A', reason: 'libc_floor' }]
|
||||
},
|
||||
// Managed orcad picks the same compat runtime, but the template's @parcel/watcher needs a newer
|
||||
// libstdc++ than CentOS 7 ships, so its preflight refuses and the host keeps relay rung B.
|
||||
managed: {
|
||||
outcome: 'refused',
|
||||
runtime: 'linux-x64-glibc217',
|
||||
code: 'orcad_candidate_preflight_failed',
|
||||
relayRung: 'B'
|
||||
}
|
||||
// Managed orcad runs on the same compat runtime and slot, so an empty CentOS 7 host is managed.
|
||||
managed: { outcome: 'activated', runtime: 'linux-x64-glibc217' }
|
||||
},
|
||||
{
|
||||
// The client uploads the runtime over SSH, so a host that cannot reach nodejs.org still runs A.
|
||||
|
||||
@@ -15,6 +15,7 @@ vi.mock('electron', () => ({ app: { getAppPath: () => process.cwd() } }))
|
||||
|
||||
import { posix } from 'node:path'
|
||||
import { NODE_RUNTIME_PIN } from '../../shared/node-runtime-pin'
|
||||
import { ORCAD_PARCEL_WATCHER_NATIVE } from '../../shared/orcad-artifacts'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { HOSTILE_HOST_CELLS, selectHostileHostCells } from './ssh-hostile-host-cells'
|
||||
import { proveManagedOrcadCell } from './ssh-hostile-host-managed-orcad'
|
||||
@@ -105,6 +106,9 @@ describe('SSH relay hostile-host matrix', () => {
|
||||
if (target.kind === 'local-sshd' && target.cell.runsOn.platform === 'darwin') {
|
||||
await assertRunsWithoutQuarantine(target, launched.nodePath)
|
||||
}
|
||||
// The relay runs on without its watcher, so only a direct load proves the slot's build fits.
|
||||
const watcher = `${first.deployed?.remoteRelayDir}/${ORCAD_PARCEL_WATCHER_NATIVE}`
|
||||
await hostExec(target, `'${launched.nodePath}' -e "require('${watcher}')"`)
|
||||
} else if (
|
||||
cell.expect.outcome !== 'legacy_opt_out' &&
|
||||
cell.expect.refusals[0]?.reason === 'libc_floor'
|
||||
|
||||
Reference in New Issue
Block a user