Repository: nodejs/node
Pull request: build: add flag to compile V8 with Temporal support

N.B.: it only works if you provide the `temporal_capi` a shared library.

I've tested it with:

- shared ICU: `nix-shell --arg withTemporal true --run 'make build-ci -j12'`
- without ICU: `nix-shell --arg icu null --arg withTemporal true --run 'make build-ci -j12'`
- small ICU: `nix-shell --argstr icu small --arg withTemporal true --run 'make build-ci -j12'`

I needed to apply the following patch, IIUC that's related to targeting a different version of `temporal_capi` (already fixed upstream https://github.com/v8/v8/commit/0cbecc1aa10df9ce1e5380a76fc5711f565491d5 and https://github.com/v8/v8/commit/e0dc7e8de2fbc08f714e4f2f3f08dd745d953da6), and V8 using some non-exposed ICU internals.

```diff
diff --git a/deps/v8/src/objects/js-date-time-format.cc b/deps/v8/src/objects/js-date-time-format.cc
index 496ce5325ac..2be0ada1992 100644
--- a/deps/v8/src/objects/js-date-time-format.cc
+++ b/deps/v8/src/objects/js-date-time-format.cc
@@ -946,8 +946,6 @@ bool CalendarEquals(temporal_rs::AnyCalendarKind kind,
     case HijriTabularTypeIIFriday:
       return other_kind == "islamic-civil" || other_kind == "islamicc" ||
              other_kind == "islamic";
-    case HijriSimulatedMecca:
-      return other_kind == "islamic-rgsa" || other_kind == "islamic";
     case HijriTabularTypeIIThursday:
       return other_kind == "islamic-tbla" || other_kind == "islamic";
     case HijriUmmAlQura:
diff --git a/deps/v8/src/objects/js-temporal-zoneinfo64.cc b/deps/v8/src/objects/js-temporal-zoneinfo64.cc
index 99dd3a84c1e..b6b399c10dc 100644
--- a/deps/v8/src/objects/js-temporal-zoneinfo64.cc
+++ b/deps/v8/src/objects/js-temporal-zoneinfo64.cc
@@ -11,12 +11,44 @@
 #include "temporal_rs/TimeZone.hpp"
 
 #ifdef V8_INTL_SUPPORT
-#include "udatamem.h"
+#include "unicode/udata.h"
+typedef struct  {
+    uint16_t    headerSize;
+    uint8_t     magic1;
+    uint8_t     magic2;
+} MappedData;
+typedef struct  {
+    MappedData  dataHeader;
+    UDataInfo   info;
+} DataHeader;
+typedef struct {
+    void*  Lookup;
+    void*  NumEntries; 
+} commonDataFuncs;
+struct UDataMemory {
+    const commonDataFuncs  *vFuncs;      /* Function Pointers for accessing TOC             */
+
+    const DataHeader *pHeader;     /* Header of the memory being described by this    */
+                                   /*   UDataMemory object.                           */
+    const void       *toc;         /* For common memory, table of contents for        */
+                                   /*   the pieces within.                            */
+    UBool             heapAllocated;  /* True if this UDataMemory Object is on the    */
+                                   /*  heap and thus needs to be deleted when closed. */
+
+    void             *mapAddr;     /* For mapped or allocated memory, the start addr. */
+                                   /* Only non-null if a close operation should unmap */
+                                   /*  the associated data.                           */
+    void             *map;         /* Handle, or other data, OS dependent.            */
+                                   /* Only non-null if a close operation should unmap */
+                                   /*  the associated data, and additional info       */
+                                   /*   beyond the mapAddr is needed to do that.      */
+    int32_t           length;      /* Length of the data in bytes; -1 if unknown.     */
+};
 #else
 // Defined in builtins-temporal-zoneinfo64-data.cc, generated by
 // include-file-as-bytes.py
-extern "C" uint32_t zoneinfo64_static_data[];
-extern "C" size_t zoneinfo64_static_data_len;
+static uint32_t zoneinfo64_static_data[] = {};
+static size_t zoneinfo64_static_data_len = 0;
 #endif
 
 namespace v8::internal {
@@ -33,7 +65,7 @@ ZoneInfo64Provider::ZoneInfo64Provider() {
   // NOT udata_getLength: this ignores the header,
   // and we're parsing resb files with the header
   auto length = memory->length;
-  const void* data = udata_getRawMemory(memory);
+  const void* data = udata_getMemory(memory);
   DCHECK_WITH_MSG(length % 4 == 0, "ICU4C should align udata to uint32_t");
   if (length % 4 != 0) {
     // This really shouldn't happen: ICU4C aligns these files
-- 
2.51.0
```

