Skip to content

External libraries

Dane Springmeyer edited this page Jul 14, 2015 · 4 revisions

The versioning scheme of node-pre-gyp handles the core complexity of producing binaries that will run against different platforms, architectures, and node versions.

But one more problem remains: your binaries will depend on a C and C++ library implementation that may or may not be stable across different operating system versions. And some node C++ modules might have external shared library dependencies. These challenges can be overcome but takes some attention. I plan to provide some basic docs about best practices and gochas developers will need to know.

Here is a start:

Shared libraries

The easiest method is to compile external deps as static libraries instead of shared libraries. This is the approach taken for a variety of dependencies like libpng, libprotobuf, or boost in https://github.com/mapnik/mapnik-packaging. Alternatively shared libraries can be copied the node C++ module and install_name_tool (OSX) or chrdir (linux) can be used to ensure those local shared libs are used instead of potentially globally installed ones. Finally, another option is to create a gyp configuration for external libraries so that they are compiled and linked on the fly. This is easy for small C/C++ libraries and difficult for larger, more complex ones. An example of this method is show in the test apps: test app 3 shows how to use gyp to statically link a statically compiled library and test app 4 shows how to use gyp to dynamically link and shared library.

OS X

OS X is dead easy. You just set compiler flags to tell the compiler how old of a mac version you want to support. For example, if you want your binaries to run on versions as old as 10.7 just do:

export CFLAGS="-mmacosx-version-min=10.7"
export CXXFLAGS="-mmacosx-version-min=10.7"
export LDFLAGS="-mmacosx-version-min=10.7"
node-pre-gyp build

Be aware that currently the Node build scripts default to targeting 10.5 so if you don't do anything your module should hypothetically work on >= 10.5: https://github.com/joyent/node/blob/d0ff900a65e4250ad49dcf02e07262af1fa5de25/common.gypi#L206

The main gocha on OS X is that sometimes C++ code requires features not possible on older OS X versions and you'll get a compiler error if the -mmacosx-version-min is set to too old a version. For example for -std=c++11 support you need at least 10.7 set.

Windows

Usually just works, but the big gocha is whether the C++ runtime has been statically linked or dynamically linked.

  • node.exe statically links the windows c++ lib so it can be installed and run without any auxiliary installs. If your module does not link any external dll's then you should be good. Your binary will run out of the box just like node.exe does (for the given architecture).

  • But if your app depends on any custom C++ libraries that have been dynamically linked then users who install your module will need to have installed the C++ Redistributable Package for the given Visual Studio version you built the binaries with. So if you built your binaries with Visual Studio 2010 then your users would need to have installed the vcredist_x86.exe from http://www.microsoft.com/en-us/download/details.aspx?id=5555. Often users already have done this once for some application in the past and errors will only present themselves from this problem on vanilla machines. And the older the visual studio version you use the more likely the user will already have installed the vcredist_x86.exe.

The key option that controls static vs dynamic linking of your module to external deps is the RuntimeLibrary type. For example node-mapnik links mapnik.dll dynamically and therefore has this override in the common.gypi, which makes it depend on vcredist_x86.exe:

                'msvs_settings': {
                    'VCCLCompilerTool': {
                        'RuntimeLibrary': '2' # /MD
                    }
                }

Linux

On linux libc versioning is a challenge. If you build on a very new linux version with the latest libc then users on older linux systems may not all the libc symbols available to run your binaries.

Here are the options:

Just use an old linux distro

If you build on the oldest debian/ubuntu/etc distribution you want to support - it will work on that distro and all in the future (glibc at least). For example, if you build on Ubuntu LTS they you can rest assured it should work on that LTS version from now in the future and all future Ubuntu releases that come after.

Hack specific malloc/glibc issues as they come up

As described at https://rjpower9000.wordpress.com/2012/04/09/fun-with-shared-libraries-version-glibc_2-14-not-found/ its possible to compile on more recent linux but still support older linux by forcing the older version of a versioned glibc symbol to be used.

Listaller is basically designed to solve this problem, as well as others.

Note: now named Limba: http://people.freedesktop.org/~mak/limba/faq/#native-replace

  • Pro: Creates portable builds out of the box without glibc issues, just have to override the CC/CXX flags

But lots of questions:

  • Does it support c++11 yet?
  • Any runtime speed penalties to using it over gcc or clang?
  • Do binaries built with it require any custom libgcc or other libraries to be around?

More references on linux issues: