Skip to content

Commit 0eb32ed

Browse files
lux01RafaelGSS
authored andcommitted
build: fix various shared library build issues
Node.js unofficially supports a shared library variant where the main node executable is a thin wrapper around node.dll/libnode.so. The key benefit of this is to support embedding Node.js in other applications. Since Node.js 12 there have been a number of issues preventing the shared library build from working correctly, primarily on Windows: * A number of functions used executables such as `mksnapshot` are not exported from `libnode.dll` using a `NODE_EXTERN` attribute * A dependency on the `Winmm` system library is missing * Incorrect defines on executable targets leads to `node.exe` claiming to export a number of functions that are actually in `libnode.dll` * Because `node.exe` attempts to export symbols, `node.lib` gets generated causing native extensions to try to link against `node.exe` not `libnode.dll`. * Similarly, because `node.dll` was renamed to `libnode.dll`, native extensions don't know to look for `libnode.lib` rather than `node.lib`. * On macOS an RPATH is added to find `libnode.dylib` relative to `node` in the same folder. This works fine from the `out/Release` folder but not from an installed prefix, where `node` will be in `bin/` and `libnode.dylib` will be in `lib/`. * Similarly on Linux, no RPATH is added so LD_LIBRARY_PATH needs setting correctly for `bin/node` to find `lib/libnode.so`. For the `libnode.lib` vs `node.lib` issue there are two possible options: 1. Ensure `node.lib` from `node.exe` does not get generated, and instead copy `libnode.lib` to `node.lib`. This means addons compiled when referencing the correct `node.lib` file will correctly depend on `libnode.dll`. The down side is that native addons compiled with stock Node.js will still try to resolve symbols against node.exe rather than libnode.dll. 2. After building `libnode.dll`, dump the exports using `dumpbin`, and process this to generate a `node.def` file to be linked into `node.exe` with the `/DEF:node.def` flag. The export entries in `node.def` will all read ``` my_symbol=libnode.my_symbol ``` so that `node.exe` will redirect all exported symbols back to `libnode.dll`. This has the benefit that addons compiled with stock Node.js will load correctly into `node.exe` from a shared library build, but means that every embedding executable also needs to perform this same trick. I went with the first option as it is the cleaner of the two solutions in my opinion. Projects wishing to generate a shared library variant of Node.js can now, for example, ``` .\vcbuild dll package vs ``` to generate a full node installation including `libnode.dll`, `Release\node.lib`, and all the necessary headers. Native addons can then be built against the shared library build easily by specifying the correct `nodedir` option. For example ``` >npx node-gyp configure --nodedir C:\Users\User\node\Release\node-v18.0.0-win-x64 ... >npx node-gyp build ... >dumpbin /dependents build\Release\binding.node Microsoft (R) COFF/PE Dumper Version 14.29.30136.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file build\Release\binding.node File Type: DLL Image has the following dependencies: KERNEL32.dll libnode.dll VCRUNTIME140.dll api-ms-win-crt-string-l1-1-0.dll api-ms-win-crt-stdio-l1-1-0.dll api-ms-win-crt-runtime-l1-1-0.dll ... ``` PR-URL: #41850 Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Beth Griggs <bgriggs@redhat.com> Reviewed-By: Richard Lau <rlau@redhat.com>
1 parent 840e61e commit 0eb32ed

13 files changed

+316
-30
lines changed

node.gyp

