Why use add_library({tgt} IMPORTED) versus target_link_libraries( -l {.so | .a})? - cmake

What is the purpose of using the statement:
add_library(<tgt> [SHARED|STATIC] IMPORTED)
From what I have found even if you create an imported library target above you still would need to specify the specific location of the actual .so or .a. This would take at least 3 cmake commands to link to an executable and the compiler still would not automatically search through the common include directories on your OS.
Example:
cmake code to link IMPORTED lib
From the CMake documentation I understand there are really 3 ways to link a library that is not built as a target in a subproject of your overall application/library.
CMake target_link_libraries() documentation
Using a CMake package for one of the shipped package scripts.
Using a linker flag:
target_link_libraries(<tgt> [SHARED|STATIC|...] -lncursesw)
Or using the IMPORTED library method (showcased in code at top).
A major difference when using the second method is that it only takes a single line of code and will search through all of your compiler's predefined include directories on you OS. Could anyone help me understand why the add_library() method is used?
Additional Realated SO Posts:
Include directories for IMPORTED libs
CMake imported library behavior

You should use add_library(<tgt> [SHARED|STATIC] IMPORTED) whenever you need to set properties such as dependencies, compile definitions, compile flags etc for <tgt>, and/or by extension, any targets that are linking against <tgt>.
Let's say you have two static libraries; libfoobar.a and libraboof.a, where libfoobar.a requires libraboof.a. Let's also say that these libraries contain some features that are enabled by -DSOME_FEATURE.
add_library(raboof STATIC IMPORTED)
set_target_properties(raboof PROPERTIES
IMPORTED_LOCATION <path-to-libraboof.a>
INTERFACE_COMPILE_DEFINITIONS "SOME_FEATURE"
)
add_library(foobar STATIC IMPORTED)
set_target_properties(foobar PROPERTIES
IMPORTED_LOCATION <path-to-libfoobar.a>
INTERFACE_LINK_LIBRARIES raboof
)
So when you link against libfoobar.a:
add_executable(my_app main.cpp)
target_link_libraries(my_app foobar)
CMake will make sure to link all dependencies in the correct order and will in this case also append -DSOME_FEATURE to the compile flags when you build my_app. Note that since we added libraboof.a as a dependency to libfoobar.a, -DSOME_FEATURE is added to any target that link against libfoobar.a through the transitive property.
If you don't use add_library(<tgt> <SHARED|STATIC> IMPORTED) in a scenario like this, you would have to manage any dependencies and required build options yourself for each target, which is quite error-prone.
This method is also often used in Config-modules for multi-component libraries to manage dependencies between the components.

Related

Is it possible to `unset` a library target in cmake?

Please see the below minimal example,
CMakeLists.txt
set(MY_DIR "/path/one")
add_library(third-party-lib INTERFACE IMPORTED)
set_target_properties(third-party-lib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
${MY_DIR}/usr/local/include)
add_executable(foo main.cpp)
target_link_libraries(foo third-party-lib)
add_subdirectory(bar)
bar/CMakeLists.txt
set(MY_DIR "/path/two")
add_library(third-party-lib INTERFACE IMPORTED)
set_target_properties(third-party-lib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
${MY_DIR}/usr/local/include)
add_executable(bar main.cpp)
target_link_libraries(bar third-party-lib)
I have a third party imported library that have properties that depend on a certain variable, in the case of the minimal example it's MY_DIR, I want to set this variable differently for different targets. Is this possible to achieve by "unset"-ing the imported target?
The current minimal example results in add_library cannot create imported target "third-party-lib" because another target with the same name already exists. I tried something like adding unset(third-party-lib) on top of bar/CMakeLists.txt, and I still see the same error.
To provide more context for the real situation, the third party library I need to link to is called by find_package, there are various versions of this third party library that are compiled with different compilers, but only one single config.cmake file is provided, and the IMPORTED_LOCATION has a variable in it's path that determines which version of the third party library it points to. I want to be able to compile one of my target linking to this third party library that is compiled by compiler A, and compile another one of my target linking to this third party library that is compiled by compiler B.

How to rewrite a find_package based library into one which can be embedded into the parent project directly?

The library libwebrtc from https://github.com/cloudwebrtc/libwebrtc-build/blob/dev/CMakeLists.txt was built to be used with make; make install and the project which wants to use the library must later use the find_package from CMake.
I, however, want to change libwebrtc so it can be added as a git submodule into my current project as a custom library, as, for instance, https://github.com/itay-grudev/SingleApplication which is compiled when I type: cmake ..; make into a static/dynamic library and then linked in my main application. (The Qt library example I references earlier was confusing since this is build outside of my main project and only linked to afterwards - which is not what I want). Sorry for that confusion.
To be able to do that, I think that the ExternalProject_Add at https://github.com/cloudwebrtc/libwebrtc-build/blob/a24a5e5947658d43339d4bfd85d3f4c52fc71057/CMakeLists.txt#L100 must be changed into a add_library call.
The problem here is that the include_directories is used by the main project before the library has been completely built.
Question
How to rewrite libwebrtc to be used as a simple static library with proper build dependencies so that my main project is only compiled/linked after the libwebrtc build was finished and custom header files were generated in the CMAKE_CURRENT_BINARY_DIR of libwebrtc.
Or in other words, how to rewrite libwebrtc to be used without having to call make install for the library and then use find_package to use that library.
The hack (which is working already)
With this hack I am already able to:
Build the library from my parent project
Depend on the generated header files which exist only after the libwebrtc has been built completely (thus, delay main project building until dependencies are meet)
Depend on the generated webrtc.a static library for the linker step
I imaging that make install will work since libwebrtc is statically linked.
add_dependencies(${PROJECT_NAME} libwebrtcx)
add_subdirectory(third-party/libwebrtcx)
include_directories(
${CMAKE_BINARY_DIR}/sources/third-party/libwebrtcx/include/webrtc
${CMAKE_BINARY_DIR}/sources/third-party/libwebrtcx/include/webrtc/third_party/libyuv/include/
${CMAKE_BINARY_DIR}/sources/third-party/libwebrtcx/webrtc/src/third_party/abseil-cpp
)
add_library(libwebrtc STATIC IMPORTED)
set_property(TARGET libwebrtc PROPERTY IMPORTED_LOCATION "${CMAKE_BINARY_DIR}/sources/third-party/libwebrtcx/webrtc/src/out/Release/obj/libwebrtc.a")
target_link_libraries(${PROJECT_NAME} libwebrtc)
Note: It requires to rename the libwebrtc project to libwebrtcx and also the ExternalProject_Add at https://github.com/cloudwebrtc/libwebrtc-build/blob/a24a5e5947658d43339d4bfd85d3f4c52fc71057/CMakeLists.txt#L100 must be renamed to libwebrtcx.
Note: It also requires to rename all CMAKE_BINARY_DIR into CMAKE_CURRENT_BINARY_DIR and CMAKE_SOURCE_DIR to CMAKE_CURRENT_SOURCE_DIR. Details can be found here: CMake: Using add_subproject with a library using Include ends up in wrong relative path