The first commit is cherry-picked from https://github.com/nodejs/node/pull/60693, cc @targos

Refs: https://github.com/nodejs/node/issues/58730

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: configure.py
patch: @@ -573,6 +573,28 @@
     dest='shared_sqlite_libpath',
     help='a directory to search for the shared sqlite DLL')
 
+shared_optgroup.add_argument('--shared-temporal_capi',
+    action='store_true',
+    dest='shared_temporal_capi',
+    default=None,
+    help='link to a shared temporal_capi DLL instead of static linking')
+
+shared_optgroup.add_argument('--shared-temporal_capi-includes',
+    action='store',
+    dest='shared_temporal_capi_includes',
+    help='directory containing temporal_capi header files')
+
+shared_optgroup.add_argument('--shared-temporal_capi-libname',
+    action='store',
+    dest='shared_temporal_capi_libname',
+    default='temporal_capi',
+    help='alternative lib name to link to [default: %(default)s]')
+
+shared_optgroup.add_argument('--shared-temporal_capi-libpath',
+    action='store',
+    dest='shared_temporal_capi_libpath',
+    help='a directory to search for the shared temporal_capi DLL')
+
 shared_optgroup.add_argument('--shared-zstd',
     action='store_true',
     dest='shared_zstd',
@@ -1009,6 +1031,13 @@
     default=None,
     help='Enable the built-in snapshot compression in V8.')
 
+
+parser.add_argument('--v8-enable-temporal-support',
+    action='store_true',
+    dest='v8_enable_temporal_support',
+    default=None,
+    help='Enable Temporal support in V8.')
+
 parser.add_argument('--node-builtin-modules-path',
     action='store',
     dest='node_builtin_modules_path',
@@ -1802,6 +1831,7 @@ 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
   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)
@@ -2357,6 +2387,7 @@ def make_bin_override():
 configure_library('nghttp3', output, pkgname='libnghttp3')
 configure_library('ngtcp2', output, pkgname='libngtcp2')
 configure_sqlite(output);
+configure_library('temporal_capi', output)
 configure_library('uvwasi', output)
 configure_library('zstd', output, pkgname='libzstd')
 configure_v8(output, configurations)

filename: shell.nix
patch: @@ -1,16 +1,20 @@
 {
   pkgs ? import ./tools/nix/pkgs.nix { },
   loadJSBuiltinsDynamically ? true, # Load `lib/**.js` from disk instead of embedding
+  withTemporal ? false,
   ncu-path ? null, # Provide this if you want to use a local version of NCU
   icu ? pkgs.icu,
-  sharedLibDeps ? import ./tools/nix/sharedLibDeps.nix { inherit pkgs; },
+  sharedLibDeps ? import ./tools/nix/sharedLibDeps.nix { inherit pkgs withTemporal; },
   ccache ? pkgs.ccache,
   ninja ? pkgs.ninja,
   devTools ? import ./tools/nix/devTools.nix { inherit pkgs ncu-path; },
   benchmarkTools ? import ./tools/nix/benchmarkTools.nix { inherit pkgs; },
   extraConfigFlags ? [
     "--without-npm"
     "--debug-node"
+  ]
+  ++ pkgs.lib.optionals withTemporal [
+    "--v8-enable-temporal-support"
   ],
 }:
 
