177

How to get CMake to link an executable to an external shared library that is not build within the same CMake project?

Just doing target_link_libraries(GLBall ${CMAKE_BINARY_DIR}/res/mylib.so) gives the error

make[2]: *** No rule to make target `res/mylib.so', needed by `GLBall'.  Stop.
make[1]: *** [CMakeFiles/GLBall.dir/all] Error 2
make: *** [all] Error 2
(GLBall is the executable)

after I copied the library into the binary dir bin/res.

I tried using find_library(RESULT mylib.so PATHS ${CMAKE_BINARY_DIR}/res)

Which fails with RESULT-NOTFOUND.

tshepang
  • 12,111
  • 21
  • 91
  • 136
Prime
  • 4,081
  • 9
  • 47
  • 64

5 Answers5

165

arrowdodger's answer is correct and preferred on many occasions. I would simply like to add an alternative to his answer:

You could add an "imported" library target, instead of a link-directory. Something like:

# Your-external "mylib", add GLOBAL if the imported library is located in directories above the current.
add_library( mylib SHARED IMPORTED )
# You can define two import-locations: one for debug and one for release.
set_target_properties( mylib PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/res/mylib.so )

And then link as if this library was built by your project:

TARGET_LINK_LIBRARIES(GLBall mylib)

Such an approach would give you a little more flexibility: Take a look at the add_library( IMPORTED) command and the many target-properties related to imported libraries.

I do not know if this will solve your problem with "updated versions of libs".

starball
  • 20,030
  • 7
  • 43
  • 238
André
  • 18,348
  • 6
  • 60
  • 74
  • 3
    That would probably be `add_library( mylib SHARED IMPORTED )` or you get an `add_library called with IMPORTED argument but no library type` error – Marvin Aug 28 '12 at 14:38
  • 4
    @Andre: I think after `IMPORTED_LOCATION` the opening bracket is wrong – Ela782 Nov 06 '14 at 16:36
  • 6
    you need to add `GLOBAL` after `IMPORTED` if you want to access the imported library in directories above the current: `add_library(breakpad STATIC IMPORTED GLOBAL)` – Roman Kruglov Feb 16 '16 at 15:23
  • @Andre IMPORTED_LOCATION seems to require the path to the file instead of the directory containing the file – SOUser Apr 02 '17 at 15:13
  • 1
    @SOUser: Yes, IMPORTED_LOCATION should point to the file, not to the directory. I have fixed that, guess the author won't complain. – Tsyvarev Dec 07 '17 at 16:24
  • 1
    I really don't know why those basic usage not supported by offical clearly! Thanks – Albert.Qing May 31 '19 at 00:25
  • ```(target_link_libraries): Cannot specify link libraries for target "mylib" which is not built by this project. ninja: error: rebuilding 'build.ninja': subcommand failed``` Why am I getting this error? can anyone help? – 김선달 Mar 18 '20 at 01:35
  • This answer is decent, but could be great with three adjustments: (1) by adding a visibility specifier to `target_link_libraries` (2) by adding `::` somewhere in the name of the `IMPORTED` target and (3) by using `find_library` to determine the value of the `IMPORTED_LOCATION` property. – Alex Reinking May 31 '22 at 18:37
123

Set libraries search path first:

link_directories(${CMAKE_BINARY_DIR}/res)

And then just do

