node-gyp некорректно связывает библиотеки на macos - PullRequest
0 голосов
/ 13 июля 2020

Я работаю над надстройкой узла, но после сборки с помощью 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' ]
}]
}

Это строится правильно, и если я помещаю такой файл в папку root проекта, все работает правильно. Но я хочу запустить проект с файлами в папке lib.

Это ошибка, когда я пытаюсь запустить проект, чтобы файлы были в папке lib, а не в папке root.

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

1 Ответ

2 голосов
/ 13 июля 2020

Проблема со связанными сторонними библиотеками заключается в том, что вам нужно будет указать места для их поиска. Аддоны узла представляют собой динамически подключаемые общие библиотеки, поэтому всякий раз, когда вам требуется аддон, соответствующий динамический c загрузчик должен будет загрузить все необходимые библиотеки.

Как мы можем проверить, какие библиотеки нам нужны?

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

Для загрузки требуемых библиотек наш загрузчик будет искать в нескольких местах, например, во всех путях, перечисленных в LD_LIBRARY_PATH на Linux.

Но мы поставляем нашу собственную библиотеку, которой нет ни в одном из стандартных путей поиска?

Это возможно для библиотеку или исполняемый файл (формат ELF в Linux или формат 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 - это токены, которые наш динамический c загрузчик собирается заменить на каталог, содержащий наш двоичный файл. Таким образом, независимо от того, где будет установлена ​​наша библиотека, если мы укажем пути относительно расположения наших двоичных файлов, загрузчик Dynami c сможет ее найти. ('$$ORIGIN' - это всего лишь небольшой обходной путь, поэтому node-gyp не испортит ситуацию при попытке подстановки значений)

Хорошее прочтение по этому топу c - эта статья

Пример

.
├── 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. Скопируйте ваши библиотеки в результирующий выходной директор y, например, build/Release
  3. Установите для rpath значение $ORIGIN или @loader_path на Linux и macOS

Таким образом, мы даем команду динамическому c загрузчик в Linux и macOS для поиска нашей библиотеки в той же папке, что и наш результирующий двоичный файл, что является поведением по умолчанию для Windows.

Копирование файлов может выполняться через дополнительную цель в наш файл gyp:

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