4

I have a simple C++ project with a dependency on Intel TBB (which requires the use of shared libraries). I'm trying to build this using the Conan package manager combined with CMake, and I've already been able to use this setup to add non-shared dependencies with no problems.


My first attempt (combining the information from conan's conanfile.txt documentation for using packages and conan's conanfile.py reference guide) used a conanfile.py like this:

import os
from conans import ConanFile, CMake

class MyProjectConan(ConanFile):
    settings = 'os', 'compiler', 'build_type', 'arch'
    requires = 'TBB/2018_U6@conan/stable'
    generators = 'cmake'

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def imports(self):
        self.copy('*.dll', src='bin', dst='bin')
        self.copy('*.dylib*', src='lib', dst='bin')
        self.copy('*.so', src='lib', dst='bin')

and a CMakeLists.txt like this:

cmake_minimum_required(VERSION 2.8.12)
project(MyProject)

add_definitions("-std=c++17")

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

add_executable(main src/main.cpp)
target_link_libraries(main ${CONAN_LIBS})

(my src/main.cpp is just a direct copy of the test file for the TBB package)

This built just fine;

conan install -if build .
conan build -bf build .

But running ./build/bin/main failed with this error:

dyld: Library not loaded: @rpath/libtbb.dylib
  Referenced from: <project-dir>/./build/bin/main
  Reason: image not found
Abort trap: 6

I'm on MacOS, and this error is similar to a known (but apparently fixed) issue, so I tried running the binary from its own directory; cd build/bin; ./main, but saw the same error. I'm not sure why this didn't work, but I moved on to another approach without spending much time debugging it.


Next I followed the conan documentation's "different approaches" for RPATHs guide to end up with this:

class MyProjectConan(ConanFile):
    # (rest of class is same as before)

    def imports(self):
        self.copy('*.dll', src='bin', dst='bin')
        self.copy('*.dylib*', src='lib', dst='lib') # changed bin to lib
        self.copy('*.so', src='lib', dst='lib') # changed bin to lib

and this:

cmake_minimum_required(VERSION 2.8.12)
project(MyProject)

add_definitions("-std=c++17")

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup(KEEP_RPATHS)

if(APPLE)
    set(CMAKE_INSTALL_RPATH "@executable_path/../lib")
else()
    set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")
endif()

add_executable(main src/main.cpp)
target_link_libraries(main ${CONAN_LIBS})

Which builds and even runs! But when I inspect the executable I see that it references /Users/me/.conan/data/TBB/2018_U6/conan/stable/package/03db91a62823ebc2b1df6e5cf549c2f674116656/lib, which is obviously not what that code is supposed to do (I was expecting to see @rpath/../lib or similar). I have also verified that the binary is indeed using this path, and not the files placed in its sibling lib folder.


At this point I'm stuck. I can see that CMake has a few options for RPATH handling, but nothing there looks particularly relevant, and I don't understand why the code in my second attempt (taken directly from conan's documentation) doesn't work.

What do I need to do to get a shared library working with conan? (ideally cross-platform, but at least on MacOS)

Dave
  • 36,791
  • 8
  • 53
  • 96
  • Hi @Dave. Have you tried to directly set DYLD_LIBRARY_PATH before running the executables? Since OSX introduce the protection system, it is not possible to run anything that needs paths to shared libs without directly setting ``DYLD_LIBRARY_PATH /path/to/executable``. Using generators like ``virtualrunenv`` and providing it in command line might help. Have you had a look to: https://docs.conan.io/en/latest/howtos/manage_shared_libraries/env_vars.html#using-virtualrunenv-generator? Also, it is sometimes better to use github issues in https://github.com/conan-io/conan, even for questions. – drodri Nov 24 '18 at 22:43
  • @drodri yes, for the first attempt if I set `DYLD_LIBRARY_PATH=./build/bin` when running the executable, it runs, which I guess solves the first mystery. But I'm looking to build an executable which just-works™️ so that isn't a great solution for me. It seems RPATHs avoid this requirement (after all, the problem in my second case isn't that it fails to find the library, but that it is using an absolute path to my `.conan` cache, instead of a relative path to the copied files); do you have any idea why it ignored my `CMAKE_INSTALL_RPATH` in the second attempt? – Dave Nov 25 '18 at 00:46
  • Here's how to build TBB as a static library: https://stackoverflow.com/a/38858420/158658 – sourcedelica Dec 13 '18 at 01:20
  • @sourcedelica note that TBB **strongly discourages** doing that (https://www.threadingbuildingblocks.org/faq/11) – Dave Dec 13 '18 at 13:11
  • Might be but we have hundreds of applications that use it that way. – sourcedelica Dec 14 '18 at 14:31

1 Answers1

3

I figured out the problem in the second attempt after looking through CMake's other variables one-by-one.

The missing setting is this:

set(CMAKE_BUILD_WITH_INSTALL_RPATH ON)

Hopefully this can be added to the Conan documentation, because with it enabled everything works exactly like it should. For final reference, my build files are:

conanfile.py

from conans import ConanFile, CMake

class MyProjectConan(ConanFile):
    settings = ('os', 'compiler', 'build_type', 'arch')
    requires = (
        'TBB/2018_U6@conan/stable',
    )
    generators = 'cmake'

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def imports(self):
        self.copy('*.dll', src='bin', dst='bin')
        self.copy('*.dylib*', src='lib', dst='lib')
        self.copy('*.so', src='lib', dst='lib')

CMakeLists.txt

cmake_minimum_required(VERSION 2.8.12)
project(MyProject)

add_definitions("-std=c++11") # to use 17, must also set '-s cppstd=17' on conan install

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup(KEEP_RPATHS)

if(APPLE)
    set(CMAKE_INSTALL_RPATH "@executable_path/../lib")
else()
    set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")
endif()
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON) # <-- this is the line which is missing in the Conan documentation!

add_executable(main src/main.cpp)
target_link_libraries(main ${CONAN_LIBS})

Build with

conan install -if build .
conan build -bf build .

Run with

./build/bin/main

And it will all just work, as long as the generated bin and lib directories remain siblings. The output binary contains only relative paths; no machine-specific paths.

Dave
  • 36,791
  • 8
  • 53
  • 96