Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/bundler/executables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ To build for macOS x64:

### Supported targets

The segments of the `--target` value can appear in any order, as long as they're delimited by `-`.
The segments of the `--target` value can appear in any order, as long as they're delimited by `-`. Linux targets also accept a libc segment: `-musl` selects the musl build, and `-glibc` (for example `bun-linux-x64-glibc`) explicitly selects the glibc build that the plain `bun-linux-*` targets below use.

| --target | Operating System | Architecture | Modern | Baseline | Libc |
| -------------------- | ---------------- | ------------ | ------ | -------- | ----- |
Expand Down
11 changes: 10 additions & 1 deletion src/options_types/compile_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,10 @@ impl CompileTarget {
_found_version = true;
continue;
}
} else if token == b"glibc" {
this.libc = Libc::Default;
found_libc = true;
continue;
} else if token == b"musl" {
this.libc = Libc::Musl;
found_libc = true;
Expand Down Expand Up @@ -328,7 +332,8 @@ impl CompileTarget {
this.baseline = false;
}

if this.libc != Libc::Default && this.os != OperatingSystem::Linux {
// Not `libc != Default`: an explicit "glibc" parses to `Default` and is just as invalid off linux.
if found_libc && this.os != OperatingSystem::Linux {
return Err(ParseError::InvalidTarget);
}

Expand All @@ -354,6 +359,7 @@ impl CompileTarget {
&& OPERATING_SYSTEM_NAMES.get(token).is_none()
&& token != b"modern"
&& token != b"baseline"
&& token != b"glibc"
&& token != b"musl"
&& token != b"android"
&& !(strings::has_prefix(token, b"v1.")
Expand Down Expand Up @@ -381,6 +387,9 @@ impl CompileTarget {
let input = strings::trim(input_, b" \t\r");
if strings::contains(input, b"musl") && !strings::contains(input, b"linux") {
bun_core::err_generic!("invalid target, musl libc only exists on linux");
} else if strings::contains(input, b"glibc") && !strings::contains(input, b"linux")
{
bun_core::err_generic!("invalid target, glibc only exists on linux");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
} else if strings::contains(input, b"android")
&& !strings::contains(input, b"linux")
{
Expand Down
95 changes: 94 additions & 1 deletion test/bundler/bun-build-compile.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, isArm64, isLinux, isMacOS, isMusl, isPosix, isWindows, tempDir } from "harness";
import { bunEnv, bunExe, isArm64, isGlibc, isLinux, isMacOS, isMusl, isPosix, isWindows, tempDir } from "harness";
import { chmodSync, closeSync, cpSync, existsSync, openSync, readSync } from "node:fs";
import { join } from "path";

Expand Down Expand Up @@ -122,6 +122,99 @@ describe("Bun.build compile", () => {
});
});

// `Bun.Build.Libc` is "glibc" | "musl". "-glibc" explicitly selects the default Linux build, which on
// a glibc host is the running bun itself, so the accepting tests below never download anything.
describe("compile target -glibc token", () => {
const arch = isArm64 ? "arm64" : "x64";

test.concurrent.skipIf(!isGlibc)("Bun.build accepts a bun-linux-<arch>-glibc target", async () => {
using dir = tempDir("build-compile-glibc-api", {
"app.js": `console.log("glibc-target-ok");`,
});

const result = await Bun.build({
entrypoints: [join(String(dir), "app.js")],
compile: {
target: `bun-linux-${arch}-glibc`,
outfile: join(String(dir), "app"),
},
});
expect(result.success).toBe(true);

await using proc = Bun.spawn({
cmd: [result.outputs[0].path],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stdout, stderr, exitCode }).toEqual({ stdout: "glibc-target-ok\n", stderr: "", exitCode: 0 });
});

test.concurrent.skipIf(!isGlibc)("bun build --compile accepts --target=bun-linux-<arch>-glibc", async () => {
using dir = tempDir("build-compile-glibc-cli", {
"app.js": `console.log("glibc-target-ok");`,
});
const outfile = join(String(dir), "app");

await using build = Bun.spawn({
cmd: [bunExe(), "build", "--compile", `--target=bun-linux-${arch}-glibc`, "app.js", "--outfile", outfile],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [, buildStderr, buildExitCode] = await Promise.all([build.stdout.text(), build.stderr.text(), build.exited]);
expect({ stderr: buildStderr, exitCode: buildExitCode }).toEqual({ stderr: "", exitCode: 0 });

await using proc = Bun.spawn({
cmd: [outfile],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stdout, stderr, exitCode }).toEqual({ stdout: "glibc-target-ok\n", stderr: "", exitCode: 0 });
});

// libc is a Linux-only axis. These are rejected while parsing the target, on every host.
test.concurrent("bun build --compile rejects -glibc combined with a non-linux OS", async () => {
using dir = tempDir("build-compile-glibc-cli-invalid", {
"app.js": `console.log("unreachable");`,
});

await using build = Bun.spawn({
cmd: [bunExe(), "build", "--compile", "--target=bun-windows-x64-glibc", "app.js", "--outfile", "app"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([build.stdout.text(), build.stderr.text(), build.exited]);
expect({ stdout, stderr, exitCode }).toEqual({
stdout: "",
stderr: "error: invalid target, glibc only exists on linux\n",
exitCode: 1,
});
});

test("Bun.build rejects -glibc combined with a non-linux OS", () => {
using dir = tempDir("build-compile-glibc-api-invalid", {
"app.js": `console.log("unreachable");`,
});

expect(() =>
Bun.build({
entrypoints: [join(String(dir), "app.js")],
compile: {
target: "bun-darwin-arm64-glibc" as Bun.Build.CompileTarget,
outfile: join(String(dir), "app"),
},
}),
).toThrowErrorMatchingInlineSnapshot(`"Unknown compile target: bun-darwin-arm64-glibc"`);
});
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

describe("compiled binary validity", () => {
test("output binary has valid executable header", async () => {
using dir = tempDir("build-compile-valid-header", {
Expand Down