Files
scriptc/packages/libnode-common/scripts/pin.mjs
T
Malte UblandClaude Opus 5.5 75aa8509f6 Assemble libnode packs with NI-1's build-pack.mjs and run island tests in CI
Stack the libnode packs on the in-process loader (#1685) and use its
pack assembler and manifest format instead of a second one:
build-pack.mjs keeps its CLI and fields (schema, nodeVersion,
moduleVersion, bridgeAbi, platform, arch, files), adds the libnode and
bridge names, strips and (on macOS) re-signs the bridge and links it with
-z defs, and exports buildPack(), which the platform packs call with their
package, target and build inputs as extra manifest fields.

libnodeRecord falls back to the installed @scriptc/libnode-<platform>
pack, found from the entry's directory and then next to the compiler,
when neither --libnode nor SCRIPTC_LIBNODE names one.

The Linux test lanes restore the libnode build that the libnode-packs
workflow cached, assemble the linux-x64 pack from it in seconds
(build.mjs --from-cache never builds Node) and export SCRIPTC_LIBNODE, so
the Node island corpus and harness tests run instead of skipping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 17:52:22 -07:00

91 lines
3.5 KiB
JavaScript

// The libnode pin and the platform table shared by the libnode pack build,
// its CI cache keys, the pack verifier, and the smoke test.
//
// A libnode pack (`@scriptc/libnode-<platform>`) holds a stripped Node.js
// shared library built from the pinned release, the C++ bridge compiled
// against that release's headers, Node's license, and manifest.json. The
// scriptc runtime dlopens the bridge on its first call into Node and talks
// to it only through the C ABI in packages/runtime/src/scr_node_bridge.h.
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
export const commonRoot = dirname(dirname(fileURLToPath(import.meta.url)));
export const repoRoot = dirname(dirname(commonRoot));
export const bridgeSource = join(commonRoot, "bridge", "scr_node_bridge.cc");
export const bridgeHeader = join(repoRoot, "packages", "runtime", "src", "scr_node_bridge.h");
export { LIBNODE_PACK_SCHEMA, bridgeAbi } from "../build-pack.mjs";
/** Bump to invalidate every cached libnode build (a change to how the
* library is built or staged that the pin does not capture). */
const BUILD_RECIPE = 1;
/**
* @typedef {{
* nodeVersion: string,
* tarballSha256: string,
* moduleVersion: number,
* configureFlags: string[],
* icu: "full" | "small",
* }} LibnodePin
*/
/** @returns {LibnodePin} */
export function readPin() {
return JSON.parse(readFileSync(join(commonRoot, "libnode.json"), "utf8"));
}
/** Pack platforms: the npm package suffix, the Node `os`/`arch`, and the
* file names inside the pack. The darwin packs build only on macOS hosts. */
export const LIBNODE_PLATFORMS = {
"linux-x64-gnu": { os: "linux", arch: "x64", target: "linux-x64-gnu" },
"linux-arm64-gnu": { os: "linux", arch: "arm64", target: "linux-arm64-gnu" },
"darwin-arm64": { os: "darwin", arch: "arm64", target: "macos-arm64" },
"darwin-x64": { os: "darwin", arch: "x64", target: "macos-x64" },
};
/** @param {string} platform */
export function platformInfo(platform) {
const info = LIBNODE_PLATFORMS[/** @type {keyof typeof LIBNODE_PLATFORMS} */ (platform)];
if (info === undefined) throw new Error(`unknown libnode pack platform ${platform}`);
return info;
}
/** The library names inside a pack for one platform and module version. */
export function packFileNames(platform, moduleVersion) {
const { os } = platformInfo(platform);
return os === "darwin"
? { libnode: `libnode.${moduleVersion}.dylib`, bridge: "libscr_node_bridge.dylib" }
: { libnode: `libnode.so.${moduleVersion}`, bridge: "libscr_node_bridge.so" };
}
function sha256(text) {
return createHash("sha256").update(text).digest("hex");
}
/** The cache key of the libnode build itself: the Node release, how it is
* configured, the platform and toolchain. The bridge is not part of it; a
* bridge change rebuilds only the bridge against the cached headers. */
export function libnodeCacheKey(platform, toolchain = process.env.SCRIPTC_LIBNODE_TOOLCHAIN) {
const pin = readPin();
platformInfo(platform);
return sha256(
JSON.stringify({
recipe: BUILD_RECIPE,
nodeVersion: pin.nodeVersion,
tarballSha256: pin.tarballSha256,
configureFlags: pin.configureFlags,
icu: pin.icu,
platform,
toolchain: toolchain ?? "default",
}),
);
}
/** The hash of everything the bridge is compiled from. */
export function bridgeHash() {
return sha256(readFileSync(bridgeSource, "utf8") + "\0" + readFileSync(bridgeHeader, "utf8"));
}