feat(release): added musl-linked linux builds

Built x64 and arm64 musl release artifacts with matching native addons, Alpine smoke coverage, and installer detection.

Fixes #3367
This commit is contained in:
roboomp
2026-07-22 19:06:20 +00:00
parent c053afa087
commit 6efcdfb66e
7 changed files with 158 additions and 7 deletions
+7 -3
View File
@@ -34,6 +34,10 @@ inputs:
cross-arch linux build, or set alone for a host-arch (x64) linux build.
required: false
default: ""
libc:
description: Optional Linux libc artifact qualifier (for example, musl)
required: false
default: ""
rust_checks:
description: Run clippy/rustfmt checks (only one matrix entry should set this)
required: false
@@ -135,7 +139,7 @@ runs:
# --- Rust flags (shared) ------------------------------------------------
- name: Configure native Rust flags
if: inputs.target == ''
if: inputs.target == '' || inputs.arch == 'x64'
shell: bash
env:
TARGET_ARCH: ${{ inputs.arch }}
@@ -178,7 +182,7 @@ runs:
if: steps.detect.outputs.on_infra == 'false'
uses: Swatinem/rust-cache@v2
with:
shared-key: native-${{ inputs.platform }}-${{ inputs.arch }}-${{ inputs.variant || 'default' }}-h${{ inputs.hash }}
shared-key: native-${{ inputs.platform }}-${{ inputs.libc || 'default' }}-${{ inputs.arch }}-${{ inputs.variant || 'default' }}-h${{ inputs.hash }}
cache-on-failure: true
save-if: ${{ inputs.save_cache == 'true' }}
cache-workspace-crates: true
@@ -305,7 +309,7 @@ runs:
- name: Upload native addon(s)
uses: actions/upload-artifact@v4
with:
name: pi-natives-${{ inputs.platform }}-${{ inputs.arch }}${{ inputs.variant && format('-{0}', inputs.variant) || '' }}-h${{ inputs.hash }}
name: pi-natives-${{ inputs.platform }}-${{ inputs.libc && format('{0}-', inputs.libc) || '' }}${{ inputs.arch }}${{ inputs.variant && format('-{0}', inputs.variant) || '' }}-h${{ inputs.hash }}
path: packages/natives/native/pi_natives.${{ inputs.platform }}-${{ inputs.arch }}*.node
if-no-files-found: error
# Explicit so the native_artifact_lookup canary keeps working even if
+40 -3
View File
@@ -144,6 +144,8 @@ jobs:
# `actions/upload-artifact` `name:` template in build-native action.
cross_platform_required=(
"pi-natives-linux-arm64-h${hash}"
"pi-natives-linux-musl-x64-baseline-h${hash}"
"pi-natives-linux-musl-arm64-h${hash}"
"pi-natives-darwin-x64-baseline-h${hash}"
"pi-natives-darwin-arm64-h${hash}"
"pi-natives-win32-x64-baseline-h${hash}"
@@ -238,7 +240,7 @@ jobs:
# building the artifacts that ship in releases. Skipped on main when
# native_artifact_lookup already found a recent run with all artifacts intact.
native_cross_platform_kata:
name: "Native: ${{ matrix.platform }} ${{ matrix.arch }}"
name: "Native: ${{ matrix.platform }} ${{ matrix.libc || '' }} ${{ matrix.arch }}"
needs: [release_metadata, native_artifact_lookup]
if: ${{ needs.release_metadata.outputs.is-release == 'true' || (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.native_artifact_lookup.outputs.cross-platform-run-id == '') }}
strategy:
@@ -246,6 +248,8 @@ jobs:
matrix:
include:
- { os: omp-kata, platform: linux, arch: arm64, target: aarch64-unknown-linux-gnu }
- { os: omp-kata, platform: linux, libc: musl, arch: x64, target: x86_64-unknown-linux-musl, variant: baseline }
- { os: omp-kata, platform: linux, libc: musl, arch: arm64, target: aarch64-unknown-linux-musl }
- { os: omp-kata, platform: win32, arch: x64, target: x86_64-pc-windows-msvc, variant: baseline }
runs-on: ${{ matrix.os }}
steps:
@@ -255,6 +259,7 @@ jobs:
hash: ${{ needs.native_artifact_lookup.outputs.source-hash }}
platform: ${{ matrix.platform }}
arch: ${{ matrix.arch }}
libc: ${{ matrix.libc }}
variant: ${{ matrix.variant }}
target: ${{ matrix.target }}
glibc: ${{ matrix.platform == 'linux' && env.GLIBC_FLOOR || '' }}
@@ -575,6 +580,16 @@ jobs:
arch: x64,
target_id: linux-x64,
binary_path: packages/coding-agent/binaries/omp-linux-x64,
native_artifact_pattern: pi-natives-linux-x64-*,
}
- {
os: ubuntu-22.04,
platform: linux,
libc: musl,
arch: x64,
target_id: linux-musl-x64,
binary_path: packages/coding-agent/binaries/omp-linux-musl-x64,
native_artifact_pattern: pi-natives-linux-musl-x64-*,
}
- {
os: ubuntu-24.04-arm,
@@ -582,6 +597,16 @@ jobs:
arch: arm64,
target_id: linux-arm64,
binary_path: packages/coding-agent/binaries/omp-linux-arm64,
native_artifact_pattern: pi-natives-linux-arm64*,
}
- {
os: ubuntu-24.04-arm,
platform: linux,
libc: musl,
arch: arm64,
target_id: linux-musl-arm64,
binary_path: packages/coding-agent/binaries/omp-linux-musl-arm64,
native_artifact_pattern: pi-natives-linux-musl-arm64*,
}
- {
os: macos-15-intel,
@@ -589,6 +614,7 @@ jobs:
arch: x64,
target_id: darwin-x64,
binary_path: packages/coding-agent/binaries/omp-darwin-x64,
native_artifact_pattern: pi-natives-darwin-x64*,
}
- {
os: macos-14,
@@ -596,6 +622,7 @@ jobs:
arch: arm64,
target_id: darwin-arm64,
binary_path: packages/coding-agent/binaries/omp-darwin-arm64,
native_artifact_pattern: pi-natives-darwin-arm64*,
}
- {
os: ubuntu-22.04,
@@ -603,6 +630,7 @@ jobs:
arch: x64,
target_id: win32-x64,
binary_path: packages/coding-agent/binaries/omp-windows-x64.exe,
native_artifact_pattern: pi-natives-win32-x64*,
}
runs-on: ${{ matrix.os }}
permissions:
@@ -635,7 +663,7 @@ jobs:
- name: Download native addon(s)
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: pi-natives-${{ matrix.platform }}-${{ matrix.arch }}*-h${{ needs.native_artifact_lookup.outputs.source-hash }}
pattern: ${{ matrix.native_artifact_pattern }}-h${{ needs.native_artifact_lookup.outputs.source-hash }}
path: packages/natives/native
merge-multiple: true
- name: Build release binary
@@ -665,8 +693,17 @@ jobs:
runtime_dir="$(mktemp -d)"
HOME="$runtime_dir/home" XDG_DATA_HOME="$runtime_dir/xdg" "${{ matrix.binary_path }}" --version
HOME="$runtime_dir/home" XDG_DATA_HOME="$runtime_dir/xdg" "${{ matrix.binary_path }}" --smoke-test
- name: Smoke musl release binary on Alpine
if: matrix.libc == 'musl'
run: |
binary="$(realpath "${{ matrix.binary_path }}")"
docker run --rm -v "$binary:/usr/local/bin/omp:ro" alpine:3.22 sh -c '
runtime_dir="$(mktemp -d)"
HOME="$runtime_dir/home" XDG_DATA_HOME="$runtime_dir/xdg" omp --version
HOME="$runtime_dir/home" XDG_DATA_HOME="$runtime_dir/xdg" omp --smoke-test
'
- name: Publish native addon package
if: ${{ !inputs.skip_npm }}
if: ${{ !inputs.skip_npm && matrix.libc != 'musl' }}
env:
# Fallback auth: setup-node wrote an .npmrc referencing
# NODE_AUTH_TOKEN; npm uses it only when OIDC has no trusted
+1 -1
View File
@@ -107,7 +107,7 @@
"build:native": "bun --cwd=packages/natives run build",
"test": "bun run --parallel test:ts test:rs",
"test:ts": "GITHUB_ACTIONS= bun run --workspaces --if-present test -- --only-failures && bun run test:scripts",
"test:scripts": "bun test scripts/ci-concurrency.test.ts scripts/ci-release-notes.test.ts",
"test:scripts": "bun test scripts/ci-concurrency.test.ts scripts/ci-release-notes.test.ts scripts/musl-release.test.ts",
"test:rs": "bun scripts/run-rs-task.ts test:rs",
"check": "bun run --parallel check:ts check:rs",
"check:ts": "bun run check:tools && bun run --workspaces --if-present check",
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Added
- Added `omp-linux-musl-x64` and `omp-linux-musl-arm64` release binaries for Alpine and other musl-based Linux distributions, with automatic musl selection in the binary installer. ([#3367](https://github.com/can1357/oh-my-pi/issues/3367))
### Fixed
- Fixed all extension loading silently failing on the cross-compiled `omp-darwin-arm64` release binary (downloaded directly or via a Homebrew tap wrapper) because `__computeBunfsPackageRoot` mis-handled `import.meta.dir = "//root/omp-darwin-arm64"`. Bun 1.3.14 reports `<bunfs-root>/<binary-name>` for the compiled entry's `import.meta.dir`, but the pre-fix function joined `metaDir + "packages"` and produced `/root/omp-darwin-arm64/packages` — the binary basename was baked into every bunfs path, so the TypeBox/legacy-pi shims and every `@oh-my-pi/pi-*` package-root override failed `existsSync` validation and `resolveCanonicalPiSpecifier` fell through to a bunfs `Bun.resolveSync` that also could not find the module. The function now detects the bunfs-root + binary-basename shape (`path.basename(path.dirname(metaDir)) === "root"`) and strips the trailing binary segment by slicing the original `metaDir`; the production bunfs shim join path also preserves Bun's bunfs-native `//root` / `B:\~BUN\root` prefix that `path.join` would otherwise collapse. ([#3329](https://github.com/can1357/oh-my-pi/issues/3329))
+14
View File
@@ -48,6 +48,20 @@ const targets: BinaryTarget[] = [
target: "bun-linux-arm64",
outfile: "packages/coding-agent/binaries/omp-linux-arm64",
},
{
id: "linux-musl-x64",
platform: "linux",
arch: "x64",
target: "bun-linux-x64-musl-baseline",
outfile: "packages/coding-agent/binaries/omp-linux-musl-x64",
},
{
id: "linux-musl-arm64",
platform: "linux",
arch: "arm64",
target: "bun-linux-arm64-musl",
outfile: "packages/coding-agent/binaries/omp-linux-musl-arm64",
},
{
id: "win32-x64",
platform: "win32",
+6
View File
@@ -201,6 +201,12 @@ install_binary() {
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac
if [ "$PLATFORM" = "linux" ]; then
if [ -f /etc/alpine-release ] || { command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; }; then
PLATFORM="linux-musl"
fi
fi
BINARY="omp-${PLATFORM}-${ARCH}"
# Get release tag
if [ -n "$REF" ]; then
+86
View File
@@ -0,0 +1,86 @@
import { afterEach, describe, expect, test } from "bun:test";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
const repoRoot = path.join(import.meta.dir, "..");
const tempDirs: string[] = [];
afterEach(async () => {
await Promise.all(tempDirs.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true })));
});
async function run(command: string[], env: NodeJS.ProcessEnv = process.env): Promise<{ exitCode: number; stdout: string; stderr: string }> {
const proc = Bun.spawn(command, {
cwd: repoRoot,
env,
stdout: "pipe",
stderr: "pipe",
});
const [exitCode, stdout, stderr] = await Promise.all([
proc.exited,
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
]);
return { exitCode, stdout, stderr };
}
async function writeExecutable(directory: string, name: string, content: string): Promise<void> {
const file = path.join(directory, name);
await Bun.write(file, content);
await fs.chmod(file, 0o755);
}
describe("musl release artifacts", () => {
test("builds the requested x64 and arm64 musl asset names with Bun's musl targets", async () => {
const result = await run([
"bun",
"scripts/ci-release-build-binaries.ts",
"--dry-run",
"--targets",
"linux-musl-x64,linux-musl-arm64",
]);
expect(result.exitCode, result.stderr).toBe(0);
expect(result.stdout).toContain(
"--target=bun-linux-x64-musl-baseline ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/binaries/omp-linux-musl-x64",
);
expect(result.stdout).toContain(
"--target=bun-linux-arm64-musl ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/binaries/omp-linux-musl-arm64",
);
});
test("selects the musl asset when the Linux host reports musl", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-musl-install-"));
tempDirs.push(dir);
const binDir = path.join(dir, "bin");
const installDir = path.join(dir, "install");
await fs.mkdir(binDir);
await writeExecutable(binDir, "uname", '#!/bin/sh\n[ "$1" = "-s" ] && echo Linux || echo x86_64\n');
await writeExecutable(binDir, "ldd", "#!/bin/sh\necho 'musl libc (x86_64)'\n");
await writeExecutable(
binDir,
"curl",
`#!/bin/sh
case "$*" in
*api.github.com*) echo '{"tag_name":"v1.0.0"}' ;;
*) while [ "$#" -gt 0 ]; do
[ "$1" = "-o" ] && { printf binary > "$2"; exit 0; }
shift
done ;;
esac
`,
);
const result = await run(["sh", "scripts/install.sh", "--binary"], {
...process.env,
PATH: `${binDir}:${process.env.PATH ?? ""}`,
HOME: dir,
PI_INSTALL_DIR: installDir,
});
expect(result.exitCode, result.stderr).toBe(0);
expect(result.stdout).toContain("Downloading omp-linux-musl-x64...");
expect(await Bun.file(path.join(installDir, "omp")).text()).toBe("binary");
});
});