mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
test(linux): judge per-arch vendored binaries against their own path
The first CI run of the architecture gate failed the x64 package job on `@parcel/watcher-linux-arm64-glibc/watcher.node`. That binary is arm64 on purpose: the package ships every architecture and its loader picks the match, so its presence in an x64 build is correct. Judge a binary against the architecture its own path names, falling back to the slice when the path names none. That keeps the case this gate exists for -- `bin/linux-arm64-*/node-pty.node` holding an x86-64 binary, which is what shipped to a Raspberry Pi 5 -- while letting multi-arch dependencies through. Dry-run over the real dependency tree flags nothing for either target arch.
This commit is contained in:
@@ -199,8 +199,28 @@ function readElfMachine(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
// Arch tokens that appear in vendored per-architecture package/directory names.
|
||||
const ARCH_TOKEN_PATTERN = /(?:^|[^a-z0-9])(arm64|aarch64|x64|x86_64)(?:[^a-z0-9]|$)/i
|
||||
const ARCH_BY_TOKEN = Object.freeze({ arm64: 'arm64', aarch64: 'arm64', x64: 'x64', x86_64: 'x64' })
|
||||
|
||||
/**
|
||||
* The architecture a path advertises, or null when it advertises none.
|
||||
*
|
||||
* Why this matters: some dependencies ship every architecture and let their loader pick
|
||||
* (`@parcel/watcher-linux-arm64-glibc/watcher.node` is arm64 on purpose inside an x64 build). Those
|
||||
* must be judged against the arch their own path declares, not against the slice.
|
||||
*/
|
||||
function declaredArchFromPath(filePath) {
|
||||
const match = ARCH_TOKEN_PATTERN.exec(filePath)
|
||||
return match ? ARCH_BY_TOKEN[match[1].toLowerCase()] : null
|
||||
}
|
||||
|
||||
function findArchViolation(filePath, targetArch) {
|
||||
const expected = ELF_MACHINE_BY_ARCH[targetArch]
|
||||
// A path that names an architecture is judged against that name, so a per-arch vendored package
|
||||
// is fine while `bin/linux-arm64-.../node-pty.node` holding an x86-64 binary is still caught.
|
||||
const declared = declaredArchFromPath(filePath)
|
||||
const expectedArch = declared ?? targetArch
|
||||
const expected = ELF_MACHINE_BY_ARCH[expectedArch]
|
||||
if (expected === undefined) {
|
||||
return null
|
||||
}
|
||||
@@ -208,7 +228,12 @@ function findArchViolation(filePath, targetArch) {
|
||||
if (machine === null || machine === expected) {
|
||||
return null
|
||||
}
|
||||
return { machine, actual: ARCH_BY_ELF_MACHINE[machine] ?? `0x${machine.toString(16)}` }
|
||||
return {
|
||||
machine,
|
||||
actual: ARCH_BY_ELF_MACHINE[machine] ?? `0x${machine.toString(16)}`,
|
||||
expectedArch,
|
||||
declared: declared !== null
|
||||
}
|
||||
}
|
||||
|
||||
function isElfFile(filePath) {
|
||||
@@ -384,7 +409,8 @@ function verifyLinuxGlibcFloor(rootDir, options = {}) {
|
||||
const detail = archOffenders
|
||||
.map(
|
||||
({ filePath, violation }) =>
|
||||
` ${relative(rootDir, filePath) || filePath} is ${violation.actual}`
|
||||
` ${relative(rootDir, filePath) || filePath} is ${violation.actual}, expected ` +
|
||||
`${violation.expectedArch}${violation.declared ? ' (from its own path)' : ''}`
|
||||
)
|
||||
.join('\n')
|
||||
throw new Error(
|
||||
@@ -446,6 +472,7 @@ module.exports = {
|
||||
MIN_GLIBC,
|
||||
ELF_MACHINE_BY_ARCH,
|
||||
readElfMachine,
|
||||
declaredArchFromPath,
|
||||
findArchViolation,
|
||||
VERSION_FLOORS,
|
||||
FLOOR_LABEL,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { describe, expect, it } from 'vitest'
|
||||
const require = createRequire(import.meta.url)
|
||||
const {
|
||||
readElfMachine,
|
||||
declaredArchFromPath,
|
||||
findArchViolation,
|
||||
ELF_MACHINE_BY_ARCH,
|
||||
parseGlibcVersion,
|
||||
@@ -348,6 +349,32 @@ describe('bundled native binary architecture', () => {
|
||||
|
||||
// The observed failure: cross-building arm64 on an x64 host packed an x86-64 pty.node, whose
|
||||
// symbol versions are valid, so every other gate here passed it.
|
||||
// Real CI hit: @parcel/watcher ships every architecture and its loader picks the match, so the
|
||||
// arm64 copy is present in an x64 build on purpose.
|
||||
it('accepts a per-arch vendored package that matches its own path', async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'orca-elf-arch-'))
|
||||
const pkg = join(dir, '@parcel', 'watcher-linux-arm64-glibc')
|
||||
await mkdir(pkg, { recursive: true })
|
||||
const file = join(pkg, 'watcher.node')
|
||||
await writeFile(file, elfHeader(ELF_MACHINE_BY_ARCH.arm64))
|
||||
expect(declaredArchFromPath(file)).toBe('arm64')
|
||||
expect(findArchViolation(file, 'x64')).toBeNull()
|
||||
await rm(dir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
// But a path that names an arch must actually hold it — this is the Pi 5 failure.
|
||||
it('flags a binary that contradicts the architecture its own path names', async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'orca-elf-arch-'))
|
||||
const nested = join(dir, 'bin', 'linux-arm64-148')
|
||||
await mkdir(nested, { recursive: true })
|
||||
const file = join(nested, 'node-pty.node')
|
||||
await writeFile(file, elfHeader(ELF_MACHINE_BY_ARCH.x64))
|
||||
expect(findArchViolation(file, 'arm64')).toMatchObject({ actual: 'x64', expectedArch: 'arm64' })
|
||||
// Still caught even when the slice being built is x64.
|
||||
expect(findArchViolation(file, 'x64')).toMatchObject({ actual: 'x64', expectedArch: 'arm64' })
|
||||
await rm(dir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it('flags an x86-64 binary in an arm64 slice', async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'orca-elf-arch-'))
|
||||
const file = join(dir, 'pty.node')
|
||||
|
||||
Reference in New Issue
Block a user