Files
scriptc/packages/runtime-pack-common/scripts/share-artifacts.mjs
T
Chris Tate dfe89d7d7b Store identical runtime artifacts once and strip the copied CLI executables
Runtime packs: variants of a unit that do not read their defines, and
flavors that compile a unit the same way, produce identical objects and
bitcode. The pack build now points those manifest entries at one file
(shareIdenticalArtifacts) and deletes the copies. Each link selects one
flavor and one variant per unit, so no link names a shared file twice. The
linux-x64-gnu pack keeps 1,191 of its 1,448 artifact files.

Native CLI packages:
- The comptime evaluator builds with -g0 and the executable strip flags,
  and the Windows build no longer ships its .pdb.
- The copied tsc-ts executable is stripped with llvm-strip, which rewrites
  the linker-signed code signature of Mach-O executables.
- On arm64 macOS, verify-native-cli.mjs --run checks the code signatures
  of the compiler, tsc-ts and the comptime evaluator.
- SCRIPTC_NATIVE_KEEP_SYMBOLS=1 keeps the symbols of all three.
- The copied runtime sources and declarations get LF line endings. A
  Windows checkout converts them to CRLF, so the Windows CLI shipped files
  that differed from @scriptc/runtime's.
2026-10-25 21:55:14 -05:00

49 lines
1.8 KiB
JavaScript

import { readdir, rm, rmdir } from "node:fs/promises";
import { join } from "node:path";
/** Points the artifacts of one runtime unit that hold the same bytes at one
* file and deletes the copies. Variants whose defines a unit does not read,
* and flavors that compile a unit the same way, produce identical objects
* and bitcode. Each link selects one flavor and one variant of each unit, so
* no link names a shared file twice. `root` is the directory artifact paths
* are relative to; flavors keep the order they were built in, so the first
* flavor's path holds the shared file. */
export async function shareIdenticalArtifacts(flavors, root) {
const firstPaths = new Map();
for (const flavor of Object.values(flavors)) {
for (const unit of flavor.runtime_units) {
let paths = firstPaths.get(unit.source);
if (paths === undefined) {
paths = new Map();
firstPaths.set(unit.source, paths);
}
for (const variant of unit.variants) {
for (const artifact of variant.bitcode === undefined
? [variant]
: [variant, variant.bitcode]) {
const first = paths.get(artifact.sha256);
if (first === undefined) {
paths.set(artifact.sha256, artifact.path);
} else if (first !== artifact.path) {
await rm(join(root, artifact.path));
artifact.path = first;
}
}
}
}
}
await removeEmptyDirectories(join(root, "artifacts"));
}
async function removeEmptyDirectories(directory) {
const entries = await readdir(directory, { withFileTypes: true });
let remaining = entries.length;
for (const entry of entries) {
if (entry.isDirectory() && (await removeEmptyDirectories(join(directory, entry.name))))
remaining--;
}
if (remaining > 0) return false;
await rmdir(directory);
return true;
}