Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
89e492240c fix(standalone): support explicit ld.so invocation via BUN_SELF_EXE
When a Bun compiled executable is invoked via explicit dynamic linker
(e.g., /lib64/ld-linux-x86-64.so.2 ./my-app), /proc/self/exe points to
the linker, not the actual binary. This causes the embedded bytecode
detection to fail.

Add BUN_SELF_EXE environment variable to override /proc/self/exe path
detection, allowing compiled executables to work in Termux/Android and
other environments requiring explicit ld.so invocation.

Fixes #26752

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 11:46:25 +00:00
3 changed files with 103 additions and 0 deletions

View File

@@ -1409,6 +1409,15 @@ pub const StandaloneModuleGraph = struct {
switch (Environment.os) {
.linux => {
// Check for override first (for Termux/explicit ld.so scenarios)
// When invoked via explicit dynamic linker (e.g., /lib64/ld-linux-x86-64.so.2 ./app),
// /proc/self/exe points to the linker, not the actual binary.
if (bun.env_var.BUN_SELF_EXE.get()) |path| {
if (std.fs.cwd().openFile(path, .{})) |file| {
return .fromStdFile(file);
} else |_| {}
}
if (std.fs.openFileAbsoluteZ("/proc/self/exe", .{})) |easymode| {
return .fromStdFile(easymode);
} else |_| {

View File

@@ -58,6 +58,10 @@ pub const BUN_INSTALL = New(kind.string, "BUN_INSTALL", .{});
pub const BUN_INSTALL_BIN = New(kind.string, "BUN_INSTALL_BIN", .{});
pub const BUN_INSTALL_GLOBAL_DIR = New(kind.string, "BUN_INSTALL_GLOBAL_DIR", .{});
pub const BUN_NEEDS_PROC_SELF_WORKAROUND = New(kind.boolean, "BUN_NEEDS_PROC_SELF_WORKAROUND", .{ .default = false });
/// Override path used for self-executable detection. Useful when running via explicit
/// dynamic linker (e.g., /lib64/ld-linux-x86-64.so.2 ./my-app) where /proc/self/exe
/// points to the linker instead of the actual binary.
pub const BUN_SELF_EXE = PlatformSpecificNew(kind.string, "BUN_SELF_EXE", null, .{});
pub const BUN_OPTIONS = New(kind.string, "BUN_OPTIONS", .{});
pub const BUN_POSTGRES_SOCKET_MONITOR = New(kind.string, "BUN_POSTGRES_SOCKET_MONITOR", .{});
pub const BUN_POSTGRES_SOCKET_MONITOR_READER = New(kind.string, "BUN_POSTGRES_SOCKET_MONITOR_READER", .{});

View File

@@ -0,0 +1,90 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
import { existsSync } from "node:fs";
// This test is only applicable on Linux where explicit ld.so invocation is possible
const isLinux = process.platform === "linux";
// Skip the entire test file on non-Linux platforms
test.skipIf(!isLinux)("compiled executable works with BUN_SELF_EXE override", async () => {
using dir = tempDir("issue-26752", {
"hello.js": `console.log("Hello from compiled Bun!");`,
});
// Compile the script into an executable
await using compileProc = Bun.spawn({
cmd: [bunExe(), "build", "--compile", "hello.js", "--outfile", "hello"],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [, compileStderr, compileExitCode] = await Promise.all([
compileProc.stdout.text(),
compileProc.stderr.text(),
compileProc.exited,
]);
expect(compileStderr).toBe("");
expect(compileExitCode).toBe(0);
const executablePath = `${dir}/hello`;
expect(existsSync(executablePath)).toBe(true);
// First, verify the executable works directly
await using directProc = Bun.spawn({
cmd: [executablePath],
cwd: String(dir),
env: bunEnv,
stderr: "pipe",
stdout: "pipe",
});
const [directStdout, directStderr, directExitCode] = await Promise.all([
directProc.stdout.text(),
directProc.stderr.text(),
directProc.exited,
]);
expect(directStdout.trim()).toBe("Hello from compiled Bun!");
expect(directStderr).toBe("");
expect(directExitCode).toBe(0);
// Find the dynamic linker (supports both x86_64 and aarch64)
const ldPaths = [
"/lib64/ld-linux-x86-64.so.2",
"/lib/ld-linux-x86-64.so.2",
"/lib/ld-linux-aarch64.so.1",
"/lib64/ld-linux-aarch64.so.1",
];
const ldPath = ldPaths.find(p => existsSync(p)) ?? null;
// Skip the ld.so test if we can't find the linker
if (!ldPath) {
console.log("Skipping ld.so test: dynamic linker not found");
return;
}
// Now test with explicit ld.so invocation and BUN_SELF_EXE override
await using ldProc = Bun.spawn({
cmd: [ldPath, executablePath],
cwd: String(dir),
env: {
...bunEnv,
BUN_SELF_EXE: executablePath,
},
stderr: "pipe",
stdout: "pipe",
});
const [ldStdout, ldStderr, ldExitCode] = await Promise.all([
ldProc.stdout.text(),
ldProc.stderr.text(),
ldProc.exited,
]);
expect(ldStdout.trim()).toBe("Hello from compiled Bun!");
expect(ldStderr).toBe("");
expect(ldExitCode).toBe(0);
});