Patch V8 build to use dynamic CRT (/MD) instead of static (/MT) #43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |