/** * Build every native tool this host can build, into its per-platform * package. * * Targets are derived from the checked-in matrix: each * `packages//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 CI provenance. * * Binaries land in `packages//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. */ import { spawnSync } from 'node:child_process' import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs' import { basename, dirname, join, resolve } from 'node:path' /** Each native tool's C source, keyed by the `tool` field in prebuilds.json. */ const TOOLS: Record = { 'landlock-run': { source: 'packages/entry/src/main.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) } 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}`) }