From 42fa83e8d79d78df8d603048b604c2a33cca479d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Tue, 28 Mar 2023 17:00:23 +0200 Subject: [PATCH] nodejs: fix cross compilation for armv7l MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v8 library supports only limited number of build platforms based on the host architecture. The rule there is the bitness (the mixing of 32bit and 64bit architectures seems to be in most cases disallowed). Thus this adds usage of the emulator of the host platform and builds tools for that. This also allows us to use ninja instead of make when build ≠ host while waiting for https://github.com/nodejs/gyp-next/pull/185 to be merged. Co-authored-by: Ivan Trubach --- .../nodejs/configure-emulator-node18.patch | 131 +++++++++++++++++ .../web/nodejs/configure-emulator.patch | 139 ++++++++++++++++++ pkgs/development/web/nodejs/nodejs.nix | 114 ++++++++------ pkgs/development/web/nodejs/v18.nix | 1 + pkgs/development/web/nodejs/v20.nix | 1 + pkgs/development/web/nodejs/v22.nix | 1 + 6 files changed, 339 insertions(+), 48 deletions(-) create mode 100644 pkgs/development/web/nodejs/configure-emulator-node18.patch create mode 100644 pkgs/development/web/nodejs/configure-emulator.patch diff --git a/pkgs/development/web/nodejs/configure-emulator-node18.patch b/pkgs/development/web/nodejs/configure-emulator-node18.patch new file mode 100644 index 0000000000000..6c158173b3c8f --- /dev/null +++ b/pkgs/development/web/nodejs/configure-emulator-node18.patch @@ -0,0 +1,131 @@ +From eaf2e55c472ce1b71e74d9d30b287be74a2e2b7a Mon Sep 17 00:00:00 2001 +From: Ivan Trubach +Date: Mon, 15 Jul 2024 11:43:43 +0300 +Subject: [PATCH] tools: support setting an emulator from configure script +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +V8’s JIT infrastructure requires binaries such as mksnapshot to be run +during the build. However, these binaries must have the same bit-width +as the host platform (e.g. a x86_64 build platform targeting ARMv6 needs +to produce a 32-bit binary). + +To work around this issue, build the binaries for the host platform and +run them on the host with an emulator (if set from configure.py). + +Based on Buildroot’s nodejs-src 0001-add-qemu-wrapper-support.patch. +https://gitlab.com/buildroot.org/buildroot/-/raw/c1d5eada4d4db9eeaa1c44dd1dea95a67c8a70ca/package/nodejs/nodejs-src/0001-add-qemu-wrapper-support.patch +--- + common.gypi | 1 + + configure.py | 9 +++++++++ + node.gyp | 3 +++ + tools/v8_gypfiles/v8.gyp | 4 ++++ + 4 files changed, 17 insertions(+) + +diff --git a/common.gypi b/common.gypi +index ec92c9df4c..6474495ab6 100644 +--- a/common.gypi ++++ b/common.gypi +@@ -13,6 +13,7 @@ + 'enable_pgo_generate%': '0', + 'enable_pgo_use%': '0', + 'python%': 'python', ++ 'emulator%': [], + + 'node_shared%': 'false', + 'force_dynamic_crt%': 0, +diff --git a/configure.py b/configure.py +index 82916748fd..f1d332eefc 100755 +--- a/configure.py ++++ b/configure.py +@@ -112,6 +112,12 @@ parser.add_argument('--dest-cpu', + choices=valid_arch, + help=f"CPU architecture to build for ({', '.join(valid_arch)})") + ++parser.add_argument('--emulator', ++ action='store', ++ dest='emulator', ++ default=None, ++ help='use an emulator to run intermediate build tools') ++ + parser.add_argument('--cross-compiling', + action='store_true', + dest='cross_compiling', +@@ -2167,6 +2173,9 @@ elif flavor == 'win' and sys.platform != 'msys': + else: + gyp_args += ['-f', 'make-' + flavor] + ++if options.emulator is not None: ++ gyp_args += ['-Demulator=' + options.emulator] ++ + if options.compile_commands_json: + gyp_args += ['-f', 'compile_commands_json'] + os.path.islink('./compile_commands.json') and os.unlink('./compile_commands.json') +diff --git a/node.gyp b/node.gyp +index 08cb3f38e8..515b305933 100644 +--- a/node.gyp ++++ b/node.gyp +@@ -332,6 +332,7 @@ + '<(SHARED_INTERMEDIATE_DIR)/node_snapshot.cc', + ], + 'action': [ ++ '<@(emulator)', + '<(node_mksnapshot_exec)', + '--build-snapshot', + '<(node_snapshot_main)', +@@ -351,6 +352,7 @@ + '<(SHARED_INTERMEDIATE_DIR)/node_snapshot.cc', + ], + 'action': [ ++ '<@(emulator)', + '<@(_inputs)', + '<@(_outputs)', + ], +@@ -1520,6 +1522,7 @@ + '<(PRODUCT_DIR)/<(node_core_target_name).def', + ], + 'action': [ ++ '<@(emulator)', + '<(PRODUCT_DIR)/gen_node_def.exe', + '<@(_inputs)', + '<@(_outputs)', +diff --git a/tools/v8_gypfiles/v8.gyp b/tools/v8_gypfiles/v8.gyp +index ba8b161f0f..d5c90dad50 100644 +--- a/tools/v8_gypfiles/v8.gyp ++++ b/tools/v8_gypfiles/v8.gyp +@@ -99,6 +99,7 @@ + '<@(torque_outputs_inc)', + ], + 'action': [ ++ '<@(emulator)', + '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)torque<(EXECUTABLE_SUFFIX)', + '-o', '<(SHARED_INTERMEDIATE_DIR)/torque-generated', + '-v8-root', '<(V8_ROOT)', +@@ -219,6 +220,7 @@ + 'action': [ + '<(python)', + '<(V8_ROOT)/tools/run.py', ++ '<@(emulator)', + '<@(_inputs)', + '<@(_outputs)', + ], +@@ -442,6 +444,7 @@ + }], + ], + 'action': [ ++ '<@(emulator)', + '>@(_inputs)', + '>@(mksnapshot_flags)', + ], +@@ -1577,6 +1580,7 @@ + 'action': [ + '<(python)', + '<(V8_ROOT)/tools/run.py', ++ '<@(emulator)', + '<@(_inputs)', + '<@(_outputs)', + ], +-- +2.44.1 + diff --git a/pkgs/development/web/nodejs/configure-emulator.patch b/pkgs/development/web/nodejs/configure-emulator.patch new file mode 100644 index 0000000000000..cb4cac8ef77bc --- /dev/null +++ b/pkgs/development/web/nodejs/configure-emulator.patch @@ -0,0 +1,139 @@ +From 438e3a08bf76bc0a9a098c16847e9029e20e240f Mon Sep 17 00:00:00 2001 +From: Ivan Trubach +Date: Mon, 15 Jul 2024 11:43:43 +0300 +Subject: [PATCH] tools: support setting an emulator from configure script +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +V8’s JIT infrastructure requires binaries such as mksnapshot to be run +during the build. However, these binaries must have the same bit-width +as the host platform (e.g. a x86_64 build platform targeting ARMv6 needs +to produce a 32-bit binary). + +To work around this issue, build the binaries for the host platform and +run them on the host with an emulator (if set from configure.py). + +Based on Buildroot’s nodejs-src 0001-add-qemu-wrapper-support.patch. +https://gitlab.com/buildroot.org/buildroot/-/raw/c1d5eada4d4db9eeaa1c44dd1dea95a67c8a70ca/package/nodejs/nodejs-src/0001-add-qemu-wrapper-support.patch +--- + common.gypi | 1 + + configure.py | 9 +++++++++ + node.gyp | 4 ++++ + tools/v8_gypfiles/v8.gyp | 4 ++++ + 4 files changed, 18 insertions(+) + +diff --git a/common.gypi b/common.gypi +index 3e1902fb78..e4940448ac 100644 +--- a/common.gypi ++++ b/common.gypi +@@ -13,6 +13,7 @@ + 'enable_pgo_generate%': '0', + 'enable_pgo_use%': '0', + 'python%': 'python', ++ 'emulator%': [], + + 'node_shared%': 'false', + 'force_dynamic_crt%': 0, +diff --git a/configure.py b/configure.py +index ee08264e91..02160ba1fa 100755 +--- a/configure.py ++++ b/configure.py +@@ -112,6 +112,12 @@ parser.add_argument('--dest-cpu', + choices=valid_arch, + help=f"CPU architecture to build for ({', '.join(valid_arch)})") + ++parser.add_argument('--emulator', ++ action='store', ++ dest='emulator', ++ default=None, ++ help='use an emulator to run intermediate build tools') ++ + parser.add_argument('--cross-compiling', + action='store_true', + dest='cross_compiling', +@@ -2114,6 +2120,9 @@ elif flavor == 'win' and sys.platform != 'msys': + else: + gyp_args += ['-f', 'make-' + flavor] + ++if options.emulator is not None: ++ gyp_args += ['-Demulator=' + options.emulator] ++ + if options.compile_commands_json: + gyp_args += ['-f', 'compile_commands_json'] + +diff --git a/node.gyp b/node.gyp +index cf016ef468..880449d9bb 100644 +--- a/node.gyp ++++ b/node.gyp +@@ -643,6 +643,7 @@ + '<(SHARED_INTERMEDIATE_DIR)/node_snapshot.cc', + ], + 'action': [ ++ '<@(emulator)', + '<(node_mksnapshot_exec)', + '--build-snapshot', + '<(node_snapshot_main)', +@@ -662,6 +663,7 @@ + '<(SHARED_INTERMEDIATE_DIR)/node_snapshot.cc', + ], + 'action': [ ++ '<@(emulator)', + '<@(_inputs)', + '<@(_outputs)', + ], +@@ -952,6 +954,7 @@ + '<(SHARED_INTERMEDIATE_DIR)/node_javascript.cc', + ], + 'action': [ ++ '<@(emulator)', + '<(node_js2c_exec)', + '<@(_outputs)', + 'lib', +@@ -1340,6 +1343,7 @@ + '<(PRODUCT_DIR)/<(node_core_target_name).def', + ], + 'action': [ ++ '<@(emulator)', + '<(PRODUCT_DIR)/gen_node_def.exe', + '<@(_inputs)', + '<@(_outputs)', +diff --git a/tools/v8_gypfiles/v8.gyp b/tools/v8_gypfiles/v8.gyp +index f822c056e5..b7a629c54b 100644 +--- a/tools/v8_gypfiles/v8.gyp ++++ b/tools/v8_gypfiles/v8.gyp +@@ -112,6 +112,7 @@ + '<@(torque_outputs_inc)', + ], + 'action': [ ++ '<@(emulator)', + '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)torque<(EXECUTABLE_SUFFIX)', + '-o', '<(SHARED_INTERMEDIATE_DIR)/torque-generated', + '-v8-root', '<(V8_ROOT)', +@@ -232,6 +233,7 @@ + 'action': [ + '<(python)', + '<(V8_ROOT)/tools/run.py', ++ '<@(emulator)', + '<@(_inputs)', + '<@(_outputs)', + ], +@@ -443,6 +445,7 @@ + }], + ], + 'action': [ ++ '<@(emulator)', + '>@(_inputs)', + '>@(mksnapshot_flags)', + ], +@@ -1668,6 +1671,7 @@ + 'action': [ + '<(python)', + '<(V8_ROOT)/tools/run.py', ++ '<@(emulator)', + '<@(_inputs)', + '<@(_outputs)', + ], +-- +2.44.1 + diff --git a/pkgs/development/web/nodejs/nodejs.nix b/pkgs/development/web/nodejs/nodejs.nix index 4ef6b18d05ac5..5eeb14f9d62e7 100644 --- a/pkgs/development/web/nodejs/nodejs.nix +++ b/pkgs/development/web/nodejs/nodejs.nix @@ -1,4 +1,4 @@ -{ lib, stdenv, fetchurl, openssl, python, zlib, libuv, util-linux, http-parser, bash +{ lib, stdenv, fetchurl, openssl, python, zlib, libuv, http-parser, icu, bash , pkg-config, which, buildPackages , testers # for `.pkgs` attribute @@ -7,7 +7,6 @@ , writeScript, coreutils, gnugrep, jq, curl, common-updater-scripts, nix, runtimeShell , gnupg , darwin, xcbuild -, procps, icu , installShellFiles }: @@ -16,13 +15,59 @@ let inherit (darwin.apple_sdk.frameworks) CoreServices ApplicationServices; - isCross = stdenv.hostPlatform != stdenv.buildPlatform; - majorVersion = lib.versions.major version; minorVersion = lib.versions.minor version; pname = if enableNpm then "nodejs" else "nodejs-slim"; + canExecute = stdenv.buildPlatform.canExecute stdenv.hostPlatform; + + # See valid_os and valid_arch in configure.py. + destOS = + let + platform = stdenv.hostPlatform; + in + if platform.isiOS then + "ios" + else if platform.isAndroid then + "android" + else if platform.isWindows then + "win" + else if platform.isDarwin then + "mac" + else if platform.isLinux then + "linux" + else if platform.isOpenBSD then + "openbsd" + else if platform.isFreeBSD then + "freebsd" + else + throw "unsupported os ${platform.uname.system}"; + destCPU = + let + platform = stdenv.hostPlatform; + in + if platform.isAarch then + "arm" + lib.optionalString platform.is64bit "64" + else if platform.isMips32 then + "mips" + lib.optionalString platform.isLittleEndian "le" + else if platform.isMips64 && platform.isLittleEndian then + "mips64el" + else if platform.isPower then + "ppc" + lib.optionalString platform.is64bit "64" + else if platform.isx86_64 then + "x64" + else if platform.isx86_32 then + "ia32" + else if platform.isS390x then + "s390x" + else if platform.isRiscV64 then + "riscv64" + else if platform.isLoongArch64 then + "loong64" + else + throw "unsupported cpu ${platform.uname.processor}"; + useSharedHttpParser = !stdenv.isDarwin && lib.versionOlder "${majorVersion}.${minorVersion}" "11.4"; sharedLibDeps = { inherit openssl zlib libuv; } // (lib.optionalAttrs useSharedHttpParser { inherit http-parser; }); @@ -61,8 +106,6 @@ let NIX_CFLAGS_COMPILE = "-D__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__=101300"; }; - depsBuildBuild = [ buildPackages.stdenv.cc openssl libuv zlib icu ]; - # NB: technically, we do not need bash in build inputs since all scripts are # wrappers over the corresponding JS scripts. There are some packages though # that use bash wrappers, e.g. polaris-web. @@ -80,33 +123,27 @@ let inherit (stdenv.hostPlatform) gcc isAarch32; in sharedConfigureFlags ++ lib.optionals (lib.versionOlder version "19") [ "--without-dtrace" - ] ++ (lib.optionals isCross [ - "--cross-compiling" - "--dest-cpu=${let platform = stdenv.hostPlatform; in - if platform.isAarch32 then "arm" - else if platform.isAarch64 then "arm64" - else if platform.isMips32 && platform.isLittleEndian then "mipsel" - else if platform.isMips32 && !platform.isLittleEndian then "mips" - else if platform.isMips64 && platform.isLittleEndian then "mips64el" - else if platform.isPower && platform.is32bit then "ppc" - else if platform.isPower && platform.is64bit then "ppc64" - else if platform.isx86_64 then "x86_64" - else if platform.isx86_32 then "x86" - else if platform.isS390 && platform.is64bit then "s390x" - else if platform.isRiscV && platform.is64bit then "riscv64" - else throw "unsupported cpu ${stdenv.hostPlatform.uname.processor}"}" - ]) ++ (lib.optionals (isCross && isAarch32 && lib.hasAttr "fpu" gcc) [ - "--with-arm-fpu=${gcc.fpu}" - ]) ++ (lib.optionals (isCross && isAarch32 && lib.hasAttr "float-abi" gcc) [ - "--with-arm-float-abi=${gcc.float-abi}" - ]) ++ extraConfigFlags; + ] ++ lib.optionals (!canExecute) [ + # 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 + # emulator and avoid cross-compiling altogether (from the build system’s + # perspective). + "--emulator=${stdenv.hostPlatform.emulator buildPackages}" + ] ++ [ + "--no-cross-compiling" + "--dest-os=${destOS}" + # Note that ARM features are detected from C macros. MIPS features are + # not (mips_arch, mips_fpu, mips_float_abi), but we don’t have equivalent + # definitions in lib/systems. + "--dest-cpu=${destCPU}" + ] ++ extraConfigFlags; configurePlatforms = []; dontDisableStatic = true; configureScript = writeScript "nodejs-configure" '' - export CC_host="$CC_FOR_BUILD" CXX_host="$CXX_FOR_BUILD" exec ${python.executable} configure.py "$@" ''; @@ -207,7 +244,7 @@ let postInstall = '' HOST_PATH=$out/bin patchShebangs --host $out - ${lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) '' + ${lib.optionalString canExecute '' $out/bin/${self.meta.mainProgram} --completion-bash > ${self.meta.mainProgram}.bash installShellCompletion ${self.meta.mainProgram}.bash ''} @@ -231,18 +268,7 @@ let mkdir -p $libv8/lib pushd out/Release/obj.target find . -path "./torque_*/**/*.o" -or -path "./v8*/**/*.o" | sort -u >files - ${if stdenv.buildPlatform.isGnu then '' - ar -cqs $libv8/lib/libv8.a @files - '' else '' - # llvm-ar supports response files, so take advantage of it if it’s available. - if [ "$(basename $(readlink -f $(command -v ar)))" = "llvm-ar" ]; then - ar -cqs $libv8/lib/libv8.a @files - else - cat files | while read -r file; do - ar -cqS $libv8/lib/libv8.a $file - done - fi - ''} + $AR -cqs $libv8/lib/libv8.a @files popd # copy v8 headers @@ -284,14 +310,6 @@ let platforms = platforms.linux ++ platforms.darwin; mainProgram = "node"; knownVulnerabilities = optional (versionOlder version "18") "This NodeJS release has reached its end of life. See https://nodejs.org/en/about/releases/."; - - # Node.js build system does not have separate host and target OS - # configurations (architectures are defined as host_arch and target_arch, - # but there is no such thing as host_os and target_os). - # - # We may be missing something here, but it doesn’t look like it is - # possible to cross-compile between different operating systems. - broken = stdenv.buildPlatform.parsed.kernel.name != stdenv.hostPlatform.parsed.kernel.name; }; passthru.python = python; # to ensure nodeEnv uses the same version diff --git a/pkgs/development/web/nodejs/v18.nix b/pkgs/development/web/nodejs/v18.nix index 1101fbdf725d5..b69f92d51c3a2 100644 --- a/pkgs/development/web/nodejs/v18.nix +++ b/pkgs/development/web/nodejs/v18.nix @@ -26,6 +26,7 @@ buildNodejs { version = "18.20.4"; sha256 = "sha256-p2x+oblq62ljoViAYmDICUtiRNZKaWUp0CBUe5qVyio="; patches = [ + ./configure-emulator-node18.patch ./disable-darwin-v8-system-instrumentation.patch ./bypass-darwin-xcrun-node16.patch ./revert-arm64-pointer-auth.patch diff --git a/pkgs/development/web/nodejs/v20.nix b/pkgs/development/web/nodejs/v20.nix index bf0e2f7feffb0..ced82cb0d7ee7 100644 --- a/pkgs/development/web/nodejs/v20.nix +++ b/pkgs/development/web/nodejs/v20.nix @@ -15,6 +15,7 @@ buildNodejs { version = "20.15.1"; sha256 = "sha256-/dU6VynZNmkaKhFRBG+0iXchy4sPyir5V4I6m0D+DDQ="; patches = [ + ./configure-emulator.patch ./disable-darwin-v8-system-instrumentation-node19.patch ./bypass-darwin-xcrun-node16.patch ./node-npm-build-npm-package-logic.patch diff --git a/pkgs/development/web/nodejs/v22.nix b/pkgs/development/web/nodejs/v22.nix index 44737d2ba8eca..58a34f8aff2a3 100644 --- a/pkgs/development/web/nodejs/v22.nix +++ b/pkgs/development/web/nodejs/v22.nix @@ -15,6 +15,7 @@ buildNodejs { version = "22.4.1"; sha256 = "sha256-ZfyFf1qoJWqvyQCzRMARXJrq4loCVB/Vzg29Tf0cX7k="; patches = [ + ./configure-emulator.patch ./disable-darwin-v8-system-instrumentation-node19.patch ./bypass-darwin-xcrun-node16.patch ./node-npm-build-npm-package-logic.patch