From b759eafff641d50d5275da39c7962771ecdbebca Mon Sep 17 00:00:00 2001 From: Audrey Dutcher Date: Sat, 22 Feb 2025 19:29:33 -0700 Subject: [PATCH] nodejs: fix cross compilation for non-emulatable platforms This was tested x86_64-linux -> x86_64-freebsd. It works by injecting the tools that would otherwise be run at build time for the same executables extracted from a native build. Without this, there are multiple issues: a) It will fail to start building since there are naming conflicts between the "host" (build system) and "normal" (host system) object files. This is resolved with a patch from buildroot. b) It will then still fail to build since it will still try to build the "host" objects, with a host compiler, but it will use the configuration flags for the "target" OS. This is resolved by importing the executables that would otherwise be run on the build system from the intermediate stage of a native build, saved in a new "dev" output. We also fake the "host" compiler as a tool which simply touches its outputs. c) Finally, there is a clang bug which causes a static assert that something is trivially copyable to fire as a false positive. We remove this check with a patch from rubyjs. --- pkgs/development/web/nodejs/nodejs.nix | 49 +++++++++++++++++++++++--- pkgs/development/web/nodejs/v22.nix | 20 +++++++++-- pkgs/development/web/nodejs/v23.nix | 28 +++++++++++++-- 3 files changed, 88 insertions(+), 9 deletions(-) diff --git a/pkgs/development/web/nodejs/nodejs.nix b/pkgs/development/web/nodejs/nodejs.nix index fece685a4fa4..95ad2c6ec584 100644 --- a/pkgs/development/web/nodejs/nodejs.nix +++ b/pkgs/development/web/nodejs/nodejs.nix @@ -22,6 +22,8 @@ let canExecute = stdenv.buildPlatform.canExecute stdenv.hostPlatform; emulator = stdenv.hostPlatform.emulator buildPackages; + canEmulate = stdenv.hostPlatform.emulatorAvailable buildPackages; + buildNode = buildPackages."${pname}_${majorVersion}"; # See valid_os and valid_arch in configure.py. destOS = @@ -126,6 +128,22 @@ let ln -s "$cctools/bin/libtool" "$out/bin/libtool" ''; + # a script which claims to be a different script but switches to simply touching its output + # when an environment variable is set. See CC_host, --cross-compiling, and postConfigure. + touchScript = real: writeScript "touch-script" '' + #!${stdenv.shell} + if [ -z "$FAKE_TOUCH" ]; then + exec "${real}" "$@" + fi + while [ "$#" != "0" ]; do + if [ "$1" == "-o" ]; then + shift + touch "$1" + fi + shift + done + ''; + package = stdenv.mkDerivation (finalAttrs: let /** the final package fixed point, after potential overrides */ @@ -148,6 +166,11 @@ let # Note: do not set TERM=dumb environment variable globally, it is used in # test-ci-js test suite to skip tests that otherwise run fine. NINJA = "TERM=dumb ninja"; + } // lib.optionalAttrs (!canExecute && !canEmulate) { + # these are used in the --cross-compiling case. see comment at postConfigure. + CC_host = touchScript "${buildPackages.stdenv.cc}/bin/cc"; + CXX_host = touchScript "${buildPackages.stdenv.cc}/bin/c++"; + AR_host = touchScript "${buildPackages.stdenv.cc}/bin/ar"; }; # NB: technically, we do not need bash in build inputs since all scripts are @@ -179,7 +202,7 @@ let dontUseNinjaCheck = true; dontUseNinjaInstall = true; - outputs = [ "out" "libv8" ]; + outputs = [ "out" "libv8" ] ++ lib.optionals (stdenv.hostPlatform == stdenv.buildPlatform) [ "dev" ]; setOutputFlags = false; moveToDev = false; @@ -188,13 +211,14 @@ let "--ninja" "--with-intl=system-icu" "--openssl-use-def-ca-store" - "--no-cross-compiling" + # --cross-compiling flag enables use of CC_host et. al + (if canExecute || canEmulate then "--no-cross-compiling" else "--cross-compiling") "--dest-os=${destOS}" "--dest-cpu=${destCPU}" ] ++ lib.optionals (destARMFPU != null) [ "--with-arm-fpu=${destARMFPU}" ] ++ lib.optionals (destARMFloatABI != null) [ "--with-arm-float-abi=${destARMFloatABI}" ] - ++ lib.optionals (!canExecute) [ + ++ lib.optionals (!canExecute && canEmulate) [ # Node.js requires matching bitness between build and host platforms, e.g. # for V8 startup snapshot builder (see tools/snapshot) and some other # tools. We apply a patch that runs these tools using a host platform @@ -225,6 +249,15 @@ let exec ${python.executable} configure.py "$@" ''; + # In order to support unsupported cross configurations, we copy some intermediate executables + # from a native build and replace all the build-system tools with a script which simply touches + # its outfile. We have to indiana-jones-swap the build-system-targeted tools since they are + # tested for efficacy at configure time. + postConfigure = lib.optionalString (!canEmulate && !canExecute) '' + cp ${buildNode.dev}/bin/* out/Release + export FAKE_TOUCH=1 + ''; + enableParallelBuilding = true; # Don't allow enabling content addressed conversion as `nodejs` @@ -343,7 +376,11 @@ let ])}" ]; - postInstall = '' + postInstall = lib.optionalString (stdenv.hostPlatform == stdenv.buildPlatform) '' + mkdir -p $dev/bin + cp out/Release/{bytecode_builtins_list_generator,mksnapshot,torque,node_js2c,gen-regexp-special-case} $dev/bin + '' + '' + HOST_PATH=$out/bin patchShebangs --host $out ${lib.optionalString canExecute '' @@ -416,7 +453,9 @@ let changelog = "https://github.com/nodejs/node/releases/tag/v${version}"; license = licenses.mit; maintainers = with maintainers; [ aduh95 ]; - platforms = platforms.linux ++ platforms.darwin; + platforms = platforms.linux ++ platforms.darwin ++ platforms.freebsd; + # This broken condition is likely too conservative. Feel free to loosen it if it works. + broken = !canExecute && !canEmulate && (stdenv.buildPlatform.parsed.cpu != stdenv.hostPlatform.parsed.cpu); mainProgram = "node"; knownVulnerabilities = optional (versionOlder version "18") "This NodeJS release has reached its end of life. See https://nodejs.org/en/about/releases/."; }; diff --git a/pkgs/development/web/nodejs/v22.nix b/pkgs/development/web/nodejs/v22.nix index e71956b3fa66..b13daffa94d3 100644 --- a/pkgs/development/web/nodejs/v22.nix +++ b/pkgs/development/web/nodejs/v22.nix @@ -1,4 +1,4 @@ -{ callPackage, openssl, python3, enableNpm ? true }: +{ lib, stdenv, buildPackages, callPackage, fetchpatch2, openssl, python3, enableNpm ? true }: let buildNodejs = callPackage ./nodejs.nix { @@ -10,8 +10,24 @@ buildNodejs { inherit enableNpm; version = "22.14.0"; sha256 = "c609946bf793b55c7954c26582760808d54c16185d79cb2fb88065e52de21914"; - patches = [ + patches = (if (stdenv.hostPlatform.emulatorAvailable buildPackages) then [ ./configure-emulator.patch + ] else [ + (fetchpatch2 { + url = "https://raw.githubusercontent.com/buildroot/buildroot/2f0c31bffdb59fb224387e35134a6d5e09a81d57/package/nodejs/nodejs-src/0003-include-obj-name-in-shared-intermediate.patch"; + hash = "sha256-3g4aS+NmmUYNOYRNc6UMJKYoaTlpP5Knt9UHegx+o0Y="; + }) + ]) ++ lib.optionals (stdenv.hostPlatform != stdenv.buildPlatform && stdenv.hostPlatform.isFreeBSD) [ + # This patch is concerning. + # https://github.com/nodejs/node/issues/54576 + # It is only supposed to affect clang >= 17, but I'm seeing it on clang 19. + # I'm keeping the predicate for this patch pretty strict out of caution, + # so if you see the error it's supposed to prevent, feel free to loosen it. + (fetchpatch2 { + url = "https://raw.githubusercontent.com/rubyjs/libv8-node/62476a398d4c9c1a670240a3b070d69544be3761/patch/v8-no-assert-trivially-copyable.patch"; + hash = "sha256-hSTLljmVzYmc3WAVeRq9EPYluXGXFeWVXkykufGQPVw="; + }) + ] ++ [ ./configure-armv6-vfpv2.patch ./disable-darwin-v8-system-instrumentation-node19.patch ./bypass-darwin-xcrun-node16.patch diff --git a/pkgs/development/web/nodejs/v23.nix b/pkgs/development/web/nodejs/v23.nix index 5c2b651e7195..3d8f97969aa4 100644 --- a/pkgs/development/web/nodejs/v23.nix +++ b/pkgs/development/web/nodejs/v23.nix @@ -1,6 +1,7 @@ { lib, stdenv, + buildPackages, callPackage, fetchpatch2, openssl, @@ -19,8 +20,31 @@ buildNodejs { version = "23.10.0"; sha256 = "b39e5fbd3debb8318ddea6af3e89b07dafb891421fb7ca99fbe19c99adabe5fd"; patches = - [ - ./configure-emulator.patch + ( + if (stdenv.hostPlatform.emulatorAvailable buildPackages) then + [ + ./configure-emulator.patch + ] + else + [ + (fetchpatch2 { + url = "https://raw.githubusercontent.com/buildroot/buildroot/2f0c31bffdb59fb224387e35134a6d5e09a81d57/package/nodejs/nodejs-src/0003-include-obj-name-in-shared-intermediate.patch"; + hash = "sha256-3g4aS+NmmUYNOYRNc6UMJKYoaTlpP5Knt9UHegx+o0Y="; + }) + ] + ) + ++ lib.optionals (stdenv.hostPlatform != stdenv.buildPlatform && stdenv.hostPlatform.isFreeBSD) [ + # This patch is concerning. + # https://github.com/nodejs/node/issues/54576 + # It is only supposed to affect clang >= 17, but I'm seeing it on clang 19. + # I'm keeping the predicate for this patch pretty strict out of caution, + # so if you see the error it's supposed to prevent, feel free to loosen it. + (fetchpatch2 { + url = "https://raw.githubusercontent.com/rubyjs/libv8-node/62476a398d4c9c1a670240a3b070d69544be3761/patch/v8-no-assert-trivially-copyable.patch"; + hash = "sha256-hSTLljmVzYmc3WAVeRq9EPYluXGXFeWVXkykufGQPVw="; + }) + ] + ++ [ ./configure-armv6-vfpv2.patch ./disable-darwin-v8-system-instrumentation-node19.patch ./bypass-darwin-xcrun-node16.patch