Skip to content

Commit 93605ab

Browse files
richardlaumcollina
authored andcommitted
fix: restore externalized Node.js dep compatibility (#3421)
* fix: restore externalized Node.js dep compatibility Restore the ability to build Undici compatible with Node.js' `configure --shared-builtin-undici/undici-path ...` build option. Scopes the `hasApk` conditional to only cover the part that requires `apk`. Makes the WASM optimizer (binaryen) optional to allow building on Linux distributions that do not package `binaryen` and must be able to rebuild everything (including tooling) from source. * ci: add workflow for externalized Node.js dep Add a workflow to test building Undici in a way compatible with Node.js built with `configure --shared-builtin-undici/undici-path ...`. This configuration is used by downstream Node.js packagers (e.g. Fedora) who require the ability to be able to build everything from source.
1 parent 62241c3 commit 93605ab

File tree

3 files changed

+138
-15
lines changed

3 files changed

+138
-15
lines changed

.github/workflows/nodejs-shared.yml

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: Node.js compiled --shared-builtin-undici/undici-path CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- current
8+
- next
9+
- 'v*'
10+
pull_request:
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
test-shared-builtin:
17+
name: Test with Node.js ${{ matrix.version }} compiled --shared-builtin-undici/undici-path
18+
strategy:
19+
fail-fast: false
20+
max-parallel: 0
21+
matrix:
22+
version: [20, 22]
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 120
25+
steps:
26+
# Checkout into a subdirectory otherwise Node.js tests will break due to finding Undici's package.json in a parent directory.
27+
- name: Checkout
28+
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
29+
with:
30+
path: ./undici
31+
persist-credentials: false
32+
33+
# Setup node, install deps, and build undici prior to building node with `--shared-builtin-undici/undici-path` and testing
34+
- name: Setup Node.js@${{ inputs.version }}
35+
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
36+
with:
37+
node-version: ${{ inputs.version }}
38+
39+
- name: Install dependencies
40+
working-directory: ./undici
41+
run: npm install
42+
43+
- name: Install wasi-libc
44+
run: sudo apt-get install -y wasi-libc
45+
46+
- name: Build WASM
47+
working-directory: ./undici
48+
run: |
49+
export EXTERNAL_PATH=${{ github.workspace }}/undici
50+
export WASM_CC=clang
51+
export WASM_CFLAGS='--target=wasm32-wasi --sysroot=/usr'
52+
export WASM_LDFLAGS='-nodefaultlibs'
53+
export WASM_LDLIBS='-lc'
54+
node build/wasm.js
55+
56+
- name: Determine latest release
57+
id: release
58+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
59+
with:
60+
result-encoding: string
61+
script: |
62+
const req = await fetch('https://nodejs.org/download/release/index.json')
63+
const releases = await req.json()
64+
65+
const latest = releases.find((r) => r.version.startsWith('v${{ matrix.version }}'))
66+
return latest.version
67+
68+
- name: Download and extract source
69+
run: curl https://nodejs.org/download/release/${{ steps.release.outputs.result }}/node-${{ steps.release.outputs.result }}.tar.xz | tar xfJ -
70+
71+
- name: Install ninja
72+
run: sudo apt-get install ninja-build
73+
74+
- name: ccache
75+
uses: hendrikmuhs/ccache-action@c92f40bee50034e84c763e33b317c77adaa81c92 #v1.2.13
76+
with:
77+
key: node(external_undici)${{ matrix.version }}
78+
79+
- name: Build node
80+
working-directory: ./node-${{ steps.release.outputs.result }}
81+
run: |
82+
export CC="ccache gcc"
83+
export CXX="ccache g++"
84+
rm -rf deps/undici
85+
./configure --shared-builtin-undici/undici-path ${{ github.workspace }}/undici/loader.js --ninja --prefix=./final
86+
make
87+
make install
88+
echo "$(pwd)/final/bin" >> $GITHUB_PATH
89+
90+
- name: Print version information
91+
run: |
92+
echo OS: $(node -p "os.version()")
93+
echo Node.js: $(node --version)
94+
echo npm: $(npm --version)
95+
echo git: $(git --version)
96+
echo external config: $(node -e "console.log(process.config)" | grep NODE_SHARED_BUILTIN_UNDICI_UNDICI_PATH)
97+
echo Node.js built-in undici version: $(node -p "process.versions.undici") # undefined for external Undici
98+
99+
- name: Run tests
100+
working-directory: ./node-${{ steps.release.outputs.result }}
101+
run: tools/test.py -p dots --flaky-tests=dontcare
102+

CONTRIBUTING.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ If you are packaging `undici` for a distro, this might help if you would like to
157157
an unbundled version instead of bundling one in `libnode.so`.
158158

159159
To enable this, pass `EXTERNAL_PATH=/path/to/global/node_modules/undici` to `build/wasm.js`.
160-
You shall also pass this path to `--shared-builtin-undici/undici-path` in Node.js's `configure.py`.
160+
Pass this path with `loader.js` appended to `--shared-builtin-undici/undici-path` in Node.js's `configure.py`.
161+
If building on a non-Alpine Linux distribution, you may need to also set the `WASM_CC`, `WASM_CFLAGS`, `WASM_LDFLAGS` and `WASM_LDLIBS` environment variables before running `build/wasm.js`.
161162

162163
<a id="benchmarks"></a>
163164
### Benchmarks

build/wasm.js

+34-14
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ let WASM_CFLAGS = process.env.WASM_CFLAGS || '--sysroot=/usr/share/wasi-sysroot
1515
let WASM_LDFLAGS = process.env.WASM_LDFLAGS || ''
1616
const WASM_LDLIBS = process.env.WASM_LDLIBS || ''
1717

18+
// For compatibility with Node.js' `configure --shared-builtin-undici/undici-path ...`
19+
const EXTERNAL_PATH = process.env.EXTERNAL_PATH
20+
1821
// These are relevant for undici and should not be overridden
1922
WASM_CFLAGS += ' -Ofast -fno-exceptions -fvisibility=hidden -mexec-model=reactor'
2023
WASM_LDFLAGS += ' -Wl,-error-limit=0 -Wl,-O3 -Wl,--lto-O3 -Wl,--strip-all'
@@ -73,6 +76,9 @@ if (process.argv[2] === '--docker') {
7376
const hasApk = (function () {
7477
try { execSync('command -v apk'); return true } catch (error) { return false }
7578
})()
79+
const hasOptimizer = (function () {
80+
try { execSync('./wasm-opt --version'); return true } catch (error) { return false }
81+
})()
7682
if (hasApk) {
7783
// Gather information about the tools used for the build
7884
const buildInfo = execSync('apk info -v').toString()
@@ -81,24 +87,38 @@ if (hasApk) {
8187
process.exit(-1)
8288
}
8389
console.log(buildInfo)
90+
}
8491

85-
// Build wasm binary
86-
execSync(`${WASM_CC} ${WASM_CFLAGS} ${WASM_LDFLAGS} \
87-
${join(WASM_SRC, 'src')}/*.c \
88-
-I${join(WASM_SRC, 'include')} \
89-
-o ${join(WASM_OUT, 'llhttp.wasm')} \
90-
${WASM_LDLIBS}`, { stdio: 'inherit' })
92+
// Build wasm binary
93+
execSync(`${WASM_CC} ${WASM_CFLAGS} ${WASM_LDFLAGS} \
94+
${join(WASM_SRC, 'src')}/*.c \
95+
-I${join(WASM_SRC, 'include')} \
96+
-o ${join(WASM_OUT, 'llhttp.wasm')} \
97+
${WASM_LDLIBS}`, { stdio: 'inherit' })
9198

99+
if (hasOptimizer) {
92100
execSync(`./wasm-opt ${WASM_OPT_FLAGS} -o ${join(WASM_OUT, 'llhttp.wasm')} ${join(WASM_OUT, 'llhttp.wasm')}`, { stdio: 'inherit' })
93-
writeWasmChunk('llhttp.wasm', 'llhttp-wasm.js')
101+
}
102+
writeWasmChunk('llhttp.wasm', 'llhttp-wasm.js')
94103

95-
// Build wasm simd binary
96-
execSync(`${WASM_CC} ${WASM_CFLAGS} -msimd128 ${WASM_LDFLAGS} \
97-
${join(WASM_SRC, 'src')}/*.c \
98-
-I${join(WASM_SRC, 'include')} \
99-
-o ${join(WASM_OUT, 'llhttp_simd.wasm')} \
100-
${WASM_LDLIBS}`, { stdio: 'inherit' })
104+
// Build wasm simd binary
105+
execSync(`${WASM_CC} ${WASM_CFLAGS} -msimd128 ${WASM_LDFLAGS} \
106+
${join(WASM_SRC, 'src')}/*.c \
107+
-I${join(WASM_SRC, 'include')} \
108+
-o ${join(WASM_OUT, 'llhttp_simd.wasm')} \
109+
${WASM_LDLIBS}`, { stdio: 'inherit' })
101110

111+
if (hasOptimizer) {
102112
execSync(`./wasm-opt ${WASM_OPT_FLAGS} --enable-simd -o ${join(WASM_OUT, 'llhttp_simd.wasm')} ${join(WASM_OUT, 'llhttp_simd.wasm')}`, { stdio: 'inherit' })
103-
writeWasmChunk('llhttp_simd.wasm', 'llhttp_simd-wasm.js')
113+
}
114+
writeWasmChunk('llhttp_simd.wasm', 'llhttp_simd-wasm.js')
115+
116+
// For compatibility with Node.js' `configure --shared-builtin-undici/undici-path ...`
117+
if (EXTERNAL_PATH) {
118+
writeFileSync(join(ROOT, 'loader.js'), `
119+
'use strict'
120+
globalThis.__UNDICI_IS_NODE__ = true
121+
module.exports = require('node:module').createRequire('${EXTERNAL_PATH}/loader.js')('./index-fetch.js')
122+
delete globalThis.__UNDICI_IS_NODE__
123+
`)
104124
}

0 commit comments

Comments
 (0)