Skip to content

arst

arst #261

name: Build, Test, Release
on: [ push ]
env:
NODE_HEADERS: 24.0.0
V8_REF: 14.2.231.21
jobs:
# First, check to see if v8 has been built for each target. If not, then a more powerful `runs-on`
# builder is used on EC2.
# nb: Windows support w/ runs-on is beta and doesn't include the same tooling as GitHub.
# https://runs-on.com
# https://github.com/runs-on/runs-on
configure:
strategy:
matrix:
host:
- runs-on: ubuntu-24.04-arm
triplet: aarch64-alpine-linux-musl
- runs-on: ubuntu-24.04-arm
triplet: aarch64-unknown-linux-gnu
- runs-on: ubuntu-24.04
triplet: x86_64-alpine-linux-musl
- runs-on: ubuntu-24.04
triplet: x86_64-unknown-linux-gnu
name: Configure [${{ matrix.host.triplet }}]
runs-on: ${{ matrix.host.runs-on }}
outputs:
aarch64-alpine-linux-musl: ${{ steps.output.outputs.aarch64-alpine-linux-musl }}
aarch64-unknown-linux-gnu: ${{ steps.output.outputs.aarch64-unknown-linux-gnu }}
x86_64-alpine-linux-musl: ${{ steps.output.outputs.x86_64-alpine-linux-musl }}
x86_64-unknown-linux-gnu: ${{ steps.output.outputs.x86_64-unknown-linux-gnu }}
steps:
- uses: actions/cache/restore@v5
id: cache
with:
key: v8/${{ env.V8_REF }}/${{ matrix.host.triplet }}
lookup-only: true
path: deps/v8/out
- shell: sh
id: output
env:
CACHE_HIT: ${{ steps.cache.outputs.cache-hit }}
TRIPLET: ${{ matrix.host.triplet }}
RUNS_ON: ${{ matrix.host.runs-on }}
run: |
set -ux
if [ "$CACHE_HIT" = true ]; then
echo "$TRIPLET=$RUNS_ON" >> "$GITHUB_OUTPUT"
else
case "$RUNS_ON" in
ubuntu-24.04)
echo "$TRIPLET=runs-on,runner=32cpu-linux-x64,run-id=$GITHUB_RUN_ID" >> "$GITHUB_OUTPUT"
;;
ubuntu-24.04-arm)
echo "$TRIPLET=runs-on,runner=32cpu-linux-arm64,run-id=$GITHUB_RUN_ID" >> "$GITHUB_OUTPUT"
;;
windows-2025)
echo "$TRIPLET=runs-on,image=windows22-base-x64,family=m7i,run-id=$GITHUB_RUN_ID" >> "$GITHUB_OUTPUT"
;;
esac
fi
# Build isolated-vm
build:
name: Build [${{ matrix.host.triplet }}]
needs: configure
strategy:
fail-fast: false
matrix:
host:
- runs-on: ${{ needs.configure.outputs.aarch64-alpine-linux-musl }}
container: alpine:edge
triplet: aarch64-alpine-linux-musl
- runs-on: macos-15
triplet: aarch64-apple-darwin
- runs-on: ${{ needs.configure.outputs.aarch64-unknown-linux-gnu }}
container: debian:sid
triplet: aarch64-unknown-linux-gnu
- runs-on: ${{ needs.configure.outputs.x86_64-alpine-linux-musl }}
container: alpine:edge
triplet: x86_64-alpine-linux-musl
- runs-on: macos-15-intel
triplet: x86_64-apple-darwin
- runs-on: windows-2025
triplet: x86_64-pc-windows
- runs-on: ${{ needs.configure.outputs.x86_64-unknown-linux-gnu }}
container: debian:sid
triplet: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.host.runs-on }}
container: ${{ matrix.host.container }}
concurrency: ${{ matrix.host.triplet }}
env:
TRIPLET: ${{ matrix.host.triplet }}
steps:
- uses: laverdet/alpine-arm64@v1
if: matrix.host.triplet == 'aarch64-alpine-linux-musl'
# Setup
- uses: actions/checkout@v5
- shell: sh
run: echo "$GITHUB_WORKSPACE/scripts" >> "$GITHUB_PATH"
# GNU `tar` & `zstd` is needed for `actions/cache`
- uses: laverdet/install@v0
if: runner.os == 'Linux'
with:
packages: tar zstd
# Install toolchain
- name: Sources [Debian]
if: endsWith(matrix.host.triplet, '-linux-gnu')
run: apt-get -qq install --no-install-recommends -y curl; curl -kfsSL https://deb.nodesource.com/setup_24.x | bash -
- name: Toolchain [Linux]
uses: laverdet/install@v0
if: runner.os == 'Linux'
with:
packages: |
bash
binutils
build-essential
clang-22
clang-tools-22
cmake
curl
libboost-all-dev
lld-22
llvm-22
ninja-build
nodejs
xxd
alpine#npm
- name: Toolchain [macOS]
if: runner.os == 'macOS'
run: |
set -ux
brew install boost cmake lld llvm ninja node
# Clever workaround for clang-scan-deps issues
# /opt/homebrew/opt/llvm/bin/../include/c++/v1/__locale_dir/support/bsd_like.h:21:10: fatal error: 'time.h' file not found
# https://github.com/Homebrew/homebrew-core/issues/221782#issuecomment-3245658786
ln -s $(brew --prefix)/etc/clang ~/.config/clang
# On Windows, the POSIX shell in GitHub actions is Git Bash. This runs in an isolated MSYS2
# installation under `C:/Program Files/Git`. The runner comes with another MSYS2 installation
# in `C:/msys64`. This action adds the root MSYS2 installation higher up in $PATH so we can
# use `pacman`, and consume binaries installed by `pacman`.
# nb: Do not be confused by the `/msys64` directory which exists in the default GitHub shell!
# It is not the same as `C:/msys64`.
# nb: LLVM uses MSVC stdlib, so incomplete c++23 support. Unofficial LLVM uses libc/UCRT but
# is not supported by v8. Cross-build via Linux may be the only way.
# nb: windows-11-arm does *not* have Pacman!
# https://github.com/actions/partner-runner-images/blob/main/images/arm-windows-11-image.md
- name: Toolchain [Windows]
if: runner.os == 'Windows'
shell: sh
run: |
set -ux
echo ::group::Toolchain
# Install updated LLVM (and hide spammy progress bar)
winget install --accept-source-agreements --silent -e --id=LLVM.LLVM --version=22.1.2 | tr '\r' '\n' | grep -Ev --line-buffered '^[ -\/|]+$'
# You want the mingw version of ninja, not the msys version. The msys version uses
# `CMD.EXE` and does not work with the cmake Ninja generator.
ARCH=$(triplet_util %a)
/c/msys64/usr/bin/pacman -Sy --noconfirm \
mingw-w64-$ARCH-boost \
"mingw64/mingw-w64-$ARCH-cmake>=4.3.0" \
mingw64/mingw-w64-$ARCH-ninja \
;
echo "/c/msys64/mingw64/bin" >> "$GITHUB_PATH"
echo "/c/msys64/usr/bin" >> "$GITHUB_PATH"
echo ::endgroup::
echo ::group::import std hack
# /c/Program Files/Microsoft Visual Studio/2022/Enterprise
VISUAL_STUDIO_PATH=$(cygpath "$(vswhere | grep installationPath: | cut -c19-)")
# /c/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Tools/MSVC/14.44.35207
MSVC_PATH="$VISUAL_STUDIO_PATH/VC/Tools/MSVC/$(ls "$VISUAL_STUDIO_PATH/VC/Tools/MSVC" | sort -V | tail -1)"
# Make the `import std` interface manifest
MODULES_FRAGMENT='{ "logical-name": "std", "source-path": "'"$(cygpath -w "$MSVC_PATH/modules/std.ixx" | sed 's \\ \\\\\\\\ g')"'", "is-std-library": true }'
ARGUMENTS_FRAGMENT='"local-arguments": { "system-include-directories": [ "'"$MSVC_PATH/modules"'" ] }'
MANIFEST_JSON='{ "version": 1, "revision": 1, "modules": [ '"$MODULES_FRAGMENT"' ], '"$ARGUMENTS_FRAGMENT"' }'
echo "$MANIFEST_JSON" > ms-stl.modules.json
# Patch cmake
patch --batch --forward --unified /c/msys64/mingw64/share/cmake/Modules/Compiler/Clang-CXX-CXXImportStd.cmake <<"EOF"
@@ -4,8 +4,7 @@
elseif (CMAKE_CXX_STANDARD_LIBRARY STREQUAL "libstdc++")
set(_clang_modules_json_impl "libstdc++")
else ()
- set(CMAKE_CXX_COMPILER_IMPORT_STD_ERROR_MESSAGE "Only `libc++` and `libstdc++` are supported" PARENT_SCOPE)
- return ()
+ set(_clang_modules_json_impl "libc++")
endif ()
if (NOT CMAKE_CXX_STDLIB_MODULES_JSON)
EOF
echo ::endgroup::
echo ::group::Dependency Walker
mkdir -p "$RUNNER_TOOL_CACHE/depwalker"
cd "$RUNNER_TOOL_CACHE/depwalker"
# `--ssl-no-revoke`: curl: (35) schannel: next InitializeSecurityContext failed:
# CRYPT_E_REVOCATION_OFFLINE (0x80092013) - The revocation function was unable to check
# revocation because the revocation server was offline.
curl -fsSL --ssl-no-revoke -o depwalker.zip https://github.com/lucasg/Dependencies/releases/download/v1.11.1/Dependencies_x64_Release.zip
unzip depwalker.zip
rm depwalker.zip
echo "$PWD" >> "$GITHUB_PATH"
echo ::endgroup::
# Restore previously built v8
- uses: actions/cache/restore@v5
id: v8-cache
with:
key: v8/${{ env.V8_REF }}/${{ matrix.host.triplet }}
path: deps/v8/out
# Build v8 on cache miss
- uses: ./.github/actions/v8
name: Build v8
if: steps.v8-cache.outputs.cache-hit != 'true'
with:
triplet: ${{ matrix.host.triplet }}
ref: ${{ env.V8_REF }}
# Cache v8
- uses: actions/cache/save@v5
if: steps.v8-cache.outputs.cache-hit != 'true'
with:
key: ${{ steps.v8-cache.outputs.cache-primary-key }}
path: deps/v8/out
# Download nodejs headers
- uses: actions/cache/restore@v5
id: nodejs-headers-cache
with:
enableCrossOsArchive: true
key: nodejs-headers-${{ env.NODE_HEADERS }}
path: deps/nodejs
- name: node headers
id: nodejs-headers
if: steps.nodejs-headers-cache.outputs.cache-hit != 'true'
env:
NODE_HEADERS: ${{ env.NODE_HEADERS }}
run: set -u; nodejs_select "deps/nodejs/$NODE_HEADERS"
- uses: actions/cache/save@v5
if: steps.nodejs-headers.outcome == 'success'
with:
enableCrossOsArchive: true
key: ${{ steps.nodejs-headers-cache.outputs.cache-primary-key }}
path: deps/nodejs
# Build isolated-vm
- uses: pnpm/action-setup@v4
- shell: sh
run: |
set -ux
pnpm install --frozen-lockfile
npx tsc -b
- name: Configure
shell: sh
env:
NODE_DIST_DIR: deps/nodejs/${{ env.NODE_HEADERS }}
V8_REF: ${{ env.V8_REF }}
run: |
set -ux
V8_REF_PATH=$PWD/deps/v8/out/$V8_REF
CMAKE_MAKE_PROGRAM=ninja
CMAKE_PREFIX_PATH=
case "$TRIPLET" in
*-darwin)
CMAKE_C_COMPILER=$(brew --prefix llvm)/bin/clang
CMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++
;;
*-linux-gnu|*-linux-musl)
CMAKE_C_COMPILER=$(realpath "$(which clang-22)")
CMAKE_CXX_COMPILER=$(realpath "$(which clang++-22)")
;;
*-windows)
CMAKE_C_COMPILER=$(which clang.exe)
CMAKE_CXX_COMPILER=$(which clang++.exe)
CMAKE_MAKE_PROGRAM=$(which ninja.exe)
CMAKE_PREFIX_PATH=C:/msys64/mingw64/lib/cmake
;;
esac
cmake \
-DCMAKE_MODULE_PATH=$(npm exec auto_js_cmake_include) \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER="$CMAKE_C_COMPILER" \
-DCMAKE_CXX_COMPILER="$CMAKE_CXX_COMPILER" \
-DCMAKE_MAKE_PROGRAM="$CMAKE_MAKE_PROGRAM" \
-DCMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH" \
-DV8_INCLUDE_DIR="$V8_REF_PATH/include" \
-DV8_INCLUDE_GN_FILE="$V8_REF_PATH/$TRIPLET.release/include/v8-gn.h" \
-DV8_LIBRARY_TYPE=static \
-DV8_LIBRARY_PATH="$V8_REF_PATH/$TRIPLET.release" \
-DIVM_INSTALL=copy \
-B build \
-G Ninja \
;
- name: Build
shell: sh
run: ninja -C build backend_napi_v8
- name: Sanity Check
shell: sh
run: node -e "require('./build/packages/backend_napi_v8/artifacts/$(triplet_util -t "$TRIPLET" %p)')"
# Diagnostics
- name: Symbols [Linux]
if: runner.os == 'Linux'
run: |
set -ux
ls -la build/packages/backend_napi_v8/backend_napi_v8.node
ls -lah build/packages/backend_napi_v8/backend_napi_v8.node
ldd build/packages/backend_napi_v8/backend_napi_v8.node 2>&1 | grep -v 'Error relocating'
nm -guC build/packages/backend_napi_v8/backend_napi_v8.node
- name: Symbols [Windows]
if: runner.os == 'Windows'
shell: sh
run: |
set -ux
ls -la build/packages/backend_napi_v8/backend_napi_v8.node
ls -lah build/packages/backend_napi_v8/backend_napi_v8.node
dependencies.exe -imports build/packages/backend_napi_v8/backend_napi_v8.node
# Upload build artifacts
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.host.triplet }}
path: build/packages/backend_napi_v8/artifacts
if-no-files-found: error
# Console on failure
- uses: laverdet/console@v1
if: failure()
# Run tests
test:
name: Test [${{ matrix.host.container || matrix.host.runs-on }} ${{ endsWith(matrix.host.runs-on, '-arm' && 'arm64') || (endsWith(matrix.host.runs-on, '-intel') && 'x64') || (startsWith(matrix.host.runs-on, 'macos') && 'arm64') || 'x64' }} node:${{ matrix.node }}]
needs: build
strategy:
fail-fast: false
matrix:
host:
- runs-on: ubuntu-24.04-arm
container: alpine:edge
- runs-on: macos-15
- runs-on: ubuntu-24.04-arm
container: debian:forky
- runs-on: ubuntu-24.04
container: alpine:edge
- runs-on: macos-15-intel
- runs-on: windows-2025
- runs-on: ubuntu-24.04
container: debian:forky
node:
- 22
- 24
runs-on: ${{ matrix.host.runs-on }}
container: ${{ matrix.host.container }}
steps:
- uses: laverdet/alpine-arm64@v1
if: matrix.host.container == 'alpine:edge'
- uses: laverdet/install@v0
if: matrix.host.container == 'alpine:edge'
with:
packages: bash
# Setup
- uses: actions/checkout@v5
- shell: sh
run: echo "$GITHUB_WORKSPACE/scripts" >> "$GITHUB_PATH"
# Get triplet information and make package.json for module
- shell: sh
id: platform
run: |
echo "platform=$(triplet_util %p)" >> "$GITHUB_OUTPUT"
echo "triplet=$(triplet_util %t)" >> "$GITHUB_OUTPUT"
# Receive artifacts
- uses: actions/download-artifact@v4
with:
name: ${{ steps.platform.outputs.triplet }}
path: build/packages/backend_napi_v8/artifacts
# Install nodejs [musl]
- shell: sh
id: musl
if: endsWith(steps.platform.outputs.triplet, '-linux-musl')
env:
NODE_VERSION: ${{ matrix.node }}
run: |
set -ux
echo https://dl-cdn.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories
echo https://dl-cdn.alpinelinux.org/alpine/edge/testing/ >> /etc/apk/repositories
case "$NODE_VERSION" in
24) echo "package=nodejs-current" >> "$GITHUB_OUTPUT" ;;
22) echo "package=nodejs" >> "$GITHUB_OUTPUT" ;;
*)
echo "Unknown node version: $NODE_VERSION" 1>&2
exit 1
;;
esac
- uses: laverdet/install@v0
if: steps.musl.outcome == 'success'
with:
packages: alpine#${{ steps.musl.outputs.package }} npm
# Install nodejs [others]
- uses: actions/setup-node@v6
if: steps.musl.outcome == 'skipped'
with:
node-version: ${{ matrix.node }}
# Install npm dependencies, build TypeScript, check module sanity
- uses: pnpm/action-setup@v4
- name: Install
shell: sh
env:
PLATFORM: ${{ steps.platform.outputs.platform }}
run: |
set -ux
npm config set script-shell /bin/sh
pnpm install --frozen-lockfile
npx tsc -b
mkdir -p node_modules/@isolated-vm
mv build/packages/backend_napi_v8/artifacts/$PLATFORM node_modules/@isolated-vm/experimental-$PLATFORM
node -e "require('@isolated-vm/experimental-$PLATFORM')"
# Run tests
- name: Test
run: pnpm run -s test
# Console on failure
- uses: laverdet/console@v1
if: failure()
release:
name: Release
needs: test
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
# Setup
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
- uses: pnpm/action-setup@v4
- name: Setup
shell: sh
id: setup
env:
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
set -ux
npm config set --location user access=public
pnpm install --frozen-lockfile
npx tsc -b
# Receive artifacts
- uses: actions/download-artifact@v4
id: artifacts
with:
path: packages/isolated-vm/artifacts
# Assemble @isolated-vm/experimental
- name: Assemble
shell: sh
working-directory: packages/isolated-vm
env:
ARTIFACTS: ${{ steps.artifacts.outputs.download-path }}
run: |
set -ux
npx napi create-npm-dirs
npx napi artifacts
# Publish
- uses: changesets/action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
createGithubReleases: false
publish: pnpm publish --no-git-checks --recursive --publish-branch experimental --provenance