Skip to content

Commit 97ad78c

Browse files
feat(zig-dtsx): ship cross-platform CLI + FFI library prebuilts
- build.zig: add `lib` step mirroring the existing `cli` step so the shared library can be cross-compiled standalone. - release.yml: refactor the 6-target build into a `build_target()` helper producing both `zig-dtsx-<plat>.zip` (CLI) and `zig-dtsx-lib-<plat>.tar.gz` (FFI shared library, flat archive layout). CLI zips renamed from `dtsx-*` to `zig-dtsx-*`. - packages/zig-dtsx: add scripts/fetch-prebuilt.mjs as a postinstall that downloads the platform-matching FFI lib from the GH release matching the package version, extracts to prebuilt/, and falls back silently for monorepo dev installs (where `zig build lib` produces the binary locally). - packages/zig-dtsx/src/index.ts: look in prebuilt/ first; handle the no-`lib`-prefix Windows DLL name. - packages/zig-dtsx/package.json: align version with the dtsx release line; add files whitelist for the npm tarball.
1 parent 47b43ab commit 97ad78c

5 files changed

Lines changed: 131 additions & 29 deletions

File tree

.github/workflows/release.yml

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,34 +30,36 @@ jobs:
3030
- name: Install Dependencies
3131
run: bun install
3232

33-
- name: Build Zig CLI Binaries
33+
- name: Build Zig CLI + Library Binaries
3434
working-directory: packages/zig-dtsx
3535
run: |
3636
mkdir -p ../dtsx/bin
3737
38-
# Linux x64
39-
zig build cli -Doptimize=ReleaseFast -Dtarget=x86_64-linux
40-
zip -j ../dtsx/bin/dtsx-linux-x64.zip zig-out/bin/zig-dtsx
38+
build_target() {
39+
local target="$1" # zig target triple (e.g. x86_64-linux)
40+
local plat="$2" # release-artifact platform slug (e.g. linux-x64)
41+
local exe_ext="$3" # CLI extension (empty for unix, .exe for windows)
42+
local lib_dir="$4" # subdirectory of zig-out/ containing the shared library (lib or bin)
43+
local lib_file="$5" # exact shared library filename inside lib_dir
4144
42-
# Linux arm64
43-
zig build cli -Doptimize=ReleaseFast -Dtarget=aarch64-linux
44-
zip -j ../dtsx/bin/dtsx-linux-arm64.zip zig-out/bin/zig-dtsx
45+
# Clean to avoid mixing artifacts between targets
46+
rm -rf zig-out
4547
46-
# macOS x64
47-
zig build cli -Doptimize=ReleaseFast -Dtarget=x86_64-macos
48-
zip -j ../dtsx/bin/dtsx-darwin-x64.zip zig-out/bin/zig-dtsx
48+
zig build cli -Doptimize=ReleaseFast -Dtarget="$target"
49+
zip -j "../dtsx/bin/zig-dtsx-${plat}.zip" "zig-out/bin/zig-dtsx${exe_ext}"
4950
50-
# macOS arm64
51-
zig build cli -Doptimize=ReleaseFast -Dtarget=aarch64-macos
52-
zip -j ../dtsx/bin/dtsx-darwin-arm64.zip zig-out/bin/zig-dtsx
51+
zig build lib -Doptimize=ReleaseFast -Dtarget="$target"
52+
# Flat tar.gz (no subdir prefix) so the npm postinstall extracts straight into prebuilt/.
53+
# Windows DLLs land in zig-out/bin/, unix shared libs in zig-out/lib/.
54+
tar -czf "../dtsx/bin/zig-dtsx-lib-${plat}.tar.gz" -C "zig-out/${lib_dir}" "${lib_file}"
55+
}
5356
54-
# Windows x64
55-
zig build cli -Doptimize=ReleaseFast -Dtarget=x86_64-windows
56-
zip -j ../dtsx/bin/dtsx-windows-x64.zip zig-out/bin/zig-dtsx.exe
57-
58-
# FreeBSD x64
59-
zig build cli -Doptimize=ReleaseFast -Dtarget=x86_64-freebsd
60-
zip -j ../dtsx/bin/dtsx-freebsd-x64.zip zig-out/bin/zig-dtsx
57+
build_target x86_64-linux linux-x64 "" lib libzig-dtsx.so
58+
build_target aarch64-linux linux-arm64 "" lib libzig-dtsx.so
59+
build_target x86_64-macos darwin-x64 "" lib libzig-dtsx.dylib
60+
build_target aarch64-macos darwin-arm64 "" lib libzig-dtsx.dylib
61+
build_target x86_64-windows windows-x64 '.exe' bin zig-dtsx.dll
62+
build_target x86_64-freebsd freebsd-x64 "" lib libzig-dtsx.so
6163
6264
- name: Publish
6365
run: pantry publish --npm --access public
@@ -68,11 +70,17 @@ jobs:
6870
uses: stacksjs/action-releaser@v1.2.9
6971
with:
7072
files: |
71-
packages/dtsx/bin/dtsx-linux-x64.zip
72-
packages/dtsx/bin/dtsx-linux-arm64.zip
73-
packages/dtsx/bin/dtsx-darwin-x64.zip
74-
packages/dtsx/bin/dtsx-darwin-arm64.zip
75-
packages/dtsx/bin/dtsx-windows-x64.zip
76-
packages/dtsx/bin/dtsx-freebsd-x64.zip
73+
packages/dtsx/bin/zig-dtsx-linux-x64.zip
74+
packages/dtsx/bin/zig-dtsx-linux-arm64.zip
75+
packages/dtsx/bin/zig-dtsx-darwin-x64.zip
76+
packages/dtsx/bin/zig-dtsx-darwin-arm64.zip
77+
packages/dtsx/bin/zig-dtsx-windows-x64.zip
78+
packages/dtsx/bin/zig-dtsx-freebsd-x64.zip
79+
packages/dtsx/bin/zig-dtsx-lib-linux-x64.tar.gz
80+
packages/dtsx/bin/zig-dtsx-lib-linux-arm64.tar.gz
81+
packages/dtsx/bin/zig-dtsx-lib-darwin-x64.tar.gz
82+
packages/dtsx/bin/zig-dtsx-lib-darwin-arm64.tar.gz
83+
packages/dtsx/bin/zig-dtsx-lib-windows-x64.tar.gz
84+
packages/dtsx/bin/zig-dtsx-lib-freebsd-x64.tar.gz
7785
env:
7886
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

