Skip to content

Commit 39b5bb5

Browse files
author
jblac
committed
build: add --shared-v8 flags
Add first-class --shared-v8 support using the same configure_library() pattern used by every other shared dependency in Node.js. This provides --shared-v8-includes, --shared-v8-libpath, and --shared-v8-libname flags with pkg-config fallback. When --shared-v8 is used, the bundled deps/v8/ is completely excluded from compilation. No v8 GYP targets are built, no bundled headers are referenced. The external v8 is linked via the standard shared mechanism. Configure-time validation checks the shared v8 against Node.js requirements. Hard errors on: version mismatch, v8 sandbox enabled, extensible RO snapshot enabled, pointer compression ABI mismatch (auto detected). warning on: V8_PROMISE_INTERNAL_FIELD_COUNT < 1 (async_hooks uses a slower fallback). These are the "floating patch" requirements decomposed into verifiable build flags, not source patches. Snapshot generation (node_mksnapshot) works correctly with shared v8 because it links against the Node.js library, which transitively links against whatever v8 is configured. No snapshot disabling needed. The existing --without-bundled-v8 flag is deprecated and aliased to --shared-v8 for backwards compatibility. Fixes: #53509
1 parent 330e3ee commit 39b5bb5

File tree

6 files changed

+309
-33
lines changed

6 files changed

+309
-33
lines changed

BUILDING.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,68 @@ shipping with these options to:
11091109
external dependencies. There may be little or no test coverage
11101110
within the Node.js project CI for these non-default options.
11111111

1112+
### Shared V8
1113+
1114+
Node.js can be built against a shared V8 library using the `--shared-v8`
1115+
configure flag. This completely excludes the bundled `deps/v8/` from
1116+
compilation and links against an external V8 instead.
1117+
1118+
```console
1119+
./configure --shared-v8 \
1120+
--shared-v8-includes=/usr/include \
1121+
--shared-v8-libpath=/usr/lib \
1122+
--shared-v8-libname=v8,v8_libplatform
1123+
```
1124+
1125+
The following flags are available:
1126+
1127+
* `--shared-v8`: Link to a shared V8 library instead of building the
1128+
bundled copy.
1129+
* `--shared-v8-includes=<path>`: Directory containing V8 header files
1130+
(`v8.h`, `v8-platform.h`, `v8config.h`, `v8-version.h`, etc.).
1131+
* `--shared-v8-libpath=<path>`: Directory containing the shared V8
1132+
library.
1133+
* `--shared-v8-libname=<name>`: Library name(s) to link against,
1134+
comma-separated for multiple. Default: `v8,v8_libplatform`.
1135+
1136+
The shared V8 must meet the build configuration requirements listed
1137+
below. Configure-time validation checks these automatically; hard
1138+
requirements produce errors, performance requirements produce warnings.
1139+
1140+
#### V8 build configuration spec
1141+
1142+
**Hard requirements** (configure errors if violated):
1143+
1144+
| Flag | Required value | Reason |
1145+
|------|---------------|--------|
1146+
| V8 major.minor version | Must match bundled `deps/v8/` | ABI compatibility (e.g., 14.3.x) |
1147+
| `v8_enable_sandbox` | `false` | Node.js C++ backing stores are not sandbox-allocated |
1148+
| `v8_enable_extensible_ro_snapshot` | `false` | Snapshot compatibility |
1149+
| `v8_enable_pointer_compression` | Auto-detected | ABI: struct layout must match. Node.js reads the shared V8's `v8config.h` and auto-matches. |
1150+
1151+
**Performance requirements** (configure warns if not met):
1152+
1153+
| Flag | Recommended value | Reason |
1154+
|------|------------------|--------|
1155+
| `v8_promise_internal_field_count` | `1` | Fast async\_hooks Promise tracking. Fallback to symbol-property tracking exists when 0, but is ~2x slower for promise-heavy workloads. |
1156+
1157+
**Recommended** (not validated):
1158+
1159+
| Flag | Recommended value | Reason |
1160+
|------|------------------|--------|
1161+
| `v8_use_siphash` | `true` | Hash table randomization |
1162+
| `v8_enable_webassembly` | `true` | Unless building with `--v8-lite-mode` |
1163+
1164+
**Standard V8 embedder API** (must be present in any compliant V8):
1165+
1166+
* `v8::Context::SetPromiseHooks()`
1167+
* `v8::Isolate::SetPromiseHook()`
1168+
* `v8::Context::SetAlignedPointerInEmbedderData()`
1169+
* `v8::SnapshotCreator` / `v8::StartupData`
1170+
* `v8::ScriptCompiler::CreateCodeCache()`
1171+
1172+
The deprecated `--without-bundled-v8` flag is aliased to `--shared-v8`.
1173+
11121174
## Note for downstream distributors of Node.js
11131175

