CMake link to external library
Asked Answered
T

5

185

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.

Toon answered 8/1, 2012 at 1:16 Comment(0)
S
127

Set libraries search path first:

link_directories(${CMAKE_BINARY_DIR}/res)

And then just do

target_link_libraries(GLBall mylib)
Shop answered 8/1, 2012 at 8:59 Comment(4)
The use of 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.Ickes
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.Weapon
You should always use find_library and use this path instead of hard coding it, cf. my answer.Nickolasnickolaus
most people coming here just want to make -lm work. They won't even know the library name.Battat
D
171

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".

Dragoman answered 11/5, 2012 at 11:19 Comment(8)
That would probably be add_library( mylib SHARED IMPORTED ) or you get an add_library called with IMPORTED argument but no library type errorMcandrew
@Andre: I think after IMPORTED_LOCATION the opening bracket is wrongTorrlow
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)Nubbly
@Andre IMPORTED_LOCATION seems to require the path to the file instead of the directory containing the fileGook
@SOUser: Yes, IMPORTED_LOCATION should point to the file, not to the directory. I have fixed that, guess the author won't complain.Reiners
I really don't know why those basic usage not supported by offical clearly! ThanksEluvium
(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?Nari
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.Akkerman
S
127

Set libraries search path first:

link_directories(${CMAKE_BINARY_DIR}/res)

And then just do

target_link_libraries(GLBall mylib)
Shop answered 8/1, 2012 at 8:59 Comment(4)
The use of 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.Ickes
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.Weapon
You should always use find_library and use this path instead of hard coding it, cf. my answer.Nickolasnickolaus
most people coming here just want to make -lm work. They won't even know the library name.Battat
N
115

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.

Nickolasnickolaus answered 28/1, 2017 at 12:8 Comment(4)
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".Nolpros
find_package is so much simpler than following these stepsFettling
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?Wildebeest
target_link_libraries(mylib "${FOO_LIB}")? The target is mylib instead of his actual target, GLBall? doesn't make much sense to meEgmont
P
16

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

Provo answered 5/5, 2021 at 19:54 Comment(3)
Your answer worked for me! It was a different use case, but I was really really stuck until I tried your way :)Stalk
Is it safe to also assume that mylib.a -> mylib?Rok
@david-c ld will prefer mylib.so over my lib.a if you did not specify --static.Endolymph
T
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

Translative answered 4/12, 2015 at 16:41 Comment(4)
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.Translative
Oh, that's sad. For completeness the link to the new issue tracker, which does currently contain no commnets: gitlab.kitware.com/cmake/cmake/issues/14185Nickolasnickolaus
The problem was solved 5 month ago, so with a recent version of CMake it should no longer be present. See gitlab.kitware.com/cmake/cmake/issues/14185Nickolasnickolaus
Fixed in CMake 3.19, to be specific.Nickolasnickolaus

© 2022 - 2024 — McMap. All rights reserved.