Files
scriptc/scripts/build-native-cli.mts
T

130 lines
5.0 KiB
TypeScript

/** Build the production CLI seed and its relocatable compiler assets. */
import { execFileSync } from "node:child_process";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { compile } from "../packages/compiler/src/index.js";
import { NATIVE_TARGETS, nativeCodegenTarget } from "../packages/compiler/src/backend/targets.js";
import { ts7Executable } from "../packages/compiler/src/frontend/ts7/rpc-api.js";
import { stageNativeCliAssets } from "./native-cli-assets.mjs";
import type { NativeToolchainManifest } from "../packages/compiler/src/native/toolchain.js";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const output = resolve(process.argv[2] ?? join(root, ".scriptc/native-cli"));
let target = nativeCodegenTarget();
if (target === null || target.platform === "wasi")
throw new Error("a supported native host is required to build the compiler");
// Release executables use the runtime pack's libc baseline, even when the
// build host has a newer libc. Sanitizer builds use the host's sanitizer SDK.
if (target.name.endsWith("-gnu") && process.env["SCRIPTC_SAN"] !== "1") {
const architecture = target.architecture === "x64" ? "x86_64" : "aarch64";
process.env["SCRIPTC_TARGET"] = `${architecture}-linux-gnu.2.34`;
target = nativeCodegenTarget()!;
}
const bin = join(output, "bin");
const lib = join(output, "lib");
const seed = join(dirname(output), ".scriptc", basename(output) + "-seed");
for (const dir of [bin, lib, seed]) mkdirSync(dir, { recursive: true });
const compiler = process.env["SCRIPTC_CC"] ?? target.defaultLinker;
const compilerArgs = process.env["SCRIPTC_CC"] === undefined ? [...target.defaultLinkerArgs] : [];
// Zig's C debug mode otherwise inserts UBSan calls into these objects. They
// must also link through the destination host's ordinary compiler driver.
const cFlags = [
"-std=c11",
"-fno-sanitize=undefined",
"-Wall",
"-Wextra",
"-Werror",
"-target",
target.linkerTargetTriple,
];
const nativeSources = join(root, "packages/compiler/native");
const functions: unknown[] = [];
const libraries: string[] = [];
for (const name of ["ts7-process", "host"]) {
const object = join(seed, name + target.outputSuffixes.obj);
execFileSync(
compiler,
[...compilerArgs, ...cFlags, "-c", join(nativeSources, name + ".c"), "-o", object],
{ stdio: "inherit" },
);
const manifest = JSON.parse(readFileSync(join(nativeSources, name + ".ffi.json"), "utf8")) as {
functions: unknown[];
};
functions.push(...manifest.functions);
libraries.push(object);
}
const ffi = join(seed, "compiler.ffi.json");
writeFileSync(ffi, JSON.stringify({ ffi_format: 6, functions, libraries }, null, 2) + "\n");
const executable = join(bin, "scriptc" + target.outputSuffixes.exe);
const comptime = join(lib, "scriptc-comptime" + target.outputSuffixes.exe);
execFileSync(
compiler,
[
...compilerArgs,
...cFlags,
"-O2",
"-I",
join(root, "packages/runtime/vendor/quickjs-ng"),
join(nativeSources, "comptime.c"),
join(
root,
"packages",
target.runtimePackPackage.replace("@scriptc/", ""),
"artifacts/vendor/quickjs/libscriptc-quickjs.a",
),
"-lm",
"-lpthread",
"-o",
comptime,
],
{ stdio: "inherit" },
);
const result = await compile(join(root, "packages/compiler/src/native/cli.ts"), {
outDir: seed,
outPath: executable,
backend: "llvm",
optimization: process.env["SCRIPTC_NATIVE_OPTIMIZATION"] === "dev" ? "dev" : "release",
strip: process.env["SCRIPTC_NATIVE_KEEP_SYMBOLS"] !== "1",
ffiProfilePath: ffi,
sanitize: process.env["SCRIPTC_SAN"] === "1",
emitIr: process.env["SCRIPTC_NATIVE_EMIT_IR"] === "1",
});
if (!result.ok)
throw new Error(
result.diagnostics
.map(
(item) =>
`${item.loc ? `${item.loc.file}:${item.loc.start}: ` : ""}${item.code}: ${item.message}`,
)
.join("\n"),
);
const compilerVersion = (
JSON.parse(readFileSync(join(root, "packages/compiler/package.json"), "utf8")) as {
version: string;
}
).version;
const assets = stageNativeCliAssets({
repository: root,
output,
target,
ts7: ts7Executable(),
compilerVersion,
});
// A cross-libc seed may use Zig on the build host. The installed compiler
// selects the destination platform's ordinary linker and SDK.
const installedTarget = NATIVE_TARGETS.find((item) => item.name === target.name)!;
const manifest: NativeToolchainManifest = {
schema: "scriptc.native-toolchain.v1",
compiler_version: compilerVersion,
target: target.name,
...assets,
linker: process.env["SCRIPTC_LINKER"] ?? installedTarget.defaultLinker,
linker_args:
process.env["SCRIPTC_LINKER"] === undefined ? [...installedTarget.defaultLinkerArgs] : [],
dsymutil: process.env["SCRIPTC_DSYMUTIL"] ?? "dsymutil",
comptime: relative(bin, comptime),
};
writeFileSync(executable + ".json", JSON.stringify(manifest, null, 2) + "\n");
console.log(executable);