11141176
The Node.js ecosystem is reliant on ABI compatibility within a major release.

configure.py

Lines changed: 158 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,28 @@
730730
dest='shared_zstd_libpath',
731731
help='a directory to search for the shared zstd DLL')
732732

733+
shared_optgroup.add_argument('--shared-v8',
734+
action='store_true',
735+
dest='shared_v8',
736+
default=None,
737+
help='link to a shared v8 DLL instead of static linking')
738+
739+
shared_optgroup.add_argument('--shared-v8-includes',
740+
action='store',
741+
dest='shared_v8_includes',
742+
help='directory containing v8 header files')
743+
744+
shared_optgroup.add_argument('--shared-v8-libname',
745+
action='store',
746+
dest='shared_v8_libname',
747+
default='v8,v8_libplatform',
748+
help='alternative lib name to link to [default: %(default)s]')
749+
750+
shared_optgroup.add_argument('--shared-v8-libpath',
751+
action='store',
752+
dest='shared_v8_libpath',
753+
help='a directory to search for the shared v8 DLL')
754+
733755
parser.add_argument_group(shared_optgroup)
734756

735757
for builtin in shareable_builtins:
@@ -1087,8 +1109,7 @@
10871109
action='store_true',
10881110
dest='without_bundled_v8',
10891111
default=False,
1090-
help='do not use V8 includes from the bundled deps folder. ' +
1091-
'(This mode is not officially supported for regular applications)')
1112+
help='DEPRECATED: Use --shared-v8 instead.')
10921113

