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

wha*_*ase 2 c++ node.js node-gyp node.js-addon

我正在开发一个节点插件,但在使用 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' ]
}]
}
Run Code Online (Sandbox Code Playgroud)

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

这是我尝试在 lib 文件夹而不是根文件夹中运行项目 so 文件时出现的错误。

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
Run Code Online (Sandbox Code Playgroud)

s1h*_*s1h 5

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

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

  • 苹果系统: otool -L your_addon.node
  • Linux: ldd you_addon.node

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

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

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

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

  • 苹果系统: 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'/.."
                  ],
                }
              }
            ]
        ],
Run Code Online (Sandbox Code Playgroud)

$$ORIGIN? @loader_path? 这是什么?

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

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

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

例子

.
??? build
?   ??? Release
?       ??? addon.node
??? index.js
??? lib
?   ??? my_library.dylib
??? package-lock.json
??? package.json
Run Code Online (Sandbox Code Playgroud)

我们的构建生成了在构建时./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",
                  ],
                }
              }
Run Code Online (Sandbox Code Playgroud)

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

窗户呢?

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)"
    }]
}
Run Code Online (Sandbox Code Playgroud)