0

I'm building a Node.js wrapper for a C shared library using node-gyp and node-addon-api. It works! But as the library was built from Golang code, I have different builds for different architectures -- the module can't compile the C library from the Go source itself.

My problem is that as soon as I move the library files into a subdirectory inside the Node module, the module no longer works. I want the library in a subdirectory so that I can provide copies built for different architectures using conditionals.

With all files in the root directory like this:

node-mylib/
┣ build/
┃ ┣ Release/
┃ ┃ ┣ .deps/
┃ ┃ ┃ ┗ Release/
┃ ┃ ┃   ┣ obj.target/
┃ ┃ ┃   ┗ mylib.node.d
┃ ┃ ┣ obj.target/
┃ ┃ ┃ ┗ mylib/
┃ ┃ ┃   ┗ mylib.o
┃ ┃ ┗ mylib.node
┃ ┣ Makefile
┃ ┣ binding.Makefile
┃ ┣ config.gypi
┃ ┣ mylib.target.mk
┃ ┗ gyp-mac-tool
┣ binding.gyp
┣ mylib.cc
┣ index.js
┣ libmylib.h
┣ libmylib.so
┣ package-lock.json
┗ package.json

and a binding.gyp like this:

{
  "targets": [
    {
      "conditions": [
        ['OS=="mac"', {
            'cflags+': ['-fvisibility=hidden'],
            'xcode_settings': {
              'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden
            }
        }]
      ],
      "defines": [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
      "include_dirs": ["<(module_root_dir)", "<!(node -p \"require('node-addon-api').include_dir\")"],
      "target_name": "mylib",
      "sources": [ "mylib.cc" ],
      "libraries": [ "-Wl,-rpath,<(module_root_dir)", '-lmylib', '-L<(module_root_dir)'],
    }
  ]
}

... then node-gyp rebuild runs fine, and node index.js returns the expected output from the library.

When I move the library into a subdirectory, like this:

node-biodiversity/
┣ build/
┃ ┣ Release/
┃ ┃ ┣ .deps/
┃ ┃ ┃ ┗ Release/
┃ ┃ ┃   ┣ obj.target/
┃ ┃ ┃   ┗ mylib.node.d
┃ ┃ ┣ obj.target/
┃ ┃ ┃ ┗ mylib/
┃ ┃ ┃   ┗ macos-arm64/
┃ ┃ ┃     ┗ mylib.o
┃ ┃ ┗ mylib.node
┃ ┣ Makefile
┃ ┣ binding.Makefile
┃ ┣ config.gypi
┃ ┣ mylib.target.mk
┃ ┗ gyp-mac-tool
┣ macos-arm64/
┃ ┣ mylib.cc
┃ ┣ libmylib.h
┃ ┗ libmylib.so
┣ binding.gyp
┣ index.js
┣ package-lock.json
┗ package.json

and update the binding.gyp like this:

{
  "targets": [
    {
      "conditions": [
        ['OS=="mac"', {
            'cflags+': ['-fvisibility=hidden'],
            'xcode_settings': {
              'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden
            }
        }]
      ],
      "defines": [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
      "include_dirs": ["<(module_root_dir)", "<(module_root_dir)/macos-arm64", "<!(node -p \"require('node-addon-api').include_dir\")"],
      "target_name": "mylib",
      "sources": [ "macos-arm64/mylib.cc" ],
      "libraries": [ "-Wl,-rpath,<(module_root_dir)/macos-arm64", '-lmylib', '-L<(module_root_dir)/macos-arm64'],
    }
  ]
}

I get this error:

Error: dlopen(/Users/toby/Code/node-mylib/build/Release/mylib.node, 1): Library not loaded: libmylib.so
  Referenced from: /Users/toby/Code/node-mylib/build/Release/mylib.node
  Reason: image not found

I've tried every combination of libraries and include_dirs and can't get it to find the library.

2 Answers2

0

The only way I could get this working was to use install_name_tool in a node-gyp postbuild:

{
  "targets": [
    {
      "conditions": [
        ['OS=="mac"', {
            'cflags+': ['-fvisibility=hidden'],
            'xcode_settings': {
              'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden
            }
        }]
      ],
      "defines": [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
      "include_dirs": ["<(module_root_dir)/macos-arm64", "<!(node -p \"require('node-addon-api').include_dir\")"],
      "target_name": "mylib",
      "sources": [ "macos-arm64/mylib.cc" ],
      "libraries": ['<(module_root_dir)/macos-arm64/libmylib.so'],
      "postbuilds": [
        {
          "postbuild_name": 'Change libmylib load path',
          "action": ['install_name_tool', '-change',  'libmylib.so', '@loader_path/../../macos-arm64/libmylib.so', '<(PRODUCT_DIR)/mylib.node'],
        },
      ],
    }
  ]
}

0

See my answer to this question: Program compiles, but cannot run because of missing library that exists

Everything I said applies to macOS too. You have 3 ways to make your library known to the runtime linker:

  • install it in one of the standard locations
  • specify an environment variable before running it
  • embed a custom location the executable when linking it
mmomtchev
  • 2,497
  • 1
  • 8
  • 23