+54-1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,16 @@
196196
'dependencies': [ 'node_aix_shared' ],
197197
}, {
198198
'dependencies': [ '<(node_lib_target_name)' ],
199+
'conditions': [
200+
['OS=="win" and node_shared=="true"', {
201+
'dependencies': ['generate_node_def'],
202+
'msvs_settings': {
203+
'VCLinkerTool': {
204+
'ModuleDefinitionFile': '<(PRODUCT_DIR)/<(node_core_target_name).def',
205+
},
206+
},
207+
}],
208+
],
199209
}],
200210
[ 'node_intermediate_lib_type=="static_library" and node_shared=="false"', {
201211
'xcode_settings': {
@@ -235,8 +245,15 @@
235245
}],
236246
[ 'node_shared=="true"', {
237247
'xcode_settings': {
238-
'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', ],
248+
'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', '-Wl,-rpath,@loader_path/../lib'],
239249
},
250+
'conditions': [
251+
['OS=="linux"', {
252+
'ldflags': [
253+
'-Wl,-rpath,\\$$ORIGIN/../lib'
254+
],
255+
}],
256+
],
240257
}],
241258
[ 'enable_lto=="true"', {
242259
'xcode_settings': {
@@ -749,6 +766,7 @@
749766
'libraries': [
750767
'Dbghelp',
751768
'Psapi',
769+
'Winmm',
752770
'Ws2_32',
753771
],
754772
}],
@@ -1502,5 +1520,40 @@
15021520
},
15031521
]
15041522
}], # end aix section
1523+
['OS=="win" and node_shared=="true"', {
1524+
'targets': [
1525+
{
1526+
'target_name': 'gen_node_def',
1527+
'type': 'executable',
1528+
'sources': [
1529+
'tools/gen_node_def.cc'
1530+
],
1531+
},
1532+
{
1533+
'target_name': 'generate_node_def',
1534+
'dependencies': [
1535+
'gen_node_def',
1536+
'<(node_lib_target_name)',
1537+
],
1538+
'type': 'none',
1539+
'actions': [
1540+
{
1541+
'action_name': 'generate_node_def_action',
1542+
'inputs': [
1543+
'<(PRODUCT_DIR)/<(node_lib_target_name).dll'
1544+
],
1545+
'outputs': [
1546+
'<(PRODUCT_DIR)/<(node_core_target_name).def',
1547+
],
1548+
'action': [
1549+
'<(PRODUCT_DIR)/gen_node_def.exe',
1550+
'<@(_inputs)',
1551+
'<@(_outputs)',
1552+
],
1553+
},
1554+
],
1555+
},
1556+
],
1557+
}], # end win section
15051558
], # end conditions block
15061559
}

node.gypi

