Linking a dynamic lib using Bazel on MacOS - c++

I'm compiling my program using Bazel, and I have a dependency on Intel TBB.
Intel TBB only provide dynamic libraries (no static) for good reasons (if you are curious: ctrl+f static here).
In my bazel WORKSPACE I defined that rule :
new_local_repository(
name = "inteltbb",
path = "./third_party/intel_tbb",
build_file = "./third_party/inteltbb.BUILD",
)
and in my "inteltbb.BUILD" I have:
cc_library(
name = "dynamic_lib",
srcs = ["build/macos_intel64_clang_cc8.1.0_os10.12.5_debug/libtbb_debug.dylib"],
hdrs = glob(["include/**/*.h"]),
visibility = ["//visibility:public"],
strip_include_prefix = "include/"
)
Then in my final program (under the cc_binary rule) I have:
deps = [
"#inteltbb//:dynamic_lib", [...]
It compile properly, find the headers successfully but at runtime it crashes saying:
____Running command line: bazel-bin/build-game
dyld: Library not loaded: #rpath/libtbb_debug.dylib
Referenced from: /private/var/tmp/_bazel_dmabin/526b91f44cfc47d856222c6b20765cc8/execroot/__main__/bazel-out/darwin_x86_64-fastbuild/bin/build-game
Reason: image not found
I check under the "bazel-bin" folder (where the executable is symlink for runtime execution: bazel-bin/build-game.runfiles/main/) and I do have:
the symlink for the executable (build-game)
a folder called (brace yourself): _solib_darwin_x86_64/_U#inteltbb_S_S_Cdynamic_Ulib___Uexternal_Sinteltbb_Sbuild_Smacos_Uintel64_U
clang_Ucc8.1.0_Uos10.12.5_Udebug that contain my libtbb_debug.dylib for intel tbb.
Also when I run: otool -l build-game | grep LC_RPATH -A2 the result is:
cmd LC_RPATH
cmdsize 152
path $ORIGIN/_solib_darwin_x86_64/_U#inteltbb_S_S_Cdynamic_Ulib___Uexternal_Sinteltbb_Sbuild_Smacos_Uintel64_Uclang_Ucc8.1.0_Uos10.12.5_Udebug (offset 12)
I don't understand why my executable doesn't find my dylib. I can't find anything wrong about the otool output but I'm not a mac expert (at all).
Any idea is welcome.
[edit]
If I edit the executable using otool to replace the path to the dylib like that:
#executable_path/_solib_darwin_x86_64/_U#inteltbb_S_S_Cdynamic_Ulib___Uexternal_Sinteltbb_Slib/libtbb_debug.dylib
Then it works fine. I doesn't feel right that bazel force me to do that at every compilation :/
[edit 2]
A few people asked me the line I used to modify the path of the lib in the executable.
First run:
otool -L build-game
To spot the path of the lib you want to change. In my case it was: #rpath/libtbb_debug.dylib.
After that run:
install_name_tool -change #rpath/libtbb_debug.dylib #executable_path/_solib_darwin_x86_64/_U#inteltbb_S_S_Cdynamic_Ulib___Uexternal_Sinteltbb_Slib/libtbb_debug.dylib build-game
to change the path. Note that you can use #executable_path to make it relative to the binary path or you can put an absolute path, up to you.

I think this is already a known issue, and it was just fixed. Sorry for the trouble.
[edit: updated after submitting the fix]

Related

Link to versioned pre-built libraries with bazel

Assume that a prec-compiled dependency is supplied by a vendor:
$ ls /opt/vendor/protobuf/lib
libprotobuf.so.3 -> libprotobuf.so.3.0.0
libprotobuf.so.3.0.0
To use this with Bazel, the following target can be created:
cc_import(
name = "protobuf",
shared_library = "lib/libprotobuf.3.0.0",
)
This way, a bazel-built application can link to the library, however, it fails to start:
error while loading shared libraries: libprotobuf.so.3: cannot open shared object file: no such file or directory
The root cause is that the actual so file has a custom SONAME field:
objdump -x libprotobuf.so.3.0.0|grep SONAME
SONAME libprotobuf.so.3
The loader will look for libprotobuf.so.3 (instead of 3.0.0), but will not find it in the sandbox, as we never told bazel about the symlink. The symlink is relative, specifying it in the cc_import target will yield to a similar error.
Is it possible to create a runnable binary with bazel that links to such a shared library that is supposed to be found via a symlink?
Setting the RPATH can be a workaround. The cc_import need to be wrapped by a cc_library:
cc_library(
name = "protobuf",
deps = [":protobuf_impl"],
linkopts = ["-Wl,-rpath=/opt/vendor/protobuf/lib"],
)
cc_import(
name = "protobuf_impl",
shared_library = "lib/libprotobuf.3.0.0",
)
This will make the binary run, but assumes that "/opt/vendor/protobuf/lib" is present on every system (incl. remote execution), and the loader during runtime still escapes the sandbox. A clearer solution would be nice.

Setting a System Wide path in Mac Yosemite

I have installed boost libraries. I am using Eclipse to make a simple boost project.
#include <stdio.h>
#include <boost/filesystem.hpp>
int main() {
boost::filesystem::path path("/Users/schoen"); // random pathname
bool result = boost::filesystem::is_directory(path);
printf("Path is a directory : %d\n", result);
return 0;
}
I have already set the path of include folder and library folder in the properties of this project. But I was getting a runtime error: dyld: Library not loaded: libboost_system.dylib. To solve this problem, I went to run configuration and set the environment variable DYLD_LIBRARY_PATH to /Users/myName/Documents/Softwares/boost_1_59_0/stage/lib. This has solved my problem.
What I need: I don't want to set the environment variable for each Boost-based project. Therefore, I tried to set my .bash_profile. I wrote the following lines in .bash_profile.
# Following lines are for Boost Library
DYLD_LIBRARY_PATH=/Users/myName/Documents/Softwares/boost_1_59_0/stage/lib
export DYLD_LIBRARY_PATH
PROBLEM: By setting the variable in .bash_profile, I am able to run my program through the terminal. The program also runs if I open the IDE (e.g. Eclipse) via terminal and then run the application. Apparently, .bash_profile can set the variable for terminal applications. How can I set the path for windowed applications too?
PS: This path setting problem is not just for Boost only, but I have to do similar things for other libraries too (such as OpenCV which is build/installed in a local directory).
Rather than adding a system wide path like this, you should add the rpath to the libraries to applications that depend on boost. To add the rpath option, you do Project Properties -> C/C++ Build -> Settings -> Miscellaneous and in the linker flags add:
-Wl,-rpath,/Users/myName/Documents/Softwares/boost_1_59_0/stage/lib
(this is if your linker is g++ or clang++, for example)
If your linker is ld explicitly, then the option is
-rpath /Users/myName/Documents/Softwares/boost_1_59_0/stage/lib
although you may need to add -macos_version_min 10.5 (or newer - probably 10.8 - this depends on the OS you're building on).
This will cause any applications built to search there for libraries as well as the default locations.
Although a location like that to me looks a bit volatile
Turns out that boost builds without setting the library name to include #rpath in the install name, which means that even when you set the -rpath in the build, because the libraries aren't mentioned to be in the #rpath, it won't find them at run time. A workaround to this is to explicitly set the install name for the boost libraries, and their internal references to their own libraries:
#!/bin/bash -p
for i in *.dylib; do
# set the rpath
install_name_tool -id #rpath/$i $i
for lib in $(otool -L $i | grep libboost | awk '{print $1}'); do
if [[ -f $lib ]]; then
install_name_tool -change $lib #rpath/$lib $i
fi
done
done
This means the binaries that are linked to these boost libraries will respect the rpath setting.
You can repeat a similar process for other libraries to ensure that they respect rpath. The key element is the install_name_tool -id "#rpath/libstuff.dylib" libstuff.dylib, which says that when you link to the library record a reference to #rpath/libstuff.dylib. If libraries don't already have this set.
Secondly, for internal references to dependent libraries, the -change option alters references to the absolute name to an rpath relative name e.g. install_name_tool -change "libstuff.dylib" "#rpath/libstuff.dylib" libdependsonlibstuff.dylib. This can even be performed on a linked binary.
If you still want to set an environment variable, there are some options available which should help you getting a solution which works in that case.

qmake - QMAKE_RPATHDIR doesn't work

I have mac os x. I have application and it needs to use shared library (framework on mac), that is developed as separate project, but concurrently and in Qt 5 too.
App.pro
.
.
else:mac: LIBS += -F$$OUT_PWD/Frameworks -framework library1
.
.
QMAKE_RPATHDIR += /usr/lib
firstly, i am telling to qmake, that library1 will reside in it's bundle in Frameworks directory (this is no problem, linking is done success)
secondly, QMAKE_RPATHDIR should tell to gcc compiler, that when app's libraries are located, it should look to those paths in QMAKE_RPATHDIR too. I specified /usr/lib, just to check if it will work, but:
otool -l app
doesn't show up any LC_RPATH (i am expecting there will be one record for /usr/lib) as in here Print rpath of executable on OSX
I really need to setup my development environment (Qt 5, Mac OS X, one base application, one core library (this will act's as SDK for plugins too) and additional plugins (shared libraries too).
ERROR is still:
dyld: Library not loaded: library1.framework/Versions/1/library1
Referenced from: /Users/Krab/projects/qtProjects/build-rootProject-Desktop_Qt_5_3_0_clang_64bit-Release/app/app.app/Contents/MacOS/app
Reason: image not found
this is obvious, because the settings in .pro files is just for linking and doesn't resolve the dynamic loading of those libraries (which should be resolved by that QMAKE_RPATHDIR directive).
You need to build your framework with the correct install name. For example:
QMAKE_LFLAGS_SONAME = -Wl,-install_name,#rpath/
Then in your application,
osx:LIBS += -F$$OUT_PWD/Frameworks -framework library1
QMAKE_RPATHDIR += /usr/lib
If your resulting "library1" framework is placed in /usr/lib, then your application should load fine. However, in almost all cases you'll want the framework placed inside your application bundle, like so:
myapp.app/Contents/Frameworks/library1.framework
In that case you'd set the rpath in your application a little differently, like so:
QMAKE_RPATHDIR += #executable_path/../Frameworks
Add
QMAKE_POST_LINK += install_name_tool -add_rpath <path to look in> $$TARGET
to your .pro file - where < path to look in> is the path you framework or dylib resides.
for example, if you place the framework right next to your executable, use
QMAKE_POST_LINK += install_name_tool -add_rpath #executable_path $$TARGET
In my case, I had a simple dylib instead of a framework linked to my executable. I had the following already:
LIBS += -L/path-to-mylibrary/ -lmylibrary
QMAKE_RPATHDIR += /path-to-mylibrary/
QMAKE_RPATHDIR was working as my executable listed the LC_RPATH command correctly (obtained by otool -l myexecutable):
Load command 25
cmd LC_RCPATH
cmdsize 56
name /path-to-mylibrary/ (offset 12)
However, my resulting executable listed the LC_LOAD_DYLIB command for my library as follows:
Load command 12
cmd LC_LOAD_DYLIB
cmdsize 48
name libmylibrary.dylib (offset 24)
As per this answer, I updated the LC_LOAD_DYLIB command to include the RPATH when looking for the dylib by adding the following to the .pro file of my executable application:
QMAKE_POST_LINK += install_name_tool -change libmylibrary.dylib #rpath/libmylibrary.dylib $$TARGET
Afterwards, my executable started listing the LC_LOAD_DYLIB command correctly:
Load command 12
cmd LC_LOAD_DYLIB
cmdsize 56
name #rpath/libmylibrary.dylib (offset 24)
As a side note, mylibrary is actually a qml plugin deployed into /qt-install-root/qt-version/platform/qml/Mylibrary/ and is loaded also from the QML engine in other projects.

SCons libraries and sub-libraries

I have a hierarchical build system based on SCons. I have a root SConstruct that calls into a SConscript that builds a shared library and then into a different SConscript that builds an executable that depends on the shared library.
So here's my question: my understanding of shared libraries on linux is that when you want to do the final ld link for the executable that will be using the shared lib, the shared lib has to be included on the executable's ld command line as a source to reference it (unless it's in a standard location in which case the -l option works).
So here's something like what my SCons files look like:
=== rootdir/SConstruct
env=DefaultEnvironment()
shared_lib = SConscript('foolib/SConscript')
env.Append( LIBS=[shared_lib] )
executable = SConscript('barexec/SConscript')
=== rootdir/foolib/SConscript
env=DefaultEnvironment()
env.Append(CPPPATH=Glob('inc'))
penv = env.Clone()
penv.Append(CPPPATH=Glob('internal/inc'))
lib = penv.SharedLibrary( 'foo', source=['foo.c', 'morefoo.c']
Return("lib")
=== rootdir/barexec/SConscript
env=DefaultEnvironment()
exe = env.Program( 'bar', source=['main.c', 'bar.c', 'rod.c'] )
Return("exe")
So the hitch here is this line:
env.Append( LIBS=[shared_lib] )
This would be a great way to add generated libraries to the command line for any other libs that need them, EXCEPT that because SCons is doing a two-pass run through the SConscripts (first to generate it's dependency tree, then to do the work), rootdir/foolib/libfoo.so winds up on the command line for ALL products, EVEN libfoo.so itself:
gcc -g -Wall -Werror -o libfoo.so foo.o morefoo.o libfoo.so
So how is this best done with SCons? For now I've resorted to this hack:
=== rootdir/SConstruct
env=DefaultEnvironment()
shared_lib = SConscript('foolib/SConscript')
env['shared_lib'] = shared_lib
executable = SConscript('barexec/SConscript')
...
=== rootdir/barexec/SConscript
env=DefaultEnvironment()
exe = env.Program( 'bar', source=['main.c', 'bar.c', 'rod.c'] + env['shared_lib'] )
Return("exe")
Is there a more SCons-y way of doing this?
You should allow the shared libraries to be found by the build.
Look for the LIBPATH and RPATH variables in the SCons documentation; these are the "Scons-y" way to set up search paths so that any generated -l options find libraries properly.
Having mentioned the above, here's what you should see gcc do based on the setup of SCons (and if it doesn't, you may have to do it manually).
The -l option always finds shared libraries provided that you also give the compiler the location of the library. There are two times this is needed: at compile time (-L option) and at runtime (-rpath generated linker option).
The LIBPATH SCons setup should generate something that looks like -L/some/directory/path for the compile-time search path.
The RPATH SCons setup should generate a linker option to embed a search path; e.g. -Wl,-rpath -Wl,\$ORIGIN/../lib would embed a search path that searches relative to the executable so that executables placed in bin search in the parallel lib directory of the installation.
Here is a better way to organize your SConsctruct/SConscript files. Usually with Hierarchical builds you should share the env with the rest of the sub-directories. Notice that I cloned the main env in the barexec directory as well, so that the foolib is only used to link that binary.
=== rootdir/SConstruct
import os
env=DefaultEnvironment()
subdirs = [
'foolib',
'barexec'
]
# The exports attribute allows you to pass variables to the subdir SConscripts
for dir in subdirs:
SConscript( os.path.join(dir, 'SConscript'), exports = ['env'])
=== rootdir/foolib/SConscript
# inports the env created in the root SConstruct
#
# Any changes made to 'env' here will be reflected in
# the root/SConstruct and in the barexec/SConscript
#
Import('env')
# Adding this 'inc' dir to the include path for all users of this 'env'
env.Append(CPPPATH=Glob('inc'))
penv = env.Clone()
# Adding this include only for targets built with penv
penv.Append(CPPPATH=Glob('internal/inc'))
penv.SharedLibrary( 'foo', source=['foo.c', 'morefoo.c'])
=== rootdir/barexec/SConscript
Import('env')
clonedEnv = env.Clone()
# The foo lib will only be used for targets compiled with the clonedEnv env
# Notice that specifying '#' in a path means relative to the root SConstruct
# for each [item] in LIBS, you will get -llib on the compilation line
# for each [item] in LIBPATH, you will get -Lpath on the compilation line
clonedEnv.Append(LIBS=['foo'], LIBPATH=['#foolib'])
clonedEnv.Program( 'bar', source=['main.c', 'bar.c', 'rod.c'] )
Additional to Brady decision i use static/global variables to store targets name and path. It's allow me more control over build.
# site_scons/project.py
class Project:
APP1_NAME = "app1_name"
APP2_NAME = "app2_name"
MYLIB1_NAME = "mylib1_name"
# etc
APP_PATH = "#build/$BuildMode/bin" # BuildMode - commonly in my projects debug or release, `#` - root of project dir
LIB_PATH = "#build/$BuildMode/lib"
#staticmethod
def appPath(name) :
return os.path.join(APP_PATH, name)
#staticmethod
def libPath(name) :
return os.path.join(LIB_PATH, name)
Define targets:
from project import Project
...
env.SharedLibrary(Project.libPath(Project.MYLIB1_NAME), source=['foo.c', 'morefoo.c'])
Application:
from project import Project
...
env.Append(LIBPATH = [Project.LIB_PATH])
env.Append(LIBS = [Project.MYLIB1_NAME])
env.Program(Project.appPath(Project.MYAPP1_NAME), source=[...])
In my projects it works fine, scons automatically find depends of library without any additional commands. And if i want to change name of library i just change my Project class.
One issue that Brady's answer doesn't address is how to get correct library paths when building out-of-source using variant dirs. Here's a very similar approach that builds two different variants:
SConstruct
# Common environment for all build modes.
common = Environment(CCFLAGS=["-Wall"], CPPPATH=["#foolib/inc"])
# Build-mode specific environments.
debug = common.Clone()
debug.Append(CCFLAGS=["-O0"])
release = common.Clone()
release.Append(CCFLAGS=["-O"], CPPDEFINES=["NDEBUG"])
# Run all builds.
SConscript("SConscript", exports={"env": debug}, variant_dir="debug")
SConscript("SConscript", exports={"env": release}, variant_dir="release")
The # in the value for CPPPATH makes the include path relative to the project root instead of the variant dir.
SConscript
Import("env")
subdirs=["barexec", "foolib"]
senv = env.Clone(FOOLIBDIR=Dir("foolib"))
SConscript(dirs=subdirs, exports={"env": senv})
This root-level SConscript is required to build the subdirectories in each variant_dir.
By using the function Dir() when setting FOOLIBDIR, the library's variant build directory is resolved relative to this file rather than where it's used.
foolib/SConscript
Import("env")
penv = env.Clone()
penv.Append(CPPPATH=["internal/inc"])
penv.SharedLibrary("foo", source=["foo.c", "morefoo.c"])
It's important to clone the environment before making any changes to avoid affecting other directories.
barexec/SConscript
Import("env")
clonedEnv = env.Clone()
clonedEnv.Append(LIBPATH=["$FOOLIBDIR"], LIBS=["foo"])
clonedEnv.Program("bar", source=["main.c", "bar.c", "rod.c"])
The library's variant build dir is added to LIBPATH so both SCons and the linker can find the correct library.
Adding "foo" to LIBS informs SCons that barexec depends on foolib which must be built first, and adds the library to the linker command line.
$FOOLIBDIR should only be added to LIBPATH when "foo" is also added to LIBS – if not, barexec might be built before foolib, resulting in linker errors because the specified library path does not (yet) exist.

Cannot open shared object file

I am trying to compile one of the projects found here
USB-I2C/SPI/GPIO Interface Adapter.
I downloaded the i2c_bridge-0.0.1-rc2.tgz package. I installed libusb and that seemed to go well with no issues. I go into the i2c_bridge-0.0.1-rc2/ directory and make. That compiles. I move into the i2c_bridge-0.0.1-rc2/i2c folder and make. It compiles and gives me ./i2c. However, when I run it, it says error while loading shared libraries: libi2cbrdg.so: cannot open shared object file: No such file or directory
The makefile in i2c_bridge-0.0.1-rc2/i2c has the library directory as ../. The libi2cbrdg.so is in this directory (i2c_bridge-0.0.1-rc2). I also copied the file to /usr/local/lib. An ls of the i2c_bridge-0.0.1-rc2/ directory is
i2c i2cbrdg.d i2cbrdg.o libi2cbrdg.a Makefile tests
i2cbrdg.c i2cbrdg.h INSTALL libi2cbrdg.so README u2c4all.sh
(That i2c is a directory)
If I sudo ./i2c, it still gives me the problem.
I had to take away the -Werror and -noWdecrepated (spelling?) options in all the makefiles to get them to compile, but that shouldn't affect this should it?
What else is necessary for it to find the .so file? If anyone can help me find out what is wrong I would be very grateful. If more information is needed I can post it.
You have to distinguish between finding so's at compile-time and at run-time. The -L flag you give at compile-time has nothing to do with localizing the library at run-time. This is rather done via a number of variables and some paths embedded in the library.
The best hot-fix for this problem is often setting LD_LIBRARY_PATH to the directory with the .so file, e.g.:
$ LD_LIBRARY_PATH=.. ./i2c
For a long-term solution, you need to either have a close look at the whole LD system with rpath and runpath, or use libtool (which solves these issues for your portably).
Copying a file to /usr/local/lib is often insufficient because ld caches the available libraries, so you need to re-run ldconfig (as root) after you copied a library to /usr/local/lib.
If you are building the code from source that needs the the library, you can put the path that the library is in in the environment variable LD_RUN_PATH before building, and the linker will save that path into the binary, so that it will automatically be looked for in the right place at runtime.
Linux specific: Alternately, put the library in /lib, /usr/lib, or some other path referenced in your /etc/ld.so.conf or its imported config fragments, and then all you need to do is run /sbin/ldconfig to refresh ld.so (the dynamic linker)'s cache of libraries.
This works for my issue,hope will help anyone.
gcc test.c -Wl,-rpath /usr/local/lib -lfcgi -o test.fcg
And -Wl,-rpath option is the key trick.