Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Hypervisor.framework requires the com.apple.security.hypervisor entitlement,
# so run macOS host binaries through a wrapper that ad-hoc signs them first.
# Cargo resolves this path relative to this file, not the working directory,
# so it also applies to the recipes that cd into src/hyperlight-js.
[target.'cfg(target_os = "macos")']
runner = "dev/macos-sign-and-run.sh"
78 changes: 75 additions & 3 deletions .github/workflows/dep_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: true
matrix:
build: [windows-2025-debug, linux-kvm-debug, linux-hyperv3-debug, windows-2025-release, linux-kvm-release, linux-hyperv3-release]
build: [windows-2025-debug, linux-kvm-debug, linux-kvm-arm64-debug, linux-hyperv3-debug, macos-hvf-debug, windows-2025-release, linux-kvm-release, linux-kvm-arm64-release, linux-hyperv3-release, macos-hvf-release]
include:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it appears not every entry has a arch field. Is this intended? I think adding it to every entry would make sense

- build: windows-2025-debug
os: [self-hosted, Windows, X64, "1ES.Pool=hld-win2025-amd"]
Expand All @@ -28,25 +28,48 @@ jobs:
- build: linux-kvm-debug
os: [self-hosted, Linux, X64, "1ES.Pool=hld-kvm-amd"]
hypervisor: kvm
arch: X64
config: debug
- build: linux-kvm-arm64-debug
os: [self-hosted, Linux, arm64, kvm]
hypervisor: kvm
arch: arm64
config: debug
- build: linux-hyperv3-debug
os: [self-hosted, Linux, X64, "1ES.Pool=hld-azlinux3-mshv-amd"]
hypervisor: hyperv3
config: debug
- build: macos-hvf-debug
os: [self-hosted, macos, arm64, hvf]
hypervisor: hvf
config: debug
- build: windows-2025-release
os: [self-hosted, Windows, X64, "1ES.Pool=hld-win2025-amd"]
hypervisor: whp
config: release
- build: linux-kvm-release
os: [self-hosted, Linux, X64, "1ES.Pool=hld-kvm-amd"]
hypervisor: kvm
arch: X64
config: release
- build: linux-kvm-arm64-release
os: [self-hosted, Linux, arm64, kvm]
hypervisor: kvm
arch: arm64
config: release
- build: linux-hyperv3-release
os: [self-hosted, Linux, X64, "1ES.Pool=hld-azlinux3-mshv-amd"]
hypervisor: hyperv3
config: release
runs-on: ${{ fromJson(
format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-amd", "JobId=build-{2}-{3}-{4}-{5}"]',
- build: macos-hvf-release
os: [self-hosted, macos, arm64, hvf]
hypervisor: hvf
config: release
runs-on: ${{ fromJson(matrix.hypervisor == 'hvf'
&& '["self-hosted", "macos", "arm64", "hvf"]'
|| matrix.arch == 'arm64'
&& '["self-hosted", "Linux", "arm64", "kvm"]'
|| format('["self-hosted", "{0}", "X64", "1ES.Pool=hld-{1}-amd", "JobId=build-{2}-{3}-{4}-{5}"]',
matrix.hypervisor == 'whp' && 'Windows' || 'Linux',
matrix.hypervisor == 'whp' && 'win2025' || matrix.hypervisor == 'hyperv3' && 'azlinux3-mshv' || 'kvm',
matrix.build,
Expand All @@ -64,13 +87,62 @@ jobs:
rust-toolchain: "1.90"
just-version: "1.51"

# Unlike upstream hyperlight, which builds its guests once per architecture
# and ships them to the test jobs as artifacts, hyperlight-js builds the JS
# guest runtime from build.rs during every `cargo build`. That makes a
# bare-metal ELF toolchain a hard requirement on macOS too.
#
# macOS's system archiver cannot produce ELF archives. cargo-hyperlight
# prefers llvm-ar on macOS for exactly that reason, but silently falls back
# to /usr/bin/ar when llvm-ar is missing from PATH, which yields a guest
# link with every QuickJS and libc symbol undefined. The runners have no
# package manager, so take llvm-ar from rustup's llvm-tools component,
# which is version-matched to the pinned toolchain.
- name: Set up LLVM guest toolchain (macOS)
if: runner.os == 'macOS'
shell: bash
Comment on lines +101 to +103

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we install this while provising runners or similar instead of here? And what's the retried for?

run: |
set -euo pipefail
llvm_bin="$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we allow an existing llvm-ar not related to the rust-toolchain?

for attempt in 1 2 3; do
if [ -x "$llvm_bin/llvm-ar" ]; then
break
fi
echo "Installing llvm-tools (attempt $attempt)"
rustup component add llvm-tools || true
[ -x "$llvm_bin/llvm-ar" ] || sleep $((attempt * 10))
done
if [ ! -x "$llvm_bin/llvm-ar" ]; then
echo "llvm-ar not found in $llvm_bin" >&2
exit 1
fi
echo "$llvm_bin" >> "$GITHUB_PATH"
"$llvm_bin/llvm-ar" --version

- name: install nodejs
uses: actions/setup-node@v7
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: 'src/js-host-api/package-lock.json'

# The cargo runner in .cargo/config.toml only signs binaries cargo itself
# launches, so the js-host-api tests -- which run under node via vitest --
# still hit HV_DENIED. Sign a private copy rather than the shared tool
# cache: these runners are not ephemeral.
- name: Sign node for Hypervisor.framework (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
signed_bin="$RUNNER_TEMP/node-signed"
mkdir -p "$signed_bin"
cp "$(command -v node)" "$signed_bin/node"
codesign -f -s - --entitlements dev/macos-entitlements.plist "$signed_bin/node"
codesign -d --entitlements - "$signed_bin/node"
echo "$signed_bin" >> "$GITHUB_PATH"
"$signed_bin/node" --version

- name: fmt
run: just fmt-check

Expand Down
106 changes: 71 additions & 35 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ concurrency:

env:
WORKING_DIR: src/js-host-api
# Comma-delimited npm package names that have not yet been published and
# therefore cannot have a trusted publisher configured. Remove each package
# in a follow-up PR after its first release and trusted-publisher setup.
FIRST_TIME_PACKAGES: ",@hyperlight-dev/js-host-api-darwin-arm64,"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes releases require NPM_TOKEN, but the caller in CreateRelease.yml does not forward secrets. Token validation will fail even if the repository secret exists. Could we also update the caller to pass it through?


jobs:
build:
Expand All @@ -57,6 +61,9 @@ jobs:
- target: x86_64-pc-windows-msvc
os: [self-hosted, Windows, X64, "1ES.Pool=hld-win2025-amd"]
build_name: win32-x64-msvc
- target: aarch64-apple-darwin
os: macos-15
build_name: darwin-arm64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
Expand All @@ -76,6 +83,33 @@ jobs:
cache: 'npm'
cache-dependency-path: 'src/js-host-api/package-lock.json'

# Building js-host-api runs hyperlight-js's build.rs, which builds the JS
# guest runtime for a bare-metal ELF target. macOS's system archiver
# cannot produce ELF archives, and cargo-hyperlight silently falls back to
# it when llvm-ar is absent from PATH, leaving every QuickJS and libc
# symbol undefined at the guest link. Take llvm-ar from rustup's
# llvm-tools component so this does not depend on a package manager.
- name: Set up LLVM guest toolchain (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
llvm_bin="$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin"
for attempt in 1 2 3; do
if [ -x "$llvm_bin/llvm-ar" ]; then
break
fi
echo "Installing llvm-tools (attempt $attempt)"
rustup component add llvm-tools || true
[ -x "$llvm_bin/llvm-ar" ] || sleep $((attempt * 10))
done
if [ ! -x "$llvm_bin/llvm-ar" ]; then
echo "llvm-ar not found in $llvm_bin" >&2
exit 1
fi
echo "$llvm_bin" >> "$GITHUB_PATH"
"$llvm_bin/llvm-ar" --version

- name: Install dependencies
working-directory: ${{ env.WORKING_DIR }}
run: npm ci --ignore-scripts --omit=optional
Expand Down Expand Up @@ -173,6 +207,12 @@ jobs:
name: bindings-win32-x64-msvc
path: ${{ env.WORKING_DIR }}/artifacts/win32-x64-msvc

- name: Download macOS arm64 artifact
uses: actions/download-artifact@v8
with:
name: bindings-darwin-arm64
path: ${{ env.WORKING_DIR }}/artifacts/darwin-arm64

- name: Download JS bindings
uses: actions/download-artifact@v8
with:
Expand All @@ -189,9 +229,11 @@ jobs:
mv artifacts/linux-x64-gnu/*.node npm/linux-x64-gnu/js-host-api.linux-x64-gnu.node
mv artifacts/linux-x64-musl/*.node npm/linux-x64-musl/js-host-api.linux-x64-musl.node
mv artifacts/win32-x64-msvc/*.node npm/win32-x64-msvc/js-host-api.win32-x64-msvc.node
mv artifacts/darwin-arm64/*.node npm/darwin-arm64/js-host-api.darwin-arm64.node
ls -la npm/linux-x64-gnu/
ls -la npm/linux-x64-musl/
ls -la npm/win32-x64-msvc/
ls -la npm/darwin-arm64/

- name: Set package versions
working-directory: ${{ env.WORKING_DIR }}
Expand All @@ -203,6 +245,7 @@ jobs:
cd npm/linux-x64-gnu && npm version "$VERSION" --no-git-tag-version --allow-same-version
cd ../linux-x64-musl && npm version "$VERSION" --no-git-tag-version --allow-same-version
cd ../win32-x64-msvc && npm version "$VERSION" --no-git-tag-version --allow-same-version
cd ../darwin-arm64 && npm version "$VERSION" --no-git-tag-version --allow-same-version
env:
VERSION: ${{ inputs.version }}

Expand Down Expand Up @@ -239,66 +282,55 @@ jobs:
run: ./test-pack.sh

# ── Authentication strategy ────────────────────────────────────
# Production releases via CreateRelease.yml use trusted publishing
# (OIDC) — npm auto-detects the id-token and exchanges it for a
# short-lived publish credential. Provenance attestations are
# generated automatically. The --provenance flag is added explicitly
# so the build fails loudly if OIDC is misconfigured.
#
# Manual workflow_dispatch requires an NPM_TOKEN repo secret because
# trusted publishing is configured for CreateRelease.yml only.
# Provenance is NOT available for token-based publishing.
# You should almost never need to publish manually — if you do,
# see docs/release.md for the full (deliberately painful) steps.
- name: Validate NPM_TOKEN for manual dispatch
if: ${{ github.event_name == 'workflow_dispatch' && !inputs.dry_run }}
# Established packages use trusted publishing (OIDC). Packages listed
# in FIRST_TIME_PACKAGES use NPM_TOKEN for this release only because
# npm has no trusted-publisher configuration until the package exists.
# Token-published packages cannot receive npm provenance attestations.
- name: Validate NPM_TOKEN for token-published packages
if: ${{ !inputs.dry_run && (github.event_name == 'workflow_dispatch' || env.FIRST_TIME_PACKAGES != '') }}
run: |
if [ -z "$NPM_TOKEN" ]; then
echo "::error::NPM_TOKEN repo secret is required for manual workflow_dispatch publishing."
echo "::error::See docs/release.md 'Manual npm publishing (emergency only)' for instructions."
echo "::error::NPM_TOKEN repo secret is required for manual publishing or first-time packages."
echo "::error::See docs/release.md for the first-time package bootstrap procedure."
exit 1
fi
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

# Set provenance flag once — OIDC path gets --provenance (fails loud
# if misconfigured), token path skips it (not supported).
- name: Set publish flags
id: publish-flags
run: |
if [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
echo "provenance=--provenance" >> "$GITHUB_OUTPUT"
else
echo "provenance=" >> "$GITHUB_OUTPUT"
fi

- name: Publish Linux GNU package
if: ${{ !inputs.dry_run }}
working-directory: ${{ env.WORKING_DIR }}/npm/linux-x64-gnu
run: npm publish --access public --ignore-scripts ${{ steps.publish-flags.outputs.provenance }}
run: npm publish --access public --ignore-scripts ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-linux-x64-gnu,')) && '' || '--provenance' }}
env:
NODE_AUTH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.NPM_TOKEN || '' }}
NODE_AUTH_TOKEN: ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-linux-x64-gnu,')) && secrets.NPM_TOKEN || '' }}

- name: Publish Linux musl package
if: ${{ !inputs.dry_run }}
working-directory: ${{ env.WORKING_DIR }}/npm/linux-x64-musl
run: npm publish --access public --ignore-scripts ${{ steps.publish-flags.outputs.provenance }}
run: npm publish --access public --ignore-scripts ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-linux-x64-musl,')) && '' || '--provenance' }}
env:
NODE_AUTH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.NPM_TOKEN || '' }}
NODE_AUTH_TOKEN: ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-linux-x64-musl,')) && secrets.NPM_TOKEN || '' }}

- name: Publish Windows package
if: ${{ !inputs.dry_run }}
working-directory: ${{ env.WORKING_DIR }}/npm/win32-x64-msvc
run: npm publish --access public --ignore-scripts ${{ steps.publish-flags.outputs.provenance }}
run: npm publish --access public --ignore-scripts ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-win32-x64-msvc,')) && '' || '--provenance' }}
env:
NODE_AUTH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.NPM_TOKEN || '' }}
NODE_AUTH_TOKEN: ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-win32-x64-msvc,')) && secrets.NPM_TOKEN || '' }}

- name: Publish macOS arm64 package

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we validate the macOS package tarball before publishing? test-pack.sh only packs and installs the Linux GNU package. The macOS CI tests exercise the build, but not installation from the release tarball.

if: ${{ !inputs.dry_run }}
working-directory: ${{ env.WORKING_DIR }}/npm/darwin-arm64
run: npm publish --access public --ignore-scripts ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-darwin-arm64,')) && '' || '--provenance' }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI is telling me the (condition) && '' || '--provenance' is buggy and always produces --provenance because '' is falsy

env:
NODE_AUTH_TOKEN: ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api-darwin-arm64,')) && secrets.NPM_TOKEN || '' }}

- name: Publish main package
if: ${{ !inputs.dry_run }}
working-directory: ${{ env.WORKING_DIR }}
run: npm publish --access public --ignore-scripts ${{ steps.publish-flags.outputs.provenance }}
run: npm publish --access public --ignore-scripts ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api,')) && '' || '--provenance' }}
env:
NODE_AUTH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.NPM_TOKEN || '' }}
NODE_AUTH_TOKEN: ${{ (github.event_name == 'workflow_dispatch' || contains(env.FIRST_TIME_PACKAGES, ',@hyperlight-dev/js-host-api,')) && secrets.NPM_TOKEN || '' }}

- name: Verify all packages published
if: ${{ !inputs.dry_run }}
Expand All @@ -309,7 +341,8 @@ jobs:
for pkg in "@hyperlight-dev/js-host-api" \
"@hyperlight-dev/js-host-api-linux-x64-gnu" \
"@hyperlight-dev/js-host-api-linux-x64-musl" \
"@hyperlight-dev/js-host-api-win32-x64-msvc"; do
"@hyperlight-dev/js-host-api-win32-x64-msvc" \
"@hyperlight-dev/js-host-api-darwin-arm64"; do
if npm view "$pkg@$VERSION" version > /dev/null 2>&1; then
echo "✅ $pkg@$VERSION published"
else
Expand Down Expand Up @@ -339,5 +372,8 @@ jobs:
echo "--- @hyperlight-dev/js-host-api-win32-x64-msvc ---"
npm pack ./npm/win32-x64-msvc --dry-run
echo ""
echo "--- @hyperlight-dev/js-host-api-darwin-arm64 ---"
npm pack ./npm/darwin-arm64 --dry-run
echo ""
echo "--- @hyperlight-dev/js-host-api ---"
npm pack --dry-run
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading