-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
build: Fix various shared library build issues. #41850
Conversation
Review requested:
|
@nodejs/platform-windows |
Welcome @lux01 and thanks for the pull request! Can you rebase this against the current master branch and resolve the conflict? |
@nodejs/build |
We do have this nightly job - https://ci.nodejs.org/view/Node.js%20Daily/job/node-test-commit-linux-as-shared-lib/ (and the AIX equivalent which is broken). Not as good as being part of the regular PR builds, but better than nothing. |
@codebytere does Electron have a similar problem? |
Build of the nightly AIX shared library runs to see if it resolves the existing failures - https://ci.nodejs.org/job/node-test-commit-aix-shared-lib/nodes=aix71-ppc64/1424/ |
common.gypi
Outdated
'conditions': [ | ||
[ 'node_shared=="true"', { | ||
'ldflags': [ | ||
'-Wl,-bnoipath', # Do not add the full path to libnode.a in executables, trust that the LIBPATH is set correctly by the user at runtime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to have the comment be in lines above the flags so that we can keep it within 80 char limit we have set in the project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I pushed some work-in-progress AIX changes to this branch instead of a new one! I'll remove the AIX changes from the PR.
I'm guessing that this breaks the ability to pre-compile addons with node-addon-api and have them run with different node binaries? It would mean that any module that ships pre-compiled binaries would either fail for shared library based exes, or have to fall back to compiling? In option 2) are there additional complications if the name of the exe is going to be different than node ? |
The run against the AIX shared library test job still failed: https://ci.nodejs.org/job/node-test-commit-aix-shared-lib/nodes=aix71-ppc64/1424/console with
@lux01 That's actually failing earlier, as the job used to try to build addons and was filing with |
Job on OSX testing with --shared option - https://ci.nodejs.org/view/All/job/node-test-commit-osx-as-shared-mdawson/nodes=osx1015/2/console One test seems to fail, but I don't believe it is related to this PR as it failed on main as well: 18:17:27 not ok 3236 pummel/test-heapdump-http2
18:17:27 ---
18:17:27 duration_ms: 1.751
18:17:27 severity: crashed
18:17:27 exitcode: -11
18:17:27 stack: |-
18:17:27 (node:11723) internal/test/binding: These APIs are for internal testing only. Do not use them.
18:17:27 (Use `node --trace-warnings ...` to show where the warning was created)
18:17:27 ... otherwise the test suite seems to run clean with the --shared config option. |
copy /Y libnode.dll %TARGET_NAME%\ > nul | ||
if errorlevel 1 echo Cannot copy libnode.dll && goto package_error | ||
|
||
mkdir %TARGET_NAME%\Release > nul |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lux01 I'm wondering why the node.lib goes into a new sub directory called Release ? The install.py seems to install libnode.lib and libnode.dll so not quite sure why there is a node.lib being copied here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed when using the --nodedir
option to build native addons. When this is specified, node-gyp
explicitly looks for node.lib
in the Release
or Debug
subfolder of the node
installation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
k thanks.
vcbuild.bat
Outdated
if errorlevel 1 echo Cannot copy node.lib && goto package_error | ||
|
||
copy /Y ..\common.gypi %TARGET_NAME%\ > nul | ||
if errorlevel 1 echo Cannot copy common.gypi && goto package_error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also wondering what this is needed for
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, node-gyp
tries to find common.gypi
in the root of the nodedir
if its not at include/node/common.gypi
. I think this may be a remnant left over from before I figured out the HEADERS_ONLY install below. I'll try removing it
@lux01, I'm wondering why this is not a problem in the regular builds but is for the shared library build? |
@@ -41,6 +41,19 @@ | |||
'defines': [ | |||
'NODE_SHARED_MODE', | |||
], | |||
'conditions': [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary for all platforms? I'm wondering since the only issues you mentioned for linux/osx don't seem related to this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you may be right that these are only necessary on Windows. Their purpose is to ensure that libuv and v8 classes are correctly annotated with __declspec(dllimport)
rather than __declspec(dllexport)
. I will refactor this condition block to only apply to Windows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldn't hurt (and is more correct) to enable these on non-Windows.
The Lines 1398 to 1402 in 0367b5c
In both the main build and the shared library build cases this resolves to
You're right, I hadn't considered that as a use case. The product I work on only uses a small number of native addons, none of which are shipped pre-compiled. Addons that only ship pre-compiled binaries would not work at all, others would have to be rebuilt. With regards to the executable name issue, https://github.com/nodejs/node-gyp/blob/0da2e0140dbf4296d5ba7ea498fac05e74bb4bbb/addon.gypi#L81 Secondly it injects a delay load hook into the native addon which is run when Windows tries to resolve the delayed As long as the host process exports the necessary symbols, its name does not matter. For option (2) to work we have to rely on this delay load behaviour and ensure that the host process, whatever it is (be it a renamed From what I can tell, Electron doesn't have this issue because it doesn't use the shared library build of node, at least on Windows. I'm inferring this because running
To get option (2) to work we would need to run the following steps after building
This is very achievable but is also very messy and means your host executable is tightly coupled with libnode.dll in a non-obvious way. The product I work on currently does this but we would prefer not to given the choice. We have a similar issue on AIX where native addons currently assume all their symbols can be found in the host executable. I think there we only have option (2) available to us. I'm still investigating... |
I build/ran on Windows and see some errors. I assume it is likely something I've not set up properly because I don't build on windows very often. @lux01 do you get a clean test pass and did you have to set up anything beyond what is outlined in https://github.com/nodejs/node/blob/master/BUILDING.md#option-1-manual-install to get everything passing? EDIT2 setting up the linux tools in the path AND running as admin, I'm down to 1 failures running with python tool\test.py C:\Users\midawson\node>python tools\test.py
Skipping pseudo-tty tests, as pseudo terminals are not available on Windows.
=== release test-fs-stat ===
Path: parallel/test-fs-stat
node:assert:991
throw newErr;
^
AssertionError [ERR_ASSERTION]: ifError got unwanted exception: EISDIR: illegal operation on a directory, fstat
at C:\Users\midawson\node\test\common\index.js:374:12
at C:\Users\midawson\node\test\common\index.js:411:15
at FSReqCallback.oncomplete (node:fs:197:21) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: [Error: EISDIR: illegal operation on a directory, fstat] {
errno: -4068,
code: 'EISDIR',
syscall: 'fstat'
},
expected: null,
operator: 'ifError'
}
Node.js v18.0.0-pre
Command: C:\Users\midawson\node\out\Release\node.exe C:\Users\midawson\node\test\parallel\test-fs-stat.js
[05:55|% 100|+ 3369|- 1]: Done And I found - #40006 so clearly the issue is not related to the PR. I'll take my results as a pass running the Node.js on windows using the shared library build with the wrapper node.exe and libnode.dll |
@lux01 - Thanks for the detailed reply in #41850 (comment) I think you are right that electron likely builds an exe that has statically linked in what it needed from Node.js versus using an external shared library. I believe that is also the case for boxnode so the shared library may be a different use case than those two. In terms of native addons, my take is that is an area where we could improve, but I don't think it should be a blocker for this PR as I don't think it breaks existing functionality, just improves what does work when built on a shared library (Correct me if I'm wrong on that). From my testing so far I can confirm that on Windows, Linux and OSX it results in a shared library and a node.exe wrapper that builds and passes the test suites (well, close enough that I think the single failure I see on each platform is not related to this PR). That leaves AIX which I think you were going to cover in a follow on PR? |
The main remaining question I have is related to NODE_EXTERN and if there would be any concern about the symbols being exported signaling some sort of commitment for support. I think that on non-windows platforms they are exported already, and from the explanation in #41850 (comment) they are exported in the windows static builds already so my thinking is that is should not change anything with respect to public exported API. I looked through our documentation and did not find anything with guidance on when to/when not to add NODE_EXTERN. I asked @jasnell and he indicated he thought it was ok as well but that maybe we should see if @bnoordhuis would chime in as well. @bnoordhuis do you have any concerns with us adding NODE_EXTERN to the methods as done in this PR? |
tools/msvs/find_python.cmd
Outdated
@@ -57,7 +57,7 @@ set "need_path_ext=" | |||
exit /b 0 | |||
|
|||
:check-python | |||
%~1 -V | |||
%* -V |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lux01 can you comment on why this change is needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed when the path to python.exe
has a space in it. Python 3.10 when installed for all users defaults to C:\Program Files\Python310\python.exe
which fails without this change:
PS C:\source\node> .\vcbuild.bat debug vs2019 x64 dll
Looking for Python
Python found in C:\Program Files\Python310\\python.exe
'C:\Program' is not recognized as an internal or external command,
operable program or batch file.
Not an executable Python program
Could not find Python.
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 <[email protected]> Reviewed-By: Beth Griggs <[email protected]> Reviewed-By: Richard Lau <[email protected]>
Thank you @mhdawson! Glad to see this finally close :) |
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 <[email protected]> Reviewed-By: Beth Griggs <[email protected]> Reviewed-By: Richard Lau <[email protected]>
Refs: #41850 I think it would be good to have some info/context for maintainers on the shared library option. Add that based on disussion in #41850 Signed-off-by: Michael Dawson <[email protected]> PR-URL: #42517 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
Refs: #41850 I think it would be good to have some info/context for maintainers on the shared library option. Add that based on disussion in #41850 Signed-off-by: Michael Dawson <[email protected]> PR-URL: #42517 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
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 <[email protected]> Reviewed-By: Beth Griggs <[email protected]> Reviewed-By: Richard Lau <[email protected]>
Refs: #41850 I think it would be good to have some info/context for maintainers on the shared library option. Add that based on disussion in #41850 Signed-off-by: Michael Dawson <[email protected]> PR-URL: #42517 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
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 <[email protected]> Reviewed-By: Beth Griggs <[email protected]> Reviewed-By: Richard Lau <[email protected]>
Refs: #41850 I think it would be good to have some info/context for maintainers on the shared library option. Add that based on disussion in #41850 Signed-off-by: Michael Dawson <[email protected]> PR-URL: #42517 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
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 <[email protected]> Reviewed-By: Beth Griggs <[email protected]> Reviewed-By: Richard Lau <[email protected]>
Refs: #41850 I think it would be good to have some info/context for maintainers on the shared library option. Add that based on disussion in #41850 Signed-off-by: Michael Dawson <[email protected]> PR-URL: #42517 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
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 <[email protected]> Reviewed-By: Beth Griggs <[email protected]> Reviewed-By: Richard Lau <[email protected]>
Refs: #41850 I think it would be good to have some info/context for maintainers on the shared library option. Add that based on disussion in #41850 Signed-off-by: Michael Dawson <[email protected]> PR-URL: #42517 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
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: nodejs/node#41850 Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Beth Griggs <[email protected]> Reviewed-By: Richard Lau <[email protected]>
Refs: nodejs/node#41850 I think it would be good to have some info/context for maintainers on the shared library option. Add that based on disussion in nodejs/node#41850 Signed-off-by: Michael Dawson <[email protected]> PR-URL: nodejs/node#42517 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
Node.js unofficially supports a shared library variant where the main
node
executable is a thin wrapper aroundnode.dll
/libnode.so
. The key benefit of this is to support embedding Node.js in other applications, for example the product I work on embeds a Node.js runtime in 3 separate applications (alongside the JVM and CLR in the same processes) in addition to having some standalone Node.js applications.Since Node.js 12 there have been a number of issues preventing the shared library build from working correctly with the most significant issues being on Windows:
mksnapshot
are not exported fromlibnode.dll
using aNODE_EXTERN
attributeWinmm
system library is missingnode.exe
claiming to export a number of functions that are actually inlibnode.dll
node.exe
attempts to export symbols,node.lib
gets generated causing native extensions to try to link againstnode.exe
notlibnode.dll
.node.dll
was renamed tolibnode.dll
, native extensions don't know to look forlibnode.lib
rather thannode.lib
.libnode.dylib
relative tonode
in the same folder. This works fine from theout/Release
folder but not from an installed prefix, wherenode
will be inbin/
andlibnode.dylib
will be inlib/
.$ORIGIN
so LD_LIBRARY_PATH needs setting correctly forbin/node
to findlib/libnode.so
.For the
libnode.lib
vsnode.lib
issue there are two possible options:node.lib
fromnode.exe
does not get generated, and instead copylibnode.lib
tonode.lib
. This means addons compiled when referencing the correctnode.lib
file will correctly depend onlibnode.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.libnode.dll
, dump the exports usingdumpbin
, and process this to generate anode.def
file to be linked intonode.exe
with the/DEF:node.def
flag. The export entries innode.def
would all readnode.exe
will redirect all exported symbols back tolibnode.dll
. This has the benefit that addons compiled with stock Node.js will load correctly intonode.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,
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 easily by specifying the correctnodedir
option.For example
I have tested my changes on Linux/x86_64, Linux/s390x, Linux/ppc64le (all on RHEL 7.9 devtoolset-10), Windows/x86_64 (VS2019 on Windows Server 2019 Datacenter Edition), and macOS/x86_64 (macOS 12.1 with Apple Clang 13.0.0). I have tried to test on AIX but the GCC 8 installation on the machine I have access to is currently broken...
Fixes #34539
Fixes #41559
This PR is essentially a set of patches that I have been maintaining for the Node.js 14.x branch internally in my day job. Ideally I would love to see these changes get back ported but I can understand if this is not desireable.
Short of making shared library builds of Node.js the default, as is common for other embeddable runtimes such as the JVM or the CLR, or by building both the standard build and the shared library build together in the CI, I don't see a way to prevent changes that break the shared library from being merged in the future.