mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
Merge remote-tracking branch 'origin/main' into ni/1-libnode-loader
# Conflicts: # packages/compiler/src/diagnostics/diagnostic.ts # packages/compiler/src/ir/function-references.test.ts
This commit is contained in:
@@ -18,6 +18,9 @@ import type { NativeToolchainManifest } from "../packages/compiler/src/native/to
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const output = resolve(process.argv[2] ?? join(root, ".scriptc/native-cli"));
|
||||
// --platform-packages builds an npm platform package's distribution, which
|
||||
// depends on the helper and runtime pack packages instead of bundling them.
|
||||
const platformPackages = process.argv.includes("--platform-packages");
|
||||
const optimization = process.env["SCRIPTC_NATIVE_OPTIMIZATION"] === "dev" ? "dev" : "release";
|
||||
const sanitize = process.env["SCRIPTC_SAN"] === "1";
|
||||
// SCRIPTC_NATIVE_PROFILE_GENERATE=<pattern> builds a compiler that writes a
|
||||
@@ -187,6 +190,7 @@ const assets = stageNativeCliAssets({
|
||||
ts7: tsgoExecutable(),
|
||||
compilerVersion,
|
||||
strip,
|
||||
platformPackages,
|
||||
});
|
||||
// A cross-libc seed may use Zig on the build host. The installed compiler
|
||||
// selects the destination platform's ordinary linker and SDK.
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// Generates packages/compiler/src/ir/fields.generated.ts from the node types
|
||||
// in ir.ts: for each IrExpr and IrStmt kind, the fields that can hold a
|
||||
// string, read by name. Code that would enumerate a node's keys reads them
|
||||
// through this switch instead: the native compiler enumerates and reads an
|
||||
// untyped object's keys through its dynamic representation, one conversion
|
||||
// per node. `--check` fails when the file is stale.
|
||||
const root = fileURLToPath(new URL("..", import.meta.url));
|
||||
const require = createRequire(join(root, "package.json"));
|
||||
const ts = require("typescript");
|
||||
const irPath = join(root, "packages/compiler/src/ir/ir.ts");
|
||||
const target = join(root, "packages/compiler/src/ir/fields.generated.ts");
|
||||
|
||||
const program = ts.createProgram([irPath], { strict: true, target: ts.ScriptTarget.ES2022 });
|
||||
const checker = program.getTypeChecker();
|
||||
const source = program.getSourceFile(irPath);
|
||||
const aliases = new Map();
|
||||
ts.forEachChild(source, (node) => {
|
||||
if (ts.isTypeAliasDeclaration(node)) aliases.set(node.name.text, node);
|
||||
});
|
||||
|
||||
/** Whether a value of `type` can be a string. */
|
||||
function holdsString(type) {
|
||||
if (type.isUnion()) return type.types.some(holdsString);
|
||||
return (type.flags & (ts.TypeFlags.StringLike | ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
|
||||
}
|
||||
|
||||
/** Whether every value of `type` is a string. */
|
||||
function onlyString(type) {
|
||||
if (type.isUnion()) return type.types.every(onlyString);
|
||||
return (type.flags & ts.TypeFlags.StringLike) !== 0;
|
||||
}
|
||||
|
||||
/** Each kind's string fields, in declaration order, merged over the unions:
|
||||
* a kind both unions declare lists the fields of both. */
|
||||
const kinds = new Map();
|
||||
for (const name of ["IrExpr", "IrStmt"]) {
|
||||
const alias = aliases.get(name);
|
||||
const union = checker.getTypeAtLocation(alias.name);
|
||||
for (const member of union.isUnion() ? union.types : [union]) {
|
||||
const kindSymbol = member.getProperty("kind");
|
||||
const kindType = checker.getTypeOfSymbolAtLocation(kindSymbol, alias);
|
||||
if (!kindType.isStringLiteral()) throw new Error(`${name} member without a literal kind`);
|
||||
const fields = kinds.get(kindType.value) ?? [];
|
||||
for (const property of member.getProperties()) {
|
||||
const type = checker.getTypeOfSymbolAtLocation(property, alias);
|
||||
if (!holdsString(type)) continue;
|
||||
const known = fields.find((field) => field.name === property.name);
|
||||
const always = onlyString(type) && (property.flags & ts.SymbolFlags.Optional) === 0;
|
||||
if (known === undefined) fields.push({ name: property.name, always });
|
||||
else known.always &&= always;
|
||||
}
|
||||
kinds.set(kindType.value, fields);
|
||||
}
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"// Generated from packages/compiler/src/ir/ir.ts by scripts/generate-ir-fields.mjs.",
|
||||
"// Run `pnpm generate:ir-fields` after changing the fields of an IR node.",
|
||||
'import type { IrExpr, IrStmt, IrType } from "./ir.js";',
|
||||
"",
|
||||
"/** Calls `visit` with each string field of `node` (including `kind`) and its",
|
||||
" * name: the string values `for (const key in node)` finds on a node, read",
|
||||
" * by name in the order ir.ts declares them. */",
|
||||
"export function eachStringField(",
|
||||
" node: IrExpr | IrStmt,",
|
||||
" visit: (value: string, field: string) => void,",
|
||||
"): void {",
|
||||
" switch (node.kind) {",
|
||||
];
|
||||
for (const [kind, fields] of [...kinds].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
|
||||
lines.push(` case ${JSON.stringify(kind)}: {`);
|
||||
for (const { name, always } of fields) {
|
||||
const read = `node.${name}`;
|
||||
if (always) lines.push(` visit(${read}, ${JSON.stringify(name)});`);
|
||||
else
|
||||
lines.push(` if (typeof ${read} === "string") visit(${read}, ${JSON.stringify(name)});`);
|
||||
}
|
||||
lines.push(" return;", " }");
|
||||
}
|
||||
lines.push(" }", "}", "");
|
||||
|
||||
// The deep walkers: every field of every object under an IR node, as a
|
||||
// recursive key walk finds them, read by name.
|
||||
const members = new Map();
|
||||
for (const name of ["IrExpr", "IrStmt", "IrType"]) {
|
||||
const union = checker.getTypeAtLocation(aliases.get(name).name);
|
||||
members.set(name, new Set(union.isUnion() ? union.types : [union]));
|
||||
}
|
||||
const nullish = ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void;
|
||||
const scalar =
|
||||
ts.TypeFlags.NumberLike |
|
||||
ts.TypeFlags.BooleanLike |
|
||||
ts.TypeFlags.BigIntLike |
|
||||
ts.TypeFlags.ESSymbolLike;
|
||||
|
||||
/** The walker an IR union member belongs to, or null. */
|
||||
function irKind(type) {
|
||||
for (const [name, set] of members) if (set.has(type)) return name;
|
||||
return null;
|
||||
}
|
||||
|
||||
let temp = 0;
|
||||
/** Lines that walk `expr` of `type`: strings to `walker.string`, IR nodes to
|
||||
* their walkers, arrays by element and other objects by field. */
|
||||
function walkLines(expr, type, indent, depth) {
|
||||
if (depth > 12) throw new Error(`field type nests too deep at ${expr}`);
|
||||
const parts = type.isUnion() ? type.types : [type];
|
||||
const present = parts.filter((part) => (part.flags & nullish) === 0);
|
||||
const optional = present.length !== parts.length;
|
||||
const strings = present.filter((part) => (part.flags & ts.TypeFlags.StringLike) !== 0);
|
||||
const objects = present.filter((part) => (part.flags & (ts.TypeFlags.StringLike | scalar)) === 0);
|
||||
for (const part of present)
|
||||
if ((part.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)
|
||||
throw new Error(`untyped field ${expr}`);
|
||||
const body = [];
|
||||
if (objects.length > 0) {
|
||||
const kinds = new Set(objects.map(irKind));
|
||||
if (kinds.size === 1 && !kinds.has(null)) {
|
||||
const [kind] = kinds;
|
||||
body.push(`walk${kind.slice(2)}Fields(${expr}, walker, skipType);`);
|
||||
} else if (objects.every((part) => checker.isArrayType(part))) {
|
||||
if (objects.length !== 1) throw new Error(`several array types at ${expr}`);
|
||||
const item = `item${temp++}`;
|
||||
body.push(`for (const ${item} of ${expr}) {`);
|
||||
body.push(...walkLines(item, checker.getTypeArguments(objects[0])[0], " ", depth + 1));
|
||||
body.push("}");
|
||||
} else if (kinds.has(null) && objects.length === 1) {
|
||||
for (const property of checker.getPropertiesOfType(objects[0])) {
|
||||
if (property.name === "loc") continue;
|
||||
const fieldType = checker.getTypeOfSymbol(property);
|
||||
const field = `${expr}.${property.name}`;
|
||||
const lines = walkLines(field, fieldType, "", depth + 1);
|
||||
if (lines.length === 0) continue;
|
||||
if (property.name === "type")
|
||||
body.push("if (!skipType) {", ...lines.map((l) => ` ${l}`), "}");
|
||||
else body.push(...lines);
|
||||
}
|
||||
} else throw new Error(`mixed object types at ${expr}: ${checker.typeToString(type)}`);
|
||||
}
|
||||
let lines = body;
|
||||
if (strings.length > 0 && objects.length > 0)
|
||||
lines = [
|
||||
`if (typeof ${expr} === "string") walker.string(${expr});`,
|
||||
...(body.length > 0 ? [`else {`, ...body.map((l) => ` ${l}`), "}"] : []),
|
||||
];
|
||||
else if (strings.length > 0) lines = [`walker.string(${expr});`];
|
||||
if (lines.length > 0 && optional && !(strings.length > 0 && objects.length > 0))
|
||||
lines = [
|
||||
`if (${expr} !== undefined && ${expr} !== null) {`,
|
||||
...lines.map((l) => ` ${l}`),
|
||||
"}",
|
||||
];
|
||||
else if (lines.length > 0 && optional)
|
||||
lines = [
|
||||
`if (${expr} !== undefined && ${expr} !== null) {`,
|
||||
...lines.map((l) => ` ${l}`),
|
||||
"}",
|
||||
];
|
||||
return lines.map((line) => indent + line);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
"/** What a field walk reports (walkExprFields): each expression and",
|
||||
" * statement node before its fields, and each string a field holds. */",
|
||||
"export interface IrFieldWalker {",
|
||||
" node(node: IrExpr | IrStmt): void;",
|
||||
" string(value: string): void;",
|
||||
"}",
|
||||
"",
|
||||
);
|
||||
for (const name of ["IrExpr", "IrStmt", "IrType"]) {
|
||||
const short = name.slice(2);
|
||||
const nodeCall = name === "IrType" ? [] : [" walker.node(node);"];
|
||||
lines.push(
|
||||
`/** Walks every field of \`node\` but \`loc\` (and \`type\` with \`skipType\`), and`,
|
||||
" * every field of each object under it: the strings and nodes a recursive",
|
||||
" * `for (const key in value)` walk with the same skips finds, read by name in",
|
||||
" * the order ir.ts declares the fields. */",
|
||||
`export function walk${short}Fields(node: ${name}, walker: IrFieldWalker, skipType: boolean): void {`,
|
||||
...nodeCall,
|
||||
" switch (node.kind) {",
|
||||
);
|
||||
const byKind = new Map();
|
||||
for (const member of members.get(name)) {
|
||||
const kind = checker.getTypeOfSymbol(member.getProperty("kind")).value;
|
||||
if (byKind.has(kind)) throw new Error(`${name} kind ${kind} declared twice`);
|
||||
byKind.set(kind, member);
|
||||
}
|
||||
for (const [kind, member] of [...byKind].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
|
||||
lines.push(` case ${JSON.stringify(kind)}: {`);
|
||||
for (const property of member.getProperties()) {
|
||||
if (property.name === "loc") continue;
|
||||
const fieldType = checker.getTypeOfSymbol(property);
|
||||
const field = `node.${property.name}`;
|
||||
const walk = walkLines(field, fieldType, "", 0);
|
||||
if (walk.length === 0) continue;
|
||||
if (property.name === "type")
|
||||
lines.push(" if (!skipType) {", ...walk.map((l) => ` ${l}`), " }");
|
||||
else lines.push(...walk.map((l) => ` ${l}`));
|
||||
}
|
||||
lines.push(" return;", " }");
|
||||
}
|
||||
lines.push(" }", "}", "");
|
||||
}
|
||||
const text = lines.join("\n");
|
||||
|
||||
if (process.argv.includes("--check")) {
|
||||
if (readFileSync(target, "utf8") !== text)
|
||||
throw new Error("fields.generated.ts is stale; run node scripts/generate-ir-fields.mjs");
|
||||
} else writeFileSync(target, text);
|
||||
@@ -2,28 +2,13 @@ import { spawnSync } from "node:child_process";
|
||||
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { basename, dirname, join, relative } from "node:path";
|
||||
import type { NativeTargetSpec } from "../packages/compiler/src/backend/targets.js";
|
||||
import { copyPackage, llvmNotice } from "./native-cli-bundle.mjs";
|
||||
import type { NativeToolchainManifest } from "../packages/compiler/src/native/toolchain.js";
|
||||
import {
|
||||
tscTsExecutable,
|
||||
tscTsPlatformPackage,
|
||||
} from "../packages/compiler/src/frontend/ts7/checker-backend.js";
|
||||
|
||||
/** Copy release payloads explicitly; development caches and build trees never ship. */
|
||||
function copyPackage(source: string, destination: string, members: readonly string[]): void {
|
||||
mkdirSync(destination, { recursive: true });
|
||||
for (const member of ["package.json", ...members]) {
|
||||
cpSync(join(source, member), join(destination, member), {
|
||||
recursive: true,
|
||||
filter: (path) => ![".cache", "node_modules", ".DS_Store"].includes(basename(path)),
|
||||
});
|
||||
}
|
||||
for (const name of readdirSync(source)) {
|
||||
if (/^(?:LICENSE|COPYING|NOTICE|SCRIPTC_LICENSE|THIRD_PARTY_NOTICES)(?:\..*)?$/.test(name)) {
|
||||
cpSync(join(source, name), join(destination, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes the symbol table and debug sections of a copied executable.
|
||||
* llvm-strip rewrites the linker-signed ad hoc code signature of a Mach-O
|
||||
* executable, which arm64 macOS requires to run it. Windows executables keep
|
||||
@@ -67,6 +52,10 @@ export function stageNativeCliAssets(options: {
|
||||
compilerVersion: string;
|
||||
/** Strip the copied tsc-ts executable. */
|
||||
strip: boolean;
|
||||
/** Leave the LLVM helper and host runtime pack out: an npm platform
|
||||
* package depends on their packages, and the compiler resolves them
|
||||
* beside itself. */
|
||||
platformPackages: boolean;
|
||||
}): Pick<
|
||||
NativeToolchainManifest,
|
||||
| "ts7"
|
||||
@@ -85,16 +74,18 @@ export function stageNativeCliAssets(options: {
|
||||
cpSync(join(repository, "LICENSE"), join(output, "LICENSE"));
|
||||
const helper = join(lib, "llvm");
|
||||
const runtime = join(lib, target.runtimePackPackage.replace("@scriptc/", ""));
|
||||
copyPackage(
|
||||
join(repository, "packages", target.helper.packageName.replace("@scriptc/", "")),
|
||||
helper,
|
||||
["bin"],
|
||||
);
|
||||
copyPackage(
|
||||
join(repository, "packages", target.runtimePackPackage.replace("@scriptc/", "")),
|
||||
runtime,
|
||||
["artifacts", "runtime-pack.json"],
|
||||
);
|
||||
if (!options.platformPackages) {
|
||||
copyPackage(
|
||||
join(repository, "packages", target.helper.packageName.replace("@scriptc/", "")),
|
||||
helper,
|
||||
["bin"],
|
||||
);
|
||||
copyPackage(
|
||||
join(repository, "packages", target.runtimePackPackage.replace("@scriptc/", "")),
|
||||
runtime,
|
||||
["artifacts", "runtime-pack.json"],
|
||||
);
|
||||
}
|
||||
const typescript = join(lib, "typescript");
|
||||
copyPackage(dirname(dirname(options.ts7)), typescript, ["lib"]);
|
||||
// tsc-ts, the default checker, from the tsc-ts package the compiler depends on.
|
||||
@@ -146,7 +137,7 @@ export function stageNativeCliAssets(options: {
|
||||
`scriptc ${options.compilerVersion} includes the existing TypeScript ${identity.version}, tsc-ts ${tscTsIdentity.version}, LLVM, and C runtime dependencies.`,
|
||||
"TypeScript licensing: lib/typescript/LICENSE and lib/typescript/NOTICE.txt.",
|
||||
"tsc-ts licensing and notices: lib/tsc-ts/LICENSE and lib/tsc-ts/THIRD_PARTY_NOTICES.md.",
|
||||
"LLVM licensing and notices: lib/llvm/LICENSE and lib/llvm/THIRD_PARTY_NOTICES.",
|
||||
llvmNotice(options.platformPackages ? target.helper.packageName : undefined),
|
||||
"Runtime dependency licenses: lib/runtime-sources/vendor/*/LICENSE*.",
|
||||
"",
|
||||
].join("\n"),
|
||||
@@ -154,9 +145,13 @@ export function stageNativeCliAssets(options: {
|
||||
return {
|
||||
ts7: relative(bin, join(typescript, "lib", basename(options.ts7))),
|
||||
tsc_ts: relative(bin, join(tscTs, "lib", basename(tscTsSource))),
|
||||
llvm_package: relative(bin, helper),
|
||||
runtime_pack: relative(bin, runtime),
|
||||
runtime_packs: [{ target: target.name, path: relative(bin, runtime) }],
|
||||
...(options.platformPackages
|
||||
? {}
|
||||
: {
|
||||
llvm_package: relative(bin, helper),
|
||||
runtime_pack: relative(bin, runtime),
|
||||
runtime_packs: [{ target: target.name, path: relative(bin, runtime) }],
|
||||
}),
|
||||
runtime_sources: relative(bin, sources),
|
||||
declarations: relative(bin, declarations),
|
||||
wasi_node_runner: relative(bin, join(wasi, "cli/wasi-runner.js")),
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { cpSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
|
||||
/** Copy release payloads explicitly; development caches and build trees never ship. */
|
||||
export function copyPackage(source, destination, members) {
|
||||
mkdirSync(destination, { recursive: true });
|
||||
for (const member of ["package.json", ...members]) {
|
||||
cpSync(join(source, member), join(destination, member), {
|
||||
recursive: true,
|
||||
filter: (path) => ![".cache", "node_modules", ".DS_Store"].includes(basename(path)),
|
||||
});
|
||||
}
|
||||
for (const name of readdirSync(source)) {
|
||||
if (/^(?:LICENSE|COPYING|NOTICE|SCRIPTC_LICENSE|THIRD_PARTY_NOTICES)(?:\..*)?$/.test(name)) {
|
||||
cpSync(join(source, name), join(destination, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The LLVM helper and host runtime pack packages a native CLI package
|
||||
* depends on. */
|
||||
export function platformPackagesOf(identity) {
|
||||
const names = Object.keys(identity.dependencies ?? {});
|
||||
const helper = names.find((name) => name.startsWith("@scriptc/llvm-"));
|
||||
const runtime = names.find((name) => name.startsWith("@scriptc/runtime-"));
|
||||
if (helper === undefined || runtime === undefined)
|
||||
throw new Error(`${identity.name} must depend on its LLVM helper and runtime pack packages`);
|
||||
return { helper, runtime };
|
||||
}
|
||||
|
||||
/** Copies the helper and runtime pack packages from `packages` (the
|
||||
* workspace's packages directory) into a distribution's lib directory, as a
|
||||
* standalone archive needs them, and returns the manifest paths, relative to
|
||||
* the distribution's bin directory in the target's path style. */
|
||||
export function bundlePlatformPackages({ packages, distribution, helper, runtime, windows }) {
|
||||
const short = (name) => name.replace("@scriptc/", "");
|
||||
copyPackage(join(packages, short(helper)), join(distribution, "lib", "llvm"), ["bin"]);
|
||||
copyPackage(join(packages, short(runtime)), join(distribution, "lib", short(runtime)), [
|
||||
"artifacts",
|
||||
"runtime-pack.json",
|
||||
]);
|
||||
const path = (name) => ["..", "lib", name].join(windows ? "\\" : "/");
|
||||
return { llvm_package: path("llvm"), runtime_pack: path(short(runtime)) };
|
||||
}
|
||||
|
||||
/** The THIRD_PARTY_NOTICES line for the LLVM helper, bundled or as a dependency. */
|
||||
export function llvmNotice(helper) {
|
||||
return helper === undefined
|
||||
? "LLVM licensing and notices: lib/llvm/LICENSE and lib/llvm/THIRD_PARTY_NOTICES."
|
||||
: `LLVM licensing and notices: LICENSE and THIRD_PARTY_NOTICES in the ${helper} package.`;
|
||||
}
|
||||
|
||||
/** Turns a distribution that bundles its LLVM helper and runtime pack into
|
||||
* an npm platform package's: removes both from lib and from the manifest at
|
||||
* `manifestPath`, as `build-native-cli.mts --platform-packages` builds it. */
|
||||
export function unbundlePlatformPackages(distribution, manifestPath, helper) {
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
for (const path of [manifest.llvm_package, manifest.runtime_pack])
|
||||
rmSync(resolve(dirname(manifestPath), path.replaceAll("\\", "/")), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
delete manifest.llvm_package;
|
||||
delete manifest.runtime_pack;
|
||||
delete manifest.runtime_packs;
|
||||
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
||||
const notices = join(distribution, "THIRD_PARTY_NOTICES");
|
||||
writeFileSync(
|
||||
notices,
|
||||
readFileSync(notices, "utf8").replace(llvmNotice(undefined), llvmNotice(helper)),
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { tmpdir } from "node:os";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { bundlePlatformPackages, llvmNotice, platformPackagesOf } from "./native-cli-bundle.mjs";
|
||||
|
||||
const repository = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const packages = resolve(process.argv[2] ?? join(repository, "packages"));
|
||||
@@ -39,6 +40,27 @@ for (const name of readdirSync(packages)
|
||||
const binary = join(bin, name.includes("win32-") ? "scriptc.exe" : "scriptc");
|
||||
const manifest = JSON.parse(readFileSync(binary + ".json", "utf8"));
|
||||
const hostPath = (value) => (name.includes("win32-") ? value.replaceAll("\\", "/") : value);
|
||||
// The standalone archive bundles the LLVM helper and runtime pack that
|
||||
// the npm platform package depends on.
|
||||
if (manifest.llvm_package === undefined) {
|
||||
const { helper, runtime } = platformPackagesOf(identity);
|
||||
const paths = bundlePlatformPackages({
|
||||
packages,
|
||||
distribution,
|
||||
helper,
|
||||
runtime,
|
||||
windows: name.includes("win32-"),
|
||||
});
|
||||
manifest.llvm_package = paths.llvm_package;
|
||||
manifest.runtime_pack = paths.runtime_pack;
|
||||
manifest.runtime_packs = [{ target: manifest.target, path: paths.runtime_pack }];
|
||||
writeFileSync(binary + ".json", JSON.stringify(manifest, null, 2) + "\n");
|
||||
const notices = join(distribution, "THIRD_PARTY_NOTICES");
|
||||
writeFileSync(
|
||||
notices,
|
||||
readFileSync(notices, "utf8").replace(llvmNotice(helper), llvmNotice(undefined)),
|
||||
);
|
||||
}
|
||||
// GitHub artifact transport drops executable modes. Restore the native
|
||||
// tools before validating and creating the standalone archive.
|
||||
for (const path of [
|
||||
|
||||
@@ -54,7 +54,27 @@ function bytes(directory) {
|
||||
return size;
|
||||
}
|
||||
try {
|
||||
const platformTarball = pack(platform);
|
||||
// A platform package depends on its LLVM helper and runtime pack packages.
|
||||
// Pack them from this checkout, and pack the platform package with the
|
||||
// exact versions pnpm would publish for its workspace dependencies.
|
||||
const dependencies = Object.keys(identity.dependencies ?? {});
|
||||
let packed = platform;
|
||||
if (dependencies.length > 0) {
|
||||
packed = join(root, "platform");
|
||||
mkdirSync(packed);
|
||||
cpSync(join(platform, "dist"), join(packed, "dist"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(packed, "package.json"),
|
||||
JSON.stringify({
|
||||
...identity,
|
||||
dependencies: Object.fromEntries(dependencies.map((name) => [name, identity.version])),
|
||||
}),
|
||||
);
|
||||
}
|
||||
const platformTarball = pack(packed);
|
||||
const dependencyTarballs = dependencies.map((name) =>
|
||||
pack(join(repository, "packages", name.replace("@scriptc/", ""))),
|
||||
);
|
||||
const wrapper = join(root, "wrapper");
|
||||
mkdirSync(wrapper);
|
||||
cpSync(join(repository, "packages/cli/scripts"), join(wrapper, "scripts"), { recursive: true });
|
||||
@@ -77,7 +97,11 @@ try {
|
||||
const tarball = pack(wrapper);
|
||||
const project = join(root, "project");
|
||||
mkdirSync(project);
|
||||
command(npm, ["install", "--offline", "--no-audit", "--no-fund", tarball], project);
|
||||
command(
|
||||
npm,
|
||||
["install", "--offline", "--no-audit", "--no-fund", tarball, ...dependencyTarballs],
|
||||
project,
|
||||
);
|
||||
const executable = join(project, "node_modules/scriptc/bin/scriptc.exe");
|
||||
assert.equal(
|
||||
command(executable, ["--version"], project, { ...env, PATH: "" }).trim(),
|
||||
@@ -99,8 +123,11 @@ try {
|
||||
);
|
||||
command(executable, ["build", entry, "-o", binary], project, compileEnv);
|
||||
assert.equal(command(binary, [], project, { PATH: "" }), "true true true\n");
|
||||
const installed = readdirSync(join(project, "node_modules/@scriptc"));
|
||||
assert.deepEqual(installed, [identity.name.slice("@scriptc/".length)]);
|
||||
const installed = readdirSync(join(project, "node_modules/@scriptc")).sort();
|
||||
assert.deepEqual(
|
||||
installed,
|
||||
[identity.name, ...dependencies].map((name) => name.slice("@scriptc/".length)).sort(),
|
||||
);
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
package: identity.name,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
||||
import { verifyLinuxAbi } from "./verify-linux-abi.mjs";
|
||||
|
||||
@@ -26,11 +27,30 @@ const within = (path) => {
|
||||
if (child === ".." || child.startsWith(".." + sep) || isAbsolute(child))
|
||||
throw new Error(`distribution references external asset: ${path}`);
|
||||
};
|
||||
// An npm platform package depends on its LLVM helper and runtime pack
|
||||
// packages at its own version instead of bundling them.
|
||||
const platformPackages = toolchain.llvm_package === undefined;
|
||||
const dependencies = identity.dependencies ?? {};
|
||||
const dependency = (prefix) => {
|
||||
const name = Object.keys(dependencies).find((key) => key.startsWith(prefix));
|
||||
if (name === undefined || ![identity.version, "workspace:*"].includes(dependencies[name]))
|
||||
throw new Error(
|
||||
`${identity.name} must depend on its ${prefix}* package at ${identity.version}`,
|
||||
);
|
||||
return dirname(createRequire(join(root, "package.json")).resolve(`${name}/package.json`));
|
||||
};
|
||||
if (platformPackages && (toolchain.runtime_pack !== undefined || toolchain.runtime_packs))
|
||||
throw new Error("a platform package's runtime pack is its dependency");
|
||||
const helperRoot = platformPackages
|
||||
? dependency("@scriptc/llvm-")
|
||||
: resolve(dirname(binary), hostPath(toolchain.llvm_package));
|
||||
const runtimeRoot = platformPackages
|
||||
? dependency("@scriptc/runtime-")
|
||||
: resolve(dirname(binary), hostPath(toolchain.runtime_pack));
|
||||
for (const name of [
|
||||
"ts7",
|
||||
"tsc_ts",
|
||||
"llvm_package",
|
||||
"runtime_pack",
|
||||
...(platformPackages ? [] : ["llvm_package", "runtime_pack"]),
|
||||
"runtime_sources",
|
||||
"declarations",
|
||||
"comptime",
|
||||
@@ -48,14 +68,11 @@ if (toolchain.lib_diagnostics !== undefined) {
|
||||
throw new Error("native toolchain needs a relative lib_diagnostics");
|
||||
within(resolve(dirname(binary), hostPath(value)));
|
||||
}
|
||||
const pack = JSON.parse(
|
||||
readFileSync(
|
||||
resolve(dirname(binary), hostPath(toolchain.runtime_pack), "runtime-pack.json"),
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
const pack = JSON.parse(readFileSync(join(runtimeRoot, "runtime-pack.json"), "utf8"));
|
||||
if (pack.version !== identity.version || pack.target.name !== toolchain.target)
|
||||
throw new Error("native compiler runtime identity mismatch");
|
||||
if (JSON.parse(readFileSync(join(helperRoot, "package.json"), "utf8")).version !== identity.version)
|
||||
throw new Error("native compiler LLVM helper version mismatch");
|
||||
const seen = new Set();
|
||||
for (const runtime of toolchain.runtime_packs ?? []) {
|
||||
if (seen.has(runtime.target)) throw new Error(`duplicate runtime target: ${runtime.target}`);
|
||||
@@ -91,9 +108,7 @@ if (process.argv.includes("--run")) {
|
||||
]) {
|
||||
verifyLinuxAbi(executable, { staticOnly: target.endsWith("-musl") });
|
||||
}
|
||||
verifyLinuxAbi(resolve(dirname(binary), toolchain.llvm_package, "bin/scriptc-llvm-codegen"), {
|
||||
staticOnly: true,
|
||||
});
|
||||
verifyLinuxAbi(join(helperRoot, "bin/scriptc-llvm-codegen"), { staticOnly: true });
|
||||
}
|
||||
const version = execFileSync(binary, ["--version"], {
|
||||
encoding: "utf8",
|
||||
|
||||
@@ -80,7 +80,8 @@ runtime pack). Both compile the same program files, taken from --programs
|
||||
|
||||
--set picks the good-code set: bench (benchmarks/runtime workloads and the
|
||||
benchmarks/builds applications), self (packages/compiler/src/native/cli.ts,
|
||||
LLVM only), corpus (tests/corpus TypeScript programs; --corpus=all adds
|
||||
LLVM only; the candidate's own compiler source must also build with the
|
||||
candidate), corpus (tests/corpus TypeScript programs; --corpus=all adds
|
||||
JavaScript entries). --quick is --set=bench --timing-runs=0, for CI.
|
||||
--shard=i/n keeps every n-th corpus program; --only filters program ids.
|
||||
|
||||
@@ -634,6 +635,30 @@ if (timingRuns > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
// The self program is the baseline's compiler source (the programs come
|
||||
// from one checkout), so a change that adds compiler code the self-hosted
|
||||
// frontend cannot lower would pass unnoticed. The candidate's own compiler
|
||||
// source must still build with the candidate (LLVM only, not compared).
|
||||
if (sets.has("self") && programsRoot !== trees.candidate) {
|
||||
const entry = join(trees.candidate, "packages/compiler/src/native/cli.ts");
|
||||
const result = await buildProgram(
|
||||
{ id: "self/candidate-source", entry, kind: "source" },
|
||||
"candidate",
|
||||
);
|
||||
report.candidateSelf = result.ok
|
||||
? { ok: true, buildMs: result.ms }
|
||||
: { ok: false, error: result.error };
|
||||
console.log(
|
||||
result.ok
|
||||
? `self candidate compiler source builds with the candidate (${fmtMs(result.ms)})`
|
||||
: `self candidate compiler source FAILS to build with the candidate`,
|
||||
);
|
||||
if (!result.ok) {
|
||||
failed = true;
|
||||
report.failures.push(`self/candidate-source: ${result.error.split("\n")[0]}`);
|
||||
}
|
||||
}
|
||||
|
||||
const counts = {};
|
||||
for (const p of report.programs) counts[p.verdict] = (counts[p.verdict] ?? 0) + 1;
|
||||
if (!report.programs.some((p) => ["identical", "allowed", "differs"].includes(p.verdict))) {
|
||||
|
||||
Reference in New Issue
Block a user