packages/zig-dtsx/build.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ pub fn build(b: *std.Build) void {
4646
const cli_step = b.step("cli", "Build only the CLI binary");
4747
cli_step.dependOn(&cli_install.step);
4848

49+
// Lib-only step (mirror of cli step — for cross-compiling the FFI shared library alone)
50+
const lib_install = b.addInstallArtifact(lib, .{});
51+
const lib_step = b.step("lib", "Build only the shared library");
52+
lib_step.dependOn(&lib_install.step);
53+
4954
// Run step
5055
const run_cmd = b.addRunArtifact(exe);
5156
run_cmd.step.dependOn(b.getInstallStep());

packages/zig-dtsx/package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@
1515
"build": "bun run build:zig",
1616
"test": "bun test",
1717
"test:zig": "zig build test",
18-
"benchmark": "bun run test/benchmark.ts"
18+
"benchmark": "bun run test/benchmark.ts",
19+
"postinstall": "node scripts/fetch-prebuilt.mjs"
1920
},
21+
"files": [
22+
"src",
23+
"scripts/fetch-prebuilt.mjs",
24+
"build.zig",
25+
"build.zig.zon",
26+
"README.md"
27+
],
2028
"dependencies": {
2129
"@stacksjs/dtsx": "workspace:*"
2230
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env node
2+
// Postinstall: fetch the platform-matching prebuilt shared library from the
3+
// GitHub release that matches this package's version. Falls back silently if
4+
// the fetch fails so that monorepo dev installs (where the binary is built
5+
// locally via `zig build lib`) aren't blocked.
6+
7+
import { spawnSync } from 'node:child_process'
8+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
9+
import { arch, platform } from 'node:os'
10+
import { dirname, join } from 'node:path'
11+
import { fileURLToPath } from 'node:url'
12+
13+
const here = dirname(fileURLToPath(import.meta.url))
14+
const pkgDir = join(here, '..')
15+
const prebuiltDir = join(pkgDir, 'prebuilt')
16+
17+
const { version } = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8'))
18+
19+
// Map node platform/arch to release-artifact slugs (mirrors release.yml build_target calls)
20+
const PLATFORM_MAP = {
21+
'darwin-arm64': { slug: 'darwin-arm64', libFile: 'libzig-dtsx.dylib' },
22+
'darwin-x64': { slug: 'darwin-x64', libFile: 'libzig-dtsx.dylib' },
23+
'linux-arm64': { slug: 'linux-arm64', libFile: 'libzig-dtsx.so' },
24+
'linux-x64': { slug: 'linux-x64', libFile: 'libzig-dtsx.so' },
25+
'win32-x64': { slug: 'windows-x64', libFile: 'zig-dtsx.dll' },
26+
'freebsd-x64': { slug: 'freebsd-x64', libFile: 'libzig-dtsx.so' },
27+
}
28+
29+
const key = `${platform()}-${arch()}`
30+
const entry = PLATFORM_MAP[key]
31+
32+
if (!entry) {
33+
console.warn(`[zig-dtsx] no prebuilt for ${key}; run \`zig build lib\` locally if you need the FFI library`)
34+
process.exit(0)
35+
}
36+
37+
// Skip if the lib already exists at the dev-build location — monorepo path.
38+
if (existsSync(join(pkgDir, 'zig-out', 'lib', entry.libFile)) || existsSync(join(pkgDir, 'zig-out', 'bin', entry.libFile))) {
39+
process.exit(0)
40+
}
41+
42+
// Skip if prebuilt already in place from a prior install of the same version.
43+
if (existsSync(join(prebuiltDir, entry.libFile))) {
44+
process.exit(0)
45+
}
46+
47+
const archiveName = `zig-dtsx-lib-${entry.slug}.tar.gz`
48+
const url = `https://github.com/stacksjs/dtsx/releases/download/v${version}/${archiveName}`
49+
50+
try {
51+
const res = await fetch(url, { redirect: 'follow' })
52+
if (!res.ok)
53+
throw new Error(`HTTP ${res.status}`)
54+
const buf = Buffer.from(await res.arrayBuffer())
55+
56+
mkdirSync(prebuiltDir, { recursive: true })
57+
const tmpArchive = join(prebuiltDir, `.${archiveName}.tmp`)
58+
writeFileSync(tmpArchive, buf)
59+
60+
const tarResult = spawnSync('tar', ['-xzf', tmpArchive, '-C', prebuiltDir], { stdio: 'inherit' })
61+
rmSync(tmpArchive, { force: true })
62+
63+
if (tarResult.status !== 0)
64+
throw new Error(`tar exited with code ${tarResult.status}`)
65+
66+
if (!existsSync(join(prebuiltDir, entry.libFile)))
67+
throw new Error(`archive did not contain expected file ${entry.libFile}`)
68+
}
69+
catch (err) {
70+
console.warn(`[zig-dtsx] could not fetch prebuilt from ${url}: ${err.message}`)
71+
console.warn(`[zig-dtsx] dev workflow: run \`zig build lib\` inside this package.`)
72+
// Exit 0 — never block installs over a missing prebuilt.
73+
process.exit(0)
74+
}

packages/zig-dtsx/src/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { dlopen, FFIType, type Pointer, ptr, suffix, toArrayBuffer } from 'bun:f
55
import { join } from 'node:path'
66

77
const LIB_NAME = `libzig-dtsx.${suffix}`
8+
// On Windows, Zig produces `zig-dtsx.dll` (no `lib` prefix); other platforms keep `libzig-dtsx.{so,dylib}`.
9+
const LIB_NAME_ALT = suffix === 'dll' ? `zig-dtsx.${suffix}` : LIB_NAME
810
const encoder = new TextEncoder()
911
const decoder = new TextDecoder()
1012
const outLenBuffer = new BigUint64Array(1)
@@ -14,10 +16,15 @@ const outLenBufferIsolated = new BigUint64Array(1)
1416
// Avoids per-call Uint8Array allocation from encoder.encode().
1517
let inputBuf = new Uint8Array(4 * 1024 * 1024) // 4 MB initial
1618

17-
// Try to find the shared library
19+
// Try to find the shared library. Order:
20+
// 1. prebuilt/ (npm postinstall fetched the platform-matching binary from the GH release)
21+
// 2. zig-out/lib/ (monorepo dev — `zig build lib` writes here for unix targets)
22+
// 3. zig-out/bin/ (monorepo dev — Windows Zig builds drop the DLL here)
1823
const libPaths = [
24+
join(import.meta.dir, '..', 'prebuilt', LIB_NAME_ALT),
25+
join(import.meta.dir, '..', 'prebuilt', LIB_NAME),
1926
join(import.meta.dir, '..', 'zig-out', 'lib', LIB_NAME),
20-
join(import.meta.dir, '..', `zig-out/lib/${LIB_NAME}`),
27+
join(import.meta.dir, '..', 'zig-out', 'bin', LIB_NAME_ALT),
2128
]
2229

2330
let lib: ReturnType<typeof dlopen> | null = null

0 commit comments

Comments
 (0)