How to model transitive dependencies between static libraries in CMake?

Given an executable myExe, and 2 static libraries myLib1 and myLib2. Given the following dependencies myExe -> myLib1 -> myLib2, how should you model transitive dependency between myLib2 and myLib1?
It seems that the correct way to do it may be:
target_link_libraries(myLib2 myLib1)
But, according to the documentation:
Specify libraries or flags to use when linking a given target and/or its dependents
Also, add_dependencies does not seem to be transitive.
So I find this confusing to use target_link_libraries and I am wondering if there is another "cleaner" way.
For express usage dependency myLib1 -> myLib2 (that is, library myLib1 uses functions defined in myLib2), use
target_link_libraries(myLib2 myLib1)
While target_link_libraries doesn't affect on the file myLib2.a (because static libraries are never linked), an effect will be seen when myLib2 will be linked into shared library or executable:
target_link_libraries(myExe myLib2)
will automatically link myExe with myLib1.
Note again, that such linkage propagation for static libraries works only when myLib2 is used in the same project which calls target_link_libraries(myLib2 myLib1).
Attempt to target_link_libraries(myExe myLib2) from another project will link just with myLib2.a file, which doesn't contain information about myLib2.

How can I create a target for an _existing_ library?

In CMake, we can add_library(mylib file1.cpp file2.cpp) and have a mylib.a in the library path get built. We can also target_include_directories(mylib INTERFACE some/directory), which effects targets depending on mylib.
But what if I have a library to begin with, which I will not be compiling. How can I add a target relating to it? That what I add as a dependency, will cause the .a file to be linked against, and for which I can set target_include_directories() ?
Note: I'm asking about CMake 3.x.
CMake provide an alternate signature for libraries that are already compiled:
add_library(
mynamespace::mylib
STATIC # or it could be SHARED
IMPORTED
)
See the official CMake documentation for more details.
with that you'll be able to add properties to the target doing so
set_target_properties(
mynamespace::mylib
PROPERTIES
IMPORTED_LOCATION "path/to/libmylib.a"
)
Little precision here, if you're using a Windows DLL, you must pass the DLL file's path in IMPORTED_LOCATION and set another property IMPORTED_IMPLIB that points to the .lib file, (not very handy).
Note that there is also a equivalent properties per configuration (Debug, and Release), that will need another property to be set (IMPORTED_CONFIGURATION), e.g. IMPORTED_LOCATION_DEBUG.
See also here and here in the documentation.
To avoid missing include files you can also precise the include directory using INTERFACE_INCLUDE_DIRECTORY property
set_target_properties(
mynamespace::mylib
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "path/to/mylib/include"
)
With this, upon link declaration using target_link_libraries(), CMake will read information of the imported target and will add include directories implicitly.
Official documentation reference.

Add only headers of an imported module to a library in CMake

In CMake there are imported modules that are used to simply add external modules to local targets. For example if we want to use boost::filesystem library in our project we could have a CMakeLists.txt like this:
project(foo CXX)
find_packge(Boost REQUIRED COMPONENTS filesystem)
add_executable(foo main.cpp)
target_link_libraries(foo Boost::filesystem)
With above configuration CMake will add proper compiler options and include directories among required libraries to building process of the foo.
Now we have to build a library instead of an executable and we don't want to link boost::filesystem libraries to our library. We want only compiler options and include directories to be added to our target. Could we use imported modules concepts here? I mean that if we could use Boost::filesystem syntax for adding those options to our target?
project(foo CXX)
find_packge(Boost REQUIRED COMPONENTS filesystem)
add_library(foo STATIC foo.cpp)
# what should be wrote here to only add headers and configs to foo not the libs?
Turning my comments into an answer
add_library(STATIC) won't link the target_link_libraries() dependencies into itself.
In short, if two static libraries would include e.g. Boost::filesystem and then you link both of those libraries into an executable (where the external symbols get actually resolved) you would get duplicate symbol errors.
So CMake by default, does not add linker options like --whole-archive for gcc or LinkLibraryDependencies for VC.
target_link_libraries(foo Boost::filesystem) should work, it just describes the dependency resolved later when building a executable or shared library.
References
ld linker question: the --whole-archive option
CMake issue #9732: Cmake does not disable Link Libray Dependencies in the project settings