Skip to content

Commit 4d46a70

Browse files
authored
feat: add Linux musl managed install targets (#179)
Add aarch64-unknown-linux-musl and x86_64-unknown-linux-musl target triples for managed install. Auto-detects musl by checking for the /lib/ld-musl-<arch>.so.1 dynamic linker. Falls back to glibc (gnu) targets when musl is not detected. This enables managed install on Alpine-based dev containers and Remote SSH sessions to musl-based Linux hosts. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
1 parent da702f8 commit 4d46a70

2 files changed

Lines changed: 77 additions & 4 deletions

File tree

src/install/managed.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { execFile } from "node:child_process";
22
import { createHash } from "node:crypto";
3-
import { createReadStream, createWriteStream } from "node:fs";
3+
import { createReadStream, createWriteStream, existsSync } from "node:fs";
44
import type { IncomingMessage, ClientRequest } from "node:http";
55
import * as https from "node:https";
66
import * as path from "node:path";
@@ -25,6 +25,8 @@ export type PatchloomTargetTriple =
2525
| "x86_64-apple-darwin"
2626
| "aarch64-unknown-linux-gnu"
2727
| "x86_64-unknown-linux-gnu"
28+
| "aarch64-unknown-linux-musl"
29+
| "x86_64-unknown-linux-musl"
2830
| "x86_64-pc-windows-msvc"
2931
| "aarch64-pc-windows-msvc";
3032

@@ -204,7 +206,8 @@ export async function clearManagedInstallFailureRecord(
204206

205207
export function detectManagedInstallTarget(
206208
platform: NodeJS.Platform = process.platform,
207-
arch: NodeJS.Architecture = process.arch
209+
arch: NodeJS.Architecture = process.arch,
210+
isMusl?: boolean
208211
): ManagedInstallTarget | undefined {
209212
if (platform === "darwin" && arch === "arm64") {
210213
return {
@@ -225,19 +228,21 @@ export function detectManagedInstallTarget(
225228
}
226229

227230
if (platform === "linux" && arch === "arm64") {
231+
const musl = isMusl ?? isMuslLinux(arch);
228232
return {
229233
platform,
230234
arch,
231-
targetTriple: "aarch64-unknown-linux-gnu",
235+
targetTriple: musl ? "aarch64-unknown-linux-musl" : "aarch64-unknown-linux-gnu",
232236
archiveFormat: ".tar.xz"
233237
};
234238
}
235239

236240
if (platform === "linux" && arch === "x64") {
241+
const musl = isMusl ?? isMuslLinux(arch);
237242
return {
238243
platform,
239244
arch,
240-
targetTriple: "x86_64-unknown-linux-gnu",
245+
targetTriple: musl ? "x86_64-unknown-linux-musl" : "x86_64-unknown-linux-gnu",
241246
archiveFormat: ".tar.xz"
242247
};
243248
}
@@ -263,6 +268,23 @@ export function detectManagedInstallTarget(
263268
return undefined;
264269
}
265270

271+
const MUSL_ARCH_MAP: Partial<Record<NodeJS.Architecture, string>> = {
272+
x64: "x86_64",
273+
arm64: "aarch64"
274+
};
275+
276+
export function isMuslLinux(arch: NodeJS.Architecture = process.arch): boolean {
277+
const muslArch = MUSL_ARCH_MAP[arch];
278+
if (!muslArch) {
279+
return false;
280+
}
281+
try {
282+
return existsSync(`/lib/ld-musl-${muslArch}.so.1`);
283+
} catch {
284+
return false;
285+
}
286+
}
287+
266288
export function resolveManagedInstallPaths(
267289
installRoot: string,
268290
target: ManagedInstallTarget

test/unit/binary.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
resolveManagedInstallPaths,
3939
resolveManagedInstallTransactionPaths,
4040
setManagedInstallFailure,
41+
isMuslLinux,
4142
verifyManagedInstallArchiveChecksum
4243
} from "../../src/install/managed.js";
4344
import { resolveMcpTargets } from "../../src/mcp/config.js";
@@ -235,6 +236,56 @@ test("detectManagedInstallTarget maps supported platforms to release targets", (
235236
assert.equal(detectManagedInstallTarget("linux", "arm"), undefined);
236237
});
237238

239+
test("detectManagedInstallTarget selects musl target when isMusl is true", () => {
240+
assert.deepEqual(detectManagedInstallTarget("linux", "x64", true), {
241+
platform: "linux",
242+
arch: "x64",
243+
targetTriple: "x86_64-unknown-linux-musl",
244+
archiveFormat: ".tar.xz"
245+
});
246+
assert.deepEqual(detectManagedInstallTarget("linux", "arm64", true), {
247+
platform: "linux",
248+
arch: "arm64",
249+
targetTriple: "aarch64-unknown-linux-musl",
250+
archiveFormat: ".tar.xz"
251+
});
252+
});
253+
254+
test("detectManagedInstallTarget selects gnu target when isMusl is false", () => {
255+
assert.deepEqual(detectManagedInstallTarget("linux", "x64", false), {
256+
platform: "linux",
257+
arch: "x64",
258+
targetTriple: "x86_64-unknown-linux-gnu",
259+
archiveFormat: ".tar.xz"
260+
});
261+
assert.deepEqual(detectManagedInstallTarget("linux", "arm64", false), {
262+
platform: "linux",
263+
arch: "arm64",
264+
targetTriple: "aarch64-unknown-linux-gnu",
265+
archiveFormat: ".tar.xz"
266+
});
267+
});
268+
269+
test("detectManagedInstallTarget ignores isMusl for non-Linux platforms", () => {
270+
const darwinTarget = detectManagedInstallTarget("darwin", "arm64", true);
271+
assert.equal(darwinTarget?.targetTriple, "aarch64-apple-darwin");
272+
const windowsTarget = detectManagedInstallTarget("win32", "x64", true);
273+
assert.equal(windowsTarget?.targetTriple, "x86_64-pc-windows-msvc");
274+
});
275+
276+
test("isMuslLinux returns false on non-Linux platforms", () => {
277+
// On macOS/Windows (where CI runs), the musl linker does not exist
278+
if (process.platform !== "linux") {
279+
assert.equal(isMuslLinux("x64"), false);
280+
assert.equal(isMuslLinux("arm64"), false);
281+
}
282+
});
283+
284+
test("isMuslLinux returns false for unsupported architectures", () => {
285+
assert.equal(isMuslLinux("arm" as NodeJS.Architecture), false);
286+
assert.equal(isMuslLinux("ia32" as NodeJS.Architecture), false);
287+
});
288+
238289
test("resolveManagedInstallPaths uses cargo-dist style archive names", () => {
239290
const target = detectManagedInstallTarget("darwin", "arm64");
240291
assert.ok(target);

0 commit comments

Comments
 (0)