diff --git a/.github/actions/build-shared/action.yml b/.github/actions/build-shared/action.yml new file mode 100644 index 00000000000000..6ad127da461967 --- /dev/null +++ b/.github/actions/build-shared/action.yml @@ -0,0 +1,72 @@ +name: Build Node.js (shared libraries) +description: > + Downloads the slim tarball built by the `build-tarball` job, extracts it, + installs Nix (+ cachix + sccache), then builds Node.js and runs the CI + test suite inside the pinned nix-shell. + +inputs: + system: + description: System label (e.g. x86_64-linux, aarch64-darwin). + required: true + extra-nix-args: + description: Additional arguments appended to the nix-shell invocation. + required: false + default: '' + cachix-auth-token: + description: Cachix auth token for nodejs.cachix.org. + required: false + default: '' + +runs: + using: composite + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + if: ${{ github.event_name != 'workflow_dispatch' }} + with: + name: tarballs + path: tarballs + + - name: Extract tarball + if: ${{ github.event_name != 'workflow_dispatch' }} + shell: bash + run: | + tar xzf tarballs/*.tar.gz -C "$RUNNER_TEMP" + echo "TAR_DIR=$RUNNER_TEMP/$(basename tarballs/*.tar.gz .tar.gz)" >> "$GITHUB_ENV" + + - uses: cachix/install-nix-action@96951a368ba55167b55f1c916f7d416bac6505fe # v31.10.3 + with: + extra_nix_config: sandbox = true + + - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 + with: + name: nodejs + authToken: ${{ inputs.cachix-auth-token }} + + - name: Configure sccache + if: github.base_ref == 'main' || github.ref_name == 'main' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + core.exportVariable('SCCACHE_GHA_ENABLED', 'on'); + core.exportVariable('ACTIONS_CACHE_SERVICE_V2', 'on'); + core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + core.exportVariable('NIX_SCCACHE', '(import {}).sccache'); + + - name: Build Node.js and run tests + shell: bash + run: | + nix-shell \ + -I "nixpkgs=$TAR_DIR/tools/nix/pkgs.nix" \ + --pure --keep TAR_DIR --keep FLAKY_TESTS \ + --keep SCCACHE_GHA_ENABLED --keep ACTIONS_CACHE_SERVICE_V2 --keep ACTIONS_RESULTS_URL --keep ACTIONS_RUNTIME_TOKEN \ + --arg loadJSBuiltinsDynamically false \ + --arg useSeparateDerivationForV8 true \ + --arg ccache "${NIX_SCCACHE:-null}" \ + --arg devTools '[]' \ + --arg benchmarkTools '[]' \ + ${{ endsWith(inputs.system, '-darwin') && '--arg withAmaro false --arg withLief false --arg withSQLite false --arg withFFI false --arg extraConfigFlags ''["--without-inspector" "--without-node-options"]'' \' || '\' }} + ${{ inputs.extra-nix-args }} \ + --run ' + make -C "$TAR_DIR" run-ci -j4 V=1 TEST_CI_ARGS="-p actions --measure-flakiness 9 --skip-tests=$CI_SKIP_TESTS" + ' "$TAR_DIR/shell.nix" diff --git a/.github/workflows/test-shared.yml b/.github/workflows/test-shared.yml index c746b2d75e9fde..0fbf0e8fc8b02e 100644 --- a/.github/workflows/test-shared.yml +++ b/.github/workflows/test-shared.yml @@ -47,6 +47,7 @@ on: - vcbuild.bat - .** - '!.github/workflows/test-shared.yml' + - '!.github/actions/build-shared/**' types: [opened, synchronize, reopened, ready_for_review] push: branches: @@ -97,6 +98,7 @@ on: - vcbuild.bat - .** - '!.github/workflows/test-shared.yml' + - '!.github/actions/build-shared/**' concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -153,50 +155,76 @@ jobs: name: '${{ matrix.system }}: with shared libraries' runs-on: ${{ matrix.runner }} steps: - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 if: ${{ github.event_name != 'workflow_dispatch' }} with: - name: tarballs - path: tarballs - - - name: Extract tarball + persist-credentials: false + sparse-checkout: .github/actions + - uses: ./.github/actions/build-shared if: ${{ github.event_name != 'workflow_dispatch' }} - run: | - tar xzf tarballs/*.tar.gz -C "$RUNNER_TEMP" - echo "TAR_DIR=$RUNNER_TEMP/$(basename tarballs/*.tar.gz .tar.gz)" >> "$GITHUB_ENV" + with: + system: ${{ matrix.system }} + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + # Builds the matrix for the `build-openssl` job. The logic lives in + # tools/nix/collect-openssl-matrix.sh. + # Output shape: + # [{ "version": "3.6.1", "attr": "openssl_3_6", "continue-on-error": false }, ...] + collect-openssl-versions: + if: github.event.pull_request.draft == false + runs-on: ubuntu-slim + outputs: + matrix: ${{ steps.query.outputs.matrix }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: tools/nix + sparse-checkout-cone-mode: false - uses: cachix/install-nix-action@96951a368ba55167b55f1c916f7d416bac6505fe # v31.10.3 with: extra_nix_config: sandbox = true + - id: query + env: + # Latest OpenSSL release we support running tests with. Anything + # newer runs with continue-on-error in `build-openssl`. + SUPPORTED_OPENSSL_VERSION: '4.0' + run: | + matrix=$(./tools/nix/collect-openssl-matrix.sh) + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" - - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 + # Builds and tests Node.js with shared libraries against every supported + # OpenSSL release version available in the repo-pinned nixpkgs. The default + # shared `openssl` from tools/nix/sharedLibDeps.nix is overridden per matrix + # entry, while all other shared libs remain at their defaults. Only runs on + # a single runner/system (aarch64-linux) to keep the matrix to a minimum. + build-openssl: + needs: + - build-tarball + - collect-openssl-versions + strategy: + fail-fast: false + matrix: + openssl: ${{ fromJSON(needs.collect-openssl-versions.outputs.matrix) }} + name: 'aarch64-linux: with shared ${{ matrix.openssl.attr }} (${{ matrix.openssl.version }})' + runs-on: ubuntu-24.04-arm + continue-on-error: ${{ matrix.openssl['continue-on-error'] }} + env: + OPENSSL_ATTR: ${{ matrix.openssl.attr }} + OPENSSL_VERSION: ${{ matrix.openssl.version }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - name: nodejs - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - - - name: Configure sccache - if: github.base_ref == 'main' || github.ref_name == 'main' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + persist-credentials: false + sparse-checkout: .github/actions + - uses: ./.github/actions/build-shared with: - script: | - core.exportVariable('SCCACHE_GHA_ENABLED', 'on'); - core.exportVariable('ACTIONS_CACHE_SERVICE_V2', 'on'); - core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - core.exportVariable('NIX_SCCACHE', '(import {}).sccache'); - - - name: Build Node.js and run tests - run: | - nix-shell \ - -I "nixpkgs=$TAR_DIR/tools/nix/pkgs.nix" \ - --pure --keep TAR_DIR --keep FLAKY_TESTS \ - --keep SCCACHE_GHA_ENABLED --keep ACTIONS_CACHE_SERVICE_V2 --keep ACTIONS_RESULTS_URL --keep ACTIONS_RUNTIME_TOKEN \ - --arg loadJSBuiltinsDynamically false \ - --arg useSeparateDerivationForV8 true \ - --arg ccache "${NIX_SCCACHE:-null}" \ - --arg devTools '[]' \ - --arg benchmarkTools '[]' \ - ${{ endsWith(matrix.system, '-darwin') && '--arg withAmaro false --arg withLief false --arg withSQLite false --arg withFFI false --arg extraConfigFlags ''["--without-inspector" "--without-node-options"]'' \' || '\' }} - --run ' - make -C "$TAR_DIR" run-ci -j4 V=1 TEST_CI_ARGS="-p actions --measure-flakiness 9 --skip-tests=$CI_SKIP_TESTS" - ' "$TAR_DIR/shell.nix" + system: aarch64-linux + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + # Override just the `openssl` attr of the default shared-lib set with + # the matrix-selected nixpkgs attribute (e.g. `openssl_3_6`). All + # other shared libs (brotli, cares, libuv, …) keep their defaults. + # `permittedInsecurePackages` whitelists just the matrix-selected + # release (e.g. `openssl-1.1.1w`) so EOL-with-extended-support + # cycles evaluate without relaxing nixpkgs' meta check globally. + extra-nix-args: --arg sharedLibDeps "(import $TAR_DIR/tools/nix/sharedLibDeps.nix {}) // { openssl = (import $TAR_DIR/tools/nix/pkgs.nix { config.permittedInsecurePackages = [ \"openssl-$OPENSSL_VERSION\" ]; }).$OPENSSL_ATTR; }" diff --git a/tools/nix/collect-openssl-matrix.sh b/tools/nix/collect-openssl-matrix.sh new file mode 100755 index 00000000000000..bb2d4ae334b164 --- /dev/null +++ b/tools/nix/collect-openssl-matrix.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# +# Emits a JSON matrix of OpenSSL releases to test Node.js against with +# shared libraries, consumed by the `build-openssl` job in +# .github/workflows/test-shared.yml. +# +# Inputs (env): +# SUPPORTED_OPENSSL_VERSION Latest OpenSSL release we support running +# tests with. Anything newer is emitted with +# "continue-on-error": true. +# +# Output (stdout): a JSON array with shape +# [{ "version": "3.6.1", "attr": "openssl_3_6", "continue-on-error": false }, ...] +# +# Usage: SUPPORTED_OPENSSL_VERSION=4.0 ./tools/nix/collect-openssl-matrix.sh + +set -eu + +: "${SUPPORTED_OPENSSL_VERSION:?SUPPORTED_OPENSSL_VERSION must be set}" + +here=$(cd -- "$(dirname -- "$0")" && pwd) + +# 1. Enumerate every `openssl_N` / `openssl_N_M` attribute exposed by the +# repo-pinned nixpkgs. `tryEval` skips aliases that raise (e.g. +# `openssl_3_0` → renamed to `openssl_3`) so we only keep attributes +# that resolve to a real derivation with a `.version`. +nix_json=$(nix-instantiate --eval --strict --json -E " + let + pkgs = import $here/pkgs.nix {}; + names = builtins.filter + (n: builtins.match \"openssl_[0-9]+(_[0-9]+)?\" n != null) + (builtins.attrNames pkgs); + safe = builtins.filter (n: + let t = builtins.tryEval pkgs.\${n}; in + t.success && (builtins.tryEval t.value.version).success) names; + in map (n: { attr = n; version = pkgs.\${n}.version; }) safe +") + +# 2. Resolve the OpenSSL version the `build` job already covers (the default +# from sharedLibDeps.nix) so we can drop it from the matrix to avoid +# duplicate coverage. +default_openssl_version=$(nix-instantiate --eval --strict --json -E " + (import $here/sharedLibDeps.nix {}).openssl.version +" | jq -r .) + +# 3. Fetch OpenSSL release versions from endoflife.date, keep entries that +# are either not past EOL or still under extended support, then pick the +# first nix attr whose `.version` starts with the release version +# followed by `.` / letter / end-of-string (so "3.6" matches "3.6.1", +# "1.1.1" matches "1.1.1w", and "1.1" does NOT swallow "1.1.1"). +# Releases without a matching nix attr and the one covered by default in +# `build` are dropped. +curl -sf https://endoflife.date/api/openssl.json \ + | jq -c \ + --argjson nix "$nix_json" \ + --arg supported "$SUPPORTED_OPENSSL_VERSION" \ + --arg default_version "$default_openssl_version" ' + (now | strftime("%Y-%m-%d")) as $today | + # Compare two dotted version strings as arrays of numbers + # (e.g. "4.1" > "4.0" => true, "4.0" > "4.0" => false). + def gt($a; $b): + ([$a, $b] | map(split(".") | map(tonumber))) as [$x, $y] + | ($x | length) as $n | ($y | length) as $m + | [range(0; if $n > $m then $n else $m end) + | ((($x[.]) // 0) - (($y[.]) // 0))] + | map(select(. != 0)) | (.[0] // 0) > 0; + [ .[] + | select(.eol == false or .eol > $today or .extendedSupport == true) + | .cycle as $v + | ($nix + | map(select(.version | test("^" + ($v | gsub("\\."; "\\.")) + "([.a-z]|$)"))) + | first) as $m + | select($m != null) + | select($m.version != $default_version) + | { version: $m.version, attr: $m.attr, "continue-on-error": gt($v; $supported) } + ]'