Skip to content

build: Fix various shared library build issues.

Rodrigo Muino Tomonari requested to merge github/fork/lux01/win-shared into master

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, 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:

  • 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, the only RPATH that is added is $ORIGIN 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 would 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 vs2019

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 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
...

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 (closed) Fixes #41559 (closed)

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.

Merge request reports

Loading