Skip to content

Commit 4b19d34

Browse files
committed
feat: add musl (Alpine Linux) support for native addon
Implements runtime libc detection and CI builds for both glibc and musl variants, enabling native addon usage on Alpine Linux. Changes: - Add linux-x64-musl to build matrix with node:22-alpine container - Implement runtime libc detection using detect-libc package - Map FFmpeg asset downloads (linux-x64-musl → ffmpeg-linux-x64.tar.gz) - Update installBuildTools to detect Alpine and use apk package manager - Add musl platform package to optionalDependencies - Update documentation to reflect musl support Fixes identified in code review: - Add container reference at job level for Alpine builds to work - Use node:22-alpine instead of alpine:latest for pre-installed Node.js - Fix import naming (GLIBC → detectLibc) per Google TS Style Guide - Correct FFmpeg asset mapping for musl builds Platform support: - macOS x64/arm64 (unchanged) - Linux x64 glibc (Ubuntu, Debian, RHEL) - Linux x64 musl (Alpine, Void Linux) - NEW User experience: Automatic libc detection, no manual package selection needed.
1 parent 0e56136 commit 4b19d34

6 files changed

Lines changed: 64 additions & 18 deletions

File tree

.github/workflows/ci.yml

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,20 @@ jobs:
154154
needs: [lint, resolve-deps]
155155
name: "build-${{ matrix.platform }}"
156156
runs-on: ${{ matrix.os }}
157+
container: ${{ matrix.container }}
157158
strategy:
158159
fail-fast: false
159160
matrix:
160161
include:
161162
- os: ubuntu-24.04
162-
platform: linux-x64
163+
platform: linux-x64-glibc
163164
arch: x64
165+
libc: glibc
166+
- os: ubuntu-24.04
167+
platform: linux-x64-musl
168+
arch: x64
169+
libc: musl
170+
container: node:22-alpine
164171
- os: macos-15-intel
165172
platform: darwin-x64
166173
arch: x64
@@ -196,16 +203,19 @@ jobs:
196203
with:
197204
repo: ${{ github.repository }}
198205
version: tags/deps-${{ needs.resolve-deps.outputs.deps_version }}
199-
# Use glibc variant for Linux (musl libs can't link into glibc shared objects)
200-
file: ffmpeg-${{ matrix.platform }}${{ runner.os == 'Linux' && '-glibc' || '' }}.tar.gz
206+
# Map platform to FFmpeg asset name:
207+
# - linux-x64-musl → ffmpeg-linux-x64.tar.gz (musl build)
208+
# - linux-x64-glibc → ffmpeg-linux-x64-glibc.tar.gz
209+
# - darwin-* → ffmpeg-darwin-*.tar.gz
210+
file: ffmpeg-${{ matrix.platform == 'linux-x64-musl' && 'linux-x64' || matrix.platform }}.tar.gz
201211
target: ffmpeg-${{ matrix.platform }}.tar.gz
202212
token: ${{ secrets.GITHUB_TOKEN }}
203213

204214
- name: Extract FFmpeg and Set Environment
205215
run: npx tsx scripts/ci/ci-workflow.ts extract-ffmpeg --archive "ffmpeg-${{ matrix.platform }}.tar.gz" --out ffmpeg-install
206216

207217
- name: Build with prebuildify
208-
run: npx tsx scripts/ci/ci-workflow.ts prebuildify --arch "${{ matrix.arch }}" --platform "${{ matrix.platform }}"${{ runner.os == 'Linux' && ' --libc glibc' || '' }}
218+
run: npx tsx scripts/ci/ci-workflow.ts prebuildify --arch "${{ matrix.arch }}" --platform "${{ matrix.platform }}"${{ matrix.libc && format(' --libc {0}', matrix.libc) || '' }}
209219

210220
- name: Build TypeScript
211221
run: npm run build:ts
@@ -218,21 +228,20 @@ jobs:
218228
PLATFORM: ${{ matrix.platform }}
219229
PLATFORM_OS: ${{ runner.os == 'macOS' && 'darwin' || 'linux' }}
220230
PLATFORM_CPU: ${{ matrix.arch }}
221-
PLATFORM_LIBC: ${{ runner.os == 'Linux' && 'glibc' || '' }}
222-
run: npx tsx scripts/ci/ci-workflow.ts package-platform --platform "$PLATFORM" --os "$PLATFORM_OS" --cpu "$PLATFORM_CPU" --prebuild "prebuilds/${{ matrix.platform }}/node.napi.node" --out packages${{ runner.os == 'Linux' && ' --libc glibc' || '' }}
231+
run: npx tsx scripts/ci/ci-workflow.ts package-platform --platform "$PLATFORM" --os "$PLATFORM_OS" --cpu "$PLATFORM_CPU" --prebuild "prebuilds/${{ matrix.platform }}/node.napi.node" --out packages${{ matrix.libc && format(' --libc {0}', matrix.libc) || '' }}
223232

224233
- name: Upload platform package artifact
225234
uses: actions/upload-artifact@v6
226235
with:
227-
name: platform-pkg-${{ matrix.platform }}${{ runner.os == 'Linux' && '-glibc' || '' }}
228-
path: packages/@pproenca-node-webcodecs-${{ matrix.platform }}${{ runner.os == 'Linux' && '-glibc' || '' }}.tar
236+
name: platform-pkg-${{ matrix.platform }}
237+
path: packages/@pproenca-node-webcodecs-${{ matrix.platform }}.tar
229238
retention-days: 14 # Extended for release timing flexibility
230239
compression-level: 0
231240