10931114
parser.add_argument('--verbose',
10941115
action='store_true',
@@ -2015,6 +2036,110 @@ def configure_library(lib, output, pkgname=None):
20152036
output['libraries'] += pkg_libs.split()
20162037

20172038

2039+
def read_v8_version_from_header(header_path):
2040+
"""Read V8 version components from v8-version.h."""
2041+
version = {}
2042+
with open(header_path, 'r') as f:
2043+
for line in f:
2044+
for component in ('V8_MAJOR_VERSION', 'V8_MINOR_VERSION',
2045+
'V8_BUILD_NUMBER', 'V8_PATCH_LEVEL'):
2046+
if '#define ' + component in line:
2047+
version[component] = int(line.strip().split()[-1])
2048+
return version
2049+
2050+
def parse_promise_field_count(header_path):
2051+
"""Read V8_PROMISE_INTERNAL_FIELD_COUNT from v8-promise.h."""
2052+
with open(header_path, 'r') as f:
2053+
for line in f:
2054+
if '#define V8_PROMISE_INTERNAL_FIELD_COUNT' in line:
2055+
# Line format: #define V8_PROMISE_INTERNAL_FIELD_COUNT <n>
2056+
return int(line.strip().split()[-1])
2057+
return 0 # V8 default if not defined
2058+
2059+
def has_define_in_header(header_path, define_name):
2060+
"""Check if a header file contains a #define for the given name."""
2061+
with open(header_path, 'r') as f:
2062+
for line in f:
2063+
if f'#define {define_name}' in line:
2064+
return True
2065+
return False
2066+
2067+
def find_shared_v8_includes():
2068+
"""Resolve shared V8 include path from --shared-v8-includes or pkg-config."""
2069+
if options.shared_v8_includes:
2070+
return options.shared_v8_includes
2071+
(_, pkg_cflags, _, _) = pkg_config('v8')
2072+
if pkg_cflags:
2073+
for flag in pkg_cflags.split():
2074+
if flag.startswith('-I'):
2075+
return flag[2:]
2076+
return None
2077+
2078+
def validate_shared_v8(shared_includes):
2079+
"""Validate that the shared V8 meets Node.js build configuration requirements.
2080+
Errors are fatal. Configure will not proceed with an incompatible V8."""
2081+
2082+
# --- Version check (hard error on major.minor mismatch) ---
2083+
bundled_header = os.path.join('deps', 'v8', 'include', 'v8-version.h')
2084+
shared_header = os.path.join(shared_includes, 'v8-version.h')
2085+
if not os.path.exists(shared_header):
2086+
alt = os.path.join(shared_includes, 'v8', 'v8-version.h')
2087+
if os.path.exists(alt):
2088+
shared_header = alt
2089+
else:
2090+
error('Could not find v8-version.h in shared V8 includes at '
2091+
f'{shared_includes}. Cannot validate V8 compatibility. '
2092+
'Use --shared-v8-includes to specify the correct path.')
2093+
2094+
bundled = read_v8_version_from_header(bundled_header)
2095+
shared = read_v8_version_from_header(shared_header)
2096+
b_ver = f"{bundled['V8_MAJOR_VERSION']}.{bundled['V8_MINOR_VERSION']}.{bundled['V8_BUILD_NUMBER']}"
2097+
s_ver = f"{shared['V8_MAJOR_VERSION']}.{shared['V8_MINOR_VERSION']}.{shared['V8_BUILD_NUMBER']}"
2098+
2099+
if bundled['V8_MAJOR_VERSION'] != shared['V8_MAJOR_VERSION'] or \
2100+
bundled['V8_MINOR_VERSION'] != shared['V8_MINOR_VERSION']:
2101+
error(f'Shared V8 version ({s_ver}) does not match required '
2102+
f'({b_ver}). Major and minor version must match.')
2103+
2104+
if bundled['V8_BUILD_NUMBER'] != shared['V8_BUILD_NUMBER']:
2105+
warn(f'Shared V8 build number ({s_ver}) differs from bundled ({b_ver}). '
2106+
f'Build may succeed but runtime behavior may differ.')
2107+
2108+
# --- Promise internal field count (warning, not error; fallback exists) ---
2109+
promise_header = os.path.join(shared_includes, 'v8-promise.h')
2110+
if os.path.exists(promise_header):
2111+
field_count = parse_promise_field_count(promise_header)
2112+
if field_count < 1:
2113+
warn(f'Shared V8 has V8_PROMISE_INTERNAL_FIELD_COUNT={field_count}. '
2114+
f'async_hooks will use slower symbol-property fallback. '
2115+
f'For best performance, rebuild V8 with '
2116+
f'v8_promise_internal_field_count=1.')
2117+
2118+
# --- Pointer compression: auto-detect from shared V8, set Node.js to match ---
2119+
v8config = os.path.join(shared_includes, 'v8config.h')
2120+
if os.path.exists(v8config):
2121+
shared_has_pc = has_define_in_header(v8config, 'V8_COMPRESS_POINTERS')
2122+
if shared_has_pc != bool(options.enable_pointer_compression):
2123+
# Auto-match instead of erroring. Node.js adapts to the shared V8
2124+
options.enable_pointer_compression = shared_has_pc
2125+
warn(f'Auto-{"enabling" if shared_has_pc else "disabling"} pointer '
2126+
f'compression to match shared V8.')
2127+
2128+
shared_has_sandbox = has_define_in_header(v8config, 'V8_ENABLE_SANDBOX')
2129+
if shared_has_sandbox:
2130+
error('Shared V8 was built with V8_ENABLE_SANDBOX. Node.js does not '
2131+
'support the V8 sandbox (backing store pointers are in C++ '
2132+
'memory, not sandbox memory). Rebuild V8 with: '
2133+
'v8_enable_sandbox=false')
2134+
2135+
# --- Extensible RO snapshot: must be disabled for snapshot compatibility ---
2136+
shared_has_ext_ro = has_define_in_header(v8config, 'V8_ENABLE_EXTENSIBLE_RO_SNAPSHOT')
2137+
if shared_has_ext_ro:
2138+
error('Shared V8 was built with V8_ENABLE_EXTENSIBLE_RO_SNAPSHOT. '
2139+
'Node.js requires this to be disabled for snapshot compatibility. '
2140+
'Rebuild V8 with: v8_enable_extensible_ro_snapshot=false')
2141+
2142+
20182143
def configure_v8(o, configs):
20192144
set_configuration_variable(configs, 'v8_enable_v8_checks', release=0, debug=1)
20202145

@@ -2064,16 +2189,40 @@ def configure_v8(o, configs):
20642189
o['variables']['node_enable_v8windbg'] = b(options.enable_v8windbg)
20652190
if options.enable_d8:
20662191
o['variables']['test_isolation_mode'] = 'noop' # Needed by d8.gyp.
2192+
20672193
if options.without_bundled_v8:
2194+
if not options.shared_v8:
2195+
warn('--without-bundled-v8 is deprecated. Use --shared-v8 instead.')
2196+
options.shared_v8 = True
2197+
2198+
if options.shared_v8:
2199+
o['variables']['node_use_bundled_v8'] = b(False)
2200+
20682201
if options.enable_d8:
2069-
raise Exception('--enable-d8 is incompatible with --without-bundled-v8.')
2202+
error('--enable-d8 is incompatible with --shared-v8')
20702203
if options.enable_v8windbg:
2071-
raise Exception('--enable-v8windbg is incompatible with --without-bundled-v8.')
2072-
(pkg_libs, pkg_cflags, pkg_libpath, _) = pkg_config("v8")
2073-
if pkg_libs and pkg_libpath:
2074-
output['libraries'] += [pkg_libpath] + pkg_libs.split()
2075-
if pkg_cflags:
2076-
output['include_dirs'] += [flag for flag in [flag.strip() for flag in pkg_cflags.split('-I')] if flag]
2204+
error('--enable-v8windbg is incompatible with --shared-v8')
2205+
2206+
# Standard configure_library call - handles pkg-config, includes,
2207+
# libpath, libname exactly like every other shared dependency.
2208+
configure_library('v8', o)
2209+
2210+
# Ensure the build can find the shared V8 at mksnapshot execution time
2211+
if options.shared_v8_libpath:
2212+
o['variables']['node_shared_v8_libpath'] = options.shared_v8_libpath
2213+
2214+
# validate shared v8 meets Node.js requirements (hard errors on failure)
2215+
shared_includes = find_shared_v8_includes()
2216+
if shared_includes:
2217+
validate_shared_v8(shared_includes)
2218+
else:
2219+
warn('Could not determine shared v8 include path. '
2220+
'Skipping build configuration validation. '
2221+
'use --shared-v8-includes to enable validation.')
2222+
2223+
else:
2224+
o['variables']['node_use_bundled_v8'] = b(True)
2225+
20772226
if options.static_zoslib_gyp:
20782227
o['variables']['static_zoslib_gyp'] = options.static_zoslib_gyp
20792228
if flavor != 'linux' and options.v8_enable_hugepage:

doc/api/cli.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4261,6 +4261,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
42614261
[context-aware]: addons.md#context-aware-addons
42624262
[debugger]: debugger.md
42634263
[debugging security implications]: https://nodejs.org/en/docs/guides/debugging-getting-started/#security-implications
4264+
[Building to use shared dependencies at runtime]: ../../BUILDING.md#building-to-use-shared-dependencies-at-runtime
42644265
[deprecation warnings]: deprecations.md#list-of-deprecated-apis
42654266
[emit_warning]: process.md#processemitwarningwarning-options
42664267
[environment_variables]: #environment-variables_1

0 commit comments

Comments
 (0)