Skip to content

Build Node.js from Source #16

Build Node.js from Source

Build Node.js from Source #16

Workflow file for this run

name: Build Node.js from Source
on:
workflow_dispatch:
inputs:
linux-x64:
description: 'Build Linux x64'
type: boolean
default: true
linux-arm64:
description: 'Build Linux ARM64'
type: boolean
default: true
macos-x64:
description: 'Build macOS x64'
type: boolean
default: true
macos-arm64:
description: 'Build macOS ARM64'
type: boolean
default: true
windows-x64:
description: 'Build Windows x64'
type: boolean
default: true
env:
ARTIFACT_RETENTION_DAYS: 1
NODE_TAG: v24.14.1
jobs:
# ─────────────────────────────────────────────
# Linux x64
# ─────────────────────────────────────────────
build-linux-x64:
if: inputs.linux-x64
runs-on: ubuntu-24.04
steps:
- name: Install build dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y -q \
python3 g++ make ninja-build git \
libssl-dev pkg-config \
xz-utils curl ca-certificates
- name: Clone Node.js ${{ env.NODE_TAG }}
run: |
git clone --depth 1 --branch "$NODE_TAG" \
https://github.com/nodejs/node.git node-src
- name: Checkout repository (for patches)
uses: actions/checkout@v6
with:
path: repo
- name: Apply patches
run: |
shopt -s nullglob
patches=(repo/patches/*.patch)
if [ ${#patches[@]} -eq 0 ]; then
echo "No patches found, skipping."
else
for patch in "${patches[@]}"; do
echo "Applying $patch"
git -C node-src apply --verbose "../${patch}"
done
fi
- name: Configure
working-directory: node-src
run: |
# --prefix: install root inside the staging directory
# --tag: cleared so the version string is clean (e.g. v24.14.1, not v24.14.1-custom)
# --ninja: use Ninja instead of Make for faster incremental builds
# --experimental-enable-pointer-compression: compress V8 heap pointers from 8 to 4 bytes, reducing heap size
# --experimental-pointer-compression-shared-cage: share the pointer compression cage across Isolates in the same process
# --without-siphash: disable SipHash for V8 hash seeds, using a simpler alternative
./configure \
--prefix=/usr/local \
--tag="" \
--ninja \
--experimental-enable-pointer-compression \
--experimental-pointer-compression-shared-cage \
--without-siphash
- name: Build (this takes 20–40 min)
working-directory: node-src
run: make -j4
- name: Smoke test
working-directory: node-src
run: ./out/Release/node --version
- name: Install into staging directory
working-directory: node-src
run: make install DESTDIR=$PWD/../stage
- name: Smoke test npm
run: ../stage/usr/local/bin/npm --version
working-directory: node-src
- name: Package tarball (full install tree)
run: |
ARCHIVE="node-${{ env.NODE_TAG }}-linux-x64.tar.gz"
# tar up everything make install placed under stage/usr/local
tar czf "$ARCHIVE" -C stage/usr/local .
echo "Archive size: $(du -sh $ARCHIVE | cut -f1)"
echo "Top-level entries:"
tar tzf "$ARCHIVE" | cut -d/ -f1-2 | sort -u | head -20
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: node-${{ env.NODE_TAG }}-linux-x64
path: node-${{ env.NODE_TAG }}-linux-x64.tar.gz
retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }}
# ─────────────────────────────────────────────
# Linux ARM64 (GitHub-hosted)
# ─────────────────────────────────────────────
build-linux-arm64:
if: inputs.linux-arm64
runs-on: ubuntu-24.04-arm
steps:
- name: Install build dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y -q \
python3 g++-12 make ninja-build git \
libssl-dev pkg-config \
xz-utils curl ca-certificates
- name: Clone Node.js ${{ env.NODE_TAG }}
run: |
git clone --depth 1 --branch "$NODE_TAG" \
https://github.com/nodejs/node.git node-src
- name: Checkout repository (for patches)
uses: actions/checkout@v6
with:
path: repo
- name: Apply patches
run: |
shopt -s nullglob
patches=(repo/patches/*.patch)
if [ ${#patches[@]} -eq 0 ]; then
echo "No patches found, skipping."
else
for patch in "${patches[@]}"; do
echo "Applying $patch"
git -C node-src apply --verbose "../${patch}"
done
fi
- name: Configure
working-directory: node-src
env:
CC: gcc-12
CXX: g++-12
run: |
# --prefix: install root inside the staging directory
# --ninja: use Ninja instead of Make for faster incremental builds
# --experimental-enable-pointer-compression: compress V8 heap pointers from 8 to 4 bytes, reducing heap size
# --experimental-pointer-compression-shared-cage: share the pointer compression cage across Isolates in the same process
# --without-siphash: disable SipHash for V8 hash seeds, using a simpler alternative
./configure \
--prefix=/usr/local \
--ninja \
--experimental-enable-pointer-compression \
--experimental-pointer-compression-shared-cage \
--without-siphash
- name: Build
working-directory: node-src
env:
CC: gcc-12
CXX: g++-12
run: make -j4
- name: Smoke test
working-directory: node-src
run: ./out/Release/node --version
- name: Install into staging directory
working-directory: node-src
run: make install DESTDIR=$PWD/../stage
- name: Smoke test npm
working-directory: node-src
run: ../stage/usr/local/bin/npm --version
- name: Package tarball (full install tree)
run: |
ARCHIVE="node-${{ env.NODE_TAG }}-linux-arm64.tar.gz"
# tar up everything make install placed under stage/usr/local
tar czf "$ARCHIVE" -C stage/usr/local .
echo "Archive size: $(du -sh $ARCHIVE | cut -f1)"
echo "Top-level entries:"
tar tzf "$ARCHIVE" | cut -d/ -f1-2 | sort -u | head -20
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: node-${{ env.NODE_TAG }}-linux-arm64
path: node-${{ env.NODE_TAG }}-linux-arm64.tar.gz
retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }}
# ─────────────────────────────────────────────
# macOS x64
# ─────────────────────────────────────────────
build-macos-x64:
if: inputs.macos-x64
runs-on: macos-15-intel # last Intel runner
steps:
- name: Install build dependencies
run: brew install python3 openssl pkg-config ninja
- name: Clone Node.js ${{ env.NODE_TAG }}
run: |
git clone --depth 1 --branch "$NODE_TAG" \
https://github.com/nodejs/node.git node-src
- name: Checkout repository (for patches)
uses: actions/checkout@v6
with:
path: repo
- name: Apply patches
run: |
shopt -s nullglob
patches=(repo/patches/*.patch)
if [ ${#patches[@]} -eq 0 ]; then
echo "No patches found, skipping."
else
for patch in "${patches[@]}"; do
echo "Applying $patch"
git -C node-src apply --verbose "../${patch}"
done
fi
- name: Configure
working-directory: node-src
run: |
# --prefix: install root inside the staging directory
# --openssl-use-def-ca-store: use the macOS system Keychain for CA certificates instead of the bundled Mozilla store
# --ninja: use Ninja instead of Make for faster incremental builds
# --experimental-enable-pointer-compression: compress V8 heap pointers from 8 to 4 bytes, reducing heap size
# --experimental-pointer-compression-shared-cage: share the pointer compression cage across Isolates in the same process
# --without-siphash: disable SipHash for V8 hash seeds, using a simpler alternative
./configure \
--prefix=/usr/local \
--openssl-use-def-ca-store \
--ninja \
--experimental-enable-pointer-compression \
--experimental-pointer-compression-shared-cage \
--without-siphash
- name: Build
working-directory: node-src
run: make -j4
- name: Smoke test
working-directory: node-src
run: ./out/Release/node --version
- name: Install into staging directory
working-directory: node-src
run: make install DESTDIR=$PWD/../stage
- name: Smoke test npm
working-directory: node-src
run: ../stage/usr/local/bin/npm --version
- name: Package tarball (full install tree)
run: |
ARCHIVE="node-${{ env.NODE_TAG }}-darwin-x64.tar.gz"
# tar up everything make install placed under stage/usr/local
tar czf "$ARCHIVE" -C stage/usr/local .
echo "Archive size: $(du -sh $ARCHIVE | cut -f1)"
echo "Top-level entries:"
tar tzf "$ARCHIVE" | cut -d/ -f1-2 | sort -u | head -20
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: node-${{ env.NODE_TAG }}-darwin-x64
path: node-${{ env.NODE_TAG }}-darwin-x64.tar.gz
retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }}
# ─────────────────────────────────────────────
# macOS ARM64 (Apple Silicon)
# ─────────────────────────────────────────────
build-macos-arm64:
if: inputs.macos-arm64
runs-on: macos-15 # M1/M2 runner — ships with Xcode 16 (Clang 16+)
steps:
- name: Select Xcode 16
# Ensures Clang 16+ is active — required by V8's C++20 TypeIndex cast
# in deps/v8/src/wasm/value-type.h (fails on Clang 15 / Xcode 15)
run: sudo xcode-select --switch /Applications/Xcode_16.app/Contents/Developer
- name: Verify Clang version (must be 16+)
run: clang++ --version
- name: Install build dependencies
run: brew install python3 openssl pkg-config ninja
- name: Clone Node.js ${{ env.NODE_TAG }}
run: |
git clone --depth 1 --branch "$NODE_TAG" \
https://github.com/nodejs/node.git node-src
- name: Checkout repository (for patches)
uses: actions/checkout@v6
with:
path: repo
- name: Apply patches
run: |
shopt -s nullglob
patches=(repo/patches/*.patch)
if [ ${#patches[@]} -eq 0 ]; then
echo "No patches found, skipping."
else
for patch in "${patches[@]}"; do
echo "Applying $patch"
git -C node-src apply --verbose "../${patch}"
done
fi
- name: Configure
working-directory: node-src
env:
# Point the build system at the Xcode 16 toolchain explicitly
CC: /Applications/Xcode_16.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
CXX: /Applications/Xcode_16.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++
run: |
# --prefix: install root inside the staging directory
# --openssl-use-def-ca-store: use the macOS system Keychain for CA certificates instead of the bundled Mozilla store
# --ninja: use Ninja instead of Make for faster incremental builds
# --experimental-enable-pointer-compression: compress V8 heap pointers from 8 to 4 bytes, reducing heap size
# --experimental-pointer-compression-shared-cage: share the pointer compression cage across Isolates in the same process
# --without-siphash: disable SipHash for V8 hash seeds, using a simpler alternative
./configure \
--prefix=/usr/local \
--openssl-use-def-ca-store \
--ninja \
--experimental-enable-pointer-compression \
--experimental-pointer-compression-shared-cage \
--without-siphash
- name: Build
working-directory: node-src
env:
CC: /Applications/Xcode_16.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
CXX: /Applications/Xcode_16.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++
run: make -j4
- name: Smoke test
working-directory: node-src
run: ./out/Release/node --version
- name: Install into staging directory
working-directory: node-src
run: make install DESTDIR=$PWD/../stage
- name: Smoke test npm
working-directory: node-src
run: ../stage/usr/local/bin/npm --version
- name: Package tarball (full install tree)
run: |
ARCHIVE="node-${{ env.NODE_TAG }}-darwin-arm64.tar.gz"
# tar up everything make install placed under stage/usr/local
tar czf "$ARCHIVE" -C stage/usr/local .
echo "Archive size: $(du -sh $ARCHIVE | cut -f1)"
echo "Top-level entries:"
tar tzf "$ARCHIVE" | cut -d/ -f1-2 | sort -u | head -20
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: node-${{ env.NODE_TAG }}-darwin-arm64
path: node-${{ env.NODE_TAG }}-darwin-arm64.tar.gz
retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }}
# ─────────────────────────────────────────────
# Windows x64
# ─────────────────────────────────────────────
build-windows-x64:
if: inputs.windows-x64
runs-on: windows-2025
steps:
- name: Install NASM (required by OpenSSL)
run: choco install nasm -y
- name: Clone Node.js ${{ env.NODE_TAG }}
run: |
git clone --depth 1 --branch "$env:NODE_TAG" `
https://github.com/nodejs/node.git node-src
- name: Checkout repository (for patches)
uses: actions/checkout@v6
with:
path: repo
- name: Apply patches
shell: pwsh
run: |
$patches = Get-ChildItem -Path repo\patches -Filter *.patch -ErrorAction SilentlyContinue
if (-not $patches) {
Write-Host "No patches found, skipping."
} else {
foreach ($patch in $patches) {
Write-Host "Applying $($patch.Name)"
git -C node-src apply --verbose "$($patch.FullName)"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
}
- name: Build (vcbuild.bat handles everything on Windows)
working-directory: node-src
shell: cmd
run: vcbuild.bat release x64 no-cctest
- name: Copy npm into Release
working-directory: node-src
shell: cmd
# vcbuild does not copy npm automatically — run the npm target explicitly
run: vcbuild.bat release x64 no-cctest npm-only
- name: List Release directory
working-directory: node-src
shell: pwsh
run: Get-ChildItem Release | Select-Object Name, Length
- name: Smoke test
working-directory: node-src
shell: cmd
run: .\Release\node.exe --version
- name: Smoke test npm
working-directory: node-src
shell: cmd
run: .\Release\npm.cmd --version
- name: Package zip (full install tree)
working-directory: node-src
shell: pwsh
run: |
$dest = "node-${{ env.NODE_TAG }}-win-x64"
New-Item -ItemType Directory -Path $dest | Out-Null
# vcbuild places the full installable layout directly in Release\
# This mirrors what the official MSI installs: node.exe, npm/npx
# launchers, and the entire node_modules tree
Copy-Item Release\* $dest\ -Recurse -Force `
-Exclude "*.pdb","*.ilk","*.exp","*.lib","obj","tools"
Compress-Archive -Path $dest -DestinationPath "..\$dest.zip"
Write-Host "Archive size: $([math]::Round((Get-Item "..\$dest.zip").Length/1MB, 1)) MB"
Write-Host "Top-level entries:"
Get-ChildItem $dest | Select-Object -ExpandProperty Name
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: node-${{ env.NODE_TAG }}-win-x64
path: node-${{ env.NODE_TAG }}-win-x64.zip
retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }}
# ─────────────────────────────────────────────
# Create GitHub Release and attach all binaries
# ─────────────────────────────────────────────
release:
needs:
- build-linux-x64
- build-linux-arm64
- build-macos-x64
- build-macos-arm64
- build-windows-x64
runs-on: ubuntu-latest
# Run as long as at least one build job succeeded; resolve-version alone is not enough
if: >-
always() && (
needs.build-linux-x64.result == 'success' ||
needs.build-linux-arm64.result == 'success' ||
needs.build-macos-x64.result == 'success' ||
needs.build-macos-arm64.result == 'success' ||
needs.build-windows-x64.result == 'success'
)
permissions:
contents: write # required to create releases and upload assets
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Download all build artifacts
uses: actions/download-artifact@v4
with:
path: artifacts # each artifact lands in artifacts/<artifact-name>/
- name: Flatten artifacts into release-assets/
run: |
mkdir release-assets
find artifacts -type f \( -name "*.tar.gz" -o -name "*.zip" \) \
-exec mv {} release-assets/ \;
ls -lh release-assets/
- name: Generate SHA256 checksums
working-directory: release-assets
run: |
sha256sum * > SHASUMS256.txt
cat SHASUMS256.txt
- name: Create GitHub Release and upload assets
run: |
if gh release view "$NODE_TAG" --repo "$GITHUB_REPOSITORY" &>/dev/null; then
echo "Release $NODE_TAG already exists — updating assets."
# Delete any existing assets that share a name with what we're about to upload
for file in release-assets/*; do
asset_name=$(basename "$file")
if gh release view "$NODE_TAG" --repo "$GITHUB_REPOSITORY" --json assets \
--jq ".assets[].name" | grep -qx "$asset_name"; then
echo "Deleting existing asset: $asset_name"
gh release delete-asset "$NODE_TAG" "$asset_name" \
--repo "$GITHUB_REPOSITORY" --yes
fi
done
# Upload the fresh assets
gh release upload "$NODE_TAG" release-assets/* \
--repo "$GITHUB_REPOSITORY"
else
echo "Creating new release $NODE_TAG."
gh release create "$NODE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "Node.js ${NODE_TAG} custom build" \
--notes "Custom build of Node.js ${NODE_TAG} from source. Verify downloads with \`SHASUMS256.txt\`." \
release-assets/*
fi