@@ -22,7 +26,7 @@ in
 pkgs.mkShell {
   inherit (pkgs.nodejs_latest) nativeBuildInputs;
 
-  buildInputs = builtins.attrValues sharedLibDeps ++ pkgs.lib.optionals useSharedICU [ icu ];
+  buildInputs = builtins.attrValues sharedLibDeps ++ pkgs.lib.optional useSharedICU icu;
 
   packages = [
     ccache

filename: tools/nix/sharedLibDeps.nix
patch: @@ -1,5 +1,6 @@
 {
   pkgs ? import ./pkgs.nix { },
+  withTemporal ? false,
 }:
 {
   inherit (pkgs)
@@ -45,3 +46,6 @@
     ];
   });
 }
+// (pkgs.lib.optionalAttrs withTemporal {
+  inherit (pkgs) temporal_capi;
+})

filename: tools/v8_gypfiles/features.gypi
patch: @@ -295,6 +295,10 @@
     # add a dependency on the ICU library.
     'v8_enable_i18n_support%': 1,
 
+    # Enable Temporal API. Enabling this feature will
+    # add a dependency on the temporal_rs library.
+    'v8_enable_temporal_support%': 0,
+
     # Lite mode disables a number of performance optimizations to reduce memory
     # at the cost of performance.
     # Sets --DV8_LITE_MODE.
@@ -410,6 +414,9 @@
       ['v8_enable_i18n_support==1', {
         'defines': ['V8_INTL_SUPPORT',],
       }],
+      ['v8_enable_temporal_support==1', {
+        'defines': ['V8_TEMPORAL_SUPPORT',],
+      }],
       # Refs: https://github.com/nodejs/node/pull/23801
       # ['v8_enable_handle_zapping==1', {
       #  'defines': ['ENABLE_HANDLE_ZAPPING',],

filename: tools/v8_gypfiles/v8.gyp
patch: @@ -27,6 +27,11 @@
           '<!@pymod_do_main(GN-scraper "<(V8_ROOT)/BUILD.gn"  "torque_files =.*?v8_enable_i18n_support.*?torque_files \\+= ")',
         ],
       }],
+      ['v8_enable_temporal_support==1', {
+        'torque_files': [
+          '<!@pymod_do_main(GN-scraper "<(V8_ROOT)/BUILD.gn"  "torque_files =.*?v8_enable_temporal_support.*?torque_files \\+= ")',
+        ],
+      }],
       ['v8_enable_webassembly==1', {
         'torque_files': [
           '<!@pymod_do_main(GN-scraper "<(V8_ROOT)/BUILD.gn"  "torque_files =.*?v8_enable_webassembly.*?torque_files \\+= ")',
@@ -660,6 +665,11 @@
               '<!@pymod_do_main(GN-scraper "<(V8_ROOT)/BUILD.gn"  "v8_header_set.\\"v8_internal_headers\\".*?v8_enable_snapshot_compression.*?sources \\+= ")',
             ],
           }],
+          ['v8_enable_temporal_support==1', {
+            'sources': [
+              '<!@pymod_do_main(GN-scraper "<(V8_ROOT)/BUILD.gn"  "v8_header_set.\\"v8_internal_headers\\".*?v8_enable_temporal_support.*?sources \\+= ")',
+            ],
+          }],
           ['v8_enable_sparkplug==1', {
             'sources': [
               '<!@pymod_do_main(GN-scraper "<(V8_ROOT)/BUILD.gn"  "v8_header_set.\\"v8_internal_headers\\".*?v8_enable_sparkplug.*?sources \\+= ")',
@@ -1117,6 +1127,11 @@
             '<!@pymod_do_main(GN-scraper "<(V8_ROOT)/BUILD.gn"  "\\"v8_base_without_compiler.*?v8_enable_snapshot_compression.*?sources \\+= ")',
           ],
         }],
+        ['v8_enable_temporal_support==1', {
+          'sources': [
+            '<!@pymod_do_main(GN-scraper "<(V8_ROOT)/BUILD.gn"  "\\"v8_base_without_compiler.*?v8_enable_temporal_support.*?sources \\+= ")',
+          ],
+        }],
         ['v8_enable_sparkplug==1', {
           'sources': [
             '<!@pymod_do_main(GN-scraper "<(V8_ROOT)/BUILD.gn"  "\\"v8_base_without_compiler.*?v8_enable_sparkplug.*?sources \\+= ")',