Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/actions/build-shared/action.yml
Original file line number Diff line number Diff line change
@@ -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 <nixpkgs> {}).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"]'' \' || '\' }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would need to be moved to inputs.extra-nix-args

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured only extras (not shared between all uses of the composite action) would go through those inputs.

${{ 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"
102 changes: 65 additions & 37 deletions .github/workflows/test-shared.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 }}
Expand Down Expand Up @@ -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
Comment on lines +201 to +204
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to wait for the build job to finish, otherwise we'd have to run 5 times the same V8 version

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok.

Copy link
Copy Markdown
Member Author

@panva panva Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though it increases the total time waiting for the workflow since we even have to wait for the slow build ones. wdyt?

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 <nixpkgs> {}).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; }"
76 changes: 76 additions & 0 deletions tools/nix/collect-openssl-matrix.sh
Original file line number Diff line number Diff line change
@@ -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 \
Comment on lines +46 to +53
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to have a manually curated list (by manually, I mean: the bot would open PRs to update the list every Sunday)? The logic is quite hard to grasp, and it's a lot of repetitive work that does not scale well if we wanted to add e.g. non-OpenSSL variants.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uff, there's an idea, nixpgs actually has BoringSSL too. I'd do this in a follow-up.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it'd have to be the same job that does nixpkgs-unstable updates, else we'd run the risk of the two being open at the same time and not being in sync between their merges.

| 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) }
]'
Loading