node-gyp 在 macOS 上未正确链接库

node-gyp not linking libraries correctly on macos

本文关键字:链接 macOS node-gyp      更新时间:2023-10-16

我正在开发一个节点插件,但是在使用node-gyp构建后查找库时出错。

这是我的binging.gyp文件:

{
"targets": [{
"target_name": "xaddon",
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"sources": [
"cppsrc/main.cc"
],
'include_dirs': [
"<!@(node -p "require('node-addon-api').include")", "lib"
],
"libraries": ["<(module_root_dir)/lib/xaddon.so"],
'dependencies': [
"<!(node -p "require('node-addon-api').gyp")"
],
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ]
}]
}

这正确构建,如果我将 so 文件放在项目的根文件夹中,一切正常。但是我想运行一个带有 lib 文件夹中文件的项目。

当我尝试运行项目时,这是错误,因此文件位于 lib 文件夹而不是根文件夹中。

Error: dlopen(#PATH_TO_PROJECT#/build/Release/xaddon.node, 1): Library not loaded: xaddon.so
Referenced from: #PATH_TO_PROJECT#/build/Release/xaddon.node
Reason: image not found

链接的第三方库的问题在于,您必须提供搜索它们的位置。 节点插件是动态链接的共享库,因此每当您需要插件时,相应的动态加载器都必须加载所有必需的库。

我们如何检查我们需要哪些库?

  • macOS:otool -L your_addon.node
  • Linux:ldd you_addon.node

为了加载这些需要库,我们的加载器将在多个地方进行搜索,例如在 Linux 上LD_LIBRARY_PATH中列出的所有路径中。

但是我们正在发布我们自己的库,它不在标准搜索路径之一中?

库或可执行文件(Linux 上的 ELF 格式或 Mach-O 格式,例如 macOS)可以指定运行时加载程序路径。这些路径被硬编码到二进制文件中,并且可以指定其他路径来搜索库。

我们如何检索二进制文件RPATH

  • macOS:otool -l your_addon.node | grep RPATH -A2
  • Linux:objdump -x your_addon.node | grep RPATH

好的,rpath了!

我们可以通过链接器标志配置我们的rpath

"conditions": [
["OS=="mac"",
{
"link_settings": {
"libraries": [
"-Wl,-rpath,@loader_path",
"-Wl,-rpath,@loader_path/..",
],
}
}
],
["OS=="linux"",
{
"link_settings": {
"libraries": [
"-Wl,-rpath,'$$ORIGIN'",
"-Wl,-rpath,'$$ORIGIN'/.."
],
}
}
]
],

$$ORIGIN@loader_path?这是怎麽?

rpath条目是硬编码的,因此将其固定为 例如 一旦您尝试在其他机器上使用插件,/home/youruser/libs/foo/bar/就会中断。

$ORIGIN@loader_path都是令牌,我们的动态加载程序将用包含二进制文件的目录替换它们。因此,无论我们的库将安装在何处,如果我们指定相对于二进制文件位置的路径,动态加载器将能够找到它。('$$ORIGIN'只是需要一点解决方法,因此node-gyp尝试替换值时不会搞砸事情)

关于这个主题的一个很好的阅读是这篇文章

.
├── build
│   └── Release
│       └── addon.node
├── index.js
├── lib
│   └── my_library.dylib
├── package-lock.json
└── package.json

我们的构建会生成./build/Release/addon.node文件,该文件在构建时与./lib/my_library.dylib链接。

使用@loader_path我们现在可以指定相对于二进制文件位置的rpath,因为@loader_path将替换为/whatever/path/to/our/package/build/Release.

{
"link_settings": {
"libraries": [
"-Wl,-rpath,@loader_path/../../lib",
],
}
}

在运行时,这将导致/whatever/path/to/our/package/build/Release/../../lib,正是我们的库所在的位置。

Windows呢?

Windows二进制文件没有/使用rpath属性。

但是,DLL 搜索顺序将开始在加载应用程序的目录中搜索。

跨平台方法

我交付所需库的方法如下:

  1. 在构建期间链接库
  2. 将库复制到生成的输出目录,例如build/Release
  3. 在 Linux 和 macOS 上将rpath设置为$ORIGIN@loader_path

这样,我们指示 Linux 和 macOS 上的动态加载程序在与生成的二进制文件相同的文件夹中搜索我们的库,这是 Windows 上的默认行为。

复制文件可以通过我们的gyp文件中的附加目标完成:

{
"target_name": "action_before_build",
"type": "none",
"copies": [{
"files": [ "/your/lib.dylib" ],
"destination": "<(PRODUCT_DIR)"
}]
}