CMake Include dependencies of imported library without linking - cmake

I am using CMake 3.5.2.
Consider the following situation. I have an imported library Foo::Foo:
add_library(Foo::Foo UNKNOWN IMPORTED)
This imported library has been populated with appropriate properties:
set_target_properties(Foo::Foo PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "/path/to/include/blah" "/another/path/include/other"
IMPORTED_LINK_INTERFACE_LIBRARIES blah other
IMPORTED_LOCATION "/path/to/libfoo.a-or-so")
I have a convenience library called bar. I need it to include Foo::Foo's include directories, but I do not want it to link against Foo::Foo.
add_library(bar STATIC "${BAR_SOURCES}")
How can I add just the include dependencies from Foo::Foo? Here is what I have tried that has failed:
# This did not include any includes from Foo::Foo
target_link_libraries(bar INTERFACE Foo::Foo)
# This included only the first include directory from Foo::Foo
target_include_directories(bar PUBLIC "$<TARGET_PROPERTY:Foo::Foo,INTERFACE_INCLUDE_DIRECTORIES>")

I have given you example a try. You should change the code in your example to:
set_target_properties(
Foo::Foo PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES
"/path/to/include/blah;/path/to/include/other"
IMPORTED_LINK_INTERFACE_LIBRARIES "blah.a"
IMPORTED_LOCATION "/path/to/libfoo.a-or-so"
)
The call to set_target_properties() only accepts "property" / "value" pairs (with spaces as delimiter). And your example just wasn't throwing any errors because you can always define your own properties (with any naming).
Please transfer your include directory list into a "CMake List" (string with semicolon separated elements).
Alternative
If you just want to "reset" the transitive libraries you can do this with e.g.:
target_link_libraries(bar Foo::Foo)
set_target_properties(bar PROPERTIES INTERFACE_LINK_LIBRARIES "")
I've used this approach when I was building a shared library in the same project as I was linking against the same (and I did not want the library dependencies of the shared library also being linked into the target using the shared library).
References
target_link_libraries()

Related

CMake: link order of imported targets incorrect

