From ff306c72f1a777ba7c8c71ac395367a37845bf05 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Tue, 30 Jun 2026 12:25:09 -0700 Subject: [PATCH] feat: add Linux musl managed install targets 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-.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 --- src/install/managed.ts | 30 +++++++++++++++++++---- test/unit/binary.test.ts | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/install/managed.ts b/src/install/managed.ts index b339495..f04cc1f 100644 --- a/src/install/managed.ts +++ b/src/install/managed.ts @@ -1,6 +1,6 @@ import { execFile } from "node:child_process"; import { createHash } from "node:crypto"; -import { createReadStream, createWriteStream } from "node:fs"; +import { createReadStream, createWriteStream, existsSync } from "node:fs"; import type { IncomingMessage, ClientRequest } from "node:http"; import * as https from "node:https"; import * as path from "node:path"; @@ -25,6 +25,8 @@ export type PatchloomTargetTriple = | "x86_64-apple-darwin" | "aarch64-unknown-linux-gnu" | "x86_64-unknown-linux-gnu" + | "aarch64-unknown-linux-musl" + | "x86_64-unknown-linux-musl" | "x86_64-pc-windows-msvc" | "aarch64-pc-windows-msvc"; @@ -204,7 +206,8 @@ export async function clearManagedInstallFailureRecord( export function detectManagedInstallTarget( platform: NodeJS.Platform = process.platform, - arch: NodeJS.Architecture = process.arch + arch: NodeJS.Architecture = process.arch, + isMusl?: boolean ): ManagedInstallTarget | undefined { if (platform === "darwin" && arch === "arm64") { return { @@ -225,19 +228,21 @@ export function detectManagedInstallTarget( } if (platform === "linux" && arch === "arm64") { + const musl = isMusl ?? isMuslLinux(arch); return { platform, arch, - targetTriple: "aarch64-unknown-linux-gnu", + targetTriple: musl ? "aarch64-unknown-linux-musl" : "aarch64-unknown-linux-gnu", archiveFormat: ".tar.xz" }; } if (platform === "linux" && arch === "x64") { + const musl = isMusl ?? isMuslLinux(arch); return { platform, arch, - targetTriple: "x86_64-unknown-linux-gnu", + targetTriple: musl ? "x86_64-unknown-linux-musl" : "x86_64-unknown-linux-gnu", archiveFormat: ".tar.xz" }; } @@ -263,6 +268,23 @@ export function detectManagedInstallTarget( return undefined; } +const MUSL_ARCH_MAP: Partial> = { + x64: "x86_64", + arm64: "aarch64" +}; + +export function isMuslLinux(arch: NodeJS.Architecture = process.arch): boolean { + const muslArch = MUSL_ARCH_MAP[arch]; + if (!muslArch) { + return false; + } + try { + return existsSync(`/lib/ld-musl-${muslArch}.so.1`); + } catch { + return false; + } +} + export function resolveManagedInstallPaths( installRoot: string, target: ManagedInstallTarget diff --git a/test/unit/binary.test.ts b/test/unit/binary.test.ts index f3662ba..5dd1f71 100644 --- a/test/unit/binary.test.ts +++ b/test/unit/binary.test.ts @@ -38,6 +38,7 @@ import { resolveManagedInstallPaths, resolveManagedInstallTransactionPaths, setManagedInstallFailure, + isMuslLinux, verifyManagedInstallArchiveChecksum } from "../../src/install/managed.js"; import { resolveMcpTargets } from "../../src/mcp/config.js"; @@ -235,6 +236,56 @@ test("detectManagedInstallTarget maps supported platforms to release targets", ( assert.equal(detectManagedInstallTarget("linux", "arm"), undefined); }); +test("detectManagedInstallTarget selects musl target when isMusl is true", () => { + assert.deepEqual(detectManagedInstallTarget("linux", "x64", true), { + platform: "linux", + arch: "x64", + targetTriple: "x86_64-unknown-linux-musl", + archiveFormat: ".tar.xz" + }); + assert.deepEqual(detectManagedInstallTarget("linux", "arm64", true), { + platform: "linux", + arch: "arm64", + targetTriple: "aarch64-unknown-linux-musl", + archiveFormat: ".tar.xz" + }); +}); + +test("detectManagedInstallTarget selects gnu target when isMusl is false", () => { + assert.deepEqual(detectManagedInstallTarget("linux", "x64", false), { + platform: "linux", + arch: "x64", + targetTriple: "x86_64-unknown-linux-gnu", + archiveFormat: ".tar.xz" + }); + assert.deepEqual(detectManagedInstallTarget("linux", "arm64", false), { + platform: "linux", + arch: "arm64", + targetTriple: "aarch64-unknown-linux-gnu", + archiveFormat: ".tar.xz" + }); +}); + +test("detectManagedInstallTarget ignores isMusl for non-Linux platforms", () => { + const darwinTarget = detectManagedInstallTarget("darwin", "arm64", true); + assert.equal(darwinTarget?.targetTriple, "aarch64-apple-darwin"); + const windowsTarget = detectManagedInstallTarget("win32", "x64", true); + assert.equal(windowsTarget?.targetTriple, "x86_64-pc-windows-msvc"); +}); + +test("isMuslLinux returns false on non-Linux platforms", () => { + // On macOS/Windows (where CI runs), the musl linker does not exist + if (process.platform !== "linux") { + assert.equal(isMuslLinux("x64"), false); + assert.equal(isMuslLinux("arm64"), false); + } +}); + +test("isMuslLinux returns false for unsupported architectures", () => { + assert.equal(isMuslLinux("arm" as NodeJS.Architecture), false); + assert.equal(isMuslLinux("ia32" as NodeJS.Architecture), false); +}); + test("resolveManagedInstallPaths uses cargo-dist style archive names", () => { const target = detectManagedInstallTarget("darwin", "arm64"); assert.ok(target);