feat: per-platform optional dependencies for native binary distribution
Add the esbuild/swc pattern for distributing platform-specific native
binaries via npm optional dependencies. Each supported platform gets its
own @gsd/engine-{platform} package containing just the .node binary.
- 5 platform package stubs (darwin-arm64, darwin-x64, linux-x64-gnu,
linux-arm64-gnu, win32-x64-msvc) with os/cpu filters
- Rewritten native loader: tries npm package first, then local build
- Version sync script keeps platform packages in lock-step with root
- GitHub Actions workflow for cross-platform build + publish on tag push
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d49af589d0
commit
bd8380315c
9 changed files with 342 additions and 16 deletions
134
.github/workflows/build-native.yml
vendored
Normal file
134
.github/workflows/build-native.yml
vendored
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
name: Build Native Binaries
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: "Publish platform packages to npm"
|
||||
required: false
|
||||
default: "false"
|
||||
type: choice
|
||||
options:
|
||||
- "false"
|
||||
- "true"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
platform: darwin-arm64
|
||||
- os: macos-13
|
||||
target: x86_64-apple-darwin
|
||||
platform: darwin-x64
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
platform: linux-x64-gnu
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
platform: linux-arm64-gnu
|
||||
cross: true
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
platform: win32-x64-msvc
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Build ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Install cross-compilation tools (Linux ARM64)
|
||||
if: matrix.cross
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
|
||||
|
||||
- name: Build native addon
|
||||
working-directory: native/crates/engine
|
||||
env:
|
||||
RUSTFLAGS: ""
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: ${{ matrix.cross && 'aarch64-linux-gnu-gcc' || '' }}
|
||||
run: cargo build --release --target ${{ matrix.target }}
|
||||
|
||||
- name: Prepare artifact (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
mkdir -p artifacts
|
||||
cp native/target/${{ matrix.target }}/release/libgsd_engine.dylib artifacts/gsd_engine.node 2>/dev/null || \
|
||||
cp native/target/${{ matrix.target }}/release/libgsd_engine.so artifacts/gsd_engine.node 2>/dev/null || \
|
||||
echo "No library found"
|
||||
ls -la artifacts/
|
||||
|
||||
- name: Prepare artifact (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
mkdir artifacts
|
||||
copy native\target\${{ matrix.target }}\release\gsd_engine.dll artifacts\gsd_engine.node
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: native-${{ matrix.platform }}
|
||||
path: artifacts/gsd_engine.node
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish platform packages
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Copy binaries to platform packages
|
||||
run: |
|
||||
for platform in darwin-arm64 darwin-x64 linux-x64-gnu linux-arm64-gnu win32-x64-msvc; do
|
||||
cp "artifacts/native-${platform}/gsd_engine.node" "native/npm/${platform}/gsd_engine.node"
|
||||
echo "Copied binary for ${platform}"
|
||||
ls -la "native/npm/${platform}/"
|
||||
done
|
||||
|
||||
- name: Sync platform package versions
|
||||
run: node native/scripts/sync-platform-versions.js
|
||||
|
||||
- name: Publish platform packages
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
for platform in darwin-arm64 darwin-x64 linux-x64-gnu linux-arm64-gnu win32-x64-msvc; do
|
||||
echo "Publishing @gsd/engine-${platform}..."
|
||||
cd "native/npm/${platform}"
|
||||
npm publish --access public || echo "Failed to publish ${platform} (may already exist)"
|
||||
cd -
|
||||
done
|
||||
|
||||
- name: Wait for npm registry propagation
|
||||
run: sleep 30
|
||||
|
||||
- name: Publish main package
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish
|
||||
20
native/npm/darwin-arm64/package.json
Normal file
20
native/npm/darwin-arm64/package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@gsd/engine-darwin-arm64",
|
||||
"version": "2.10.2",
|
||||
"description": "GSD native engine binary for macOS ARM64",
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"main": "gsd_engine.node",
|
||||
"files": [
|
||||
"gsd_engine.node"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/gsd-build/gsd-2.git"
|
||||
}
|
||||
}
|
||||
20
native/npm/darwin-x64/package.json
Normal file
20
native/npm/darwin-x64/package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@gsd/engine-darwin-x64",
|
||||
"version": "2.10.2",
|
||||
"description": "GSD native engine binary for macOS Intel",
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"main": "gsd_engine.node",
|
||||
"files": [
|
||||
"gsd_engine.node"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/gsd-build/gsd-2.git"
|
||||
}
|
||||
}
|
||||
20
native/npm/linux-arm64-gnu/package.json
Normal file
20
native/npm/linux-arm64-gnu/package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@gsd/engine-linux-arm64-gnu",
|
||||
"version": "2.10.2",
|
||||
"description": "GSD native engine binary for Linux ARM64 (glibc)",
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"main": "gsd_engine.node",
|
||||
"files": [
|
||||
"gsd_engine.node"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/gsd-build/gsd-2.git"
|
||||
}
|
||||
}
|
||||
20
native/npm/linux-x64-gnu/package.json
Normal file
20
native/npm/linux-x64-gnu/package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@gsd/engine-linux-x64-gnu",
|
||||
"version": "2.10.2",
|
||||
"description": "GSD native engine binary for Linux x64 (glibc)",
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"main": "gsd_engine.node",
|
||||
"files": [
|
||||
"gsd_engine.node"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/gsd-build/gsd-2.git"
|
||||
}
|
||||
}
|
||||
20
native/npm/win32-x64-msvc/package.json
Normal file
20
native/npm/win32-x64-msvc/package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@gsd/engine-win32-x64-msvc",
|
||||
"version": "2.10.2",
|
||||
"description": "GSD native engine binary for Windows x64 (MSVC)",
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"main": "gsd_engine.node",
|
||||
"files": [
|
||||
"gsd_engine.node"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/gsd-build/gsd-2.git"
|
||||
}
|
||||
}
|
||||
65
native/scripts/sync-platform-versions.cjs
Normal file
65
native/scripts/sync-platform-versions.cjs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Synchronize platform package versions with the root package version.
|
||||
*
|
||||
* Reads version from root package.json, writes it to all platform
|
||||
* package.json files and updates optionalDependencies in root package.json.
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
const npmDir = path.resolve(__dirname, "..", "npm");
|
||||
|
||||
const rootPkgPath = path.join(rootDir, "package.json");
|
||||
const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
|
||||
const version = rootPkg.version;
|
||||
|
||||
console.log(`[sync-platform-versions] Syncing to version ${version}`);
|
||||
|
||||
const platformPackages = [
|
||||
"darwin-arm64",
|
||||
"darwin-x64",
|
||||
"linux-x64-gnu",
|
||||
"linux-arm64-gnu",
|
||||
"win32-x64-msvc",
|
||||
];
|
||||
|
||||
// Update each platform package.json
|
||||
for (const platform of platformPackages) {
|
||||
const pkgPath = path.join(npmDir, platform, "package.json");
|
||||
if (!fs.existsSync(pkgPath)) {
|
||||
console.warn(` Skipping ${platform}: ${pkgPath} not found`);
|
||||
continue;
|
||||
}
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
||||
if (pkg.version !== version) {
|
||||
console.log(` ${platform}: ${pkg.version} -> ${version}`);
|
||||
pkg.version = version;
|
||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
||||
} else {
|
||||
console.log(` ${platform}: already ${version}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update optionalDependencies in root package.json
|
||||
let rootChanged = false;
|
||||
const optDeps = rootPkg.optionalDependencies || {};
|
||||
for (const platform of platformPackages) {
|
||||
const depName = `@gsd/engine-${platform}`;
|
||||
if (optDeps[depName] && optDeps[depName] !== version) {
|
||||
console.log(` root optionalDependencies ${depName}: ${optDeps[depName]} -> ${version}`);
|
||||
optDeps[depName] = version;
|
||||
rootChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (rootChanged) {
|
||||
rootPkg.optionalDependencies = optDeps;
|
||||
fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2) + "\n");
|
||||
console.log(" Updated root package.json optionalDependencies");
|
||||
}
|
||||
|
||||
console.log("[sync-platform-versions] Done.");
|
||||
|
|
@ -55,7 +55,8 @@
|
|||
"pi:install-global": "node scripts/install-pi-global.js",
|
||||
"pi:uninstall-global": "node scripts/uninstall-pi-global.js",
|
||||
"sync-pkg-version": "node scripts/sync-pkg-version.cjs",
|
||||
"prepublishOnly": "npm run sync-pkg-version && npm run build"
|
||||
"sync-platform-versions": "node native/scripts/sync-platform-versions.cjs",
|
||||
"prepublishOnly": "npm run sync-pkg-version && npm run sync-platform-versions && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^1.1.0",
|
||||
|
|
@ -82,6 +83,11 @@
|
|||
"typescript": "^5.4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@gsd/engine-darwin-arm64": "2.10.2",
|
||||
"@gsd/engine-darwin-x64": "2.10.2",
|
||||
"@gsd/engine-linux-x64-gnu": "2.10.2",
|
||||
"@gsd/engine-linux-arm64-gnu": "2.10.2",
|
||||
"@gsd/engine-win32-x64-msvc": "2.10.2",
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"overrides": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
* Native addon loader.
|
||||
*
|
||||
* Locates and loads the compiled Rust N-API addon (`.node` file).
|
||||
* Tries platform-tagged release builds first, then falls back to dev builds.
|
||||
* Resolution order:
|
||||
* 1. @gsd/engine-{platform} npm optional dependency (production install)
|
||||
* 2. native/addon/gsd_engine.{platform}.node (local release build)
|
||||
* 3. native/addon/gsd_engine.dev.node (local debug build)
|
||||
*/
|
||||
|
||||
import { createRequire } from "node:module";
|
||||
|
|
@ -15,31 +18,49 @@ const require = createRequire(import.meta.url);
|
|||
const addonDir = path.resolve(__dirname, "..", "..", "..", "native", "addon");
|
||||
const platformTag = `${process.platform}-${process.arch}`;
|
||||
|
||||
const candidates = [
|
||||
path.join(addonDir, `gsd_engine.${platformTag}.node`),
|
||||
path.join(addonDir, "gsd_engine.dev.node"),
|
||||
];
|
||||
/** Map Node.js platform/arch to the npm package suffix */
|
||||
const platformPackageMap: Record<string, string> = {
|
||||
"darwin-arm64": "darwin-arm64",
|
||||
"darwin-x64": "darwin-x64",
|
||||
"linux-x64": "linux-x64-gnu",
|
||||
"linux-arm64": "linux-arm64-gnu",
|
||||
"win32-x64": "win32-x64-msvc",
|
||||
};
|
||||
|
||||
function loadNative(): Record<string, unknown> {
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
// 1. Try the platform-specific npm optional dependency
|
||||
const packageSuffix = platformPackageMap[platformTag];
|
||||
if (packageSuffix) {
|
||||
try {
|
||||
return require(candidate) as Record<string, unknown>;
|
||||
return require(`@gsd/engine-${packageSuffix}`) as Record<string, unknown>;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
errors.push(`${candidate}: ${message}`);
|
||||
errors.push(`@gsd/engine-${packageSuffix}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try local release build (native/addon/gsd_engine.{platform}.node)
|
||||
const releasePath = path.join(addonDir, `gsd_engine.${platformTag}.node`);
|
||||
try {
|
||||
return require(releasePath) as Record<string, unknown>;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
errors.push(`${releasePath}: ${message}`);
|
||||
}
|
||||
|
||||
// 3. Try local dev build (native/addon/gsd_engine.dev.node)
|
||||
const devPath = path.join(addonDir, "gsd_engine.dev.node");
|
||||
try {
|
||||
return require(devPath) as Record<string, unknown>;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
errors.push(`${devPath}: ${message}`);
|
||||
}
|
||||
|
||||
const details = errors.map((e) => ` - ${e}`).join("\n");
|
||||
const supportedPlatforms = [
|
||||
"darwin-arm64",
|
||||
"darwin-x64",
|
||||
"linux-x64",
|
||||
"linux-arm64",
|
||||
"win32-x64",
|
||||
];
|
||||
const supportedPlatforms = Object.keys(platformPackageMap);
|
||||
throw new Error(
|
||||
`Failed to load gsd_engine native addon for ${platformTag}.\n\n` +
|
||||
`Tried:\n${details}\n\n` +
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue