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