1{
2 lib,
3 stdenv,
4 fetchurl,
5 openssl,
6 python,
7 zlib,
8 libuv,
9 sqlite,
10 http-parser,
11 icu,
12 bash,
13 ninja,
14 pkgconf,
15 unixtools,
16 runCommand,
17 buildPackages,
18 testers,
19 # for `.pkgs` attribute
20 callPackage,
21 # Updater dependencies
22 writeScript,
23 coreutils,
24 gnugrep,
25 jq,
26 curl,
27 common-updater-scripts,
28 runtimeShell,
29 gnupg,
30 installShellFiles,
31}:
32
33{
34 enableNpm ? true,
35 version,
36 sha256,
37 patches ? [ ],
38}@args:
39
40let
41
42 majorVersion = lib.versions.major version;
43 minorVersion = lib.versions.minor version;
44
45 pname = if enableNpm then "nodejs" else "nodejs-slim";
46
47 canExecute = stdenv.buildPlatform.canExecute stdenv.hostPlatform;
48 emulator = stdenv.hostPlatform.emulator buildPackages;
49 canEmulate = stdenv.hostPlatform.emulatorAvailable buildPackages;
50 buildNode = buildPackages."${pname}_${majorVersion}";
51
52 # See valid_os and valid_arch in configure.py.
53 destOS =
54 let
55 platform = stdenv.hostPlatform;
56 in
57 if platform.isiOS then
58 "ios"
59 else if platform.isAndroid then
60 "android"
61 else if platform.isWindows then
62 "win"
63 else if platform.isDarwin then
64 "mac"
65 else if platform.isLinux then
66 "linux"
67 else if platform.isOpenBSD then
68 "openbsd"
69 else if platform.isFreeBSD then
70 "freebsd"
71 else
72 throw "unsupported os ${platform.uname.system}";
73 destARMFPU =
74 let
75 platform = stdenv.hostPlatform;
76 in
77 if platform.isAarch32 && platform ? gcc.fpu then
78 lib.throwIfNot (builtins.elem platform.gcc.fpu [
79 "vfp"
80 "vfpv2"
81 "vfpv3"
82 "vfpv3-d16"
83 "neon"
84 ]) "unsupported ARM FPU ${platform.gcc.fpu}" platform.gcc.fpu
85 else
86 null;
87 destARMFloatABI =
88 let
89 platform = stdenv.hostPlatform;
90 in
91 if platform.isAarch32 && platform ? gcc.float-abi then
92 lib.throwIfNot (builtins.elem platform.gcc.float-abi [
93 "soft"
94 "softfp"
95 "hard"
96 ]) "unsupported ARM float ABI ${platform.gcc.float-abi}" platform.gcc.float-abi
97 else
98 null;
99 # TODO: also handle MIPS flags (mips_arch, mips_fpu, mips_float_abi).
100
101 useSharedHttpParser =
102 !stdenv.hostPlatform.isDarwin && lib.versionOlder "${majorVersion}.${minorVersion}" "11.4";
103 useSharedSQLite = lib.versionAtLeast version "22.5";
104
105 sharedLibDeps = {
106 inherit openssl zlib libuv;
107 }
108 // (lib.optionalAttrs useSharedHttpParser {
109 inherit http-parser;
110 })
111 // (lib.optionalAttrs useSharedSQLite {
112 inherit sqlite;
113 });
114
115 copyLibHeaders = map (name: "${lib.getDev sharedLibDeps.${name}}/include/*") (
116 builtins.attrNames sharedLibDeps
117 );
118
119 # Currently stdenv sets CC/LD/AR/etc environment variables to program names
120 # instead of absolute paths. If we add cctools to nativeBuildInputs, that
121 # would shadow stdenv’s bintools and potentially break other parts of the
122 # build. The correct behavior is to use absolute paths, and there is a PR for
123 # that, see https://github.com/NixOS/nixpkgs/pull/314920. As a temporary
124 # workaround, we use only a single program we need (and that is not part of
125 # the stdenv).
126 darwin-cctools-only-libtool =
127 # Would be nice to have onlyExe builder similar to onlyBin…
128 runCommand "darwin-cctools-only-libtool" { cctools = lib.getBin buildPackages.cctools; } ''
129 mkdir -p "$out/bin"
130 ln -s "$cctools/bin/libtool" "$out/bin/libtool"
131 '';
132
133 # a script which claims to be a different script but switches to simply touching its output
134 # when an environment variable is set. See CC_host, --cross-compiling, and postConfigure.
135 touchScript =
136 real:
137 writeScript "touch-script" ''
138 #!${stdenv.shell}
139 if [ -z "$FAKE_TOUCH" ]; then
140 exec "${real}" "$@"
141 fi
142 while [ "$#" != "0" ]; do
143 if [ "$1" == "-o" ]; then
144 shift
145 touch "$1"
146 fi
147 shift
148 done
149 '';
150
151 downloadDir = if lib.strings.hasInfix "-rc." version then "download/rc" else "dist";
152
153 package = stdenv.mkDerivation (
154 finalAttrs:
155 let
156 /**
157 the final package fixed point, after potential overrides
158 */
159 self = finalAttrs.finalPackage;
160 in
161 {
162 inherit pname version;
163
164 src = fetchurl {
165 url = "https://nodejs.org/${downloadDir}/v${version}/node-v${version}.tar.xz";
166 inherit sha256;
167 };
168
169 strictDeps = true;
170
171 env = {
172 # Tell ninja to avoid ANSI sequences, otherwise we don’t see build
173 # progress in Nix logs.
174 #
175 # Note: do not set TERM=dumb environment variable globally, it is used in
176 # test-ci-js test suite to skip tests that otherwise run fine.
177 NINJA = "TERM=dumb ninja";
178 }
179 // lib.optionalAttrs (!canExecute && !canEmulate) {
180 # these are used in the --cross-compiling case. see comment at postConfigure.
181 CC_host = touchScript "${buildPackages.stdenv.cc}/bin/cc";
182 CXX_host = touchScript "${buildPackages.stdenv.cc}/bin/c++";
183 AR_host = touchScript "${buildPackages.stdenv.cc}/bin/ar";
184 };
185
186 # NB: technically, we do not need bash in build inputs since all scripts are
187 # wrappers over the corresponding JS scripts. There are some packages though
188 # that use bash wrappers, e.g. polaris-web.
189 buildInputs = [
190 zlib
191 libuv
192 openssl
193 http-parser
194 icu
195 bash
196 ]
197 ++ lib.optionals useSharedSQLite [ sqlite ];
198
199 nativeBuildInputs = [
200 installShellFiles
201 ninja
202 pkgconf
203 python
204 ]
205 ++ lib.optionals stdenv.buildPlatform.isDarwin [
206 # gyp checks `sysctl -n hw.memsize` if `sys.platform == "darwin"`.
207 unixtools.sysctl
208 ]
209 ++ lib.optionals stdenv.hostPlatform.isDarwin [
210 # For gyp-mac-tool if `flavor == "mac"`.
211 darwin-cctools-only-libtool
212 ];
213
214 # We currently rely on Makefile and stdenv for build phases, so do not let
215 # ninja’s setup hook to override default stdenv phases.
216 dontUseNinjaBuild = true;
217 dontUseNinjaCheck = true;
218 dontUseNinjaInstall = true;
219
220 outputs = [
221 "out"
222 "libv8"
223 ]
224 ++ lib.optionals (stdenv.hostPlatform == stdenv.buildPlatform) [ "dev" ];
225 setOutputFlags = false;
226 moveToDev = false;
227
228 configureFlags = [
229 "--ninja"
230 "--with-intl=system-icu"
231 "--openssl-use-def-ca-store"
232 # --cross-compiling flag enables use of CC_host et. al
233 (if canExecute || canEmulate then "--no-cross-compiling" else "--cross-compiling")
234 "--dest-os=${destOS}"
235 "--dest-cpu=${stdenv.hostPlatform.node.arch}"
236 ]
237 ++ lib.optionals (destARMFPU != null) [ "--with-arm-fpu=${destARMFPU}" ]
238 ++ lib.optionals (destARMFloatABI != null) [ "--with-arm-float-abi=${destARMFloatABI}" ]
239 ++ lib.optionals (!canExecute && canEmulate) [
240 # Node.js requires matching bitness between build and host platforms, e.g.
241 # for V8 startup snapshot builder (see tools/snapshot) and some other
242 # tools. We apply a patch that runs these tools using a host platform
243 # emulator and avoid cross-compiling altogether (from the build system’s
244 # perspective).
245 "--emulator=${emulator}"
246 ]
247 ++ lib.optionals (lib.versionOlder version "19") [ "--without-dtrace" ]
248 ++ lib.optionals (!enableNpm) [ "--without-npm" ]
249 ++ lib.concatMap (name: [
250 "--shared-${name}"
251 "--shared-${name}-libpath=${lib.getLib sharedLibDeps.${name}}/lib"
252 /**
253 Closure notes: we explicitly avoid specifying --shared-*-includes,
254 as that would put the paths into bin/nodejs.
255 Including pkg-config in build inputs would also have the same effect!
256
257 FIXME: the statement above is outdated, we have to include pkg-config
258 in build inputs for system-icu.
259 */
260 ]) (builtins.attrNames sharedLibDeps);
261
262 configurePlatforms = [ ];
263
264 dontDisableStatic = true;
265
266 configureScript = writeScript "nodejs-configure" ''
267 exec ${python.executable} configure.py "$@"
268 '';
269
270 # In order to support unsupported cross configurations, we copy some intermediate executables
271 # from a native build and replace all the build-system tools with a script which simply touches
272 # its outfile. We have to indiana-jones-swap the build-system-targeted tools since they are
273 # tested for efficacy at configure time.
274 postConfigure = lib.optionalString (!canEmulate && !canExecute) ''
275 cp ${buildNode.dev}/bin/* out/Release
276 export FAKE_TOUCH=1
277 '';
278
279 enableParallelBuilding = true;
280
281 # Don't allow enabling content addressed conversion as `nodejs`
282 # checksums it's image before conversion happens and image loading
283 # breaks:
284 # $ nix build -f. nodejs --arg config '{ contentAddressedByDefault = true; }'
285 # $ ./result/bin/node
286 # Check failed: VerifyChecksum(blob).
287 __contentAddressed = false;
288
289 passthru.interpreterName = "nodejs";
290
291 passthru.pkgs = callPackage ../../node-packages/default.nix {
292 nodejs = self;
293 };
294
295 setupHook = ./setup-hook.sh;
296
297 pos = builtins.unsafeGetAttrPos "version" args;
298
299 inherit patches;
300
301 __darwinAllowLocalNetworking = true; # for tests
302
303 doCheck = canExecute;
304
305 # See https://github.com/nodejs/node/issues/22006
306 enableParallelChecking = false;
307
308 # Some dependencies required for tools/doc/node_modules (and therefore
309 # test-addons, jstest and others) target are not included in the tarball.
310 # Run test targets that do not require network access.
311 checkTarget = lib.concatStringsSep " " (
312 [
313 "build-js-native-api-tests"
314 "build-node-api-tests"
315 "tooltest"
316 "cctest"
317 ]
318 ++ lib.optionals (!stdenv.buildPlatform.isDarwin || lib.versionAtLeast version "20") [
319 # There are some test failures on macOS before v20 that are not worth the
320 # time to debug for a version that would be eventually removed in less
321 # than a year (Node.js 18 will be EOL at 2025-04-30). Note that these
322 # failures are specific to Nix sandbox on macOS and should not affect
323 # actual functionality.
324 "test-ci-js"
325 ]
326 );
327
328 checkFlags = [
329 # Do not create __pycache__ when running tests.
330 "PYTHONDONTWRITEBYTECODE=1"
331 ]
332 ++ lib.optionals (stdenv.buildPlatform.isDarwin && stdenv.buildPlatform.isx86_64) [
333 # Python 3.12 introduced a warning for calling `os.fork()` in a
334 # multi‐threaded program. For some reason, the Node.js
335 # `tools/pseudo-tty.py` program used for PTY‐related tests
336 # triggers this warning on Hydra, on `x86_64-darwin` only,
337 # despite not creating any threads itself. This causes the
338 # Node.js test runner to misinterpret the warnings as part of the
339 # test output and fail. It does not reproduce reliably off Hydra
340 # on Intel Macs, or occur on the `aarch64-darwin` builds.
341 #
342 # This seems likely to be related to Rosetta 2, but it could also
343 # be some strange x86‐64‐only threading behaviour of the Darwin
344 # system libraries, or a bug in CPython, or something else
345 # haunted about the Nixpkgs/Hydra build environment. We silence
346 # the warnings in the hope that closing our eyes will make the
347 # ghosts go away.
348 "PYTHONWARNINGS=ignore::DeprecationWarning"
349 ]
350 ++ lib.optionals (!stdenv.buildPlatform.isDarwin || lib.versionAtLeast version "20") [
351 "FLAKY_TESTS=skip"
352 # Skip some tests that are not passing in this context
353 "CI_SKIP_TESTS=${
354 lib.concatStringsSep "," (
355 [
356 # Tests don't work in sandbox.
357 "test-child-process-exec-env"
358 "test-child-process-uid-gid"
359 "test-fs-write-stream-eagain"
360 "test-process-euid-egid"
361 "test-process-initgroups"
362 "test-process-setgroups"
363 "test-process-uid-gid"
364 "test-setproctitle"
365 # This is a bit weird, but for some reason fs watch tests fail with
366 # sandbox.
367 "test-fs-promises-watch"
368 "test-fs-watch"
369 "test-fs-watch-encoding"
370 "test-fs-watch-non-recursive"
371 "test-fs-watch-recursive-add-file"
372 "test-fs-watch-recursive-add-file-to-existing-subfolder"
373 "test-fs-watch-recursive-add-file-to-new-folder"
374 "test-fs-watch-recursive-add-file-with-url"
375 "test-fs-watch-recursive-add-folder"
376 "test-fs-watch-recursive-assert-leaks"
377 "test-fs-watch-recursive-promise"
378 "test-fs-watch-recursive-symlink"
379 "test-fs-watch-recursive-sync-write"
380 "test-fs-watch-recursive-update-file"
381 "test-fs-watchfile"
382 "test-runner-run"
383 "test-runner-watch-mode"
384 "test-watch-mode-files_watcher"
385 ]
386 ++ lib.optionals (!lib.versionAtLeast version "22") [
387 "test-tls-multi-key"
388 ]
389 ++ lib.optionals stdenv.hostPlatform.is32bit [
390 # utime (actually utimensat) fails with EINVAL on 2038 timestamp
391 "test-fs-utimes-y2K38"
392 ]
393 ++ lib.optionals stdenv.buildPlatform.isDarwin [
394 # Disable tests that don’t work under macOS sandbox.
395 "test-macos-app-sandbox"
396 "test-os"
397 "test-os-process-priority"
398
399 # Debugger tests failing on macOS 15.4
400 "test-debugger-extract-function-name"
401 "test-debugger-random-port-with-inspect-port"
402 "test-debugger-launch"
403 "test-debugger-pid"
404
405 # Those are annoyingly flaky, but not enough to be marked as such upstream.
406 "test-wasi"
407 ]
408 ++ lib.optionals (stdenv.buildPlatform.isDarwin && stdenv.buildPlatform.isx86_64) [
409 # These tests fail on x86_64-darwin (even without sandbox).
410 # TODO: revisit at a later date.
411 "test-fs-readv"
412 "test-fs-readv-sync"
413 "test-vm-memleak"
414
415 # Those are annoyingly flaky, but not enough to be marked as such upstream.
416 "test-tick-processor-arguments"
417 "test-set-raw-mode-reset-signal"
418 ]
419 # Those are annoyingly flaky, but not enough to be marked as such upstream.
420 ++ lib.optional (majorVersion == "22") "test-child-process-stdout-flush-exit"
421 )
422 }"
423 ];
424
425 postInstall =
426 let
427 # nodejs_18 does not have node_js2c, and we don't want to rebuild the other ones
428 # FIXME: fix this cleanly in staging
429 tools =
430 if majorVersion == "18" then
431 "{bytecode_builtins_list_generator,mksnapshot,torque,gen-regexp-special-case}"
432 else
433 "{bytecode_builtins_list_generator,mksnapshot,torque,node_js2c,gen-regexp-special-case}";
434 in
435 lib.optionalString (stdenv.hostPlatform == stdenv.buildPlatform) ''
436 mkdir -p $dev/bin
437 cp out/Release/${tools} $dev/bin
438 ''
439 + ''
440
441 HOST_PATH=$out/bin patchShebangs --host $out
442
443 ${lib.optionalString canExecute ''
444 $out/bin/node --completion-bash > node.bash
445 installShellCompletion node.bash
446 ''}
447
448 ${lib.optionalString enableNpm ''
449 mkdir -p $out/share/bash-completion/completions
450 ln -s $out/lib/node_modules/npm/lib/utils/completion.sh \
451 $out/share/bash-completion/completions/npm
452 for dir in "$out/lib/node_modules/npm/man/"*; do
453 mkdir -p $out/share/man/$(basename "$dir")
454 for page in "$dir"/*; do
455 ln -rs $page $out/share/man/$(basename "$dir")
456 done
457 done
458 ''}
459
460 # install the missing headers for node-gyp
461 # TODO: use propagatedBuildInputs instead of copying headers.
462 cp -r ${lib.concatStringsSep " " copyLibHeaders} $out/include/node
463
464 # assemble a static v8 library and put it in the 'libv8' output
465 mkdir -p $libv8/lib
466 pushd out/Release/obj
467 find . -path "**/torque_*/**/*.o" -or -path "**/v8*/**/*.o" \
468 -and -not -name "torque.*" \
469 -and -not -name "mksnapshot.*" \
470 -and -not -name "gen-regexp-special-case.*" \
471 -and -not -name "bytecode_builtins_list_generator.*" \
472 | sort -u >files
473 test -s files # ensure that the list is not empty
474 $AR -cqs $libv8/lib/libv8.a @files
475 popd
476
477 # copy v8 headers
478 cp -r deps/v8/include $libv8/
479
480 # create a pkgconfig file for v8
481 major=$(grep V8_MAJOR_VERSION deps/v8/include/v8-version.h | cut -d ' ' -f 3)
482 minor=$(grep V8_MINOR_VERSION deps/v8/include/v8-version.h | cut -d ' ' -f 3)
483 patch=$(grep V8_PATCH_LEVEL deps/v8/include/v8-version.h | cut -d ' ' -f 3)
484 mkdir -p $libv8/lib/pkgconfig
485 cat > $libv8/lib/pkgconfig/v8.pc << EOF
486 Name: v8
487 Description: V8 JavaScript Engine
488 Version: $major.$minor.$patch
489 Libs: -L$libv8/lib -lv8 -pthread -licui18n -licuuc
490 Cflags: -I$libv8/include
491 EOF
492 ''
493 + lib.optionalString (stdenv.hostPlatform == stdenv.buildPlatform) ''
494 cp -r $out/include $dev/include
495 '';
496
497 passthru.tests = {
498 version = testers.testVersion {
499 package = self;
500 version = "v${lib.head (lib.strings.splitString "-rc." version)}";
501 };
502 };
503
504 passthru.updateScript = import ./update.nix {
505 inherit
506 writeScript
507 common-updater-scripts
508 coreutils
509 curl
510 fetchurl
511 gnugrep
512 gnupg
513 jq
514 runtimeShell
515 ;
516 inherit lib;
517 inherit majorVersion;
518 };
519
520 meta = with lib; {
521 description = "Event-driven I/O framework for the V8 JavaScript engine";
522 homepage = "https://nodejs.org";
523 changelog = "https://github.com/nodejs/node/releases/tag/v${version}";
524 license = licenses.mit;
525 maintainers = with maintainers; [ aduh95 ];
526 platforms = platforms.linux ++ platforms.darwin ++ platforms.freebsd;
527 # This broken condition is likely too conservative. Feel free to loosen it if it works.
528 broken =
529 !canExecute && !canEmulate && (stdenv.buildPlatform.parsed.cpu != stdenv.hostPlatform.parsed.cpu);
530 mainProgram = "node";
531 knownVulnerabilities = optional (versionOlder version "18") "This NodeJS release has reached its end of life. See https://nodejs.org/en/about/releases/.";
532 };
533
534 passthru.python = python; # to ensure nodeEnv uses the same version
535 }
536 );
537in
538package