232241
- name: Attest platform package
233242
uses: actions/attest-build-provenance@v3
234243
with:
235-
subject-path: packages/@pproenca/node-webcodecs-${{ matrix.platform }}${{ runner.os == 'Linux' && '-glibc' || '' }}/bin/node.napi.node
244+
subject-path: packages/@pproenca/node-webcodecs-${{ matrix.platform }}/bin/node.napi.node
236245

237246
# ============================================================================
238247
# Test Node version compatibility using prebuilt binaries

docs/libc-tagging-implementation.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,16 @@ npm install @pproenca/node-webcodecs
158158
- Amazon Linux
159159
- Most mainstream Linux distributions
160160

161-
### Unsupported Linux Distributions (musl) ⚠️
162-
- Alpine Linux
161+
### Supported Linux Distributions (musl)
162+
- Alpine Linux (v3.14+)
163163
- Void Linux (musl variant)
164-
- Other musl-based distros
164+
- Other musl-based distributions
165165

166-
**Impact:** Low — most Node.js deployments use glibc-based distributions.
166+
**Installation:**
167+
```bash
168+
npm install @pproenca/node-webcodecs
169+
# Automatically selects musl package on Alpine
170+
```
167171

168172
---
169173

lib/binding.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
// Native binding loader using esbuild-style platform resolution.
55
// Tries platform-specific package first, falls back to node-gyp-build for local dev.
66

7+
import * as detectLibc from 'detect-libc';
78
import { resolve, dirname, join } from 'node:path';
89

910
const PLATFORMS: Record<string, string> = {
1011
'darwin-arm64': '@pproenca/node-webcodecs-darwin-arm64',
1112
'darwin-x64': '@pproenca/node-webcodecs-darwin-x64',
12-
'linux-x64': '@pproenca/node-webcodecs-linux-x64-glibc',
13+
'linux-x64-glibc': '@pproenca/node-webcodecs-linux-x64-glibc',
14+
'linux-x64-musl': '@pproenca/node-webcodecs-linux-x64-musl',
1315
};
1416

1517
/**
@@ -20,7 +22,24 @@ const PLATFORMS: Record<string, string> = {
2022
* 2. node-gyp-build (local development fallback)
2123
*/
2224
function loadBinding(): unknown {
23-
const platform = `${process.platform}-${process.arch}`;
25+
let platform = `${process.platform}-${process.arch}`;
26+
27+
// Detect libc on Linux
28+
if (process.platform === 'linux') {
29+
const libc = detectLibc.familySync(); // Returns 'glibc' or 'musl'
30+
if (libc) {
31+
platform = `${platform}-${libc}`;
32+
} else {
33+
// Fallback to glibc (most common)
34+
platform = `${platform}-glibc`;
35+
console.warn(
36+
`Warning: Could not detect libc, falling back to glibc. ` +
37+
`If you're on Alpine Linux, please install the musl package manually: ` +
38+
`npm install @pproenca/node-webcodecs-linux-x64-musl`
39+
);
40+
}
41+
}
42+
2443
const pkg = PLATFORMS[platform];
2544

2645
if (!pkg) {

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,11 @@
7878
"optionalDependencies": {
7979
"@pproenca/node-webcodecs-darwin-arm64": "0.1.1-alpha.8",
8080
"@pproenca/node-webcodecs-darwin-x64": "0.1.1-alpha.8",
81-
"@pproenca/node-webcodecs-linux-x64-glibc": "0.1.1-alpha.8"
81+
"@pproenca/node-webcodecs-linux-x64-glibc": "0.1.1-alpha.8",
82+
"@pproenca/node-webcodecs-linux-x64-musl": "0.1.1-alpha.8"
8283
},
8384
"dependencies": {
85+
"detect-libc": "^2.1.2",
8486
"node-gyp-build": "^4.8.0"
8587
},
8688
"devDependencies": {

scripts/ci/ci-workflow.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,19 @@ export function installBuildTools(runner: CommandRunner, osName: string): void {
109109
return;
110110
}
111111
if (osName === 'linux') {
112-
runner.runOrThrow('sudo', ['apt-get', 'update'], {stdio: 'inherit'});
113-
runner.runOrThrow('sudo', ['apt-get', 'install', '-y', 'pkg-config'], {stdio: 'inherit'});
112+
// Detect if running in Alpine (musl container)
113+
const isAlpine = existsSync('/etc/alpine-release');
114+
115+
if (isAlpine) {
116+
// Alpine Linux - use apk
117+
runner.runOrThrow('apk', ['add', '--no-cache', 'build-base', 'python3', 'pkgconf'], {
118+
stdio: 'inherit',
119+
});
120+
} else {
121+
// Ubuntu/Debian - use apt-get
122+
runner.runOrThrow('sudo', ['apt-get', 'update'], {stdio: 'inherit'});
123+
runner.runOrThrow('sudo', ['apt-get', 'install', '-y', 'pkg-config'], {stdio: 'inherit'});
124+
}
114125
return;
115126
}
116127
throw new Error(`Unsupported OS: ${osName}`);

scripts/create-platform-packages.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const PLATFORMS: PlatformConfig[] = [
3030
{name: 'darwin-arm64', os: 'darwin', cpu: 'arm64'},
3131
{name: 'darwin-x64', os: 'darwin', cpu: 'x64'},
3232
{name: 'linux-x64-glibc', os: 'linux', cpu: 'x64', libc: 'glibc'},
33+
{name: 'linux-x64-musl', os: 'linux', cpu: 'x64', libc: 'musl'},
3334
];
3435

3536
function resolveRootDir(): string {

0 commit comments

Comments
 (0)