Files
bun.sh/packages/bun-release/src/platform.ts
vfilanovsky-openai 16435f3561 Make sure bun can be installed on Alpine Linux (musl) on arm64 hardware (#22892)
### What does this PR do?
This PRs adjusts the "arch" string for Linux-musl variant to make sure
it can be installed on ARM64 platforms using `npm`. Without this fix,
installing bun on Alpine Linux on arm64 fails because the native binary
cannot be found.

#### Why it fails
Bun attempts to find/download the native binaries during the postinstall
phase (see
[install.ts](https://github.com/oven-sh/bun/blob/bun-v1.1.42/packages/bun-release/src/npm/install.ts)).
The platform matching logic lives in
[platform.ts](https://github.com/oven-sh/bun/blob/bun-v1.1.42/packages/bun-release/src/platform.ts).
Note how the "musl" variant is marked [as
"aarch64"](https://github.com/oven-sh/bun/blob/bun-v1.1.42/packages/bun-release/src/platform.ts#L63-L69),
while the regular "glibc" variant is marked [as
"arm64"](https://github.com/oven-sh/bun/blob/bun-v1.1.42/packages/bun-release/src/platform.ts#L44-L49).
On Alpine Linux distributions (or when using "node-alpine" docker image)
we're supposed to be using the "musl" binary. However, since bun marks
it as "aarch64" while the matching logic relies on `process.arch`, it
never gets matched. Node.js uses "arm64", _not_ "aarch64" (see
["process.arch"
docs](https://nodejs.org/docs/latest-v22.x/api/process.html#processarch)).
In short - a mismatch between the expected arch ("aarch64") and the
actual reported arch ("arm64") prevents bun from finding the right
binary when installing with npm/pnpm.

### How did you verify your code works?
Verified by running the installer on Alpine Linux on arm64.

cc @magus
2025-09-23 20:14:57 -07:00

161 lines
3.3 KiB
TypeScript

import { debug } from "./console";
import { exists, read } from "./fs";
import { spawn } from "./spawn";
export const os = process.platform;
export const arch = os === "darwin" && process.arch === "x64" && isRosetta2() ? "arm64" : process.arch;
export const avx2 =
arch === "x64" &&
((os === "linux" && isLinuxAVX2()) || (os === "darwin" && isDarwinAVX2()) || (os === "win32" && isWindowsAVX2()));
export const abi = os === "linux" && isLinuxMusl() ? "musl" : undefined;
export type Platform = {
os: string;
arch: string;
abi?: "musl";
avx2?: boolean;
bin: string;
exe: string;
};
export const platforms: Platform[] = [
{
os: "darwin",
arch: "arm64",
bin: "bun-darwin-aarch64",
exe: "bin/bun",
},
{
os: "darwin",
arch: "x64",
avx2: true,
bin: "bun-darwin-x64",
exe: "bin/bun",
},
{
os: "darwin",
arch: "x64",
bin: "bun-darwin-x64-baseline",
exe: "bin/bun",
},
{
os: "linux",
arch: "arm64",
bin: "bun-linux-aarch64",
exe: "bin/bun",
},
{
os: "linux",
arch: "x64",
avx2: true,
bin: "bun-linux-x64",
exe: "bin/bun",
},
{
os: "linux",
arch: "x64",
bin: "bun-linux-x64-baseline",
exe: "bin/bun",
},
{
os: "linux",
arch: "arm64",
abi: "musl",
bin: "bun-linux-aarch64-musl",
exe: "bin/bun",
},
{
os: "linux",
arch: "x64",
abi: "musl",
avx2: true,
bin: "bun-linux-x64-musl",
exe: "bin/bun",
},
{
os: "linux",
arch: "x64",
abi: "musl",
bin: "bun-linux-x64-musl-baseline",
exe: "bin/bun",
},
{
os: "win32",
arch: "x64",
avx2: true,
bin: "bun-windows-x64",
exe: "bin/bun.exe",
},
{
os: "win32",
arch: "x64",
bin: "bun-windows-x64-baseline",
exe: "bin/bun.exe",
},
];
export const supportedPlatforms: Platform[] = platforms
.filter(
platform =>
platform.os === os &&
platform.arch === arch &&
(!platform.avx2 || avx2) &&
(!platform.abi || abi === platform.abi),
)
.sort((a, b) => (a.avx2 === b.avx2 ? 0 : a.avx2 ? -1 : 1));
function isLinuxMusl(): boolean {
try {
return exists("/etc/alpine-release");
} catch (error) {
debug("isLinuxMusl failed", error);
return false;
}
}
function isLinuxAVX2(): boolean {
try {
return read("/proc/cpuinfo").includes("avx2");
} catch (error) {
debug("isLinuxAVX2 failed", error);
return false;
}
}
function isDarwinAVX2(): boolean {
try {
const { exitCode, stdout } = spawn("sysctl", ["-n", "machdep.cpu"]);
return exitCode === 0 && stdout.includes("AVX2");
} catch (error) {
debug("isDarwinAVX2 failed", error);
return false;
}
}
function isRosetta2(): boolean {
try {
const { exitCode, stdout } = spawn("sysctl", ["-n", "sysctl.proc_translated"]);
return exitCode === 0 && stdout.includes("1");
} catch (error) {
debug("isRosetta2 failed", error);
return false;
}
}
function isWindowsAVX2(): boolean {
try {
return (
spawn("powershell", [
"-c",
`(Add-Type -MemberDefinition '[DllImport("kernel32.dll")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);' -Name 'Kernel32' -Namespace 'Win32' -PassThru)::IsProcessorFeaturePresent(40);`,
]).stdout.trim() === "True"
);
} catch (error) {
debug("isWindowsAVX2 failed", error);
return false;
}
}