ci: musl builds (#15154)

Co-authored-by: Electroid <Electroid@users.noreply.github.com>
Co-authored-by: Meghan Denny <meghan@bun.sh>
This commit is contained in:
Ashcon Partovi
2024-11-15 21:01:55 -08:00
committed by GitHub
parent 4fedc41545
commit 39d8ade27c
26 changed files with 3323 additions and 714 deletions

744
.buildkite/ci.mjs Normal file → Executable file
View File

@@ -8,13 +8,14 @@
import { writeFileSync } from "node:fs"; import { writeFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import { import {
getBootstrapVersion,
getBuildNumber,
getCanaryRevision, getCanaryRevision,
getChangedFiles, getChangedFiles,
getCommit, getCommit,
getCommitMessage, getCommitMessage,
getLastSuccessfulBuild, getLastSuccessfulBuild,
getMainBranch, getMainBranch,
getRepositoryOwner,
getTargetBranch, getTargetBranch,
isBuildkite, isBuildkite,
isFork, isFork,
@@ -22,102 +23,162 @@ import {
isMergeQueue, isMergeQueue,
printEnvironment, printEnvironment,
spawnSafe, spawnSafe,
toYaml,
uploadArtifact,
} from "../scripts/utils.mjs"; } from "../scripts/utils.mjs";
function toYaml(obj, indent = 0) { /**
const spaces = " ".repeat(indent); * @typedef PipelineOptions
let result = ""; * @property {string} [buildId]
* @property {boolean} [buildImages]
* @property {boolean} [publishImages]
* @property {boolean} [skipTests]
*/
for (const [key, value] of Object.entries(obj)) { /**
if (value === undefined) { * @param {PipelineOptions} options
continue; */
} function getPipeline(options) {
const { buildId, buildImages, publishImages, skipTests } = options;
if (value === null) {
result += `${spaces}${key}: null\n`;
continue;
}
if (Array.isArray(value)) {
result += `${spaces}${key}:\n`;
value.forEach(item => {
if (typeof item === "object" && item !== null) {
result += `${spaces}- \n${toYaml(item, indent + 2)
.split("\n")
.map(line => `${spaces} ${line}`)
.join("\n")}\n`;
} else {
result += `${spaces}- ${item}\n`;
}
});
continue;
}
if (typeof value === "object") {
result += `${spaces}${key}:\n${toYaml(value, indent + 2)}`;
continue;
}
if (
typeof value === "string" &&
(value.includes(":") || value.includes("#") || value.includes("'") || value.includes('"') || value.includes("\n"))
) {
result += `${spaces}${key}: "${value.replace(/"/g, '\\"')}"\n`;
continue;
}
result += `${spaces}${key}: ${value}\n`;
}
return result;
}
function getPipeline(buildId) {
/** /**
* Helpers * Helpers
*/ */
const getKey = platform => { /**
const { os, arch, abi, baseline } = platform; * @param {string} text
* @returns {string}
if (abi) { * @link https://github.com/buildkite/emojis#emoji-reference
if (baseline) { */
return `${os}-${arch}-${abi}-baseline`; const getEmoji = string => {
} if (string === "amazonlinux") {
return `${os}-${arch}-${abi}`; return ":aws:";
} }
if (baseline) { return `:${string}:`;
return `${os}-${arch}-baseline`;
}
return `${os}-${arch}`;
}; };
const getLabel = platform => { /**
const { os, arch, abi, baseline, release } = platform; * @typedef {"linux" | "darwin" | "windows"} Os
let label = release ? `:${os}: ${release} ${arch}` : `:${os}: ${arch}`; * @typedef {"aarch64" | "x64"} Arch
* @typedef {"musl"} Abi
*/
/**
* @typedef Target
* @property {Os} os
* @property {Arch} arch
* @property {Abi} [abi]
* @property {boolean} [baseline]
*/
/**
* @param {Target} target
* @returns {string}
*/
const getTargetKey = target => {
const { os, arch, abi, baseline } = target;
let key = `${os}-${arch}`;
if (abi) {
key += `-${abi}`;
}
if (baseline) {
key += "-baseline";
}
return key;
};
/**
* @param {Target} target
* @returns {string}
*/
const getTargetLabel = target => {
const { os, arch, abi, baseline } = target;
let label = `${getEmoji(os)} ${arch}`;
if (abi) { if (abi) {
label += `-${abi}`; label += `-${abi}`;
} }
if (baseline) { if (baseline) {
label += `-baseline`; label += "-baseline";
} }
return label; return label;
}; };
// https://buildkite.com/docs/pipelines/command-step#retry-attributes /**
const getRetry = (limit = 3) => { * @typedef Platform
* @property {Os} os
* @property {Arch} arch
* @property {Abi} [abi]
* @property {boolean} [baseline]
* @property {string} [distro]
* @property {string} release
*/
/**
* @param {Platform} platform
* @returns {string}
*/
const getPlatformKey = platform => {
const { os, arch, abi, baseline, distro, release } = platform;
const target = getTargetKey({ os, arch, abi, baseline });
if (distro) {
return `${target}-${distro}-${release.replace(/\./g, "")}`;
}
return `${target}-${release.replace(/\./g, "")}`;
};
/**
* @param {Platform} platform
* @returns {string}
*/
const getPlatformLabel = platform => {
const { os, arch, baseline, distro, release } = platform;
let label = `${getEmoji(distro || os)} ${release} ${arch}`;
if (baseline) {
label += "-baseline";
}
return label;
};
/**
* @param {Platform} platform
* @returns {string}
*/
const getImageKey = platform => {
const { os, arch, distro, release } = platform;
if (distro) {
return `${os}-${arch}-${distro}-${release.replace(/\./g, "")}`;
}
return `${os}-${arch}-${release.replace(/\./g, "")}`;
};
/**
* @param {Platform} platform
* @returns {string}
*/
const getImageLabel = platform => {
const { os, arch, distro, release } = platform;
return `${getEmoji(distro || os)} ${release} ${arch}`;
};
/**
* @param {number} [limit]
* @link https://buildkite.com/docs/pipelines/command-step#retry-attributes
*/
const getRetry = (limit = 0) => {
return { return {
automatic: [ automatic: [
{ exit_status: 1, limit: 1 }, { exit_status: 1, limit },
{ exit_status: -1, limit }, { exit_status: -1, limit: 3 },
{ exit_status: 255, limit }, { exit_status: 255, limit: 3 },
{ signal_reason: "agent_stop", limit }, { signal_reason: "agent_stop", limit: 3 },
], ],
}; };
}; };
// https://buildkite.com/docs/pipelines/managing-priorities /**
* @returns {number}
* @link https://buildkite.com/docs/pipelines/managing-priorities
*/
const getPriority = () => { const getPriority = () => {
if (isFork()) { if (isFork()) {
return -1; return -1;
@@ -131,132 +192,286 @@ function getPipeline(buildId) {
return 0; return 0;
}; };
/**
* @param {Target} target
* @returns {Record<string, string | undefined>}
*/
const getBuildEnv = target => {
const { baseline, abi } = target;
return {
ENABLE_BASELINE: baseline ? "ON" : "OFF",
ABI: abi === "musl" ? "musl" : undefined,
};
};
/**
* @param {Target} target
* @returns {string}
*/
const getBuildToolchain = target => {
const { os, arch, abi, baseline } = target;
let key = `${os}-${arch}`;
if (abi) {
key += `-${abi}`;
}
if (baseline) {
key += "-baseline";
}
return key;
};
/**
* Agents
*/
/**
* @typedef {Record<string, string | undefined>} Agent
*/
/**
* @param {Platform} platform
* @returns {boolean}
*/
const isUsingNewAgent = platform => {
const { os, distro } = platform;
if (os === "linux" && distro === "alpine") {
return true;
}
return false;
};
/**
* @param {"v1" | "v2"} version
* @param {Platform} platform
* @param {string} [instanceType]
* @returns {Agent}
*/
const getEmphemeralAgent = (version, platform, instanceType) => {
const { os, arch, abi, distro, release } = platform;
if (version === "v1") {
return {
robobun: true,
os,
arch,
distro,
release,
};
}
let image;
if (distro) {
image = `${os}-${arch}-${distro}-${release}`;
} else {
image = `${os}-${arch}-${release}`;
}
if (buildImages && !publishImages) {
image += `-build-${getBuildNumber()}`;
} else {
image += `-v${getBootstrapVersion()}`;
}
return {
robobun: true,
robobun2: true,
os,
arch,
abi,
distro,
release,
"image-name": image,
"instance-type": instanceType,
};
};
/**
* @param {Target} target
* @returns {Agent}
*/
const getBuildAgent = target => {
const { os, arch, abi } = target;
if (isUsingNewAgent(target)) {
const instanceType = arch === "aarch64" ? "c8g.8xlarge" : "c7i.8xlarge";
return getEmphemeralAgent("v2", target, instanceType);
}
return {
queue: `build-${os}`,
os,
arch,
abi,
};
};
/**
* @param {Target} target
* @returns {Agent}
*/
const getZigAgent = target => {
const { abi, arch } = target;
// if (abi === "musl") {
// const instanceType = arch === "aarch64" ? "c8g.large" : "c7i.large";
// return getEmphemeralAgent("v2", target, instanceType);
// }
return {
queue: "build-zig",
};
};
/**
* @param {Platform} platform
* @returns {Agent}
*/
const getTestAgent = platform => {
const { os, arch, release } = platform;
if (isUsingNewAgent(platform)) {
const instanceType = arch === "aarch64" ? "t4g.large" : "t3.large";
return getEmphemeralAgent("v2", platform, instanceType);
}
if (os === "darwin") {
return {
os,
arch,
release,
queue: "test-darwin",
};
}
return getEmphemeralAgent("v1", platform);
};
/** /**
* Steps * Steps
*/ */
const getBuildVendorStep = platform => { /**
const { os, arch, abi, baseline } = platform; * @typedef Step
* @property {string} key
* @property {string} [label]
* @property {Record<string, string | undefined>} [agents]
* @property {Record<string, string | undefined>} [env]
* @property {string} command
* @property {string[]} [depends_on]
* @property {Record<string, string | undefined>} [retry]
* @property {boolean} [cancel_on_build_failing]
* @property {boolean} [soft_fail]
* @property {number} [parallelism]
* @property {number} [concurrency]
* @property {string} [concurrency_group]
* @property {number} [priority]
* @property {number} [timeout_in_minutes]
* @link https://buildkite.com/docs/pipelines/command-step
*/
/**
* @param {Platform} platform
* @returns {Step}
*/
const getBuildImageStep = platform => {
const { os, arch, distro, release } = platform;
const action = publishImages ? "publish-image" : "create-image";
return { return {
key: `${getKey(platform)}-build-vendor`, key: `${getImageKey(platform)}-build-image`,
label: `build-vendor`, label: `${getImageLabel(platform)} - build-image`,
agents: { agents: {
os, queue: "build-image",
arch,
abi,
queue: abi ? `build-${os}-${abi}` : `build-${os}`,
}, },
env: {
DEBUG: "1",
},
command: `node ./scripts/machine.mjs ${action} --ci --cloud=aws --os=${os} --arch=${arch} --distro=${distro} --distro-version=${release}`,
};
};
/**
* @param {Target} target
* @returns {Step}
*/
const getBuildVendorStep = target => {
return {
key: `${getTargetKey(target)}-build-vendor`,
label: `${getTargetLabel(target)} - build-vendor`,
agents: getBuildAgent(target),
retry: getRetry(), retry: getRetry(),
cancel_on_build_failing: isMergeQueue(), cancel_on_build_failing: isMergeQueue(),
env: { env: getBuildEnv(target),
ENABLE_BASELINE: baseline ? "ON" : "OFF",
},
command: "bun run build:ci --target dependencies", command: "bun run build:ci --target dependencies",
}; };
}; };
const getBuildCppStep = platform => { /**
const { os, arch, abi, baseline } = platform; * @param {Target} target
* @returns {Step}
*/
const getBuildCppStep = target => {
return { return {
key: `${getKey(platform)}-build-cpp`, key: `${getTargetKey(target)}-build-cpp`,
label: `build-cpp`, label: `${getTargetLabel(target)} - build-cpp`,
agents: { agents: getBuildAgent(target),
os,
arch,
abi,
queue: abi ? `build-${os}-${abi}` : `build-${os}`,
},
retry: getRetry(), retry: getRetry(),
cancel_on_build_failing: isMergeQueue(), cancel_on_build_failing: isMergeQueue(),
env: { env: {
BUN_CPP_ONLY: "ON", BUN_CPP_ONLY: "ON",
ENABLE_BASELINE: baseline ? "ON" : "OFF", ...getBuildEnv(target),
}, },
command: "bun run build:ci --target bun", command: "bun run build:ci --target bun",
}; };
}; };
const getBuildZigStep = platform => { /**
const { os, arch, abi, baseline } = platform; * @param {Target} target
const toolchain = getKey(platform); * @returns {Step}
*/
const getBuildZigStep = target => {
const toolchain = getBuildToolchain(target);
return { return {
key: `${getKey(platform)}-build-zig`, key: `${getTargetKey(target)}-build-zig`,
label: `build-zig`, label: `${getTargetLabel(target)} - build-zig`,
agents: { agents: getZigAgent(target),
queue: "build-zig", retry: getRetry(1), // FIXME: Sometimes zig build hangs, so we need to retry once
},
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(), cancel_on_build_failing: isMergeQueue(),
env: { env: getBuildEnv(target),
ENABLE_BASELINE: baseline ? "ON" : "OFF",
},
command: `bun run build:ci --target bun-zig --toolchain ${toolchain}`, command: `bun run build:ci --target bun-zig --toolchain ${toolchain}`,
}; };
}; };
const getBuildBunStep = platform => { /**
const { os, arch, abi, baseline } = platform; * @param {Target} target
* @returns {Step}
*/
const getBuildBunStep = target => {
return { return {
key: `${getKey(platform)}-build-bun`, key: `${getTargetKey(target)}-build-bun`,
label: `build-bun`, label: `${getTargetLabel(target)} - build-bun`,
depends_on: [ depends_on: [
`${getKey(platform)}-build-vendor`, `${getTargetKey(target)}-build-vendor`,
`${getKey(platform)}-build-cpp`, `${getTargetKey(target)}-build-cpp`,
`${getKey(platform)}-build-zig`, `${getTargetKey(target)}-build-zig`,
], ],
agents: { agents: getBuildAgent(target),
os,
arch,
abi,
queue: `build-${os}`,
},
retry: getRetry(), retry: getRetry(),
cancel_on_build_failing: isMergeQueue(), cancel_on_build_failing: isMergeQueue(),
env: { env: {
BUN_LINK_ONLY: "ON", BUN_LINK_ONLY: "ON",
ENABLE_BASELINE: baseline ? "ON" : "OFF", ...getBuildEnv(target),
}, },
command: "bun run build:ci --target bun", command: "bun run build:ci --target bun",
}; };
}; };
/**
* @param {Platform} platform
* @returns {Step}
*/
const getTestBunStep = platform => { const getTestBunStep = platform => {
const { os, arch, abi, distro, release } = platform; const { os } = platform;
let name;
if (os === "darwin" || os === "windows") {
name = getLabel({ ...platform, release });
} else {
name = getLabel({ ...platform, os: distro, release });
}
let agents;
if (os === "darwin") {
agents = { os, arch, abi, queue: `test-darwin` };
} else if (os === "windows") {
agents = { os, arch, abi, robobun: true };
} else {
agents = { os, arch, abi, distro, release, robobun: true };
}
let command; let command;
if (os === "windows") { if (os === "windows") {
command = `node .\\scripts\\runner.node.mjs --step ${getKey(platform)}-build-bun`; command = `node .\\scripts\\runner.node.mjs --step ${getTargetKey(platform)}-build-bun`;
} else { } else {
command = `./scripts/runner.node.mjs --step ${getKey(platform)}-build-bun`; command = `./scripts/runner.node.mjs --step ${getTargetKey(platform)}-build-bun`;
} }
let parallelism; let parallelism;
if (os === "darwin") { if (os === "darwin") {
parallelism = 2; parallelism = 2;
} else { } else {
parallelism = 10; parallelism = 10;
} }
let depends; let depends;
let env; let env;
if (buildId) { if (buildId) {
@@ -264,21 +479,19 @@ function getPipeline(buildId) {
BUILDKITE_ARTIFACT_BUILD_ID: buildId, BUILDKITE_ARTIFACT_BUILD_ID: buildId,
}; };
} else { } else {
depends = [`${getKey(platform)}-build-bun`]; depends = [`${getTargetKey(platform)}-build-bun`];
} }
let retry; let retry;
if (os !== "windows") { if (os !== "windows") {
// When the runner fails on Windows, Buildkite only detects an exit code of 1. // When the runner fails on Windows, Buildkite only detects an exit code of 1.
// Because of this, we don't know if the run was fatal, or soft-failed. // Because of this, we don't know if the run was fatal, or soft-failed.
retry = getRetry(); retry = getRetry(1);
} }
return { return {
key: `${getKey(platform)}-${distro}-${release.replace(/\./g, "")}-test-bun`, key: `${getPlatformKey(platform)}-test-bun`,
label: `${name} - test-bun`, label: `${getPlatformLabel(platform)} - test-bun`,
depends_on: depends, depends_on: depends,
agents, agents: getTestAgent(platform),
retry, retry,
cancel_on_build_failing: isMergeQueue(), cancel_on_build_failing: isMergeQueue(),
soft_fail: isMainBranch(), soft_fail: isMainBranch(),
@@ -292,66 +505,145 @@ function getPipeline(buildId) {
* Config * Config
*/ */
/**
* @type {Platform[]}
*/
const buildPlatforms = [ const buildPlatforms = [
{ os: "darwin", arch: "aarch64" }, { os: "darwin", arch: "aarch64", release: "14" },
{ os: "darwin", arch: "x64" }, { os: "darwin", arch: "x64", release: "14" },
{ os: "linux", arch: "aarch64" }, { os: "linux", arch: "aarch64", distro: "debian", release: "11" },
// { os: "linux", arch: "aarch64", abi: "musl" }, // TODO: { os: "linux", arch: "x64", distro: "debian", release: "11" },
{ os: "linux", arch: "x64" }, { os: "linux", arch: "x64", baseline: true, distro: "debian", release: "11" },
{ os: "linux", arch: "x64", baseline: true }, { os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.20" },
// { os: "linux", arch: "x64", abi: "musl" }, // TODO: { os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.20" },
{ os: "windows", arch: "x64" }, { os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.20" },
{ os: "windows", arch: "x64", baseline: true }, { os: "windows", arch: "x64", release: "2019" },
{ os: "windows", arch: "x64", baseline: true, release: "2019" },
]; ];
/**
* @type {Platform[]}
*/
const testPlatforms = [ const testPlatforms = [
{ os: "darwin", arch: "aarch64", distro: "sonoma", release: "14" }, { os: "darwin", arch: "aarch64", release: "14" },
{ os: "darwin", arch: "aarch64", distro: "ventura", release: "13" }, { os: "darwin", arch: "aarch64", release: "13" },
{ os: "darwin", arch: "x64", distro: "sonoma", release: "14" }, { os: "darwin", arch: "x64", release: "14" },
{ os: "darwin", arch: "x64", distro: "ventura", release: "13" }, { os: "darwin", arch: "x64", release: "13" },
{ os: "linux", arch: "aarch64", distro: "debian", release: "12" }, { os: "linux", arch: "aarch64", distro: "debian", release: "12" },
// { os: "linux", arch: "aarch64", distro: "debian", release: "11" },
// { os: "linux", arch: "aarch64", distro: "debian", release: "10" },
{ os: "linux", arch: "x64", distro: "debian", release: "12" },
// { os: "linux", arch: "x64", distro: "debian", release: "11" },
// { os: "linux", arch: "x64", distro: "debian", release: "10" },
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "12" },
// { os: "linux", arch: "x64", baseline: true, distro: "debian", release: "11" },
// { os: "linux", arch: "x64", baseline: true, distro: "debian", release: "10" },
// { os: "linux", arch: "aarch64", distro: "ubuntu", release: "24.04" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "22.04" }, { os: "linux", arch: "aarch64", distro: "ubuntu", release: "22.04" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "20.04" }, { os: "linux", arch: "aarch64", distro: "ubuntu", release: "20.04" },
// { os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "edge" }, // TODO: // { os: "linux", arch: "x64", distro: "ubuntu", release: "24.04" },
{ os: "linux", arch: "x64", distro: "debian", release: "12" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "22.04" }, { os: "linux", arch: "x64", distro: "ubuntu", release: "22.04" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "20.04" }, { os: "linux", arch: "x64", distro: "ubuntu", release: "20.04" },
{ os: "linux", arch: "x64", distro: "debian", release: "12", baseline: true }, // { os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "24.04" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "22.04", baseline: true }, { os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "22.04" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "20.04", baseline: true }, { os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "20.04" },
// { os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "edge" }, // TODO: // { os: "linux", arch: "aarch64", distro: "amazonlinux", release: "2023" },
{ os: "windows", arch: "x64", distro: "server", release: "2019" }, // { os: "linux", arch: "aarch64", distro: "amazonlinux", release: "2" },
{ os: "windows", arch: "x64", distro: "server", release: "2019", baseline: true }, // { os: "linux", arch: "x64", distro: "amazonlinux", release: "2023" },
// { os: "linux", arch: "x64", distro: "amazonlinux", release: "2" },
// { os: "linux", arch: "x64", baseline: true, distro: "amazonlinux", release: "2023" },
// { os: "linux", arch: "x64", baseline: true, distro: "amazonlinux", release: "2" },
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.20" },
// { os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.17" },
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.20" },
// { os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.17" },
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.20" },
// { os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.17" },
{ os: "windows", arch: "x64", release: "2019" },
{ os: "windows", arch: "x64", baseline: true, release: "2019" },
]; ];
const imagePlatforms = new Map(
[...buildPlatforms, ...testPlatforms]
.filter(platform => buildImages && isUsingNewAgent(platform))
.map(platform => [getImageKey(platform), platform]),
);
/**
* @type {Step[]}
*/
const steps = [];
if (imagePlatforms.size) {
steps.push({
group: ":docker:",
steps: [...imagePlatforms.values()].map(platform => getBuildImageStep(platform)),
});
}
for (const platform of buildPlatforms) {
const { os, arch, abi, baseline } = platform;
/** @type {Step[]} */
const platformSteps = [];
if (buildImages || !buildId) {
platformSteps.push(
getBuildVendorStep(platform),
getBuildCppStep(platform),
getBuildZigStep(platform),
getBuildBunStep(platform),
);
}
if (!skipTests) {
platformSteps.push(
...testPlatforms
.filter(
testPlatform =>
testPlatform.os === os &&
testPlatform.arch === arch &&
testPlatform.abi === abi &&
testPlatform.baseline === baseline,
)
.map(testPlatform => getTestBunStep(testPlatform)),
);
}
if (!platformSteps.length) {
continue;
}
if (imagePlatforms.has(getImageKey(platform))) {
for (const step of platformSteps) {
if (step.agents?.["image-name"]) {
step.depends_on ??= [];
step.depends_on.push(`${getImageKey(platform)}-build-image`);
}
}
}
steps.push({
key: getTargetKey(platform),
group: getTargetLabel(platform),
steps: platformSteps,
});
}
if (isMainBranch() && !isFork()) {
steps.push({
label: ":github:",
agents: {
queue: "test-darwin",
},
depends_on: buildPlatforms.map(platform => `${getTargetKey(platform)}-build-bun`),
command: ".buildkite/scripts/upload-release.sh",
});
}
return { return {
priority: getPriority(), priority: getPriority(),
steps: [ steps,
...buildPlatforms.map(platform => {
const { os, arch, baseline } = platform;
let steps = [
...testPlatforms
.filter(platform => platform.os === os && platform.arch === arch && baseline === platform.baseline)
.map(platform => getTestBunStep(platform)),
];
if (!buildId) {
steps.unshift(
getBuildVendorStep(platform),
getBuildCppStep(platform),
getBuildZigStep(platform),
getBuildBunStep(platform),
);
}
return {
key: getKey(platform),
group: getLabel(platform),
steps,
};
}),
],
}; };
} }
@@ -369,7 +661,6 @@ async function main() {
console.log(" - No build found"); console.log(" - No build found");
} }
let changedFiles; let changedFiles;
if (!isFork()) { if (!isFork()) {
console.log("Checking changed files..."); console.log("Checking changed files...");
@@ -377,7 +668,7 @@ async function main() {
console.log(" - Base Ref:", baseRef); console.log(" - Base Ref:", baseRef);
const headRef = lastBuild?.commit_id || getTargetBranch() || getMainBranch(); const headRef = lastBuild?.commit_id || getTargetBranch() || getMainBranch();
console.log(" - Head Ref:", headRef); console.log(" - Head Ref:", headRef);
changedFiles = await getChangedFiles(undefined, baseRef, headRef); changedFiles = await getChangedFiles(undefined, baseRef, headRef);
if (changedFiles) { if (changedFiles) {
if (changedFiles.length) { if (changedFiles.length) {
@@ -418,6 +709,31 @@ async function main() {
} }
} }
console.log("Checking if CI should re-build images...");
let buildImages;
{
const message = getCommitMessage();
const match = /\[(build images?|images? build)\]/i.exec(message);
if (match) {
const [, reason] = match;
console.log(" - Yes, because commit message contains:", reason);
buildImages = true;
}
}
console.log("Checking if CI should publish images...");
let publishImages;
{
const message = getCommitMessage();
const match = /\[(publish images?|images? publish)\]/i.exec(message);
if (match) {
const [, reason] = match;
console.log(" - Yes, because commit message contains:", reason);
publishImages = true;
buildImages = true;
}
}
console.log("Checking if build should be skipped..."); console.log("Checking if build should be skipped...");
let skipBuild; let skipBuild;
if (!forceBuild) { if (!forceBuild) {
@@ -434,6 +750,17 @@ async function main() {
} }
} }
console.log("Checking if tests should be skipped...");
let skipTests;
{
const message = getCommitMessage();
const match = /\[(skip tests?|tests? skip|no tests?|tests? no)\]/i.exec(message);
if (match) {
console.log(" - Yes, because commit message contains:", match[1]);
skipTests = true;
}
}
console.log("Checking if build is a named release..."); console.log("Checking if build is a named release...");
let buildRelease; let buildRelease;
{ {
@@ -447,7 +774,13 @@ async function main() {
} }
console.log("Generating pipeline..."); console.log("Generating pipeline...");
const pipeline = getPipeline(lastBuild && skipBuild && !forceBuild ? lastBuild.id : undefined); const pipeline = getPipeline({
buildId: lastBuild && skipBuild && !forceBuild ? lastBuild.id : undefined,
buildImages,
publishImages,
skipTests,
});
const content = toYaml(pipeline); const content = toYaml(pipeline);
const contentPath = join(process.cwd(), ".buildkite", "ci.yml"); const contentPath = join(process.cwd(), ".buildkite", "ci.yml");
writeFileSync(contentPath, content); writeFileSync(contentPath, content);
@@ -455,14 +788,17 @@ async function main() {
console.log("Generated pipeline:"); console.log("Generated pipeline:");
console.log(" - Path:", contentPath); console.log(" - Path:", contentPath);
console.log(" - Size:", (content.length / 1024).toFixed(), "KB"); console.log(" - Size:", (content.length / 1024).toFixed(), "KB");
if (isBuildkite) {
await uploadArtifact(contentPath);
}
if (isBuildkite) { if (isBuildkite) {
console.log("Setting canary revision..."); console.log("Setting canary revision...");
const canaryRevision = buildRelease ? 0 : await getCanaryRevision(); const canaryRevision = buildRelease ? 0 : await getCanaryRevision();
await spawnSafe(["buildkite-agent", "meta-data", "set", "canary", `${canaryRevision}`]); await spawnSafe(["buildkite-agent", "meta-data", "set", "canary", `${canaryRevision}`], { stdio: "inherit" });
console.log("Uploading pipeline..."); console.log("Uploading pipeline...");
await spawnSafe(["buildkite-agent", "pipeline", "upload", contentPath]); await spawnSafe(["buildkite-agent", "pipeline", "upload", contentPath], { stdio: "inherit" });
} }
} }

View File

@@ -202,6 +202,12 @@ function create_release() {
bun-linux-x64-profile.zip bun-linux-x64-profile.zip
bun-linux-x64-baseline.zip bun-linux-x64-baseline.zip
bun-linux-x64-baseline-profile.zip bun-linux-x64-baseline-profile.zip
bun-linux-aarch64-musl.zip
bun-linux-aarch64-musl-profile.zip
bun-linux-x64-musl.zip
bun-linux-x64-musl-profile.zip
bun-linux-x64-musl-baseline.zip
bun-linux-x64-musl-baseline-profile.zip
bun-windows-x64.zip bun-windows-x64.zip
bun-windows-x64-profile.zip bun-windows-x64-profile.zip
bun-windows-x64-baseline.zip bun-windows-x64-baseline.zip

18
ci/linux/Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
ARG IMAGE=debian:11
FROM $IMAGE
COPY ./scripts/bootstrap.sh /tmp/bootstrap.sh
ENV CI=true
RUN sh /tmp/bootstrap.sh && rm -rf /tmp/*
WORKDIR /workspace/bun
COPY bunfig.toml bunfig.toml
COPY package.json package.json
COPY CMakeLists.txt CMakeLists.txt
COPY cmake/ cmake/
COPY scripts/ scripts/
COPY patches/ patches/
COPY *.zig ./
COPY src/ src/
COPY packages/ packages/
COPY test/ test/
RUN bun i
RUN bun run build:ci

View File

@@ -0,0 +1,27 @@
#!/bin/sh
# This script sets the hostname of the current machine.
execute() {
echo "$ $@" >&2
if ! "$@"; then
echo "Command failed: $@" >&2
exit 1
fi
}
main() {
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <hostname>" >&2
exit 1
fi
if [ -f "$(which hostnamectl)" ]; then
execute hostnamectl set-hostname "$1"
else
echo "Error: hostnamectl is not installed." >&2
exit 1
fi
}
main "$@"

View File

@@ -0,0 +1,22 @@
#!/bin/sh
# This script starts tailscale on the current machine.
execute() {
echo "$ $@" >&2
if ! "$@"; then
echo "Command failed: $@" >&2
exit 1
fi
}
main() {
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <auth-key>" >&2
exit 1
fi
execute tailscale up --reset --ssh --accept-risk=lose-ssh --auth-key="$1"
}
main "$@"

View File

@@ -2,7 +2,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"bootstrap": "brew install gh jq cirruslabs/cli/tart cirruslabs/cli/sshpass hashicorp/tap/packer && packer init darwin", "bootstrap": "brew install gh jq cirruslabs/cli/tart cirruslabs/cli/sshpass hashicorp/tap/packer && packer init darwin",
"login": "gh auth token | tart login ghcr.io --username $(gh api user --jq .login) --password-stdin", "login": "token=$(gh auth token); username=$(gh api user --jq .login); echo \"Login as $username...\"; echo \"$token\" | tart login ghcr.io --username \"$username\" --password-stdin; echo \"$token\" | docker login ghcr.io --username \"$username\" --password-stdin",
"fetch:image-name": "echo ghcr.io/oven-sh/bun-vm", "fetch:image-name": "echo ghcr.io/oven-sh/bun-vm",
"fetch:darwin-version": "echo 1", "fetch:darwin-version": "echo 1",
"fetch:macos-version": "sw_vers -productVersion | cut -d. -f1", "fetch:macos-version": "sw_vers -productVersion | cut -d. -f1",

View File

@@ -105,14 +105,6 @@ else()
unsupported(CMAKE_HOST_SYSTEM_NAME) unsupported(CMAKE_HOST_SYSTEM_NAME)
endif() endif()
if(EXISTS "/lib/ld-musl-aarch64.so.1")
set(IS_MUSL ON)
elseif(EXISTS "/lib/ld-musl-x86_64.so.1")
set(IS_MUSL ON)
else()
set(IS_MUSL OFF)
endif()
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "arm64|ARM64|aarch64|AARCH64") if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "arm64|ARM64|aarch64|AARCH64")
set(HOST_OS "aarch64") set(HOST_OS "aarch64")
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64|X86_64|x64|X64|amd64|AMD64") elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64|X86_64|x64|X64|amd64|AMD64")
@@ -144,6 +136,16 @@ else()
set(WARNING WARNING) set(WARNING WARNING)
endif() endif()
if(LINUX)
if(EXISTS "/etc/alpine-release")
set(DEFAULT_ABI "musl")
else()
set(DEFAULT_ABI "gnu")
endif()
optionx(ABI "musl|gnu" "The ABI to use (e.g. musl, gnu)" DEFAULT ${DEFAULT_ABI})
endif()
# TODO: This causes flaky zig builds in CI, so temporarily disable it. # TODO: This causes flaky zig builds in CI, so temporarily disable it.
# if(CI) # if(CI)
# set(DEFAULT_VENDOR_PATH ${CACHE_PATH}/vendor) # set(DEFAULT_VENDOR_PATH ${CACHE_PATH}/vendor)

View File

@@ -484,14 +484,12 @@ set(BUN_ZIG_OUTPUT ${BUILD_PATH}/bun-zig.o)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|arm64|ARM64|aarch64|AARCH64") if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|arm64|ARM64|aarch64|AARCH64")
set(IS_ARM64 ON)
if(APPLE) if(APPLE)
set(ZIG_CPU "apple_m1") set(ZIG_CPU "apple_m1")
else() else()
set(ZIG_CPU "native") set(ZIG_CPU "native")
endif() endif()
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|X86_64|x64|X64|amd64|AMD64") elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|X86_64|x64|X64|amd64|AMD64")
set(IS_X86_64 ON)
if(ENABLE_BASELINE) if(ENABLE_BASELINE)
set(ZIG_CPU "nehalem") set(ZIG_CPU "nehalem")
else() else()
@@ -761,8 +759,8 @@ if(NOT WIN32)
) )
if(DEBUG) if(DEBUG)
# TODO: this shouldn't be necessary long term # TODO: this shouldn't be necessary long term
if (NOT IS_MUSL) if (NOT ABI STREQUAL "musl")
set(ABI_PUBLIC_FLAGS target_compile_options(${bun} PUBLIC
-fsanitize=null -fsanitize=null
-fsanitize-recover=all -fsanitize-recover=all
-fsanitize=bounds -fsanitize=bounds
@@ -773,14 +771,9 @@ if(NOT WIN32)
-fsanitize=returns-nonnull-attribute -fsanitize=returns-nonnull-attribute
-fsanitize=unreachable -fsanitize=unreachable
) )
set(ABI_PRIVATE_FLAGS target_link_libraries(${bun} PRIVATE
-fsanitize=null -fsanitize=null
) )
else()
set(ABI_PUBLIC_FLAGS
)
set(ABI_PRIVATE_FLAGS
)
endif() endif()
target_compile_options(${bun} PUBLIC target_compile_options(${bun} PUBLIC
@@ -798,10 +791,6 @@ if(NOT WIN32)
-Wno-unused-function -Wno-unused-function
-Wno-nullability-completeness -Wno-nullability-completeness
-Werror -Werror
${ABI_PUBLIC_FLAGS}
)
target_link_libraries(${bun} PRIVATE
${ABI_PRIVATE_FLAGS}
) )
else() else()
# Leave -Werror=unused off in release builds so we avoid errors from being used in ASSERT # Leave -Werror=unused off in release builds so we avoid errors from being used in ASSERT
@@ -846,7 +835,9 @@ if(WIN32)
/delayload:IPHLPAPI.dll /delayload:IPHLPAPI.dll
) )
endif() endif()
elseif(APPLE) endif()
if(APPLE)
target_link_options(${bun} PUBLIC target_link_options(${bun} PUBLIC
-dead_strip -dead_strip
-dead_strip_dylibs -dead_strip_dylibs
@@ -856,63 +847,36 @@ elseif(APPLE)
-fno-keep-static-consts -fno-keep-static-consts
-Wl,-map,${bun}.linker-map -Wl,-map,${bun}.linker-map
) )
else() endif()
# Try to use lld-16 if available, otherwise fallback to lld
# Cache it so we don't have to re-run CMake to pick it up
if((NOT DEFINED LLD_NAME) AND (NOT CI OR BUN_LINK_ONLY))
find_program(LLD_EXECUTABLE_NAME lld-${LLVM_VERSION_MAJOR})
if(NOT LLD_EXECUTABLE_NAME) if(LINUX)
if(CI) if(NOT ABI STREQUAL "musl")
# Ensure we don't use a differing version of lld in CI vs clang if(ARCH STREQUAL "aarch64")
message(FATAL_ERROR "lld-${LLVM_VERSION_MAJOR} not found. Please make sure you have LLVM ${LLVM_VERSION_MAJOR}.x installed and set to lld-${LLVM_VERSION_MAJOR}") target_link_options(${bun} PUBLIC
endif() -Wl,--wrap=fcntl64
-Wl,--wrap=statx
# To make it easier for contributors, allow differing versions of lld vs clang/cmake )
find_program(LLD_EXECUTABLE_NAME lld) endif()
if(ARCH STREQUAL "x64")
target_link_options(${bun} PUBLIC
-Wl,--wrap=fcntl
-Wl,--wrap=fcntl64
-Wl,--wrap=fstat
-Wl,--wrap=fstat64
-Wl,--wrap=fstatat
-Wl,--wrap=fstatat64
-Wl,--wrap=lstat
-Wl,--wrap=lstat64
-Wl,--wrap=mknod
-Wl,--wrap=mknodat
-Wl,--wrap=stat
-Wl,--wrap=stat64
-Wl,--wrap=statx
)
endif() endif()
if(NOT LLD_EXECUTABLE_NAME) target_link_options(${bun} PUBLIC
message(FATAL_ERROR "LLD not found. Please make sure you have LLVM ${LLVM_VERSION_MAJOR}.x installed and lld is available in your PATH as lld-${LLVM_VERSION_MAJOR}")
endif()
# normalize to basename so it can be used with -fuse-ld
get_filename_component(LLD_NAME ${LLD_EXECUTABLE_NAME} NAME CACHE)
message(STATUS "Using linker: ${LLD_NAME} (${LLD_EXECUTABLE_NAME})")
elseif(NOT DEFINED LLD_NAME)
set(LLD_NAME lld-${LLVM_VERSION_MAJOR})
endif()
if (NOT IS_MUSL)
if (IS_ARM64)
set(ARCH_WRAP_FLAGS
-Wl,--wrap=fcntl64
-Wl,--wrap=statx
)
elseif(IS_X86_64)
set(ARCH_WRAP_FLAGS
-Wl,--wrap=fcntl
-Wl,--wrap=fcntl64
-Wl,--wrap=fstat
-Wl,--wrap=fstat64
-Wl,--wrap=fstatat
-Wl,--wrap=fstatat64
-Wl,--wrap=lstat
-Wl,--wrap=lstat64
-Wl,--wrap=mknod
-Wl,--wrap=mknodat
-Wl,--wrap=stat
-Wl,--wrap=stat64
-Wl,--wrap=statx
)
endif()
else()
set(ARCH_WRAP_FLAGS
)
endif()
if (NOT IS_MUSL)
set(ABI_WRAP_FLAGS
-Wl,--wrap=cosf -Wl,--wrap=cosf
-Wl,--wrap=exp -Wl,--wrap=exp
-Wl,--wrap=expf -Wl,--wrap=expf
@@ -929,13 +893,10 @@ else()
-Wl,--wrap=sinf -Wl,--wrap=sinf
-Wl,--wrap=tanf -Wl,--wrap=tanf
) )
else()
set(ABI_WRAP_FLAGS
)
endif() endif()
target_link_options(${bun} PUBLIC target_link_options(${bun} PUBLIC
-fuse-ld=${LLD_NAME} --ld-path=${LLD_PROGRAM}
-fno-pic -fno-pic
-static-libstdc++ -static-libstdc++
-static-libgcc -static-libgcc
@@ -944,8 +905,6 @@ else()
-Wl,--as-needed -Wl,--as-needed
-Wl,--gc-sections -Wl,--gc-sections
-Wl,-z,stack-size=12800000 -Wl,-z,stack-size=12800000
${ARCH_WRAP_FLAGS}
${ABI_WRAP_FLAGS}
-Wl,--compress-debug-sections=zlib -Wl,--compress-debug-sections=zlib
-Wl,-z,lazy -Wl,-z,lazy
-Wl,-z,norelro -Wl,-z,norelro
@@ -1095,12 +1054,12 @@ endif()
if(NOT BUN_CPP_ONLY) if(NOT BUN_CPP_ONLY)
set(CMAKE_STRIP_FLAGS "") set(CMAKE_STRIP_FLAGS "")
if (APPLE) if(APPLE)
# We do not build with exceptions enabled. These are generated by lolhtml # We do not build with exceptions enabled. These are generated by lolhtml
# and other dependencies. We build lolhtml with abort on panic, so it # and other dependencies. We build lolhtml with abort on panic, so it
# shouldn't be including these in the first place. # shouldn't be including these in the first place.
set(CMAKE_STRIP_FLAGS --remove-section=__TEXT,__eh_frame --remove-section=__TEXT,__unwind_info --remove-section=__TEXT,__gcc_except_tab) set(CMAKE_STRIP_FLAGS --remove-section=__TEXT,__eh_frame --remove-section=__TEXT,__unwind_info --remove-section=__TEXT,__gcc_except_tab)
elseif(LINUX) elseif(LINUX AND NOT ABI STREQUAL "musl")
# When you use llvm-strip to do this, it doesn't delete it from the binary and instead keeps it as [LOAD #2 [R]] # When you use llvm-strip to do this, it doesn't delete it from the binary and instead keeps it as [LOAD #2 [R]]
# So, we must use GNU strip to do this. # So, we must use GNU strip to do this.
set(CMAKE_STRIP_FLAGS -R .eh_frame -R .gcc_except_table) set(CMAKE_STRIP_FLAGS -R .eh_frame -R .gcc_except_table)
@@ -1193,10 +1152,12 @@ if(NOT BUN_CPP_ONLY)
endif() endif()
if(CI) if(CI)
set(bunTriplet bun-${OS}-${ARCH})
if(ABI STREQUAL "musl")
set(bunTriplet ${bunTriplet}-musl)
endif()
if(ENABLE_BASELINE) if(ENABLE_BASELINE)
set(bunTriplet bun-${OS}-${ARCH}-baseline) set(bunTriplet ${bunTriplet}-baseline)
else()
set(bunTriplet bun-${OS}-${ARCH})
endif() endif()
string(REPLACE bun ${bunTriplet} bunPath ${bun}) string(REPLACE bun ${bunTriplet} bunPath ${bun})
set(bunFiles ${bunExe} features.json) set(bunFiles ${bunExe} features.json)

View File

@@ -0,0 +1,6 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(ABI musl)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -1,5 +1,6 @@
set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(ABI gnu)
set(CMAKE_C_COMPILER_WORKS ON) set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON) set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -1,6 +1,7 @@
set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x64) set(CMAKE_SYSTEM_PROCESSOR x64)
set(ENABLE_BASELINE ON) set(ENABLE_BASELINE ON)
set(ABI gnu)
set(CMAKE_C_COMPILER_WORKS ON) set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON) set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -0,0 +1,7 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x64)
set(ENABLE_BASELINE ON)
set(ABI musl)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -0,0 +1,6 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x64)
set(ABI musl)
set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -1,5 +1,6 @@
set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x64) set(CMAKE_SYSTEM_PROCESSOR x64)
set(ABI gnu)
set(CMAKE_C_COMPILER_WORKS ON) set(CMAKE_C_COMPILER_WORKS ON)
set(CMAKE_CXX_COMPILER_WORKS ON) set(CMAKE_CXX_COMPILER_WORKS ON)

View File

@@ -29,7 +29,7 @@ execute_process(
) )
if(NOT GIT_DIFF_RESULT EQUAL 0) if(NOT GIT_DIFF_RESULT EQUAL 0)
message(${WARNING} "Command failed: ${GIT_DIFF_COMMAND} ${GIT_DIFF_ERROR}") message(WARNING "Command failed: ${GIT_DIFF_COMMAND} ${GIT_DIFF_ERROR}")
return() return()
endif() endif()

View File

@@ -4,7 +4,7 @@ if(NOT ENABLE_LLVM)
return() return()
endif() endif()
if(CMAKE_HOST_WIN32 OR CMAKE_HOST_APPLE OR IS_MUSL) if(CMAKE_HOST_WIN32 OR CMAKE_HOST_APPLE OR ABI STREQUAL "musl")
set(DEFAULT_LLVM_VERSION "18.1.8") set(DEFAULT_LLVM_VERSION "18.1.8")
else() else()
set(DEFAULT_LLVM_VERSION "16.0.6") set(DEFAULT_LLVM_VERSION "16.0.6")
@@ -52,6 +52,7 @@ if(UNIX)
/usr/lib/llvm-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}/bin /usr/lib/llvm-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}/bin
/usr/lib/llvm-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}/bin /usr/lib/llvm-${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}/bin
/usr/lib/llvm-${LLVM_VERSION_MAJOR}/bin /usr/lib/llvm-${LLVM_VERSION_MAJOR}/bin
/usr/lib/llvm${LLVM_VERSION_MAJOR}/bin
) )
endif() endif()
endif() endif()
@@ -122,6 +123,9 @@ else()
find_llvm_command(CMAKE_STRIP llvm-strip) find_llvm_command(CMAKE_STRIP llvm-strip)
endif() endif()
find_llvm_command(CMAKE_RANLIB llvm-ranlib) find_llvm_command(CMAKE_RANLIB llvm-ranlib)
if(LINUX)
find_llvm_command(LLD_PROGRAM ld.lld)
endif()
if(APPLE) if(APPLE)
find_llvm_command(CMAKE_DSYMUTIL dsymutil) find_llvm_command(CMAKE_DSYMUTIL dsymutil)
endif() endif()

View File

@@ -63,7 +63,7 @@ else()
message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}") message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif() endif()
if(IS_MUSL) if(ABI STREQUAL "musl")
set(WEBKIT_SUFFIX "-musl") set(WEBKIT_SUFFIX "-musl")
endif() endif()

View File

@@ -11,7 +11,7 @@ if(APPLE)
elseif(WIN32) elseif(WIN32)
set(DEFAULT_ZIG_TARGET ${DEFAULT_ZIG_ARCH}-windows-msvc) set(DEFAULT_ZIG_TARGET ${DEFAULT_ZIG_ARCH}-windows-msvc)
elseif(LINUX) elseif(LINUX)
if(IS_MUSL) if(ABI STREQUAL "musl")
set(DEFAULT_ZIG_TARGET ${DEFAULT_ZIG_ARCH}-linux-musl) set(DEFAULT_ZIG_TARGET ${DEFAULT_ZIG_ARCH}-linux-musl)
else() else()
set(DEFAULT_ZIG_TARGET ${DEFAULT_ZIG_ARCH}-linux-gnu) set(DEFAULT_ZIG_TARGET ${DEFAULT_ZIG_ARCH}-linux-gnu)

230
scripts/agent.mjs Executable file
View File

@@ -0,0 +1,230 @@
#!/usr/bin/env node
// An agent that starts buildkite-agent and runs others services.
import { join } from "node:path";
import { realpathSync } from "node:fs";
import {
isWindows,
getOs,
getArch,
getKernel,
getAbi,
getAbiVersion,
getDistro,
getDistroVersion,
getHostname,
getCloud,
getCloudMetadataTag,
which,
getEnv,
writeFile,
spawnSafe,
} from "./utils.mjs";
import { parseArgs } from "node:util";
/**
* @param {"install" | "start"} action
*/
async function doBuildkiteAgent(action) {
const username = "buildkite-agent";
const command = which("buildkite-agent", { required: true });
let homePath, cachePath, logsPath, agentLogPath, pidPath;
if (isWindows) {
throw new Error("TODO: Windows");
} else {
homePath = "/var/lib/buildkite-agent";
cachePath = "/var/cache/buildkite-agent";
logsPath = "/var/log/buildkite-agent";
agentLogPath = join(logsPath, "buildkite-agent.log");
pidPath = join(logsPath, "buildkite-agent.pid");
}
async function install() {
const command = process.execPath;
const args = [realpathSync(process.argv[1]), "start"];
if (isOpenRc()) {
const servicePath = "/etc/init.d/buildkite-agent";
const service = `#!/sbin/openrc-run
name="buildkite-agent"
description="Buildkite Agent"
command=${escape(command)}
command_args=${escape(args.map(escape).join(" "))}
command_user=${escape(username)}
pidfile=${escape(pidPath)}
start_stop_daemon_args=" \
--background \
--make-pidfile \
--stdout ${escape(agentLogPath)} \
--stderr ${escape(agentLogPath)}"
depend() {
need net
use dns logger
}
`;
writeFile(servicePath, service, { mode: 0o755 });
await spawnSafe(["rc-update", "add", "buildkite-agent", "default"], { stdio: "inherit", privileged: true });
}
if (isSystemd()) {
const servicePath = "/etc/systemd/system/buildkite-agent.service";
const service = `
[Unit]
Description=Buildkite Agent
After=syslog.target
After=network-online.target
[Service]
Type=simple
User=${username}
ExecStart=${escape(command)} ${escape(args.map(escape).join(" "))}
RestartSec=5
Restart=on-failure
KillMode=process
[Journal]
Storage=persistent
StateDirectory=${escape(agentLogPath)}
[Install]
WantedBy=multi-user.target
`;
writeFile(servicePath, service);
await spawnSafe(["systemctl", "daemon-reload"], { stdio: "inherit", privileged: true });
await spawnSafe(["systemctl", "enable", "buildkite-agent"], { stdio: "inherit", privileged: true });
}
}
async function start() {
const cloud = await getCloud();
let token = getEnv("BUILDKITE_AGENT_TOKEN", false);
if (!token && cloud) {
token = await getCloudMetadataTag("buildkite:token");
}
let shell;
if (isWindows) {
const pwsh = which(["pwsh", "powershell"], { required: true });
shell = `${pwsh} -Command`;
} else {
const sh = which(["bash", "sh"], { required: true });
shell = `${sh} -c`;
}
const flags = ["enable-job-log-tmpfile", "no-feature-reporting"];
const options = {
"name": getHostname(),
"token": token || "xxx",
"shell": shell,
"job-log-path": logsPath,
"build-path": join(homePath, "builds"),
"hooks-path": join(homePath, "hooks"),
"plugins-path": join(homePath, "plugins"),
"experiment": "normalised-upload-paths,resolve-commit-after-checkout,agent-api",
};
let ephemeral;
if (cloud) {
const jobId = await getCloudMetadataTag("buildkite:job-uuid");
if (jobId) {
options["acquire-job"] = jobId;
flags.push("disconnect-after-job");
ephemeral = true;
}
}
if (ephemeral) {
options["git-clone-flags"] = "-v --depth=1";
options["git-fetch-flags"] = "-v --prune --depth=1";
} else {
options["git-mirrors-path"] = join(cachePath, "git");
}
const tags = {
"os": getOs(),
"arch": getArch(),
"kernel": getKernel(),
"abi": getAbi(),
"abi-version": getAbiVersion(),
"distro": getDistro(),
"distro-version": getDistroVersion(),
"cloud": cloud,
};
if (cloud) {
const requiredTags = ["robobun", "robobun2"];
for (const tag of requiredTags) {
const value = await getCloudMetadataTag(tag);
if (typeof value === "string") {
tags[tag] = value;
}
}
}
options["tags"] = Object.entries(tags)
.filter(([, value]) => value)
.map(([key, value]) => `${key}=${value}`)
.join(",");
await spawnSafe(
[
command,
"start",
...flags.map(flag => `--${flag}`),
...Object.entries(options).map(([key, value]) => `--${key}=${value}`),
],
{
stdio: "inherit",
},
);
}
if (action === "install") {
await install();
} else if (action === "start") {
await start();
}
}
/**
* @returns {boolean}
*/
function isSystemd() {
return !!which("systemctl");
}
/**
* @returns {boolean}
*/
function isOpenRc() {
return !!which("rc-service");
}
function escape(string) {
return JSON.stringify(string);
}
async function main() {
const { positionals: args } = parseArgs({
allowPositionals: true,
});
if (!args.length || args.includes("install")) {
console.log("Installing agent...");
await doBuildkiteAgent("install");
console.log("Agent installed.");
}
if (args.includes("start")) {
console.log("Starting agent...");
await doBuildkiteAgent("start");
console.log("Agent started.");
}
}
await main();

File diff suppressed because it is too large Load Diff

0
scripts/build.mjs Normal file → Executable file
View File

0
scripts/features.mjs Normal file → Executable file
View File

1201
scripts/machine.mjs Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@ import {
} from "./utils.mjs"; } from "./utils.mjs";
import { userInfo } from "node:os"; import { userInfo } from "node:os";
const cwd = dirname(import.meta.dirname); const cwd = import.meta.dirname ? dirname(import.meta.dirname) : process.cwd();
const testsPath = join(cwd, "test"); const testsPath = join(cwd, "test");
const spawnTimeout = 5_000; const spawnTimeout = 5_000;
@@ -232,7 +232,7 @@ async function runTests() {
if (testRunner === "bun") { if (testRunner === "bun") {
await runTest(title, () => spawnBunTest(execPath, testPath, { cwd: vendorPath })); await runTest(title, () => spawnBunTest(execPath, testPath, { cwd: vendorPath }));
} else { } else {
const testRunnerPath = join(import.meta.dirname, "..", "test", "runners", `${testRunner}.ts`); const testRunnerPath = join(cwd, "test", "runners", `${testRunner}.ts`);
if (!existsSync(testRunnerPath)) { if (!existsSync(testRunnerPath)) {
throw new Error(`Unsupported test runner: ${testRunner}`); throw new Error(`Unsupported test runner: ${testRunner}`);
} }
@@ -632,7 +632,7 @@ function parseTestStdout(stdout, testPath) {
const removeStart = lines.length - skipCount; const removeStart = lines.length - skipCount;
const removeCount = skipCount - 2; const removeCount = skipCount - 2;
const omitLine = `${getAnsi("gray")}... omitted ${removeCount} tests ...${getAnsi("reset")}`; const omitLine = `${getAnsi("gray")}... omitted ${removeCount} tests ...${getAnsi("reset")}`;
lines = lines.toSpliced(removeStart, removeCount, omitLine); lines.splice(removeStart, removeCount, omitLine);
} }
skipCount = 0; skipCount = 0;
} }
@@ -1133,6 +1133,13 @@ function addPath(...paths) {
return paths.join(":"); return paths.join(":");
} }
/**
* @returns {string | undefined}
*/
function getTestLabel() {
return getBuildLabel()?.replace(" - test-bun", "");
}
/** /**
* @param {TestResult | TestResult[]} result * @param {TestResult | TestResult[]} result
* @param {boolean} concise * @param {boolean} concise
@@ -1140,7 +1147,7 @@ function addPath(...paths) {
*/ */
function formatTestToMarkdown(result, concise) { function formatTestToMarkdown(result, concise) {
const results = Array.isArray(result) ? result : [result]; const results = Array.isArray(result) ? result : [result];
const buildLabel = getBuildLabel(); const buildLabel = getTestLabel();
const buildUrl = getBuildUrl(); const buildUrl = getBuildUrl();
const platform = buildUrl ? `<a href="${buildUrl}">${buildLabel}</a>` : buildLabel; const platform = buildUrl ? `<a href="${buildUrl}">${buildLabel}</a>` : buildLabel;
@@ -1273,7 +1280,7 @@ function reportAnnotationToBuildKite({ label, content, style = "error", priority
const cause = error ?? signal ?? `code ${status}`; const cause = error ?? signal ?? `code ${status}`;
throw new Error(`Failed to create annotation: ${label}`, { cause }); throw new Error(`Failed to create annotation: ${label}`, { cause });
} }
const buildLabel = getBuildLabel(); const buildLabel = getTestLabel();
const buildUrl = getBuildUrl(); const buildUrl = getBuildUrl();
const platform = buildUrl ? `<a href="${buildUrl}">${buildLabel}</a>` : buildLabel; const platform = buildUrl ? `<a href="${buildUrl}">${buildLabel}</a>` : buildLabel;
let errorMessage = `<details><summary><a><code>${label}</code></a> - annotation error on ${platform}</summary>`; let errorMessage = `<details><summary><a><code>${label}</code></a> - annotation error on ${platform}</summary>`;

622
scripts/utils.mjs Normal file → Executable file
View File

@@ -3,9 +3,18 @@
import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process"; import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import { appendFileSync, existsSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import {
import { writeFile } from "node:fs/promises"; appendFileSync,
import { hostname, tmpdir as nodeTmpdir, userInfo } from "node:os"; chmodSync,
existsSync,
mkdirSync,
mkdtempSync,
readdirSync,
readFileSync,
writeFileSync,
} from "node:fs";
import { connect } from "node:net";
import { hostname, tmpdir as nodeTmpdir, userInfo, release } from "node:os";
import { dirname, join, relative, resolve } from "node:path"; import { dirname, join, relative, resolve } from "node:path";
import { normalize as normalizeWindows } from "node:path/win32"; import { normalize as normalizeWindows } from "node:path/win32";
@@ -53,8 +62,9 @@ export function getSecret(name, options = { required: true, redact: true }) {
command.push("--skip-redaction"); command.push("--skip-redaction");
} }
const { error, stdout: secret } = spawnSync(command); const { error, stdout } = spawnSync(command);
if (error || !secret.trim()) { const secret = stdout.trim();
if (error || !secret) {
const orgId = getEnv("BUILDKITE_ORGANIZATION_SLUG", false); const orgId = getEnv("BUILDKITE_ORGANIZATION_SLUG", false);
const clusterId = getEnv("BUILDKITE_CLUSTER_ID", false); const clusterId = getEnv("BUILDKITE_CLUSTER_ID", false);
@@ -106,8 +116,8 @@ export function setEnv(name, value) {
* @property {string} [cwd] * @property {string} [cwd]
* @property {number} [timeout] * @property {number} [timeout]
* @property {Record<string, string | undefined>} [env] * @property {Record<string, string | undefined>} [env]
* @property {string} [stdout] * @property {string} [stdin]
* @property {string} [stderr] * @property {boolean} [privileged]
*/ */
/** /**
@@ -119,20 +129,93 @@ export function setEnv(name, value) {
* @property {Error} [error] * @property {Error} [error]
*/ */
/**
* @param {TemplateStringsArray} strings
* @param {...any} values
* @returns {string[]}
*/
export function $(strings, ...values) {
const result = [];
for (let i = 0; i < strings.length; i++) {
result.push(...strings[i].trim().split(/\s+/).filter(Boolean));
if (i < values.length) {
const value = values[i];
if (Array.isArray(value)) {
result.push(...value);
} else if (typeof value === "string") {
if (result.at(-1)?.endsWith("=")) {
result[result.length - 1] += value;
} else {
result.push(value);
}
}
}
}
return result;
}
/** @type {string[] | undefined} */
let priviledgedCommand;
/**
* @param {string[]} command
* @param {SpawnOptions} options
*/
function parseCommand(command, options) {
if (options?.privileged) {
return [...getPrivilegedCommand(), ...command];
}
return command;
}
/**
* @returns {string[]}
*/
function getPrivilegedCommand() {
if (typeof priviledgedCommand !== "undefined") {
return priviledgedCommand;
}
if (isWindows) {
return (priviledgedCommand = []);
}
const sudo = ["sudo", "-n"];
const { error: sudoError } = spawnSync([...sudo, "true"]);
if (!sudoError) {
return (priviledgedCommand = sudo);
}
const su = ["su", "-s", "sh", "root", "-c"];
const { error: suError } = spawnSync([...su, "true"]);
if (!suError) {
return (priviledgedCommand = su);
}
const doas = ["doas", "-u", "root"];
const { error: doasError } = spawnSync([...doas, "true"]);
if (!doasError) {
return (priviledgedCommand = doas);
}
return (priviledgedCommand = []);
}
/** /**
* @param {string[]} command * @param {string[]} command
* @param {SpawnOptions} options * @param {SpawnOptions} options
* @returns {Promise<SpawnResult>} * @returns {Promise<SpawnResult>}
*/ */
export async function spawn(command, options = {}) { export async function spawn(command, options = {}) {
debugLog("$", ...command); const [cmd, ...args] = parseCommand(command, options);
debugLog("$", cmd, ...args);
const [cmd, ...args] = command; const stdin = options["stdin"];
const spawnOptions = { const spawnOptions = {
cwd: options["cwd"] ?? process.cwd(), cwd: options["cwd"] ?? process.cwd(),
timeout: options["timeout"] ?? undefined, timeout: options["timeout"] ?? undefined,
env: options["env"] ?? undefined, env: options["env"] ?? undefined,
stdio: ["ignore", "pipe", "pipe"], stdio: [stdin ? "pipe" : "ignore", "pipe", "pipe"],
...options, ...options,
}; };
@@ -145,6 +228,16 @@ export async function spawn(command, options = {}) {
const result = new Promise((resolve, reject) => { const result = new Promise((resolve, reject) => {
const subprocess = nodeSpawn(cmd, args, spawnOptions); const subprocess = nodeSpawn(cmd, args, spawnOptions);
if (typeof stdin !== "undefined") {
subprocess.stdin?.on("error", error => {
if (error.code !== "EPIPE") {
reject(error);
}
});
subprocess.stdin?.write(stdin);
subprocess.stdin?.end();
}
subprocess.stdout?.on("data", chunk => { subprocess.stdout?.on("data", chunk => {
stdout += chunk; stdout += chunk;
}); });
@@ -215,9 +308,9 @@ export async function spawnSafe(command, options) {
* @returns {SpawnResult} * @returns {SpawnResult}
*/ */
export function spawnSync(command, options = {}) { export function spawnSync(command, options = {}) {
debugLog("$", ...command); const [cmd, ...args] = parseCommand(command, options);
debugLog("$", cmd, ...args);
const [cmd, ...args] = command;
const spawnOptions = { const spawnOptions = {
cwd: options["cwd"] ?? process.cwd(), cwd: options["cwd"] ?? process.cwd(),
timeout: options["timeout"] ?? undefined, timeout: options["timeout"] ?? undefined,
@@ -245,8 +338,8 @@ export function spawnSync(command, options = {}) {
} else { } else {
exitCode = status ?? 1; exitCode = status ?? 1;
signalCode = signal || undefined; signalCode = signal || undefined;
stdout = stdoutBuffer.toString(); stdout = stdoutBuffer?.toString();
stderr = stderrBuffer.toString(); stderr = stderrBuffer?.toString();
} }
if (exitCode !== 0 && isWindows) { if (exitCode !== 0 && isWindows) {
@@ -258,7 +351,7 @@ export function spawnSync(command, options = {}) {
if (error || signalCode || exitCode !== 0) { if (error || signalCode || exitCode !== 0) {
const description = command.map(arg => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg)).join(" "); const description = command.map(arg => (arg.includes(" ") ? `"${arg.replace(/"/g, '\\"')}"` : arg)).join(" ");
const cause = error || stderr.trim() || stdout.trim() || undefined; const cause = error || stderr?.trim() || stdout?.trim() || undefined;
if (signalCode) { if (signalCode) {
error = new Error(`Command killed with ${signalCode}: ${description}`, { cause }); error = new Error(`Command killed with ${signalCode}: ${description}`, { cause });
@@ -670,7 +763,7 @@ export async function curl(url, options = {}) {
try { try {
if (filename && ok) { if (filename && ok) {
const buffer = await response.arrayBuffer(); const buffer = await response.arrayBuffer();
await writeFile(filename, new Uint8Array(buffer)); writeFile(filename, new Uint8Array(buffer));
} else if (arrayBuffer && ok) { } else if (arrayBuffer && ok) {
body = await response.arrayBuffer(); body = await response.arrayBuffer();
} else if (json && ok) { } else if (json && ok) {
@@ -735,7 +828,7 @@ export function readFile(filename, options = {}) {
} }
const relativePath = relative(process.cwd(), absolutePath); const relativePath = relative(process.cwd(), absolutePath);
debugLog("cat", relativePath); debugLog("$", "cat", relativePath);
let content; let content;
try { try {
@@ -752,6 +845,51 @@ export function readFile(filename, options = {}) {
return content; return content;
} }
/**
* @param {string} filename
* @param {string | Buffer} content
* @param {object} [options]
* @param {number} [options.mode]
*/
export function writeFile(filename, content, options = {}) {
const parent = dirname(filename);
if (!existsSync(parent)) {
mkdirSync(parent, { recursive: true });
}
writeFileSync(filename, content);
if (options["mode"]) {
chmodSync(filename, options["mode"]);
}
}
/**
* @param {string | string[]} command
* @param {object} [options]
* @param {boolean} [options.required]
* @returns {string | undefined}
*/
export function which(command, options = {}) {
const commands = Array.isArray(command) ? command : [command];
const path = getEnv("PATH", false) || "";
const binPaths = path.split(isWindows ? ";" : ":");
for (const binPath of binPaths) {
for (const command of commands) {
const commandPath = join(binPath, command);
if (existsSync(commandPath)) {
return commandPath;
}
}
}
if (options["required"]) {
const description = commands.join(" or ");
throw new Error(`Command not found: ${description}`);
}
}
/** /**
* @param {string} [cwd] * @param {string} [cwd]
* @param {string} [base] * @param {string} [base]
@@ -840,7 +978,7 @@ export function getBuildUrl() {
*/ */
export function getBuildLabel() { export function getBuildLabel() {
if (isBuildkite) { if (isBuildkite) {
const label = getEnv("BUILDKITE_GROUP_LABEL", false) || getEnv("BUILDKITE_LABEL", false); const label = getEnv("BUILDKITE_LABEL", false) || getEnv("BUILDKITE_GROUP_LABEL", false);
if (label) { if (label) {
return label; return label;
} }
@@ -854,6 +992,22 @@ export function getBuildLabel() {
} }
} }
/**
* @returns {number}
*/
export function getBootstrapVersion() {
if (isWindows) {
return 0; // TODO
}
const scriptPath = join(import.meta.dirname, "bootstrap.sh");
const scriptContent = readFile(scriptPath, { cache: true });
const match = /# Version: (\d+)/.exec(scriptContent);
if (match) {
return parseInt(match[1]);
}
return 0;
}
/** /**
* @typedef {object} BuildArtifact * @typedef {object} BuildArtifact
* @property {string} [job] * @property {string} [job]
@@ -1027,6 +1181,17 @@ export async function getLastSuccessfulBuild() {
} }
} }
/**
* @param {string} filename
* @param {string} [cwd]
*/
export async function uploadArtifact(filename, cwd) {
if (isBuildkite) {
const relativePath = relative(cwd ?? process.cwd(), filename);
await spawnSafe(["buildkite-agent", "artifact", "upload", relativePath], { cwd, stdio: "inherit" });
}
}
/** /**
* @param {string} string * @param {string} string
* @returns {string} * @returns {string}
@@ -1035,6 +1200,17 @@ export function stripAnsi(string) {
return string.replace(/\u001b\[\d+m/g, ""); return string.replace(/\u001b\[\d+m/g, "");
} }
/**
* @param {string} string
* @returns {string}
*/
export function escapeYaml(string) {
if (/[:"{}[\],&*#?|\-<>=!%@`]/.test(string)) {
return `"${string.replace(/"/g, '\\"')}"`;
}
return string;
}
/** /**
* @param {string} string * @param {string} string
* @returns {string} * @returns {string}
@@ -1173,24 +1349,79 @@ export function getArch() {
return parseArch(process.arch); return parseArch(process.arch);
} }
/**
* @returns {string}
*/
export function getKernel() {
const kernel = release();
const match = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(kernel);
if (match) {
const [, major, minor, patch] = match;
if (patch) {
return `${major}.${minor}.${patch}`;
}
return `${major}.${minor}`;
}
return kernel;
}
/** /**
* @returns {"musl" | "gnu" | undefined} * @returns {"musl" | "gnu" | undefined}
*/ */
export function getAbi() { export function getAbi() {
if (isLinux) { if (!isLinux) {
const arch = getArch() === "x64" ? "x86_64" : "aarch64"; return;
const muslLibPath = `/lib/ld-musl-${arch}.so.1`; }
if (existsSync(muslLibPath)) {
if (existsSync("/etc/alpine-release")) {
return "musl";
}
const arch = getArch() === "x64" ? "x86_64" : "aarch64";
const muslLibPath = `/lib/ld-musl-${arch}.so.1`;
if (existsSync(muslLibPath)) {
return "musl";
}
const gnuLibPath = `/lib/ld-linux-${arch}.so.2`;
if (existsSync(gnuLibPath)) {
return "gnu";
}
const { error, stdout } = spawnSync(["ldd", "--version"]);
if (!error) {
if (/musl/i.test(stdout)) {
return "musl"; return "musl";
} }
if (/gnu|glibc/i.test(stdout)) {
const gnuLibPath = `/lib/ld-linux-${arch}.so.2`;
if (existsSync(gnuLibPath)) {
return "gnu"; return "gnu";
} }
} }
} }
/**
* @returns {string | undefined}
*/
export function getAbiVersion() {
if (!isLinux) {
return;
}
const { error, stdout } = spawnSync(["ldd", "--version"]);
if (!error) {
const match = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(stdout);
if (match) {
const [, major, minor, patch] = match;
if (patch) {
return `${major}.${minor}.${patch}`;
}
return `${major}.${minor}`;
}
}
}
/** /**
* @typedef {object} Target * @typedef {object} Target
* @property {"darwin" | "linux" | "windows"} os * @property {"darwin" | "linux" | "windows"} os
@@ -1360,17 +1591,24 @@ export async function downloadTarget(target, release) {
} }
/** /**
* @returns {string | undefined} * @returns {string}
*/ */
export function getTailscaleIp() { export function getTailscale() {
let tailscale = "tailscale";
if (isMacOS) { if (isMacOS) {
const tailscaleApp = "/Applications/Tailscale.app/Contents/MacOS/tailscale"; const tailscaleApp = "/Applications/Tailscale.app/Contents/MacOS/tailscale";
if (existsSync(tailscaleApp)) { if (existsSync(tailscaleApp)) {
tailscale = tailscaleApp; return tailscaleApp;
} }
} }
return "tailscale";
}
/**
* @returns {string | undefined}
*/
export function getTailscaleIp() {
const tailscale = getTailscale();
const { error, stdout } = spawnSync([tailscale, "ip", "--1"]); const { error, stdout } = spawnSync([tailscale, "ip", "--1"]);
if (!error) { if (!error) {
return stdout.trim(); return stdout.trim();
@@ -1419,7 +1657,31 @@ export function getUsername() {
} }
/** /**
* @returns {string} * @typedef {object} User
* @property {string} username
* @property {number} uid
* @property {number} gid
*/
/**
* @param {string} username
* @returns {Promise<User>}
*/
export async function getUser(username) {
if (isWindows) {
throw new Error("TODO: Windows");
}
const [uid, gid] = await Promise.all([
spawnSafe(["id", "-u", username]).then(({ stdout }) => parseInt(stdout.trim())),
spawnSafe(["id", "-g", username]).then(({ stdout }) => parseInt(stdout.trim())),
]);
return { username, uid, gid };
}
/**
* @returns {string | undefined}
*/ */
export function getDistro() { export function getDistro() {
if (isMacOS) { if (isMacOS) {
@@ -1427,6 +1689,11 @@ export function getDistro() {
} }
if (isLinux) { if (isLinux) {
const alpinePath = "/etc/alpine-release";
if (existsSync(alpinePath)) {
return "alpine";
}
const releasePath = "/etc/os-release"; const releasePath = "/etc/os-release";
if (existsSync(releasePath)) { if (existsSync(releasePath)) {
const releaseFile = readFile(releasePath, { cache: true }); const releaseFile = readFile(releasePath, { cache: true });
@@ -1438,10 +1705,8 @@ export function getDistro() {
const { error, stdout } = spawnSync(["lsb_release", "-is"]); const { error, stdout } = spawnSync(["lsb_release", "-is"]);
if (!error) { if (!error) {
return stdout.trim(); return stdout.trim().toLowerCase();
} }
return "Linux";
} }
if (isWindows) { if (isWindows) {
@@ -1449,17 +1714,13 @@ export function getDistro() {
if (!error) { if (!error) {
return stdout.trim(); return stdout.trim();
} }
return "Windows";
} }
return `${process.platform} ${process.arch}`;
} }
/** /**
* @returns {string | undefined} * @returns {string | undefined}
*/ */
export function getDistroRelease() { export function getDistroVersion() {
if (isMacOS) { if (isMacOS) {
const { error, stdout } = spawnSync(["sw_vers", "-productVersion"]); const { error, stdout } = spawnSync(["sw_vers", "-productVersion"]);
if (!error) { if (!error) {
@@ -1468,6 +1729,16 @@ export function getDistroRelease() {
} }
if (isLinux) { if (isLinux) {
const alpinePath = "/etc/alpine-release";
if (existsSync(alpinePath)) {
const release = readFile(alpinePath, { cache: true }).trim();
if (release.includes("_")) {
const [version] = release.split("_");
return `${version}-edge`;
}
return release;
}
const releasePath = "/etc/os-release"; const releasePath = "/etc/os-release";
if (existsSync(releasePath)) { if (existsSync(releasePath)) {
const releaseFile = readFile(releasePath, { cache: true }); const releaseFile = readFile(releasePath, { cache: true });
@@ -1491,6 +1762,231 @@ export function getDistroRelease() {
} }
} }
/**
* @typedef {"aws" | "google"} Cloud
*/
/** @type {Cloud | undefined} */
let detectedCloud;
/**
* @returns {Promise<boolean | undefined>}
*/
export async function isAws() {
if (typeof detectedCloud === "string") {
return detectedCloud === "aws";
}
async function checkAws() {
if (isLinux) {
const kernel = release();
if (kernel.endsWith("-aws")) {
return true;
}
const { error: systemdError, stdout } = await spawn(["systemd-detect-virt"]);
if (!systemdError) {
if (stdout.includes("amazon")) {
return true;
}
}
const dmiPath = "/sys/devices/virtual/dmi/id/board_asset_tag";
if (existsSync(dmiPath)) {
const dmiFile = readFileSync(dmiPath, { encoding: "utf-8" });
if (dmiFile.startsWith("i-")) {
return true;
}
}
}
if (isWindows) {
const executionEnv = getEnv("AWS_EXECUTION_ENV", false);
if (executionEnv === "EC2") {
return true;
}
const { error: powershellError, stdout } = await spawn([
"powershell",
"-Command",
"Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object Manufacturer",
]);
if (!powershellError) {
return stdout.includes("Amazon");
}
}
const instanceId = await getCloudMetadata("instance-id", "google");
if (instanceId) {
return true;
}
}
if (await checkAws()) {
detectedCloud = "aws";
return true;
}
}
/**
* @returns {Promise<boolean | undefined>}
*/
export async function isGoogleCloud() {
if (typeof detectedCloud === "string") {
return detectedCloud === "google";
}
async function detectGoogleCloud() {
if (isLinux) {
const vendorPaths = [
"/sys/class/dmi/id/sys_vendor",
"/sys/class/dmi/id/bios_vendor",
"/sys/class/dmi/id/product_name",
];
for (const vendorPath of vendorPaths) {
if (existsSync(vendorPath)) {
const vendorFile = readFileSync(vendorPath, { encoding: "utf-8" });
if (vendorFile.includes("Google")) {
return true;
}
}
}
}
const instanceId = await getCloudMetadata("id", "google");
if (instanceId) {
return true;
}
}
if (await detectGoogleCloud()) {
detectedCloud = "google";
return true;
}
}
/**
* @returns {Promise<Cloud | undefined>}
*/
export async function getCloud() {
if (typeof detectedCloud === "string") {
return detectedCloud;
}
if (await isAws()) {
return "aws";
}
if (await isGoogleCloud()) {
return "google";
}
}
/**
* @param {string | Record<Cloud, string>} name
* @param {Cloud} [cloud]
* @returns {Promise<string | undefined>}
*/
export async function getCloudMetadata(name, cloud) {
cloud ??= await getCloud();
if (!cloud) {
return;
}
if (typeof name === "object") {
name = name[cloud];
}
let url;
let headers;
if (cloud === "aws") {
url = new URL(name, "http://169.254.169.254/latest/meta-data/");
} else if (cloud === "google") {
url = new URL(name, "http://metadata.google.internal/computeMetadata/v1/instance/");
headers = { "Metadata-Flavor": "Google" };
} else {
throw new Error(`Unsupported cloud: ${inspect(cloud)}`);
}
const { error, body } = await curl(url, { headers, retries: 0 });
if (error) {
return;
}
return body.trim();
}
/**
* @param {string} tag
* @param {Cloud} [cloud]
* @returns {Promise<string | undefined>}
*/
export function getCloudMetadataTag(tag, cloud) {
const metadata = {
"aws": `tags/instance/${tag}`,
};
return getCloudMetadata(metadata, cloud);
}
/**
* @param {string} name
* @returns {Promise<string | undefined>}
*/
export async function getBuildMetadata(name) {
if (isBuildkite) {
const { error, stdout } = await spawn(["buildkite-agent", "meta-data", "get", name]);
if (!error) {
const value = stdout.trim();
if (value) {
return value;
}
}
}
}
/**
* @typedef ConnectOptions
* @property {string} hostname
* @property {number} port
* @property {number} [retries]
*/
/**
* @param {ConnectOptions} options
* @returns {Promise<Error | undefined>}
*/
export async function waitForPort(options) {
const { hostname, port, retries = 10 } = options;
let cause;
for (let i = 0; i < retries; i++) {
if (cause) {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
const connected = new Promise((resolve, reject) => {
const socket = connect({ host: hostname, port });
socket.on("connect", () => {
socket.destroy();
resolve();
});
socket.on("error", error => {
socket.destroy();
reject(error);
});
});
try {
return await connected;
} catch (error) {
cause = error;
}
}
return cause;
}
/** /**
* @returns {Promise<number | undefined>} * @returns {Promise<number | undefined>}
*/ */
@@ -1536,6 +2032,52 @@ export function getGithubUrl() {
return new URL(getEnv("GITHUB_SERVER_URL", false) || "https://github.com"); return new URL(getEnv("GITHUB_SERVER_URL", false) || "https://github.com");
} }
/**
* @param {object} obj
* @param {number} indent
* @returns {string}
*/
export function toYaml(obj, indent = 0) {
const spaces = " ".repeat(indent);
let result = "";
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
continue;
}
if (value === null) {
result += `${spaces}${key}: null\n`;
continue;
}
if (Array.isArray(value)) {
result += `${spaces}${key}:\n`;
value.forEach(item => {
if (typeof item === "object" && item !== null) {
result += `${spaces}- \n${toYaml(item, indent + 2)
.split("\n")
.map(line => `${spaces} ${line}`)
.join("\n")}\n`;
} else {
result += `${spaces}- ${item}\n`;
}
});
continue;
}
if (typeof value === "object") {
result += `${spaces}${key}:\n${toYaml(value, indent + 2)}`;
continue;
}
if (
typeof value === "string" &&
(value.includes(":") || value.includes("#") || value.includes("'") || value.includes('"') || value.includes("\n"))
) {
result += `${spaces}${key}: "${value.replace(/"/g, '\\"')}"\n`;
continue;
}
result += `${spaces}${key}: ${value}\n`;
}
return result;
}
/** /**
* @param {string} title * @param {string} title
* @param {function} [fn] * @param {function} [fn]
@@ -1575,11 +2117,13 @@ export function printEnvironment() {
startGroup("Machine", () => { startGroup("Machine", () => {
console.log("Operating System:", getOs()); console.log("Operating System:", getOs());
console.log("Architecture:", getArch()); console.log("Architecture:", getArch());
console.log("Kernel:", getKernel());
if (isLinux) { if (isLinux) {
console.log("ABI:", getAbi()); console.log("ABI:", getAbi());
console.log("ABI Version:", getAbiVersion());
} }
console.log("Distro:", getDistro()); console.log("Distro:", getDistro());
console.log("Release:", getDistroRelease()); console.log("Distro Version:", getDistroVersion());
console.log("Hostname:", getHostname()); console.log("Hostname:", getHostname());
if (isCI) { if (isCI) {
console.log("Tailscale IP:", getTailscaleIp()); console.log("Tailscale IP:", getTailscaleIp());

View File

@@ -78,6 +78,14 @@ case $platform in
;; ;;
esac esac
case "$target" in
'linux'*)
if [ -f /etc/alpine-release ]; then
target="$target-musl"
fi
;;
esac
if [[ $target = darwin-x64 ]]; then if [[ $target = darwin-x64 ]]; then
# Is this process running in Rosetta? # Is this process running in Rosetta?
# redirect stderr to devnull to avoid error message when not running in Rosetta # redirect stderr to devnull to avoid error message when not running in Rosetta
@@ -91,19 +99,20 @@ GITHUB=${GITHUB-"https://github.com"}
github_repo="$GITHUB/oven-sh/bun" github_repo="$GITHUB/oven-sh/bun"
if [[ $target = darwin-x64 ]]; then # If AVX2 isn't supported, use the -baseline build
# If AVX2 isn't supported, use the -baseline build case "$target" in
'darwin-x64'*)
if [[ $(sysctl -a | grep machdep.cpu | grep AVX2) == '' ]]; then if [[ $(sysctl -a | grep machdep.cpu | grep AVX2) == '' ]]; then
target=darwin-x64-baseline target="$target-baseline"
fi fi
fi ;;
'linux-x64'*)
if [[ $target = linux-x64 ]]; then
# If AVX2 isn't supported, use the -baseline build # If AVX2 isn't supported, use the -baseline build
if [[ $(cat /proc/cpuinfo | grep avx2) = '' ]]; then if [[ $(cat /proc/cpuinfo | grep avx2) = '' ]]; then
target=linux-x64-baseline target="$target-baseline"
fi fi
fi ;;
esac
exe_name=bun exe_name=bun