Build libnode #33
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 | |
| fetch v8 | |
| cd v8 | |
| git checkout branch-heads/${{ steps.v8ver.outputs.v8_branch }} | |
| gclient sync --with_branch_heads --no-history | |
| - name: Build V8 monolith | |
| shell: cmd | |
| run: | | |
| cd v8-src\v8 | |
| gn gen out\release --args="is_debug=false target_cpu=\"${{ matrix.arch }}\" is_clang=true is_component_build=false v8_monolithic=true v8_static_library=true v8_use_external_startup_data=false v8_enable_i18n_support=false v8_enable_sandbox=false use_custom_libcxx=false treat_warnings_as_errors=false symbol_level=0" | |
| ninja -C out\release v8_monolith | |
| - 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 monolith library | |
| Copy-Item "v8-src/v8/out/release/obj/v8_monolith.lib" "$outDir/lib/" | |
| # Copy V8 headers | |
| 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 |