Files
bun.sh/scripts/build.mjs
robobun 81b4a40fbd [publish images] Remove sccache, use ccache only (#25682)
## Summary
- Remove sccache support entirely, use ccache only
- Missing ccache no longer fails the build (just skips caching)
- Remove S3 distributed cache support

## Changes
- Remove `cmake/tools/SetupSccache.cmake` and S3 distributed cache
support
- Simplify `CMakeLists.txt` to only use ccache
- Update `SetupCcache.cmake` to not fail when ccache is missing
- Replace sccache with ccache in bootstrap scripts (sh, ps1)
- Update `.buildkite/Dockerfile` to install ccache instead of sccache
- Update `flake.nix` and `shell.nix` to use ccache
- Update documentation (CONTRIBUTING.md, contributing.mdx,
building-windows.mdx)
- Remove `scripts/build-cache/` directory (was only for sccache S3
access)

## Test plan
- [x] Build completes successfully with `bun bd`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-26 20:24:27 -08:00

323 lines
8.7 KiB
JavaScript
Executable File

import { spawn as nodeSpawn } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { basename, join, relative, resolve } from "node:path";
import {
formatAnnotationToHtml,
getEnv,
getSecret,
isCI,
isWindows,
parseAnnotations,
parseBoolean,
printEnvironment,
reportAnnotationToBuildKite,
startGroup,
} from "./utils.mjs";
if (globalThis.Bun) {
await import("./glob-sources.mjs");
}
// https://cmake.org/cmake/help/latest/manual/cmake.1.html#generate-a-project-buildsystem
const generateFlags = [
["-S", "string", "path to source directory"],
["-B", "string", "path to build directory"],
["-D", "string", "define a build option (e.g. -DCMAKE_BUILD_TYPE=Release)"],
["-G", "string", "build generator (e.g. -GNinja)"],
["-W", "string", "enable warnings (e.g. -Wno-dev)"],
["--fresh", "boolean", "force a fresh build"],
["--log-level", "string", "set the log level"],
["--debug-output", "boolean", "print debug output"],
["--toolchain", "string", "the toolchain to use"],
];
// https://cmake.org/cmake/help/latest/manual/cmake.1.html#generate-a-project-buildsystem
const buildFlags = [
["--config", "string", "build configuration (e.g. --config Release)"],
["--target", "string", "build target"],
["-t", "string", "same as --target"],
["--parallel", "number", "number of parallel jobs"],
["-j", "number", "same as --parallel"],
["--verbose", "boolean", "enable verbose output"],
["-v", "boolean", "same as --verbose"],
];
async function build(args) {
const startTime = Date.now();
if (process.platform === "win32" && !process.env["VSINSTALLDIR"]) {
const shellPath = join(import.meta.dirname, "vs-shell.ps1");
const scriptPath = import.meta.filename;
return spawn("pwsh", ["-NoProfile", "-NoLogo", "-File", shellPath, process.argv0, scriptPath, ...args]);
}
if (isCI) {
printEnvironment();
}
const env = {
...process.env,
FORCE_COLOR: "1",
CLICOLOR_FORCE: "1",
};
const generateOptions = parseOptions(args, generateFlags);
const buildOptions = parseOptions(args, buildFlags);
const ciCppBuild = isCI && !!process.env.BUN_CPP_ONLY;
const buildPath = resolve(generateOptions["-B"] || buildOptions["--build"] || "build");
generateOptions["-B"] = buildPath;
buildOptions["--build"] = buildPath;
if (!generateOptions["-S"]) {
generateOptions["-S"] = process.cwd();
}
if (!generateOptions["-DCACHE_STRATEGY"]) {
generateOptions["-DCACHE_STRATEGY"] = parseBoolean(getEnv("RELEASE", false) || "false") ? "none" : "auto";
}
const toolchain = generateOptions["--toolchain"];
if (toolchain) {
const toolchainPath = resolve(import.meta.dirname, "..", "cmake", "toolchains", `${toolchain}.cmake`);
generateOptions["--toolchain"] = toolchainPath;
}
const generateArgs = Object.entries(generateOptions).flatMap(([flag, value]) =>
flag.startsWith("-D") ? [`${flag}=${value}`] : [flag, value],
);
try {
await Bun.file(buildPath + "/CMakeCache.txt").delete();
} catch (e) {}
await startGroup("CMake Configure", () => spawn("cmake", generateArgs, { env }));
const envPath = resolve(buildPath, ".env");
if (existsSync(envPath)) {
const envFile = readFileSync(envPath, "utf8");
for (const line of envFile.split(/\r\n|\n|\r/)) {
const [key, value] = line.split("=");
env[key] = value;
}
}
const buildArgs = Object.entries(buildOptions)
.sort(([a], [b]) => (a === "--build" ? -1 : a.localeCompare(b)))
.flatMap(([flag, value]) => [flag, value]);
await startGroup("CMake Build", () => spawn("cmake", buildArgs, { env }));
if (ciCppBuild) {
await startGroup("ccache stats", () => {
spawn("ccache", ["--show-stats"], { env });
});
}
printDuration("total", Date.now() - startTime);
}
function isBuildkite() {
return process.env.BUILDKITE === "true";
}
function parseOptions(args, flags = []) {
const options = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
for (const [flag, type] of flags) {
if (arg === flag) {
if (type === "boolean") {
options[arg] = undefined;
} else {
options[arg] = args[++i];
}
} else if (arg.startsWith(flag)) {
const delim = arg.indexOf("=");
if (delim === -1) {
options[flag] = arg.slice(flag.length);
} else {
options[arg.slice(0, delim)] = arg.slice(delim + 1);
}
}
}
}
return options;
}
async function spawn(command, args, options, label) {
const effectiveArgs = args.filter(Boolean);
const description = [command, ...effectiveArgs].map(arg => (arg.includes(" ") ? JSON.stringify(arg) : arg)).join(" ");
let env = options?.env;
console.log("$", description);
label ??= basename(command);
const pipe = process.env.CI === "true";
if (isBuildkite()) {
if (process.env.BUN_LINK_ONLY && isWindows) {
env ||= options?.env || { ...process.env };
// Pass signing secrets directly to the build process
// The PowerShell signing script will handle certificate decoding
env.SM_CLIENT_CERT_PASSWORD = getSecret("SM_CLIENT_CERT_PASSWORD", {
redact: true,
required: true,
});
env.SM_CLIENT_CERT_FILE = getSecret("SM_CLIENT_CERT_FILE", {
redact: true,
required: true,
});
env.SM_API_KEY = getSecret("SM_API_KEY", {
redact: true,
required: true,
});
env.SM_KEYPAIR_ALIAS = getSecret("SM_KEYPAIR_ALIAS", {
redact: true,
required: true,
});
env.SM_HOST = getSecret("SM_HOST", {
redact: true,
required: true,
});
}
}
const subprocess = nodeSpawn(command, effectiveArgs, {
stdio: pipe ? "pipe" : "inherit",
...options,
env,
});
let killedManually = false;
function onKill() {
clearOnKill();
if (!subprocess.killed) {
killedManually = true;
subprocess.kill?.();
}
}
function clearOnKill() {
process.off("beforeExit", onKill);
process.off("SIGINT", onKill);
process.off("SIGTERM", onKill);
}
// Kill the entire process tree so everything gets cleaned up. On Windows, job
// control groups make this haappen automatically so we don't need to do this
// on Windows.
if (process.platform !== "win32") {
process.once("beforeExit", onKill);
process.once("SIGINT", onKill);
process.once("SIGTERM", onKill);
}
let timestamp;
subprocess.on("spawn", () => {
timestamp = Date.now();
});
let stdoutBuffer = "";
let done;
if (pipe) {
const stdout = new Promise(resolve => {
subprocess.stdout.on("end", resolve);
subprocess.stdout.on("data", data => {
stdoutBuffer += data.toString();
process.stdout.write(data);
});
});
const stderr = new Promise(resolve => {
subprocess.stderr.on("end", resolve);
subprocess.stderr.on("data", data => {
stdoutBuffer += data.toString();
process.stderr.write(data);
});
});
done = Promise.all([stdout, stderr]);
}
const { error, exitCode, signalCode } = await new Promise(resolve => {
subprocess.on("error", error => {
clearOnKill();
resolve({ error });
});
subprocess.on("exit", (exitCode, signalCode) => {
clearOnKill();
resolve({ exitCode, signalCode });
});
});
if (done) {
await done;
}
printDuration(label, Date.now() - timestamp);
if (exitCode === 0) {
return;
}
if (isBuildkite()) {
let annotated;
try {
const { annotations } = parseAnnotations(stdoutBuffer);
for (const annotation of annotations) {
const content = formatAnnotationToHtml(annotation);
reportAnnotationToBuildKite({
priority: 10,
label: annotation.title || annotation.filename,
content,
});
annotated = true;
}
} catch (error) {
console.error(`Failed to parse annotations:`, error);
}
if (!annotated) {
const content = formatAnnotationToHtml({
filename: relative(process.cwd(), import.meta.filename),
title: "build failed",
content: stdoutBuffer,
source: "build",
level: "error",
});
reportAnnotationToBuildKite({
priority: 10,
label: "build failed",
content,
});
}
}
if (signalCode) {
if (!killedManually) {
console.error(`Command killed: ${signalCode}`);
}
} else {
console.error(`Command exited: code ${exitCode}`);
}
process.exit(exitCode ?? 1);
}
function printDuration(label, duration) {
if (duration > 60000) {
console.log(`${label} took ${(duration / 60000).toFixed(2)} minutes`);
} else {
console.log(`${label} took ${(duration / 1000).toFixed(2)} seconds`);
}
}
build(process.argv.slice(2));