build.ts 4.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. /**
  2. * Build every native tool this host can build, into its per-platform
  3. * package.
  4. *
  5. * Targets are derived from the checked-in matrix: each
  6. * `packages/<name>/prebuilds.json` whose `platform` matches this host names
  7. * the binaries to produce; the TOOLS table below maps each `tool` to its C
  8. * source. Builds are NATIVE-ONLY — each Linux architecture compiles its own
  9. * binary with the distro's `musl-gcc` (static musl: runs on glibc and musl
  10. * distros alike, no loader or libc expectations on the consumer host), and
  11. * CI's per-arch runners are the builders of record. No cross toolchain
  12. * exists here on purpose: native runners replace it, and the audit surface
  13. * is the reviewed C source plus the CI job that built the binary.
  14. *
  15. * Binaries land in `packages/<name>/bin/` — git-ignored (root
  16. * `.gitignore`), packed into the platform package's npm tarball behind its
  17. * `prepack` gate (`scripts/verify-launcher-binary.mjs`).
  18. *
  19. * Run: `pnpm run build:native` (Linux with musl-gcc on PATH:
  20. * `apt-get install musl-tools`). Non-Linux hosts fail fast — no platform
  21. * package exists for them to build.
  22. */
  23. import { spawnSync } from 'node:child_process'
  24. import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs'
  25. import { basename, dirname, join, resolve } from 'node:path'
  26. /** Each native tool's C source, keyed by the `tool` field in prebuilds.json. */
  27. const TOOLS: Record<string, { source: string }> = {
  28. 'landlock-run': { source: 'packages/entry/src/main.c' },
  29. }
  30. const repoRoot = resolve(import.meta.dirname, '..')
  31. if (process.platform !== 'linux') {
  32. 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.`)
  33. process.exit(1)
  34. }
  35. const hostPlatform = `linux-${process.arch}`
  36. /** This host's platform packages, from the checked-in matrix. */
  37. const targets: { packageDir: string; tool: string; binaryPath: string; kind: string }[] = []
  38. const packagesRoot = join(repoRoot, 'packages')
  39. for (const name of readdirSync(packagesRoot).sort()) {
  40. const prebuildsFile = join(packagesRoot, name, 'prebuilds.json')
  41. if (!existsSync(prebuildsFile)) continue
  42. const prebuilds = JSON.parse(readFileSync(prebuildsFile, 'utf8')) as {
  43. platform: string
  44. binaries: { tool: string; kind: string; path: string }[]
  45. }
  46. if (prebuilds.platform !== hostPlatform) continue
  47. for (const binary of prebuilds.binaries) {
  48. targets.push({ packageDir: join(packagesRoot, name), tool: binary.tool, binaryPath: binary.path, kind: binary.kind })
  49. }
  50. }
  51. if (targets.length === 0) {
  52. console.error(`build: no platform package declares binaries for ${hostPlatform} — supported platforms are the packages/*/prebuilds.json "platform" values.`)
  53. process.exit(1)
  54. }
  55. for (const target of targets) {
  56. const tool = TOOLS[target.tool]
  57. if (tool === undefined) {
  58. console.error(`build: prebuilds.json names unknown tool "${target.tool}" — add it to the TOOLS table in scripts/build.ts.`)
  59. process.exit(1)
  60. }
  61. if (target.kind !== 'static-musl') {
  62. console.error(`build: unknown binary kind "${target.kind}" — the only toolchain here is static musl.`)
  63. process.exit(1)
  64. }
  65. const binary = join(target.packageDir, target.binaryPath)
  66. mkdirSync(dirname(binary), { recursive: true })
  67. // -static against musl: self-contained, no loader/libc expectations on the
  68. // consumer host. -Werror is safe to keep hard: CI pins the builder images,
  69. // and a new warning on a toolchain bump deserves a look, not a pass.
  70. const result = spawnSync('musl-gcc', [
  71. '-std=c11', '-Os', '-Wall', '-Wextra', '-Werror', '-static', '-s',
  72. '-o', binary, join(repoRoot, tool.source),
  73. ], { stdio: ['ignore', 'inherit', 'inherit'] })
  74. if (result.error !== undefined || result.status !== 0) {
  75. console.error('build: musl-gcc failed' +
  76. (result.error ? ` (${result.error.message} — is musl-tools installed?)` : ''))
  77. process.exit(1)
  78. }
  79. console.log(`build: built ${basename(target.packageDir)}/${target.binaryPath}`)
  80. }