From a96cf6342b435d70772d66eb7e1d266aeee39a4e Mon Sep 17 00:00:00 2001 From: StefanStojanovic Date: Mon, 6 Apr 2026 21:29:57 +0200 Subject: [PATCH 1/2] build,win: fix Temporal build --- configure.py | 8 ++++ deps/crates/cargo_build.py | 60 +++++++++++++++++++++++++++ deps/crates/crates.gyp | 83 +++++++++++++++++++++++++++++--------- 3 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 deps/crates/cargo_build.py diff --git a/configure.py b/configure.py index 9ec5b2fc4694c6..5fe79ec4cfb896 100755 --- a/configure.py +++ b/configure.py @@ -1780,6 +1780,14 @@ def configure_node(o): o['variables']['target_arch'] = target_arch o['variables']['node_byteorder'] = sys.byteorder + # On Windows, cargo may default to the GNU target (e.g. x86_64-pc-windows-gnu) + # but Node.js requires MSVC-compatible libraries. Set explicit Rust target + # triple for the target architecture. + o['variables']['cargo_rust_target'] = '' + if flavor == 'win': + o['variables']['cargo_rust_target'] = \ + 'aarch64-pc-windows-msvc' if target_arch == 'arm64' else 'x86_64-pc-windows-msvc' + # Allow overriding the compiler - needed by embedders. if options.use_clang: o['variables']['clang'] = 1 diff --git a/deps/crates/cargo_build.py b/deps/crates/cargo_build.py new file mode 100644 index 00000000000000..eaac737c77cc5f --- /dev/null +++ b/deps/crates/cargo_build.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +"""Invoke cargo with the correct Rust target on Windows cross-compilation. + +Works around three issues: GYP's MSVS generator mangles executable names in +host-toolset actions (breaking direct cargo calls), GYP cannot set different +variable values per toolset, and cargo's output directory layout uses Rust +target triples while MSBuild expects platform names (x64/arm64). This script +reads MSBuild's $(Platform) to select the right Rust target and copies the +built library to a path MSBuild can resolve per-project. +""" + +import os +import shutil +import subprocess +import sys + + +def main(): + # Arguments: [cargo_args...] + if len(sys.argv) < 3: + print('Usage: cargo_build.py [cargo_args...]', file=sys.stderr) + sys.exit(1) + + platform = sys.argv[1] # x64 or arm64 + output_dir = sys.argv[2] # SHARED_INTERMEDIATE_DIR + cargo_args = sys.argv[3:] + build_profile = 'release' if '--release' in cargo_args else 'debug' + + cargo = os.environ.get('CARGO', 'cargo') + if not os.path.isabs(cargo) and shutil.which(cargo) is None: + home = os.environ.get('USERPROFILE', '') + cargo_home = os.path.join(home, '.cargo', 'bin', 'cargo.exe') + if os.path.isfile(cargo_home): + cargo = cargo_home + + rust_target_map = { + 'x64': 'x86_64-pc-windows-msvc', + 'arm64': 'aarch64-pc-windows-msvc', + } + rust_target = rust_target_map.get(platform) + if rust_target is None: + print(f'Unsupported platform: {platform}', file=sys.stderr) + sys.exit(1) + + cmd = [cargo, 'rustc', '--target', rust_target, '--target-dir', output_dir] + cargo_args + ret = subprocess.call(cmd) + if ret != 0: + sys.exit(ret) + + # Copy output to the platform-specific directory that MSBuild expects. + src = os.path.join(output_dir, rust_target, build_profile, 'node_crates.lib') + dst_dir = os.path.join(output_dir, platform, build_profile) + os.makedirs(dst_dir, exist_ok=True) + dst = os.path.join(dst_dir, 'node_crates.lib') + shutil.copy2(src, dst) + + +if __name__ == '__main__': + main() diff --git a/deps/crates/crates.gyp b/deps/crates/crates.gyp index 106000028ea794..fa2387281839f2 100644 --- a/deps/crates/crates.gyp +++ b/deps/crates/crates.gyp @@ -2,24 +2,46 @@ 'variables': { 'cargo%': 'cargo', 'cargo_vendor_dir': './vendor', + 'cargo_rust_target%': '', }, 'conditions': [ ['build_type == "Release"', { 'variables': { 'cargo_build_flags': ['--release'], - 'node_crates_libpath': '<(SHARED_INTERMEDIATE_DIR)/release/<(STATIC_LIB_PREFIX)node_crates<(STATIC_LIB_SUFFIX)', }, + 'conditions': [ + ['cargo_rust_target!=""', { + 'variables': { + 'node_crates_libpath': '<(SHARED_INTERMEDIATE_DIR)/$(Platform)/release/node_crates.lib', + }, + }, { + 'variables': { + 'node_crates_libpath': '<(SHARED_INTERMEDIATE_DIR)/release/<(STATIC_LIB_PREFIX)node_crates<(STATIC_LIB_SUFFIX)', + }, + }], + ], }, { 'variables': { 'cargo_build_flags': [], - 'node_crates_libpath': '<(SHARED_INTERMEDIATE_DIR)/debug/<(STATIC_LIB_PREFIX)node_crates<(STATIC_LIB_SUFFIX)', }, + 'conditions': [ + ['cargo_rust_target!=""', { + 'variables': { + 'node_crates_libpath': '<(SHARED_INTERMEDIATE_DIR)/$(Platform)/debug/node_crates.lib', + }, + }, { + 'variables': { + 'node_crates_libpath': '<(SHARED_INTERMEDIATE_DIR)/debug/<(STATIC_LIB_PREFIX)node_crates<(STATIC_LIB_SUFFIX)', + }, + }], + ], }] ], 'targets': [ { 'target_name': 'node_crates', 'type': 'none', + 'toolsets': ['host', 'target'], 'hard_dependency': 1, 'sources': [ 'Cargo.toml', @@ -39,29 +61,54 @@ }], ], }, - 'actions': [ - { - 'action_name': 'cargo_build', - 'inputs': [ - '<@(_sources)' - ], - 'outputs': [ - '<(node_crates_libpath)' + 'conditions': [ + ['cargo_rust_target!=""', { + 'actions': [ + { + 'action_name': 'cargo_build', + 'inputs': [ + '<@(_sources)' + ], + 'outputs': [ + '<(node_crates_libpath)' + ], + 'action': [ + '<(python)', + 'cargo_build.py', + '$(Platform)', + '<(SHARED_INTERMEDIATE_DIR)', + '<@(cargo_build_flags)', + '--frozen', + ], + } ], - 'action': [ - '<(cargo)', - 'rustc', - '<@(cargo_build_flags)', - '--frozen', - '--target-dir', - '<(SHARED_INTERMEDIATE_DIR)' + }, { + 'actions': [ + { + 'action_name': 'cargo_build', + 'inputs': [ + '<@(_sources)' + ], + 'outputs': [ + '<(node_crates_libpath)' + ], + 'action': [ + '<(cargo)', + 'rustc', + '<@(cargo_build_flags)', + '--frozen', + '--target-dir', + '<(SHARED_INTERMEDIATE_DIR)' + ], + } ], - } + }], ], }, { 'target_name': 'temporal_capi', 'type': 'none', + 'toolsets': ['host', 'target'], 'sources': [], 'dependencies': [ 'node_crates', From 4fffe5ebc3c4f3d59deea601afa025e3f6e537c0 Mon Sep 17 00:00:00 2001 From: Richard Lau Date: Fri, 10 Apr 2026 17:05:24 +0000 Subject: [PATCH 2/2] build: enable Temporal by default Enabling Temporal support requires `cargo` and `rustc`, which are new build toolchain requirements. Add a `--v8-disable-temporal-support` option to `configure.py` to explicitly opt-out of Temporal support (i.e. no need for Rust). If the existing `--v8-enable-temporal-support` option is not explicitly passed to `configure.py`: - Attempt to detect `cargo` and `rustc`. - If neither `cargo` and `rustc` are detected, print a warning and disable Temporal support. - If both `cargo` and `rustc` are detected, enable Temporal support. If `--v8-enable-temporal-support` is passed to `configure.py`, then the build will error and stop if `cargo` and/or `rustc` are not detected. To avoid ambiguity, `configure.py` will error and stop if both `--v8-disable-temporal-support` and `--v8-enable-temporal-support` are used. Signed-off-by: Richard Lau --- configure.py | 81 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/configure.py b/configure.py index 5fe79ec4cfb896..4a6d43cb789e05 100755 --- a/configure.py +++ b/configure.py @@ -1156,6 +1156,11 @@ default=None, help='Enable the built-in snapshot compression in V8.') +parser.add_argument('--v8-disable-temporal-support', + action='store_true', + dest='v8_disable_temporal_support', + default=None, + help='Disable Temporal support in V8.') parser.add_argument('--v8-enable-temporal-support', action='store_true', @@ -1450,11 +1455,7 @@ def get_cargo_version(cargo): stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) except OSError: - error('''No acceptable cargo found! - - Please make sure you have cargo installed on your system and/or - consider adjusting the CARGO environment variable if you have installed - it in a non-standard prefix.''') + return '0.0' with proc: cargo_ret = to_utf8(proc.communicate()[0]) @@ -1473,11 +1474,7 @@ def get_rustc_version(rustc): stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) except OSError: - error('''No acceptable rustc compiler found! - - Please make sure you have a rust compiler installed on your system and/or - consider adjusting the RUSTC environment variable if you have installed - it in a non-standard prefix.''') + return '0.0' with proc: rustc_ret = to_utf8(proc.communicate()[0]) @@ -1538,23 +1535,51 @@ def check_compiler(o): o['variables']['llvm_version'] = get_llvm_version(CC) if is_clang else '0.0' # cargo and rustc are needed for Temporal. - if options.v8_enable_temporal_support and not options.shared_temporal_capi: + if not options.v8_disable_temporal_support or not options.shared_temporal_capi: # Minimum cargo and rustc versions should match values in BUILDING.md. min_cargo_ver_tuple = (1, 82) min_rustc_ver_tuple = (1, 82) cargo = os.environ.get('CARGO', 'cargo') cargo_ver = get_cargo_version(cargo) print_verbose(f'Detected cargo (CARGO={cargo}): {cargo_ver}') - cargo_ver_tuple = tuple(map(int, cargo_ver.split('.'))) - if cargo_ver_tuple < min_cargo_ver_tuple: - warn(f'cargo {cargo_ver} too old, need cargo {".".join(map(str, min_cargo_ver_tuple))}') + if cargo_ver == '0.0': + # Error if --v8-enable-temporal-support is explicitly set, + # otherwise disable support for Temporal. + if options.v8_enable_temporal_support: + error('''No acceptable cargo found! + + Enabling Temporal support requires cargo. + Please make sure you have cargo installed on your system and/or + consider adjusting the CARGO environment variable if you have installed + it in a non-standard prefix.''') + else: + warn('cargo not found! Support for Temporal will be disabled.') + options.v8_disable_temporal_support = True + else: + cargo_ver_tuple = tuple(map(int, cargo_ver.split('.'))) + if cargo_ver_tuple < min_cargo_ver_tuple: + warn(f'cargo {cargo_ver} too old, need cargo {".".join(map(str, min_cargo_ver_tuple))}') # cargo supports RUSTC environment variable to override "rustc". rustc = os.environ.get('RUSTC', 'rustc') rustc_ver = get_rustc_version(rustc) - print_verbose(f'Detected rustc (RUSTC={rustc}): {rustc_ver}') - rust_ver_tuple = tuple(map(int, rustc_ver.split('.'))) - if rust_ver_tuple < min_rustc_ver_tuple: - warn(f'rustc {rustc_ver} too old, need rustc {".".join(map(str, min_rustc_ver_tuple))}') + if rustc_ver == '0.0': + # Error if --v8-enable-temporal-support is explicitly set, + # otherwise disable support for Temporal. + if options.v8_enable_temporal_support: + error('''No acceptable rustc compiler found! + + Enabling Temporal support requires a Rust toolchain. + Please make sure you have a Rust compiler installed on your system and/or + consider adjusting the RUSTC environment variable if you have installed + it in a non-standard prefix.''') + else: + warn(f'{rustc} not found! Support for Temporal will be disabled.') + options.v8_disable_temporal_support = True + else: + print_verbose(f'Detected rustc (RUSTC={rustc}): {rustc_ver}') + rust_ver_tuple = tuple(map(int, rustc_ver.split('.'))) + if rust_ver_tuple < min_rustc_ver_tuple: + warn(f'rustc {rustc_ver} too old, need rustc {".".join(map(str, min_rustc_ver_tuple))}') # Need xcode_version or gas_version when openssl asm files are compiled. if options.without_ssl or options.openssl_no_asm or options.shared_openssl: @@ -2065,7 +2090,19 @@ def configure_v8(o, configs): o['variables']['v8_enable_external_code_space'] = 1 if options.enable_pointer_compression else 0 o['variables']['v8_enable_31bit_smis_on_64bit_arch'] = 1 if options.enable_pointer_compression else 0 o['variables']['v8_enable_extensible_ro_snapshot'] = 0 - o['variables']['v8_enable_temporal_support'] = 1 if options.v8_enable_temporal_support else 0 + # TODO(richardlau): Temporal objects in V8 currently reference a private + # ICU header file and fail to compile with shared ICU or no ICU. For now, + # if auto-detecting, warn and disable Temporal in those cases. + # Refs: https://github.com/nodejs/node/issues/62676 + if (not options.v8_disable_temporal_support and not options.v8_enable_temporal_support): + match options.with_intl: + case 'none': + warn('Temporal support disabled when compiling without ICU') + options.v8_disable_temporal_support = True + case 'system-icu': + warn('Temporal support disabled when compiling with a shared ICU library') + options.v8_disable_temporal_support = True + o['variables']['v8_enable_temporal_support'] = 0 if options.v8_disable_temporal_support else 1 o['variables']['v8_trace_maps'] = 1 if options.trace_maps else 0 o['variables']['node_use_v8_platform'] = b(not options.without_v8_platform) o['variables']['node_use_bundled_v8'] = b(not options.without_bundled_v8) @@ -2097,6 +2134,10 @@ def configure_v8(o, configs): raise Exception( 'Only one of the --v8-enable-object-print or --v8-disable-object-print options ' 'can be specified at a time.') + if all(opt in sys.argv for opt in ['--v8-enable-temporal-support', '--v8-disable-temporal-support']): + raise Exception( + 'Only one of the --v8-enable-temporal-support or --v8-disable-temporal-support options ' + 'can be specified at a time.') if sys.platform != 'darwin': if o['variables']['v8_enable_webassembly'] and o['variables']['target_arch'] == 'x64': o['variables']['v8_enable_wasm_simd256_revec'] = 1 @@ -2762,7 +2803,7 @@ def make_bin_override(): # will fail to run python scripts. gyp_args += ['-Dpython=' + python] -if options.v8_enable_temporal_support and not options.shared_temporal_capi: +if not options.v8_disable_temporal_support or not options.shared_temporal_capi: cargo = os.environ.get('CARGO') if cargo: gyp_args += ['-Dcargo=' + cargo]