+14-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
[ 'clang==1', {
3030
'cflags': [ '-Werror=undefined-inline', ]
3131
}],
32-
[ 'node_shared=="false" and "<(_type)"=="executable"', {
32+
[ '"<(_type)"=="executable"', {
3333
'msvs_settings': {
3434
'VCManifestTool': {
3535
'EmbedManifest': 'true',
@@ -41,6 +41,19 @@
4141
'defines': [
4242
'NODE_SHARED_MODE',
4343
],
44+
'conditions': [
45+
['"<(_type)"=="executable"', {
46+
'defines': [
47+
'USING_UV_SHARED',
48+
'USING_V8_SHARED',
49+
'BUILDING_NODE_EXTENSION'
50+
],
51+
'defines!': [
52+
'BUILDING_V8_SHARED=1',
53+
'BUILDING_UV_SHARED=1'
54+
]
55+
}],
56+
],
4457
}],
4558
[ 'OS=="win"', {
4659
'defines!': [

src/debug_utils.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ template <typename... Args>
3535
inline std::string SPrintF(const char* format, Args&&... args);
3636
template <typename... Args>
3737
inline void FPrintF(FILE* file, const char* format, Args&&... args);
38-
void FWrite(FILE* file, const std::string& str);
38+
void NODE_EXTERN_PRIVATE FWrite(FILE* file, const std::string& str);
3939

4040
// Listing the AsyncWrap provider types first enables us to cast directly
4141
// from a provider type to a debug category.
@@ -57,7 +57,7 @@ enum class DebugCategory {
5757
CATEGORY_COUNT
5858
};
5959

60-
class EnabledDebugList {
60+
class NODE_EXTERN_PRIVATE EnabledDebugList {
6161
public:
6262
bool enabled(DebugCategory category) const {
6363
DCHECK_GE(static_cast<int>(category), 0);
@@ -168,7 +168,7 @@ void CheckedUvLoopClose(uv_loop_t* loop);
168168
void PrintLibuvHandleInformation(uv_loop_t* loop, FILE* stream);
169169

170170
namespace per_process {
171-
extern EnabledDebugList enabled_debug_list;
171+
extern NODE_EXTERN_PRIVATE EnabledDebugList enabled_debug_list;
172172

173173
template <typename... Args>
174174
inline void FORCE_INLINE Debug(DebugCategory cat,

src/env.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ class Environment;
558558
struct AllocatedBuffer;
559559

560560
typedef size_t SnapshotIndex;
561-
class IsolateData : public MemoryRetainer {
561+
class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
562562
public:
563563
IsolateData(v8::Isolate* isolate,
564564
uv_loop_t* event_loop,

src/node.h

+10
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@
3232
# define NODE_EXTERN __attribute__((visibility("default")))
3333
#endif
3434

35+
// Declarations annotated with NODE_EXTERN_PRIVATE do not form part of
36+
// the public API. They are implementation details that can and will
37+
// change between releases, even in semver patch releases. Do not use
38+
// any such symbol in external code.
39+
#ifdef NODE_SHARED_MODE
40+
#define NODE_EXTERN_PRIVATE NODE_EXTERN
41+
#else
42+
#define NODE_EXTERN_PRIVATE
43+
#endif
44+
3545
#ifdef BUILDING_NODE_EXTENSION
3646
# undef BUILDING_V8_SHARED
3747
# undef BUILDING_UV_SHARED

src/node_internals.h

+8-7
Original file line numberDiff line numberDiff line change
@@ -320,13 +320,14 @@ enum InitializationSettingsFlags : uint64_t {
320320
};
321321

322322
// TODO(codebytere): eventually document and expose to embedders.
323-
InitializationResult InitializeOncePerProcess(int argc, char** argv);
324-
InitializationResult InitializeOncePerProcess(
325-
int argc,
326-
char** argv,
327-
InitializationSettingsFlags flags,
328-
ProcessFlags::Flags process_flags = ProcessFlags::kNoFlags);
329-
void TearDownOncePerProcess();
323+
InitializationResult NODE_EXTERN_PRIVATE InitializeOncePerProcess(int argc,
324+
char** argv);
325+
InitializationResult NODE_EXTERN_PRIVATE InitializeOncePerProcess(
326+
int argc,
327+
char** argv,
328+
InitializationSettingsFlags flags,
329+
ProcessFlags::Flags process_flags = ProcessFlags::kNoFlags);
330+
void NODE_EXTERN_PRIVATE TearDownOncePerProcess();
330331
void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s);
331332
void SetIsolateMiscHandlers(v8::Isolate* isolate, const IsolateSettings& s);
332333
void SetIsolateCreateParamsForNode(v8::Isolate::CreateParams* params);

src/node_native_module.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ using NativeModuleCacheMap =
2929
// This class should not depend on any Environment, or depend on access to
3030
// the its own singleton - that should be encapsulated in NativeModuleEnv
3131
// instead.
32-
class NativeModuleLoader {
32+
class NODE_EXTERN_PRIVATE NativeModuleLoader {
3333
public:
3434
NativeModuleLoader(const NativeModuleLoader&) = delete;
3535
NativeModuleLoader& operator=(const NativeModuleLoader&) = delete;

src/node_options.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ void Parse(
491491
namespace per_process {
492492

493493
extern Mutex cli_options_mutex;
494-
extern std::shared_ptr<PerProcessOptions> cli_options;
494+
extern NODE_EXTERN_PRIVATE std::shared_ptr<PerProcessOptions> cli_options;
495495

496496
} // namespace per_process
497497

src/node_snapshot_builder.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace node {
1313
class ExternalReferenceRegistry;
1414
struct SnapshotData;
1515

16-
class SnapshotBuilder {
16+
class NODE_EXTERN_PRIVATE SnapshotBuilder {
1717
public:
1818
static std::string Generate(const std::vector<std::string> args,
1919
const std::vector<std::string> exec_args);

src/util.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
#include "v8.h"
2828

29+
#include "node.h"
30+
2931
#include <climits>
3032
#include <cstddef>
3133
#include <cstdio>
@@ -110,8 +112,8 @@ struct AssertionInfo {
110112
const char* message;
111113
const char* function;
112114
};
113-
[[noreturn]] void Assert(const AssertionInfo& info);
114-
[[noreturn]] void Abort();
115+
[[noreturn]] void NODE_EXTERN_PRIVATE Assert(const AssertionInfo& info);
116+
[[noreturn]] void NODE_EXTERN_PRIVATE Abort();
115117
void DumpBacktrace(FILE* fp);
116118

117119
// Windows 8+ does not like abort() in Release mode

0 commit comments

Comments
 (0)