Files
Chris Tate 3f66c8e20b Make LLVM the sole production backend
- Remove C emission, debugging options, and automatic backend fallback.
- Complete LLVM lowering and native bootstrap coverage across supported targets.
- Link executables and libraries with precompiled C runtime packs, including iOS and Android.
2026-09-29 20:54:09 -05:00

98 lines
4.1 KiB
JavaScript

#!/usr/bin/env node
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
const [mode, infoArg, outputArg] = process.argv.slice(2);
if ((mode !== "cc" && mode !== "ld") || !infoArg || !outputArg) {
console.error("usage: node link.mjs <cc|ld> <native-link-info.json> <output>");
process.exitCode = 2;
} else {
const info = JSON.parse(readFileSync(infoArg, "utf8"));
if (info.schema !== "scriptc.native-link-info.v1") {
throw new Error(`unsupported native link info schema: ${info.schema}`);
}
const runtimePackage = JSON.parse(
readFileSync(join(info.runtime_pack.root, "package.json"), "utf8"),
);
if (
runtimePackage.name !== info.runtime_pack.package ||
runtimePackage.version !== info.runtime_pack.version
) {
throw new Error(
`runtime pack identity mismatch: expected ${info.runtime_pack.package}@${info.runtime_pack.version} ` +
`at ${info.runtime_pack.root}, found ${runtimePackage.name ?? "<unnamed>"}@${runtimePackage.version ?? "<unversioned>"}`,
);
}
const run = (command, args, options = {}) => {
const result = spawnSync(command, args, { stdio: "inherit", ...options });
if (result.error) throw result.error;
if (result.status !== 0) throw new Error(`${command} exited ${result.status}`);
};
const capture = (command, args) => {
const result = spawnSync(command, args, { encoding: "utf8" });
if (result.error) throw result.error;
if (result.status !== 0) throw new Error(result.stderr || `${command} exited ${result.status}`);
return result.stdout.trim();
};
const infoPath = resolve(infoArg);
const output = resolve(outputArg);
const buildDir = join(dirname(infoPath), `.native-link-${mode}`);
rmSync(buildDir, { recursive: true, force: true });
mkdirSync(buildDir, { recursive: true });
const runtimeObjects = [];
const vendorArchives = [];
for (const [kind, artifacts] of [["objects", info.runtime_pack.objects], ["archives", info.runtime_pack.archives]]) {
for (const artifact of artifacts) {
if (artifact.path.startsWith("/") || artifact.path.split(/[\\/]/).includes("..")) throw new Error("invalid runtime artifact path");
const bytes = readFileSync(join(info.runtime_pack.root, artifact.path));
if (bytes.length !== artifact.size || createHash("sha256").update(bytes).digest("hex") !== artifact.sha256) {
throw new Error(`runtime pack hash mismatch: ${artifact.path}`);
}
const destination = join(buildDir, artifact.path);
mkdirSync(dirname(destination), { recursive: true });
writeFileSync(destination, bytes, { mode: 0o400 });
(kind === "objects" ? runtimeObjects : vendorArchives).push(destination);
}
}
const inputs = [
info.program.object,
...info.ffi.libraries,
...runtimeObjects,
...vendorArchives,
];
const libraries = info.link.system_libraries.map((name) => `-l${name}`);
const frameworks = info.link.frameworks.flatMap((name) => ["-framework", name]);
if (mode === "cc") {
// Darwin compiler drivers add libSystem themselves. It remains explicit
// in the document because a direct ld invocation must name it.
const driverLibraries = info.link.system_libraries
.filter((name) => name !== "System")
.map((name) => `-l${name}`);
run("clang", [
...info.link.driver_flags,
...inputs,
...driverLibraries,
...frameworks,
"-o", output,
]);
} else {
const sdk = capture("xcrun", ["--sdk", "macosx", "--show-sdk-path"]);
const sdkVersion = capture("xcrun", ["--sdk", "macosx", "--show-sdk-version"]);
const linker = capture("xcrun", ["--sdk", "macosx", "--find", "ld"]);
run(linker, [
"-arch", info.target.architecture,
"-platform_version", "macos", info.target.minimum_os, sdkVersion,
"-syslibroot", sdk,
...(info.link.driver_flags.includes("-Wl,-dead_strip") ? ["-dead_strip"] : []),
...inputs,
...libraries,
...frameworks,
"-o", output,
]);
}
}