I have the following scenario:
I import two prebuilt libraries into my project (libA, libB)
libB has a dependency on libA
The executable depends on both libA and libB
However, the relative linking order in my link.txt is incorrect
/usr/bin/c++ CMakeFiles/bin.dir/main.cpp.o -o bin ../libA.a ../libB.a
I would expect libA.a to be listed after libB.a.
The CMakeLists.txt looks something along the following lines
cmake_minimum_required(VERSION 3.13)
project(cmake_test)
set(lib_dir ${CMAKE_CURRENT_SOURCE_DIR})
add_library(MY::libA IMPORTED INTERFACE)
set_target_properties(MY::libA PROPERTIES INTERFACE_LINK_LIBRARIES "${lib_dir}/libA.a")
add_library(MY::libB IMPORTED INTERFACE)
set_target_properties(MY::libB PROPERTIES INTERFACE_LINK_LIBRARIES "MY::libA;${lib_dir}/libB.a")
add_executable(bin ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(bin PUBLIC MY::libB MY::libA)
Below a description of my attempts to solve the problem. Some without success and some with sucecss but using modifications that render the code usless for the production environment.
Successful attempts:
Remove the depedency of bin on libA (i.e. replace the last line by target_link_libraries(bin PUBLIC MY::libB). This works but I cannot remove the dependency in real code.
Replace the target type IMPORTED INTERFACE by IMPORTED STATIC. Use IMPORTED_LOCATION instead of INTERFACE_LINK_LIBRARIES and use target_link_libraries to express the dependency of libB on libA. In this case the link.txt yields: [...] -o bin ../libA.a ../libB.a ../libA.a. As soon as I revert the target type for libB the link order breaks down again. In the production environment, however, one of the targets is created by conan as IMPORTED INTERFACE.
Attempts without success (same behaviour as described):
Create a separate IMPORTED target (use IMPORTED_LOCATION) for every lib and group them inside an INTERFACE target
Sprinkle the code with ADD_DEPENDENCIES
Remove libA from the INTERFACE_LINK_LIBRARIES in line 9 and use target_link_libraries(MY::libB INTERFACE MY::libA) instead. Same result.
Example code that shows the same failure using INTERFACES as a building block
cmake_minimum_required(VERSION 3.13)
project(cmake_test)
set(lib_dir ${CMAKE_CURRENT_SOURCE_DIR})
# libA
add_library(MY::libA_file1 IMPORTED STATIC)
set_target_properties(MY::libA_file1 PROPERTIES IMPORTED_LOCATION "${lib_dir}/libA.a")
add_library(libA INTERFACE)
target_link_libraries(libA INTERFACE MY::libA_file1)
# libB
add_library(MY::libB_file1 IMPORTED STATIC)
set_target_properties(MY::libB_file1 PROPERTIES IMPORTED_LOCATION "${lib_dir}/libB.a")
add_library(libB INTERFACE)
target_link_libraries(libB INTERFACE MY::libB_file1 libA)
add_executable(bin ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(bin PUBLIC libA libB)
You incorrectly think about INTERFACE_LINK_LIBRARIES property as a "content" of the library's target, which is ordered by target_link_libraries call.
Using
target_link_libraries(MY::libB INTERFACE MY::libA)
you setup link dependency between library targets MY::libB and MY::libA. That is, "content" of MY::libB target should come before "content" of MY::libA target in the linking command line.
But INTERFACE_LINK_LIBRARIES property is NOT a "content" of the library target! It is just an additional link dependency.
As opposite, IMPORTED_LOCATION (for non-INTERFACE IMPORTED target) is a "content" of the library, and target_link_libraries affects on its ordering.
It seems that you cannot add link dependency for a library, using INTERFACE library target. You should use IMPORTED library target for that purpose:
# Collect libraries related to 'libA'
file(GLOB libs_A "${lib_dir}/libA*.a")
# For each library create IMPORTED target with IMPORTED_LOCATION property.
set(libs_A_targets)
foreach(lib_A ${libs_A})
# Form a unique name for the IMPORTED target: subtarget_A_*
string(REGEX REPLACE "^${lib_dir}/libA([^.]*).a$" "subtarget_A_\\1" lib_A_target ${lib_A})
# Create a target with this name
add_library(${lib_A_target} STATIC IMPORTED)
set_target_properties(${lib_A_target} PROPERTIES IMPORTED_LOCATION ${lib_A})
# And add the target into the list
list(APPEND libs_A_targets ${lib_A_target})
endforeach()
# In a similar way collect libraries for libB.
set(lib_B_targets ...)
# Now link each libB* library with each libA*.
foreach(lib_B_target ${libs_B_targets})
target_link_libraries(${lib_B_target} INTERFACE ${libs_A_targets})
endforeach()
# Now interface libraries, which combine libA* and libB*, can be created
add_library(libA INTERFACE)
target_link_libraries(libA INTERFACE ${libs_A_targets})
add_library(libB INTERFACE)
target_link_libraries(libB INTERFACE ${libs_B_targets})
# Now these INTERFACE libraries can be linked into an executable in any order
add_executable(bin ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(bin PUBLIC libA libB)

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.

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

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.

CMake ordering of include paths

I have an IMPORTED static library (say foo) that I want to link against. I'm building it within my source tree using ExternalProject_Add, and then create an IMPORTED library target for which I set the appropriate properties (INTERFACE_INCLUDE_DIRECTORIES for the headers, and IMPORTED_LOCATION for the binary).
I also have a globally installed version of the same library, whose headers are installed under /usr/local/include. Unfortunately, another package (specifically FindPackage(LibLZMA)) used by my build also has headers installed in /usr/local/include.
The pseudo-script looks like this:
FindPackage(libLZMA)
add_library(foo STATIC IMPORTED)
set_property(TARGET foo PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/lib/libfoo.a)
set_property(TARGET foo PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${foo_INCLUDE_DIRS} ${LIBLZMA_INCLUDE_DIRS})
add_executable(bar ${bar_SOURCES}
target_link_libraries(bar foo)
Now the problem is that cmake generates the following order of includes (they are all added as system includes) for bar:
/usr/local/include
foo_INCLUDE_PATH
So when building, I'm actually picking up the headers for the global installation of foo, rather than for my local, vendored version. I do however link my local version, but the two don't match, leading to runtime errors.
So, I need some way to ensure that foo_INCLUDE_PATH is searched first (or, more generally, that /usr/local/include is searched last). How can this be done?

Are there other means of obtaining the properties of a target in cmake?

I have created a C++ static library, and in order to make it searchable easily, I create the following cmake files:
lib.cmake
# The installation prefix configured by this project.
set(_IMPORT_PREFIX "C:/------/install/win32")
# Create imported target boost
add_library(lib STATIC IMPORTED)
set_target_properties(lib PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "lib_define1;lib_define2"
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/../include"
)
# Load information for each installed configuration.
get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
file(GLOB CONFIG_FILES "${_DIR}/lib-*.cmake")
foreach(f ${CONFIG_FILES})
include(${f})
endforeach()
lib-debug.cmake
# Import target "boost" for configuration "Debug"
set_property(TARGET lib APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(boost PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/Debug/staticlib/lib.lib"
)
When I want to use this library in an executable, I can simply invoke it by calling find_package command:
find_package(lib REQUIRED)
if(lib_FOUND)
message("lib has been found")
else()
message("lib cannot be found")
endif(boost_FOUND)
It works and if I want to know the head file directory of the library, I will have to call it this way:
get_target_property(lib_dir lib INTERFACE_INCLUDE_DIRECTORIES)
I was just wondering whether there are other ways of obtaining the properties of an target. In this case I expect some variable like lib_INCLUDE_DIRECTORIES will exist.
No, CMake does not automatically define variables for the properties of a target (or of anything else). If you need the value of a property, you have to query it explicitly (using get_property or the specific getters like get_target_property etc.).
In your specific case, INTERFACE_INCLUDE_DIRECTORIES is a property which I would expect you would not need to query at all. The whole point of INTERFACE_* properties is to propagate usage requirements automatically; their propagation is implemented in CMake itself.