src: add support for externally shared js builtins
Refs: https://github.com/nodejs/node/issues/44000 - add infra to support externally shared js builtins in support of distos that want to externalize deps that include JS/WASM instead of native code - add support for externalizing - cjs_module_lexer/lexer - cjs_module_lexer/dist/lexer - undici/undici Signed-off-by: Michael Dawson <mdawson@devrus.com> PR-URL: https://github.com/nodejs/node/pull/44376 Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
This commit is contained in:
parent
36805e8524
commit
ca5be26b31
38
BUILDING.md
38
BUILDING.md
@ -1053,6 +1053,44 @@ To make `./myModule.js` available via `require('myModule')` and
|
|||||||
> .\vcbuild link-module './myModule.js' link-module './myModule2.js'
|
> .\vcbuild link-module './myModule.js' link-module './myModule2.js'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Building to use shared dependencies at runtime
|
||||||
|
|
||||||
|
By default Node.js is built so that all dependencies are bundled into
|
||||||
|
the Node.js binary itself. This provides a single binary that includes
|
||||||
|
the correct versions of all dependencies on which it depends.
|
||||||
|
|
||||||
|
Some Node.js distributions, however, prefer to manage dependencies.
|
||||||
|
A number of `configure` options are provided to support this use case.
|
||||||
|
|
||||||
|
* For dependencies with native code, the first set of options allow
|
||||||
|
Node.js to be built so that it uses a shared library
|
||||||
|
at runtime instead of building and including the dependency
|
||||||
|
in the Node.js binary itself. These options are in the
|
||||||
|
`Shared libraries` section of the `configure` help
|
||||||
|
(run `./configure --help` to get the complete list).
|
||||||
|
They provide the ability to enable the use of a shared library,
|
||||||
|
to set the name of the shared library, and to set the paths that
|
||||||
|
contain the include and shared library files.
|
||||||
|
|
||||||
|
* For dependencies with JavaScript code (including WASM), the second
|
||||||
|
set of options allow the Node.js binary to be built so that it loads
|
||||||
|
the JavaScript for dependencies at runtime instead of being built into
|
||||||
|
the Node.js binary itself. These options are in the `Shared builtins`
|
||||||
|
section of the `configure` help
|
||||||
|
(run `./configure --help` to get the complete list). They
|
||||||
|
provide the ability to set the path to an external JavaScript file
|
||||||
|
for the dependency to be used at runtime.
|
||||||
|
|
||||||
|
It is the responsibility of any distribution
|
||||||
|
shipping with these options to:
|
||||||
|
|
||||||
|
* ensure that the shared dependencies available at runtime
|
||||||
|
match what is expected by the Node.js binary. A
|
||||||
|
mismatch may result in crashes or unexpected behavior.
|
||||||
|
* fully test that Node.js operates as expected with the
|
||||||
|
external dependencies. There may be little or no test coverage
|
||||||
|
within the Node.js project CI for these non-default options.
|
||||||
|
|
||||||
## Note for downstream distributors of Node.js
|
## Note for downstream distributors of Node.js
|
||||||
|
|
||||||
The Node.js ecosystem is reliant on ABI compatibility within a major release.
|
The Node.js ecosystem is reliant on ABI compatibility within a major release.
|
||||||
|
31
configure.py
31
configure.py
@ -57,6 +57,11 @@ valid_intl_modes = ('none', 'small-icu', 'full-icu', 'system-icu')
|
|||||||
with open ('tools/icu/icu_versions.json') as f:
|
with open ('tools/icu/icu_versions.json') as f:
|
||||||
icu_versions = json.load(f)
|
icu_versions = json.load(f)
|
||||||
|
|
||||||
|
shareable_builtins = {'cjs_module_lexer/lexer': 'deps/cjs-module-lexer/lexer.js',
|
||||||
|
'cjs_module_lexer/dist/lexer': 'deps/cjs-module-lexer/dist/lexer.js',
|
||||||
|
'undici/undici': 'deps/undici/undici.js'
|
||||||
|
}
|
||||||
|
|
||||||
# create option groups
|
# create option groups
|
||||||
shared_optgroup = parser.add_argument_group("Shared libraries",
|
shared_optgroup = parser.add_argument_group("Shared libraries",
|
||||||
"Flags that allows you to control whether you want to build against "
|
"Flags that allows you to control whether you want to build against "
|
||||||
@ -70,6 +75,9 @@ intl_optgroup = parser.add_argument_group("Internationalization",
|
|||||||
"library you want to build against.")
|
"library you want to build against.")
|
||||||
http2_optgroup = parser.add_argument_group("HTTP2",
|
http2_optgroup = parser.add_argument_group("HTTP2",
|
||||||
"Flags that allows you to control HTTP2 features in Node.js")
|
"Flags that allows you to control HTTP2 features in Node.js")
|
||||||
|
shared_builtin_optgroup = parser.add_argument_group("Shared builtins",
|
||||||
|
"Flags that allows you to control whether you want to build against "
|
||||||
|
"internal builtins or shared files.")
|
||||||
|
|
||||||
# Options should be in alphabetical order but keep --prefix at the top,
|
# Options should be in alphabetical order but keep --prefix at the top,
|
||||||
# that's arguably the one people will be looking for most.
|
# that's arguably the one people will be looking for most.
|
||||||
@ -422,6 +430,16 @@ shared_optgroup.add_argument('--shared-cares-libpath',
|
|||||||
|
|
||||||
parser.add_argument_group(shared_optgroup)
|
parser.add_argument_group(shared_optgroup)
|
||||||
|
|
||||||
|
for builtin in shareable_builtins:
|
||||||
|
builtin_id = 'shared_builtin_' + builtin + '_path'
|
||||||
|
shared_builtin_optgroup.add_argument('--shared-builtin-' + builtin + '-path',
|
||||||
|
action='store',
|
||||||
|
dest='node_shared_builtin_' + builtin.replace('/', '_') + '_path',
|
||||||
|
help='Path to shared file for ' + builtin + ' builtin. '
|
||||||
|
'Will be used instead of bundled version at runtime')
|
||||||
|
|
||||||
|
parser.add_argument_group(shared_builtin_optgroup)
|
||||||
|
|
||||||
static_optgroup.add_argument('--static-zoslib-gyp',
|
static_optgroup.add_argument('--static-zoslib-gyp',
|
||||||
action='store',
|
action='store',
|
||||||
dest='static_zoslib_gyp',
|
dest='static_zoslib_gyp',
|
||||||
@ -1960,6 +1978,19 @@ configure_static(output)
|
|||||||
configure_inspector(output)
|
configure_inspector(output)
|
||||||
configure_section_file(output)
|
configure_section_file(output)
|
||||||
|
|
||||||
|
# configure shareable builtins
|
||||||
|
output['variables']['node_builtin_shareable_builtins'] = []
|
||||||
|
for builtin in shareable_builtins:
|
||||||
|
builtin_id = 'node_shared_builtin_' + builtin.replace('/', '_') + '_path'
|
||||||
|
if getattr(options, builtin_id):
|
||||||
|
if options.with_intl == 'none':
|
||||||
|
option_name = '--shared-builtin-' + builtin + '-path'
|
||||||
|
error(option_name + ' is incompatible with --with-intl=none' )
|
||||||
|
else:
|
||||||
|
output['defines'] += [builtin_id.upper() + '=' + getattr(options, builtin_id)]
|
||||||
|
else:
|
||||||
|
output['variables']['node_builtin_shareable_builtins'] += [shareable_builtins[builtin]]
|
||||||
|
|
||||||
# Forward OSS-Fuzz settings
|
# Forward OSS-Fuzz settings
|
||||||
output['variables']['ossfuzz'] = b(options.ossfuzz)
|
output['variables']['ossfuzz'] = b(options.ossfuzz)
|
||||||
|
|
||||||
|
106
doc/contributing/maintaining-dependencies.md
Normal file
106
doc/contributing/maintaining-dependencies.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# Maintaining Dependencies
|
||||||
|
|
||||||
|
Node.js depends on additional components beyond the Node.js code
|
||||||
|
itself. These dependencies provide both native and JavaScript code
|
||||||
|
and are built together with the code under the `src` and `lib`
|
||||||
|
directories to create the Node.js binaries.
|
||||||
|
|
||||||
|
All dependencies are located within the `deps` directory.
|
||||||
|
|
||||||
|
Any code which meets one or more of these conditions should
|
||||||
|
be managed as a dependency:
|
||||||
|
|
||||||
|
* originates in an upstream project and is maintained
|
||||||
|
in that upstream project.
|
||||||
|
* is not built from the `preferred form of the work for
|
||||||
|
making modifications to it` (see
|
||||||
|
[GNU GPL v2, section 3.](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||||
|
when `make node` is run. A good example is
|
||||||
|
WASM code generated from C (the preferred form).
|
||||||
|
Typically generation is only supported on a subset of platforms, needs
|
||||||
|
additional tools, and is pre-built outside of the `make node`
|
||||||
|
step and then committed as a WASM binary in the directory
|
||||||
|
for the dependency under the `deps` directory.
|
||||||
|
|
||||||
|
By default all dependencies are bundled into the Node.js
|
||||||
|
binary, however, `configure` options should be available to
|
||||||
|
use an externalized version at runtime when:
|
||||||
|
|
||||||
|
* the dependency provides native code and is available as
|
||||||
|
a shared library in one or more of the common Node.js
|
||||||
|
distributions.
|
||||||
|
* the dependency provides JavaScript and is not built
|
||||||
|
from the `preferred form of the work for making modifications
|
||||||
|
to it` when `make node` is run.
|
||||||
|
|
||||||
|
Many distributions use externalized dependencies for one or
|
||||||
|
more of these reasons:
|
||||||
|
|
||||||
|
1. They have a requirement to build everything that they ship
|
||||||
|
from the `preferred form of the work for making
|
||||||
|
modifications to it`. This means that they need to
|
||||||
|
replace any pre-built components (for example WASM
|
||||||
|
binaries) with an equivalent that they have built.
|
||||||
|
2. They manage the dependency separately as it is used by
|
||||||
|
more applications than just Node.js. Linking against
|
||||||
|
a shared library allows them to manage updates and
|
||||||
|
CVE fixes against the library instead of having to
|
||||||
|
patch all of the individual applications.
|
||||||
|
3. They have a system wide configuration for the
|
||||||
|
dependency that all applications should respect.
|
||||||
|
|
||||||
|
## Supporting externalized dependencies with native code.
|
||||||
|
|
||||||
|
Support for externalized dependencies with native code for which a
|
||||||
|
shared library is available can added by:
|
||||||
|
|
||||||
|
* adding options to `configure.py`. These are added to the
|
||||||
|
shared\_optgroup and include an options to:
|
||||||
|
* enable use of a shared library
|
||||||
|
* set the name of the shared library
|
||||||
|
* set the path to the directory with the includes for the
|
||||||
|
shared library
|
||||||
|
* set the path to where to find the shared library at
|
||||||
|
runtime
|
||||||
|
* add a call to configure\_library() to `configure.py` for the
|
||||||
|
library at the end of list of existing configure\_library() calls.
|
||||||
|
If there are additional libraries that are required it is
|
||||||
|
possible to list more than one with the `pkgname` option.
|
||||||
|
* in `node.gypi` guard the build for the dependency
|
||||||
|
with `node_shared_depname` so that is is only built if
|
||||||
|
the dependency is being bundled into Node.js itself. For example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[ 'node_shared_brotli=="false"', {
|
||||||
|
'dependencies': [ 'deps/brotli/brotli.gyp:brotli' ],
|
||||||
|
}],
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supporting externalizable dependencies with JavaScript codeIZA
|
||||||
|
|
||||||
|
Support for an externalizable dependency with JavaScript code
|
||||||
|
can be added by:
|
||||||
|
|
||||||
|
* adding an entry to the `sharable_builtins` map in
|
||||||
|
`configure.py`. The path should correspond to the file
|
||||||
|
within the deps directory that is normally bundled into
|
||||||
|
Node.js. For example `deps/cjs-module-lexer/lexer.js`.
|
||||||
|
This will add a new option for building with that dependency
|
||||||
|
externalized. After adding the entry you can see
|
||||||
|
the new option by running `./configure --help`.
|
||||||
|
|
||||||
|
* adding a call to `AddExternalizedBuiltin` to the constructor
|
||||||
|
for BuildinLoader in `src/node_builtins.cc` for the
|
||||||
|
dependency using the `NODE_SHARED_BUILTLIN` #define generated for
|
||||||
|
the dependency. After running `./configure` with the new
|
||||||
|
option you can find the #define in `config.gypi`. You can cut and
|
||||||
|
paste one of the existing entries and then update to match the
|
||||||
|
inport name for the dependency and the #define generated.
|
||||||
|
|
||||||
|
## Supporting non-externalized dependencies with JavaScript code
|
||||||
|
|
||||||
|
If the dependency consists of JavaScript in the
|
||||||
|
`preferred form of the work for making modifications to it`, it
|
||||||
|
can be added as a non-externalizable dependency. In this case
|
||||||
|
simply add the path to the JavaScript file in the `deps_files`
|
||||||
|
list in the `node.gyp` file.
|
4
node.gyp
4
node.gyp
@ -48,9 +48,7 @@
|
|||||||
'deps/v8/tools/tickprocessor-driver.mjs',
|
'deps/v8/tools/tickprocessor-driver.mjs',
|
||||||
'deps/acorn/acorn/dist/acorn.js',
|
'deps/acorn/acorn/dist/acorn.js',
|
||||||
'deps/acorn/acorn-walk/dist/walk.js',
|
'deps/acorn/acorn-walk/dist/walk.js',
|
||||||
'deps/cjs-module-lexer/lexer.js',
|
'<@(node_builtin_shareable_builtins)',
|
||||||
'deps/cjs-module-lexer/dist/lexer.js',
|
|
||||||
'deps/undici/undici.js',
|
|
||||||
],
|
],
|
||||||
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
|
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
|
||||||
'conditions': [
|
'conditions': [
|
||||||
|
@ -33,6 +33,24 @@ BuiltinLoader BuiltinLoader::instance_;
|
|||||||
|
|
||||||
BuiltinLoader::BuiltinLoader() : config_(GetConfig()), has_code_cache_(false) {
|
BuiltinLoader::BuiltinLoader() : config_(GetConfig()), has_code_cache_(false) {
|
||||||
LoadJavaScriptSource();
|
LoadJavaScriptSource();
|
||||||
|
#if defined(NODE_HAVE_I18N_SUPPORT)
|
||||||
|
#ifdef NODE_SHARED_BUILTIN_CJS_MODULE_LEXER_LEXER_PATH
|
||||||
|
AddExternalizedBuiltin(
|
||||||
|
"internal/deps/cjs-module-lexer/lexer",
|
||||||
|
STRINGIFY(NODE_SHARED_BUILTIN_CJS_MODULE_LEXER_LEXER_PATH));
|
||||||
|
#endif // NODE_SHARED_BUILTIN_CJS_MODULE_LEXER_LEXER_PATH
|
||||||
|
|
||||||
|
#ifdef NODE_SHARED_BUILTIN_CJS_MODULE_LEXER_DIST_LEXER_PATH
|
||||||
|
AddExternalizedBuiltin(
|
||||||
|
"internal/deps/cjs-module-lexer/dist/lexer",
|
||||||
|
STRINGIFY(NODE_SHARED_BUILTIN_CJS_MODULE_LEXER_DIST_LEXER_PATH));
|
||||||
|
#endif // NODE_SHARED_BUILTIN_CJS_MODULE_LEXER_DIST_LEXER_PATH
|
||||||
|
|
||||||
|
#ifdef NODE_SHARED_BUILTIN_UNDICI_UNDICI_PATH
|
||||||
|
AddExternalizedBuiltin("internal/deps/undici/undici",
|
||||||
|
STRINGIFY(NODE_SHARED_BUILTIN_UNDICI_UNDICI_PATH));
|
||||||
|
#endif // NODE_SHARED_BUILTIN_UNDICI_UNDICI_PATH
|
||||||
|
#endif // NODE_HAVE_I18N_SUPPORT
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltinLoader* BuiltinLoader::GetInstance() {
|
BuiltinLoader* BuiltinLoader::GetInstance() {
|
||||||
@ -220,6 +238,29 @@ MaybeLocal<String> BuiltinLoader::LoadBuiltinSource(Isolate* isolate,
|
|||||||
#endif // NODE_BUILTIN_MODULES_PATH
|
#endif // NODE_BUILTIN_MODULES_PATH
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(NODE_HAVE_I18N_SUPPORT)
|
||||||
|
void BuiltinLoader::AddExternalizedBuiltin(const char* id,
|
||||||
|
const char* filename) {
|
||||||
|
std::string source;
|
||||||
|
int r = ReadFileSync(&source, filename);
|
||||||
|
if (r != 0) {
|
||||||
|
fprintf(
|
||||||
|
stderr, "Cannot load externalized builtin: \"%s:%s\".\n", id, filename);
|
||||||
|
ABORT();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
icu::UnicodeString utf16 = icu::UnicodeString::fromUTF8(
|
||||||
|
icu::StringPiece(source.data(), source.length()));
|
||||||
|
auto source_utf16 = std::make_unique<icu::UnicodeString>(utf16);
|
||||||
|
Add(id,
|
||||||
|
UnionBytes(reinterpret_cast<const uint16_t*>((*source_utf16).getBuffer()),
|
||||||
|
utf16.length()));
|
||||||
|
// keep source bytes for builtin alive while BuiltinLoader exists
|
||||||
|
GetInstance()->externalized_source_bytes_.push_back(std::move(source_utf16));
|
||||||
|
}
|
||||||
|
#endif // NODE_HAVE_I18N_SUPPORT
|
||||||
|
|
||||||
// Returns Local<Function> of the compiled module if return_code_cache
|
// Returns Local<Function> of the compiled module if return_code_cache
|
||||||
// is false (we are only compiling the function).
|
// is false (we are only compiling the function).
|
||||||
// Otherwise return a Local<Object> containing the cache.
|
// Otherwise return a Local<Object> containing the cache.
|
||||||
|
@ -3,10 +3,14 @@
|
|||||||
|
|
||||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#if defined(NODE_HAVE_I18N_SUPPORT)
|
||||||
|
#include <unicode/unistr.h>
|
||||||
|
#endif // NODE_HAVE_I18N_SUPPORT
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "node_mutex.h"
|
#include "node_mutex.h"
|
||||||
#include "node_union_bytes.h"
|
#include "node_union_bytes.h"
|
||||||
@ -132,11 +136,18 @@ class NODE_EXTERN_PRIVATE BuiltinLoader {
|
|||||||
static void HasCachedBuiltins(
|
static void HasCachedBuiltins(
|
||||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
|
|
||||||
|
#if defined(NODE_HAVE_I18N_SUPPORT)
|
||||||
|
static void AddExternalizedBuiltin(const char* id, const char* filename);
|
||||||
|
#endif // NODE_HAVE_I18N_SUPPORT
|
||||||
|
|
||||||
static BuiltinLoader instance_;
|
static BuiltinLoader instance_;
|
||||||
BuiltinCategories builtin_categories_;
|
BuiltinCategories builtin_categories_;
|
||||||
BuiltinSourceMap source_;
|
BuiltinSourceMap source_;
|
||||||
BuiltinCodeCacheMap code_cache_;
|
BuiltinCodeCacheMap code_cache_;
|
||||||
UnionBytes config_;
|
UnionBytes config_;
|
||||||
|
#if defined(NODE_HAVE_I18N_SUPPORT)
|
||||||
|
std::list<std::unique_ptr<icu::UnicodeString>> externalized_source_bytes_;
|
||||||
|
#endif // NODE_HAVE_I18N_SUPPORT
|
||||||
|
|
||||||
// Used to synchronize access to the code cache map
|
// Used to synchronize access to the code cache map
|
||||||
Mutex code_cache_mutex_;
|
Mutex code_cache_mutex_;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user