Skip to content

Patch V8 build to use dynamic CRT (/MD) instead of static (/MT) #43

Patch V8 build to use dynamic CRT (/MD) instead of static (/MT)

Patch V8 build to use dynamic CRT (/MD) instead of static (/MT) #43

Workflow file for this run

name: Build libnode
on:
push:
branches: [main]
workflow_dispatch:
inputs:
node_version:
description: 'Node.js version (e.g., 22.13.0). Leave empty for latest LTS.'
required: false
default: ''
force_rebuild:
description: 'Force rebuild even if release exists'
required: false
default: 'false'
type: choice
options:
- 'false'
- 'true'
include_x86:
description: 'Include Windows x86 build (not supported on Node >= 25)'
required: false
default: 'false'
type: choice
options:
- 'false'
- 'true'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NODE_LTS_MAJOR: 22
jobs:
resolve-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.resolve.outputs.version }}
skip_build: ${{ steps.decide.outputs.skip_build }}
steps:
- name: Resolve Node.js version
id: resolve
run: |
if [ -n "${{ github.event.inputs.node_version }}" ]; then
VERSION="${{ github.event.inputs.node_version }}"
else
VERSION=$(curl -s https://nodejs.org/dist/index.json | jq -r '[.[] | select(.lts != false) | select(.version | startswith("v${{ env.NODE_LTS_MAJOR }}"))][0].version' | sed 's/^v//')
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Resolved Node.js version: $VERSION"
- name: Check if release already exists
id: check_release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ steps.resolve.outputs.version }}"
TAG="libnode-v${VERSION}"
if gh release view "$TAG" --repo ${{ github.repository }} > /dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "::notice::Release $TAG already exists"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "::notice::Release $TAG does not exist, will build"
fi
- name: Decide what to build
id: decide
run: |
FORCE="${{ github.event.inputs.force_rebuild }}"
EXISTS="${{ steps.check_release.outputs.exists }}"
if [ "$EXISTS" == "true" ] && [ "$FORCE" != "true" ]; then
echo "skip_build=true" >> $GITHUB_OUTPUT
echo "::warning::Skipping build - release already exists"
else
echo "skip_build=false" >> $GITHUB_OUTPUT
fi
build-windows:
needs: resolve-version
if: needs.resolve-version.outputs.skip_build != 'true'
runs-on: windows-latest
strategy:
matrix:
arch:
- x64
include: ${{ github.event.inputs.include_x86 == 'true' && fromJson('[{"arch":"x86"}]') || fromJson('[]') }}
steps:
- name: Free disk space
shell: pwsh
run: |
$dirs = @(
"C:\hostedtoolcache\CodeQL",
"C:\hostedtoolcache\Java_Temurin-Hotspot_jdk",
"C:\hostedtoolcache\Ruby",
"C:\hostedtoolcache\go",
"C:\mingw64",
"C:\Program Files\dotnet\sdk",
"C:\Program Files\dotnet\shared"
)
foreach ($d in $dirs) {
if (Test-Path $d) { Remove-Item -Recurse -Force $d -ErrorAction SilentlyContinue }
}
Get-PSDrive C | Select-Object Used,Free
- name: Install NASM
shell: pwsh
run: |
choco install nasm --no-progress -y --timeout 300
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
- name: Clone Node.js
run: |
git clone --depth 1 --branch v${{ needs.resolve-version.outputs.version }} https://github.com/nodejs/node.git node-src
- name: Build Node.js (shared library)
shell: cmd
run: |
cd node-src
set NUMBER_OF_PROCESSORS=2
call vcbuild.bat release dll ${{ matrix.arch }} full-icu
- name: Prepare artifacts
shell: pwsh
run: |
$version = "${{ needs.resolve-version.outputs.version }}"
$arch = "${{ matrix.arch }}"
$outDir = "libnode-v$version-win-$arch"
New-Item -ItemType Directory -Force -Path "$outDir/lib"
New-Item -ItemType Directory -Force -Path "$outDir/include"
New-Item -ItemType Directory -Force -Path "$outDir/bin"
# Copy libraries
Copy-Item "node-src/out/Release/libnode.dll" "$outDir/lib/"
Copy-Item "node-src/out/Release/libnode.lib" "$outDir/lib/"
Copy-Item "node-src/out/Release/lib/libuv.lib" "$outDir/lib/"
Copy-Item "node-src/out/Release/lib/v8_libbase.lib" "$outDir/lib/"
Copy-Item "node-src/out/Release/lib/v8_libplatform.lib" "$outDir/lib/"
# Copy abseil library (required by v8_libbase in V8 13+/Node.js 25+)
if (Test-Path "node-src/out/Release/lib/abseil.lib") {
Copy-Item "node-src/out/Release/lib/abseil.lib" "$outDir/lib/"
}
# Copy headers (top-level + all subdirectories containing .h files)
Copy-Item -Recurse "node-src/src/*.h" "$outDir/include/"
Get-ChildItem -Path "node-src/src" -Directory | ForEach-Object {
$hFiles = Get-ChildItem -Path $_.FullName -Filter "*.h" -ErrorAction SilentlyContinue
if ($hFiles) {
$subdir = $_.Name
New-Item -ItemType Directory -Force -Path "$outDir/include/$subdir" | Out-Null
Copy-Item "$($_.FullName)/*.h" "$outDir/include/$subdir/"
}
}
Copy-Item -Recurse "node-src/deps/v8/include/*" "$outDir/include/"
Copy-Item -Recurse "node-src/deps/uv/include/*" "$outDir/include/"
# Copy node executable
Copy-Item "node-src/out/Release/node.exe" "$outDir/bin/"
# Create zip
Compress-Archive -Path "$outDir" -DestinationPath "libnode-v$version-win-$arch.zip"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: libnode-win-${{ matrix.arch }}
path: libnode-v${{ needs.resolve-version.outputs.version }}-win-${{ matrix.arch }}.zip
build-macos:
needs: resolve-version
if: needs.resolve-version.outputs.skip_build != 'true'
runs-on: macos-15
steps:
- name: Clone Node.js
run: |
git clone --depth 1 --branch v${{ needs.resolve-version.outputs.version }} https://github.com/nodejs/node.git node-src
- name: Build Node.js (shared library)
run: |
cd node-src
./configure --shared --with-intl=full-icu
make -j$(sysctl -n hw.ncpu)
- name: Prepare artifacts
run: |
VERSION="${{ needs.resolve-version.outputs.version }}"
OUTDIR="libnode-v$VERSION-darwin-arm64"
mkdir -p "$OUTDIR/lib"
mkdir -p "$OUTDIR/include"
mkdir -p "$OUTDIR/bin"
# Copy libraries (dylib is versioned, e.g., libnode.127.dylib)
cp node-src/out/Release/libnode*.dylib "$OUTDIR/lib/"
# Create unversioned symlink for convenience
cd "$OUTDIR/lib" && ln -sf libnode.*.dylib libnode.dylib && cd -
# Copy headers (top-level + all subdirectories containing .h files)
cp node-src/src/*.h "$OUTDIR/include/"
find node-src/src -mindepth 1 -maxdepth 1 -type d | while IFS= read -r dir; do
if ls "$dir"/*.h &>/dev/null; then
subdir=$(basename "$dir")
mkdir -p "$OUTDIR/include/$subdir"
cp "$dir"/*.h "$OUTDIR/include/$subdir/"
fi
done
cp -R node-src/deps/v8/include/* "$OUTDIR/include/"
cp -R node-src/deps/uv/include/* "$OUTDIR/include/"
# Copy node executable
cp node-src/out/Release/node "$OUTDIR/bin/"
# Create tarball
tar -czvf "libnode-v$VERSION-darwin-arm64.tar.gz" "$OUTDIR"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: libnode-darwin-arm64
path: libnode-v${{ needs.resolve-version.outputs.version }}-darwin-arm64.tar.gz
build-linux:
needs: resolve-version
if: needs.resolve-version.outputs.skip_build != 'true'
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential python3
- name: Clone Node.js
run: |
git clone --depth 1 --branch v${{ needs.resolve-version.outputs.version }} https://github.com/nodejs/node.git node-src
- name: Build Node.js (shared library)
run: |
cd node-src
./configure --shared --with-intl=full-icu
make -j$(nproc)
- name: Prepare artifacts
run: |
VERSION="${{ needs.resolve-version.outputs.version }}"
OUTDIR="libnode-v$VERSION-linux-x64"
mkdir -p "$OUTDIR/lib"
mkdir -p "$OUTDIR/include"
mkdir -p "$OUTDIR/bin"
# Copy shared library
cp node-src/out/Release/lib.target/libnode.so* "$OUTDIR/lib/" || cp node-src/out/Release/libnode.so* "$OUTDIR/lib/"
# Copy static libraries
find node-src/out/Release -name "libuv.a" -exec cp {} "$OUTDIR/lib/" \;
find node-src/out/Release -name "v8_libbase.a" -exec cp {} "$OUTDIR/lib/" \;
find node-src/out/Release -name "v8_libplatform.a" -exec cp {} "$OUTDIR/lib/" \;
# Copy abseil library (required by v8_libbase in V8 13+/Node.js 25+)
find node-src/out/Release -name "libabseil.a" -exec cp {} "$OUTDIR/lib/" \; 2>/dev/null || true
# Copy headers (top-level + all subdirectories containing .h files)
cp node-src/src/*.h "$OUTDIR/include/"
find node-src/src -mindepth 1 -maxdepth 1 -type d | while IFS= read -r dir; do
if ls "$dir"/*.h &>/dev/null; then
subdir=$(basename "$dir")
mkdir -p "$OUTDIR/include/$subdir"
cp "$dir"/*.h "$OUTDIR/include/$subdir/"
fi
done
cp -R node-src/deps/v8/include/* "$OUTDIR/include/"
cp -R node-src/deps/uv/include/* "$OUTDIR/include/"
# Copy node executable
cp node-src/out/Release/node "$OUTDIR/bin/"
# Create tarball
tar -czvf "libnode-v$VERSION-linux-x64.tar.gz" "$OUTDIR"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: libnode-linux-x64
path: libnode-v${{ needs.resolve-version.outputs.version }}-linux-x64.tar.gz
build-v8-windows:
needs: resolve-version
if: needs.resolve-version.outputs.skip_build != 'true'
runs-on: windows-latest
strategy:
matrix:
arch: [x64, x86]
env:
DEPOT_TOOLS_WIN_TOOLCHAIN: 0
steps:
- name: Free disk space
shell: pwsh
run: |
$dirs = @(
"C:\hostedtoolcache\CodeQL",
"C:\hostedtoolcache\Java_Temurin-Hotspot_jdk",
"C:\hostedtoolcache\Ruby",
"C:\hostedtoolcache\go",
"C:\mingw64",
"C:\Program Files\dotnet\sdk",
"C:\Program Files\dotnet\shared"
)
foreach ($d in $dirs) {
if (Test-Path $d) { Remove-Item -Recurse -Force $d -ErrorAction SilentlyContinue }
}
Get-PSDrive C | Select-Object Used,Free
- name: Resolve V8 version from Node.js
id: v8ver
shell: bash
run: |
VERSION="${{ needs.resolve-version.outputs.version }}"
# Fetch v8-version.h from the Node.js tag on GitHub
HEADER=$(curl -s "https://raw.githubusercontent.com/nodejs/node/v${VERSION}/deps/v8/include/v8-version.h")
MAJOR=$(echo "$HEADER" | grep '#define V8_MAJOR_VERSION' | head -1 | awk '{print $3}')
MINOR=$(echo "$HEADER" | grep '#define V8_MINOR_VERSION' | head -1 | awk '{print $3}')
echo "v8_branch=${MAJOR}.${MINOR}" >> $GITHUB_OUTPUT
echo "Resolved V8 branch: ${MAJOR}.${MINOR}"
- name: Setup depot_tools
uses: newkdev/setup-depot-tools@v1.0.1
- name: Fetch V8
shell: cmd
run: |
mkdir v8-src
cd v8-src || exit /b 1
call fetch --no-history v8 || exit /b 1
cd v8 || exit /b 1
call git fetch origin +refs/branch-heads/${{ steps.v8ver.outputs.v8_branch }}:refs/branch-heads/${{ steps.v8ver.outputs.v8_branch }} || exit /b 1
call git checkout refs/branch-heads/${{ steps.v8ver.outputs.v8_branch }} || exit /b 1
cd ..
call gclient sync --no-history || exit /b 1
- name: Patch V8 build to use dynamic CRT (/MD)
shell: pwsh
run: |
$buildGn = "v8-src/v8/build/config/win/BUILD.gn"
(Get-Content $buildGn -Raw) -replace 'configs = \[ ":static_crt" \]', 'configs = [ ":dynamic_crt" ]' | Set-Content $buildGn
Write-Host "Patched $buildGn to use dynamic CRT (/MD)"
- name: Build V8 monolith
shell: cmd
run: |
cd v8-src\v8 || exit /b 1
mkdir out\release
(echo is_debug = false
echo target_cpu = "${{ matrix.arch }}"
echo is_clang = true
echo is_component_build = false
echo v8_monolithic = true
echo v8_static_library = true
echo v8_use_external_startup_data = false
echo v8_enable_i18n_support = false
echo v8_enable_sandbox = false
echo v8_enable_pointer_compression = false
echo use_custom_libcxx = false
echo treat_warnings_as_errors = false
echo symbol_level = 0) > out\release\args.gn
echo === args.gn ===
type out\release\args.gn
echo === gn gen ===
call gn gen out\release || exit /b 1
echo === ninja ===
call ninja -C out\release v8_monolith || exit /b 1
echo === verifying output ===
dir out\release\obj\v8_monolith.lib || exit /b 1
- name: Prepare artifacts
shell: pwsh
run: |
$version = "${{ needs.resolve-version.outputs.version }}"
$arch = "${{ matrix.arch }}"
$outDir = "v8-v$version-win-$arch"
New-Item -ItemType Directory -Force -Path "$outDir/lib"
New-Item -ItemType Directory -Force -Path "$outDir/include"
Copy-Item "v8-src/v8/out/release/obj/v8_monolith.lib" "$outDir/lib/"
Copy-Item -Recurse "v8-src/v8/include/*" "$outDir/include/"
Compress-Archive -Path "$outDir" -DestinationPath "v8-v$version-win-$arch.zip"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: v8-win-${{ matrix.arch }}
path: v8-v${{ needs.resolve-version.outputs.version }}-win-${{ matrix.arch }}.zip
release:
needs: [resolve-version, build-windows, build-macos, build-linux, build-v8-windows]
if: |
always() &&
needs.resolve-version.outputs.skip_build != 'true' &&
needs.build-windows.result == 'success' &&
needs.build-macos.result == 'success' &&
needs.build-linux.result == 'success' &&
needs.build-v8-windows.result == 'success'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download libnode artifacts
uses: actions/download-artifact@v4
with:
pattern: libnode-*
path: artifacts
- name: Download V8 artifacts
uses: actions/download-artifact@v4
with:
pattern: v8-*
path: artifacts
- name: Create Release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.resolve-version.outputs.version }}"
TAG="libnode-v${VERSION}"
FILES=$(find artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \))
# Delete existing release if present (force rebuild case)
gh release delete "$TAG" --repo ${{ github.repository }} --yes 2>/dev/null || true
git push --delete origin "$TAG" 2>/dev/null || true
gh release create "$TAG" \
--repo ${{ github.repository }} \
--title "libnode v${VERSION}" \
--notes "Pre-built Node.js ${VERSION} libraries for embedding in C++ projects.
## libnode (Node.js shared library)
### Windows (x64)
- libnode.dll / libnode.lib
- libuv.lib
- v8_libbase.lib, v8_libplatform.lib, abseil.lib
### macOS (arm64)
- libnode.*.dylib (versioned)
- libnode.dylib (symlink)
### Linux (x64)
- libnode.so
- libuv.a
- v8_libbase.a, v8_libplatform.a, libabseil.a
All libnode archives include Node.js, V8, and libuv headers.
## Standalone V8 (monolithic static library)
### Windows (x64)
- v8_monolith.lib (x64)
### Windows (x86)
- v8_monolith.lib (x86)
V8 archives include V8 headers. Built without ICU for a smaller binary.
## Usage
- **libnode**: Link against libnode for full Node.js runtime embedding (require(), npm modules, async I/O, etc.).
- **Standalone V8**: Link against v8_monolith for lightweight JavaScript execution without Node.js APIs." \
$FILES