target_link_libraries(GLBall mylib)
arrowd
  • 33,231
  • 8
  • 79
  • 110
  • 56
    The use of [`link_directories`](http://www.cmake.org/cmake/help/v2.8.10/cmake.html#command:link_directories) is discouraged, even in its own documentation. I think it would be better here to resolve the failing `find_library` call in the original question, or use @Andre's solution. – Fraser Mar 27 '13 at 23:41
  • 5
    I find the "imported" library target to be more robust, as it targets the location of the particular library, instead simply giving a global search path. See Andre's answer. – Mark Lakata Mar 18 '16 at 21:44
  • 2
    You should always use `find_library` and use this path instead of hard coding it, cf. [my answer](http://stackoverflow.com/a/41909627/2799037). – usr1234567 Jan 28 '17 at 12:12
  • most people coming here just want to make -lm work. They won't even know the library name. – mckenzm Nov 30 '22 at 02:45
112

I assume you want to link to a library called foo, its filename is usually something link foo.dll or libfoo.so.

1. Find the library
You have to find the library. This is a good idea, even if you know the path to your library. CMake will error out if the library vanished or got a new name. This helps to spot error early and to make it clear to the user (may yourself) what causes a problem.
To find a library foo and store the path in FOO_LIB use

    find_library(FOO_LIB foo)

CMake will figure out itself how the actual file name is. It checks the usual places like /usr/lib, /usr/lib64 and the paths in PATH.

You already know the location of your library. Add it to the CMAKE_PREFIX_PATH when you call CMake, then CMake will look for your library in the passed paths, too.

Sometimes you need to add hints or path suffixes, see the documentation for details: https://cmake.org/cmake/help/latest/command/find_library.html

2. Link the library From 1. you have the full library name in FOO_LIB. You use this to link the library to your target GLBall as in

  target_link_libraries(GLBall PRIVATE "${FOO_LIB}")

You should add PRIVATE, PUBLIC, or INTERFACE after the target, cf. the documentation: https://cmake.org/cmake/help/latest/command/target_link_libraries.html

If you don't add one of these visibility specifiers, it will either behave like PRIVATE or PUBLIC, depending on the CMake version and the policies set.

3. Add includes (This step might be not mandatory.)
If you also want to include header files, use find_path similar to find_library and search for a header file. Then add the include directory with target_include_directories similar to target_link_libraries.

Documentation: https://cmake.org/cmake/help/latest/command/find_path.html and https://cmake.org/cmake/help/latest/command/target_include_directories.html

If available for the external software, you can replace find_library and find_path by find_package.

Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
usr1234567
  • 21,601
  • 16
  • 108
  • 128
  • 9
    IMHO this is the best answer. However, I had trouble because I wasn't calling "find_library" after "project" and "target_link_libraries" after "add_executable". – smoothware Jun 05 '17 at 19:34
  • 3
    `find_package` is so much simpler than following these steps – activedecay Jun 29 '18 at 21:22
  • 3
    I guess I don't understand step 2. For a shared library ${FOO_LIB} will be like /full/path/to/libfoo.dylib. How is that useful? target_link_libraries is not creating "-L/full/path/to -lfoo", so find_library is not returning anything useful, other than verifying that the library is in the location where I already know it is. What am I missing? – guymac Dec 06 '18 at 17:56
  • 1
    `target_link_libraries(mylib "${FOO_LIB}")`? The target is `mylib` instead of his actual target, `GLBall`? doesn't make much sense to me – Bersan May 04 '20 at 12:07
13

Let's say you have an executable like:

add_executable(GLBall GLBall.cpp)

If the external library has headers, give the path to its include folder:

target_include_directories(GLBall PUBLIC "/path/to/include")

Add the library directory path:

target_link_directories(GLBall PUBLIC "/path/to/lib/directory")

Finally, link the library name

target_link_libraries(GLBall mylib)

Note that the prefix and extension of the library file are removed:

libmylib.a ➜ mylib
mylib.so ➜ mylib

Sorush
  • 3,275
  • 4
  • 28
  • 42
5

One more alternative, in the case you are working with the Appstore, need "Entitlements" and as such need to link with an Apple-Framework.

For Entitlements to work (e.g. GameCenter) you need to have a "Link Binary with Libraries"-buildstep and then link with "GameKit.framework". CMake "injects" the libraries on a "low level" into the commandline, hence Xcode doesn't really know about it, and as such you will not get GameKit enabled in the Capabilities screen.

One way to use CMake and have a "Link with Binaries"-buildstep is to generate the xcodeproj with CMake, and then use 'sed' to 'search & replace' and add the GameKit in the way XCode likes it...

The script looks like this (for Xcode 6.3.1).

s#\/\* Begin PBXBuildFile section \*\/#\/\* Begin PBXBuildFile section \*\/\
    26B12AA11C10544700A9A2BA \/\* GameKit.framework in Frameworks \*\/ = {isa = PBXBuildFile; fileRef = 26B12AA01C10544700A9A2BA \/\* GameKit.framework xxx\*\/; };#g

s#\/\* Begin PBXFileReference section \*\/#\/\* Begin PBXFileReference section \*\/\
    26B12AA01C10544700A9A2BA \/\* GameKit.framework xxx\*\/ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = System\/Library\/Frameworks\/GameKit.framework; sourceTree = SDKROOT; };#g

s#\/\* End PBXFileReference section \*\/#\/\* End PBXFileReference section \*\/\
\
\/\* Begin PBXFrameworksBuildPhase section \*\/\
    26B12A9F1C10543B00A9A2BA \/\* Frameworks \*\/ = {\
        isa = PBXFrameworksBuildPhase;\
        buildActionMask = 2147483647;\
        files = (\
            26B12AA11C10544700A9A2BA \/\* GameKit.framework in Frameworks xxx\*\/,\
        );\
        runOnlyForDeploymentPostprocessing = 0;\
    };\
\/\* End PBXFrameworksBuildPhase section \*\/\
#g

s#\/\* CMake PostBuild Rules \*\/,#\/\* CMake PostBuild Rules \*\/,\
            26B12A9F1C10543B00A9A2BA \/\* Frameworks xxx\*\/,#g
s#\/\* Products \*\/,#\/\* Products \*\/,\
            26B12AA01C10544700A9A2BA \/\* GameKit.framework xxx\*\/,#g

save this to "gamecenter.sed" and then "apply" it like this ( it changes your xcodeproj! )

sed -i.pbxprojbak -f gamecenter.sed myproject.xcodeproj/project.pbxproj

You might have to change the script-commands to fit your need.

Warning: it's likely to break with different Xcode-version as the project-format could change, the (hardcoded) unique number might not really by unique - and generally the solutions by other people are better - so unless you need to Support the Appstore + Entitlements (and automated builds), don't do this.

This is a CMake bug, see http://cmake.org/Bug/view.php?id=14185 and http://gitlab.kitware.com/cmake/cmake/issues/14185

usr1234567
  • 21,601
  • 16
  • 108
  • 128
kalmiya
  • 2,988
  • 30
  • 38
  • Specifically - getting cmake to link with an external library is not the problem (there are several solutions above). Getting this to work in an automated way, so that it works with the Apple Appstore *and* entitlements is a challenge. In that specific case, the above solutions do not work because XCode will not 'see' the libraries linked in that way, and entitlements will just not work. Afaik cmake can't add the libaries the way xcode needs it in an 'appstore-compatible way'- again, feel free to enlighten me. – kalmiya Jan 30 '17 at 10:02
  • 1
    Oh, that's sad. For completeness the link to the new issue tracker, which does currently contain no commnets: https://gitlab.kitware.com/cmake/cmake/issues/14185 – usr1234567 Jan 30 '17 at 10:16
  • The problem was solved 5 month ago, so with a recent version of CMake it should no longer be present. See https://gitlab.kitware.com/cmake/cmake/issues/14185 – usr1234567 Dec 31 '19 at 12:29
  • Fixed in CMake 3.19, to be specific. – usr1234567 Feb 27 '21 at 08:05