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.
This commit is contained in:
Audrey Dutcher
2025-02-22 19:29:33 -07:00
parent 2443e179da
commit b759eafff6
3 changed files with 88 additions and 9 deletions

View File

@@ -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/.";
};

View File

@@ -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

View File

@@ -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