mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
feat(native): add prebuilt Node-API flock support
This commit is contained in:
@@ -1,86 +1,92 @@
|
||||
/**
|
||||
* Build every native tool this host can build, into its per-platform
|
||||
* package.
|
||||
*
|
||||
* Targets are derived from the checked-in matrix: each
|
||||
* `packages/<name>/prebuilds.json` whose `platform` matches this host names
|
||||
* the binaries to produce; the TOOLS table below maps each `tool` to its C
|
||||
* source. Builds are NATIVE-ONLY — each Linux architecture compiles its own
|
||||
* binary with the distro's `musl-gcc` (static musl: runs on glibc and musl
|
||||
* distros alike, no loader or libc expectations on the consumer host), and
|
||||
* CI's per-arch runners are the builders of record. No cross toolchain
|
||||
* exists here on purpose: native runners replace it, and the audit surface
|
||||
* is the reviewed C source plus the CI job that built the binary.
|
||||
*
|
||||
* Binaries land in `packages/<name>/bin/` — git-ignored (root
|
||||
* `.gitignore`), packed into the platform package's npm tarball behind its
|
||||
* `prepack` gate (`scripts/verify-launcher-binary.mjs`).
|
||||
*
|
||||
* Run: `pnpm run build:native` (Linux with musl-gcc on PATH:
|
||||
* `apt-get install musl-tools`). Non-Linux hosts fail fast — no platform
|
||||
* package exists for them to build.
|
||||
* Build this host's declared system binaries. Landlock is a static musl
|
||||
* executable; flock uses stable Node-API with separate Linux libc builds.
|
||||
* Node headers come from the Node installation running this script.
|
||||
*/
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs'
|
||||
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync } from 'node:fs'
|
||||
import { basename, dirname, join, resolve } from 'node:path'
|
||||
import { parseArgs } from 'node:util'
|
||||
|
||||
/** Each native tool's C source, keyed by the `tool` field in prebuilds.json. */
|
||||
const TOOLS: Record<string, { source: string }> = {
|
||||
'landlock-run': { source: 'packages/entry/src/main.c' },
|
||||
const root = resolve(import.meta.dirname, '..')
|
||||
const { values } = parseArgs({ options: { 'host-addon-only': { type: 'boolean' } }, allowPositionals: false })
|
||||
const hostAddonOnly = values['host-addon-only'] === true
|
||||
const sources: Record<string, string> = {
|
||||
'landlock-run': 'packages/entry/src/main.c',
|
||||
flock: 'packages/entry/src/flock.c',
|
||||
}
|
||||
|
||||
const repoRoot = resolve(import.meta.dirname, '..')
|
||||
|
||||
if (process.platform !== 'linux') {
|
||||
console.error(`build: native tools are built natively per Linux architecture (no cross toolchain) — nothing to build on ${process.platform}. CI's per-arch runners build and rehearse every platform package.`)
|
||||
process.exit(1)
|
||||
}
|
||||
const hostPlatform = `linux-${process.arch}`
|
||||
|
||||
/** This host's platform packages, from the checked-in matrix. */
|
||||
const targets: { packageDir: string; tool: string; binaryPath: string; kind: string }[] = []
|
||||
const packagesRoot = join(repoRoot, 'packages')
|
||||
for (const name of readdirSync(packagesRoot).sort()) {
|
||||
const prebuildsFile = join(packagesRoot, name, 'prebuilds.json')
|
||||
if (!existsSync(prebuildsFile)) continue
|
||||
const prebuilds = JSON.parse(readFileSync(prebuildsFile, 'utf8')) as {
|
||||
platform: string
|
||||
binaries: { tool: string; kind: string; path: string }[]
|
||||
}
|
||||
if (prebuilds.platform !== hostPlatform) continue
|
||||
for (const binary of prebuilds.binaries) {
|
||||
targets.push({ packageDir: join(packagesRoot, name), tool: binary.tool, binaryPath: binary.path, kind: binary.kind })
|
||||
}
|
||||
}
|
||||
if (targets.length === 0) {
|
||||
console.error(`build: no platform package declares binaries for ${hostPlatform} — supported platforms are the packages/*/prebuilds.json "platform" values.`)
|
||||
process.exit(1)
|
||||
interface Binary {
|
||||
tool: string
|
||||
kind: string
|
||||
path: string
|
||||
napi?: number
|
||||
libc?: string
|
||||
}
|
||||
|
||||
for (const target of targets) {
|
||||
const tool = TOOLS[target.tool]
|
||||
if (tool === undefined) {
|
||||
console.error(`build: prebuilds.json names unknown tool "${target.tool}" — add it to the TOOLS table in scripts/build.ts.`)
|
||||
process.exit(1)
|
||||
}
|
||||
if (target.kind !== 'static-musl') {
|
||||
console.error(`build: unknown binary kind "${target.kind}" — the only toolchain here is static musl.`)
|
||||
process.exit(1)
|
||||
}
|
||||
const binary = join(target.packageDir, target.binaryPath)
|
||||
mkdirSync(dirname(binary), { recursive: true })
|
||||
|
||||
// -static against musl: self-contained, no loader/libc expectations on the
|
||||
// consumer host. -Werror is safe to keep hard: CI pins the builder images,
|
||||
// and a new warning on a toolchain bump deserves a look, not a pass.
|
||||
const result = spawnSync('musl-gcc', [
|
||||
'-std=c11', '-Os', '-Wall', '-Wextra', '-Werror', '-static', '-s',
|
||||
'-o', binary, join(repoRoot, tool.source),
|
||||
], { stdio: ['ignore', 'inherit', 'inherit'] })
|
||||
if (result.error !== undefined || result.status !== 0) {
|
||||
console.error('build: musl-gcc failed' +
|
||||
(result.error ? ` (${result.error.message} — is musl-tools installed?)` : ''))
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`build: built ${basename(target.packageDir)}/${target.binaryPath}`)
|
||||
if (process.platform !== 'linux' && process.platform !== 'darwin') {
|
||||
if (hostAddonOnly) process.exit(0)
|
||||
throw new Error('build: system binaries are built on Linux or macOS; no native target for this host')
|
||||
}
|
||||
const host = `${process.platform}-${process.arch}`
|
||||
const libc = process.platform === 'linux'
|
||||
? ((process.report.getReport() as { header: { glibcVersionRuntime?: string } }).header.glibcVersionRuntime ? 'glibc' : 'musl')
|
||||
: undefined
|
||||
const headers = resolve(dirname(process.execPath), '../include/node')
|
||||
let built = 0
|
||||
|
||||
for (const name of readdirSync(join(root, 'packages')).sort()) {
|
||||
const dir = join(root, 'packages', name)
|
||||
const metadata = join(dir, 'prebuilds.json')
|
||||
if (!existsSync(metadata)) continue
|
||||
const spec = JSON.parse(readFileSync(metadata, 'utf8')) as { platform: string; binaries: Binary[] }
|
||||
if (spec.platform !== host) continue
|
||||
|
||||
for (const binary of spec.binaries) {
|
||||
if (hostAddonOnly && (binary.kind !== 'node-api' || (binary.libc !== undefined && binary.libc !== libc))) continue
|
||||
const source = sources[binary.tool]
|
||||
if (source === undefined) throw new Error(`build: unknown tool ${binary.tool}`)
|
||||
const output = join(dir, binary.path)
|
||||
mkdirSync(dirname(output), { recursive: true })
|
||||
let compiler: string
|
||||
let flags: string[]
|
||||
|
||||
if (binary.kind === 'static-musl' && process.platform === 'linux' && binary.tool === 'landlock-run') {
|
||||
compiler = 'musl-gcc'
|
||||
flags = ['-std=c11', '-Os', '-Wall', '-Wextra', '-Werror', '-static', '-s']
|
||||
} else if (binary.kind === 'node-api' && binary.tool === 'flock' && binary.napi === 8) {
|
||||
if (!existsSync(join(headers, 'node_api.h'))) {
|
||||
throw new Error(`build: Node-API headers missing at ${headers}; use a Node installation with development headers`)
|
||||
}
|
||||
compiler = process.platform === 'linux' && binary.libc === 'musl' ? 'musl-gcc' : 'cc'
|
||||
flags = ['-std=c11', '-O2', '-Wall', '-Wextra', '-Werror', '-fPIC', '-fvisibility=hidden', '-DNAPI_VERSION=8', '-I', headers]
|
||||
if (process.platform === 'darwin') {
|
||||
if (binary.libc !== undefined) throw new Error('build: macOS flock does not select a Linux libc')
|
||||
flags.push('-bundle', '-undefined', 'dynamic_lookup', '-mmacosx-version-min=11.0')
|
||||
} else {
|
||||
if (binary.libc !== 'glibc' && binary.libc !== 'musl') {
|
||||
throw new Error('build: Linux flock must select glibc or musl')
|
||||
}
|
||||
flags.push('-shared')
|
||||
}
|
||||
} else {
|
||||
throw new Error(`build: unsupported ${binary.tool}/${binary.kind} target on ${host}`)
|
||||
}
|
||||
|
||||
mkdirSync(join(root, '.release'), { recursive: true })
|
||||
const temporary = mkdtempSync(join(root, '.release', 'native-build-'))
|
||||
try {
|
||||
const pending = join(temporary, basename(output))
|
||||
const result = spawnSync(compiler, [...flags, '-o', pending, join(root, source)], { stdio: 'inherit' })
|
||||
if (result.error) throw result.error
|
||||
if (result.status !== 0) throw new Error(`build: ${compiler} failed for ${binary.path}`)
|
||||
// Readers never see a truncated addon when source checks build concurrently.
|
||||
renameSync(pending, output)
|
||||
} finally {
|
||||
rmSync(temporary, { recursive: true, force: true })
|
||||
}
|
||||
console.log(`build: built ${basename(dir)}/${binary.path}`)
|
||||
built++
|
||||
}
|
||||
}
|
||||
if (built === 0) throw new Error(`build: no declared binaries for ${host}`)
|
||||
|
||||
Reference in New Issue
Block a user