Why the use of generator expression to link object library? - cmake

I understand the purpose of object libraries, but why does linking it to a target need to involve a generator expression?
add_library(myObjects OBJECT a.cpp b.cpp)
add_library(mainLib ${other_srcs} $<TARGET_OBJECTS:myObjects>) # Why not "PRIVATE myObjects" just like other files and targets?
From my understanding, generator expressions are useful for evaluating things available only during generation phase. Why would it be the case that object libraries, which are simply collection of object files, and their location not be known during the configuration phase?

In addition to what #Alex Reinking has already said, the location of the object file may itself depend on the configuration, e.g. with the "Ninja Multi-Config" generator the object files of a target t are located in <build-dir>/t/CMakeFiles/t.dir/<config>/<path-of-source-file-relative-to-dir-in-which-target-is-defined>/<src-file-name>.o
So you're exactly in the situation that you only know the location at generation time, hence the need for a generator expression.

add_library(mainLib ${other_srcs} $<TARGET_OBJECTS:myObjects>) # Why not "myObjects" just like other files and targets?
So:
add_library(mainLib ${other_srcs} myObjects)
Because that would try to find a file named myObjects and compile it. The file has no extensions, so cmake will exit because the file does not exists and he doesn't know how to compile it anyway.
If you meant:
add_library(mainLib ${other_srcs})
target_link_libraries(mainLib PUBLIC myObjects)
The link_libraries dependency on object libraries did not work in older cmake when OBJECT libraries were introduced.
The documentation on object libraries explicitly mentions the form add_library(... $<TARGET_OBJECTS:...) and notes that since cmkae 3.12 target_link_libraries may be used with object libraries.

Why would it be the case that object libraries, which are simply collection of object files, and their location not be known during the configuration phase?
Simple: the target_sources command may be used to add sources (and therefore objects) to a target at any point. If a variable were expanded immediately, one would miss objects that were added later.
In multi-config builds, it is also unknown at configuration time whether you refer to the Debug or Release objects.
Imagine that add_library(target a.cpp b.cpp) created a variable named target_OBJECTS containing /path/to/a.o;/path/to/b.o. Then the following sequence demonstrates the problem:
add_library(target a.cpp b.cpp)
add_executable(main main.cpp ${target_OBJECTS}) # (1)
# ... later ...
target_sources(target PRIVATE c.cpp)
# ... later ...
add_executable(utility util.cpp ${target_OBJECTS}) # (2)
Now how would (2) know about /path/to/c.o? You could have target_sources update that variable, but now a single object library has dependents that see different subsets of it. That's horribly confusing. Moreover, (1) wouldn't see c.o, which is maybe what's desired in this example, but that's not always true. Maybe your code is just organized to define custom commands to create generated sources later.
Making this imperative, rather than declarative like $<TARGET_OBJECTS:target>, imposes structure on CMake code akin to requiring targets in a Makefile to be topologically sorted. It also changes the meaning from "the object files for this target" to "the object files for this target at this point in CMake execution time", which is strictly more complicated to reason about while not offering any expressive advantage.
If you replace this code with the CMake 3.12+ object library linking support, it looks even more vexing:
add_library(target a.cpp b.cpp)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE target) # (1)
# ... later ...
target_sources(target PRIVATE c.cpp)
# ... later ...
add_executable(utility util.cpp)
target_link_libraries(utility PRIVATE target) # (2)
Now, looking at either call to target_link_libraries(... target), one would reasonably expect the same objects to be added.

Related

Should my CMake imported targets have dependencies?

Is there a proper and clean way to add dependencies to an IMPORTED target in CMake (v3.17)?
I'm using CommonAPI-DBus, which does install a CommonAPI-DBusConfig.cmake file but doesn't define "useful" targets, so I want to wrap it with my own target. My preferred name is CommonAPI::DBus (because my ideal setup would have DBus as one of many components in the CommonAPI namespace), and because of the :: it has to be an imported target, fine.
CommonAPI-DBus depends on DBus-1, which isn't implicitly imported anywhere, so I want my target to also bring in DBus-1 (which I can get with find_package(DBus-1)), and that's where I run into problems.
I see two options:
1) Copy the dbus-1 properties to my target:
The DBus-1 package creates a dbus-1 target, so I can set up my target like:
if(NOT TARGET CommonAPI::DBus)
add_library(CommonAPI::DBus INTERFACE IMPORTED)
set_target_properties(CommonAPI::DBus PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${COMMONAPI_DBUS_INCLUDE_DIRS}"
INTERFACE_LINK_LIBRARIES "${CommonAPI_DBus_LIBRARY}"
)
foreach(var IN ITEMS INTERFACE_INCLUDE_DIRECTORIES INTERFACE_LINK_LIBRARIES INTERFACE_COMPILE_DEFINITIONS)
get_target_property(tmp dbus-1 ${var})
if(tmp)
message("Coping ${var} from dbus-1: ${tmp}")
set_property(TARGET CommonAPI::DBus APPEND PROPERTY ${var} ";${tmp}")
endif()
endforeach()
# Get the dbus-1 library
get_target_property(tmp dbus-1 IMPORTED_LOCATION)
set_property(TARGET CommonAPI::DBus APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${tmp})
unset(tmp)
endif()
But that's pretty ugly, and a lot of code to maintain
2) Ignore the dbus-1 target and use the exported variables
find_package(dbus-1) does export DBus1_INCLUDE_DIR and DBus1_LIBRARY, etc. I could simply use these properties, however this feels like an "old" way of using CMake. In my opinion the main advantage of CMake is the ability to define targets which dictate own interface and encapsulate their setup logic, rather than downstream artifacts using private variables from its setup script.
I suppose what I want is an INTERFACE target that uses :: to make it clear that I'm using a namespace.

How to correctly pass a dependency from one target to the next?

After switching a target cwd_r from (in the project root's CMakeLists.txt)
pkg_check_modules(libcwd_r libcwd_r IMPORTED_TARGET GLOBAL)
to
find_package(libcwd_r CONFIG)
I am getting suddenly an error for previously working code (in events/CMakeLists.txt):
add_executable(test1 test1.cxx)
target_link_libraries(test1 PRIVATE AICxx::events AICxx::utils AICxx::cwds)
this used to worked fine, and I'd like it to work again without that I have to
change the events/CMakeLists.txt that these two lines are a part of.
The error is:
CMake Error at events/CMakeLists.txt:59 (add_executable):
Target "test1" links to target "Threads::Threads" but the target was not
found. Perhaps a find_package() call is missing for an IMPORTED target, or
an ALIAS target is missing?
The way that Threads::Threads gets dragged in here is because it is
a PUBLIC dependency of AICxx::cwds.
To narrow things down, lets first change the second line into
target_link_libraries(test1 PRIVATE AICxx::cwds)
simply leaving away the AICxx::events AICxx::utils. As expected this gives the exact same error, because linking doesn't happen yet.
The target AICxx::cwds is defined in cwds/CMakeLists.txt with
find_package(Threads REQUIRED)
[...snip...]
add_library(cwds_ObjLib OBJECT)
[...snip...]
# Add dependencies.
target_link_libraries(cwds_ObjLib
PUBLIC
Boost::boost
Threads::Threads
)
if (OptionEnableLibcwd)
target_link_libraries(cwds_ObjLib
PUBLIC
${libcwd_r_TARGET}
)
endif ()
# Create an ALIAS target.
add_library(AICxx::cwds ALIAS cwds_ObjLib)
where OptionEnableLibcwd is true and libcwd_r_TARGET is set to the correct target name (Libcwd::cwd_r) of libcwd_r.
Note how cwds adds a dependency on Threads::Threads (and Boost). But since I only
changed how libcwd_r is found that can't be what gives the problem, right?
To test that hypothesis - I also removed the Threads::Threads from the target_link_libraries in this cwds/CMakeLists.txt, and indeed still the exact same error. Hence (as expected) it is complaining about something that it inherited from Libcwd::cwd_r.
My question is now: if the (found) target Libcwd::cwd_r specifies a dependency Threads::Threads, then why do I get this error? Shouldn't it be hidden from the final user (test1) who doesn't even know that Threads::Threads is being used (it just links with AICxx::cwds)? The answer to that is yes! Because before, when I was using pkg_check_modules it worked!
So what has changed? What is wrong? How can I fix this?
And most importantly, what does cmake expect with regard to passing down dependencies like this? The error seems to indicate that I have to add a find_package(Threads) everywhere?!
The immediate reason for the difference between the two is that pkg_check_modules isn't adding the target Threads::Threads (or any target) to the INTERFACE_LINK_LIBRARIES property of the returned target PkgConfig::libcwd_r. Specifically, the latter has the property
INTERFACE_LINK_LIBRARIES = /usr/local/lib/libcwd_r.so;/usr/lib/libdl.so
(slightly changed to make the paths shorter here). This being extracted from its .pc file:
prefix=/usr/local
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
datadir=${prefix}/share
includedir=${prefix}/include
Name: libcwd_r
Description: C++ Debugging Support Library for threaded applications
Version: 1.1.2
Libs: -L${libdir} -lcwd_r -ldl
Cflags: -DCWDEBUG -DLIBCWD_THREAD_SAFE -I${includedir}
While the cmake package of libcwd does set this target. Namely it has the property,
INTERFACE_LINK_LIBRARIES = Threads::Threads;dl
which is set in the installed libcwd_rTargets.cmake file (one of the four files of the cmake package):
set_target_properties(Libcwd::cwd_r PROPERTIES
COMPATIBLE_INTERFACE_STRING "cwd_r_MAJOR_VERSION"
INTERFACE_COMPILE_DEFINITIONS "CWDEBUG;LIBCWD_THREAD_SAFE;VERSION=\"1.1.2\""
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
INTERFACE_LINK_LIBRARIES "Threads::Threads;dl"
INTERFACE_cwd_r_MAJOR_VERSION "5"
)
With this "new" dependency, the error occurs because this dependency doesn't have a accompanied find_package.
Namely, an imported target like that has the scope of its (sub)directory and needs to have a find_package where it is being introduced, in the same scope. Aka, where ever you have Threads::Threads literally in your CMakeLists.txt, that CMakeLists.txt also needs to have a find_package(Threads).
In this case however, the Threads::Threads is used in the libcwd_rTargets.cmake file. The correct way to add the find_package for dependencies like this is with find_dependency by adding that to the package' *Config.cmake file:
include(CMakeFindDependencyMacro)
find_dependency(Threads)
include("${CMAKE_CURRENT_LIST_DIR}/libcwd_rTargets.cmake")
where the first two lines were missing; hence the error that I got. See for example the answer of cmake developer Matthew Woehlke at the end of this thread.

How to use condition in cmake generator expression

I would like to compile a library as shared or static based on other variable eg:
add_library(MyLibrary $<$<IF:${BUILD_SHARED_LIBS},SHARED,STATIC> ${SOURCES})
For clarity I expect this to be equivalent with the following:
if(BUILD_SHARED_LIBS)
add_library(MyLibrary SHARED ${SOURCES})
elseif()
add_library(MyLibrary STATIC ${SOURCES})
endif()
AFAIK, you cannot do that with generator expressions (no way to query that aspect according to doc), since BUILD_SHARED_LIBS is there for exactly that reason; to allow you to select its value during configuration (using the -D command line option). This will work only if you do not set the library type explicitly as in your code, but like this
add_library(MyLibrary ${SOURCES})
Actually, that is the recommended practice. If you need to influence its value in association with some other condition, you can override it with the usual if()/else() logic, making sure that you print at least an informative message() for the user.
However, an even better approach would be to push those decisions to the user (via options) and check for the illegal combinations, issuing a message(FATAL_ERROR). Even if that condition is determined automatically, this is still a tactic that has merit.

CMake: Is there a difference between set_property(TARGET ...) and set_target_properties?

In CMake, assuming one is just setting one property, is there any difference between
set_target_properties(target PROPERTIES prop value)
and
set_property(TARGET target PROPERTY prop value)
?
Cf.
https://cmake.org/cmake/help/v3.0/command/set_property.html
https://cmake.org/cmake/help/v3.0/command/set_target_properties.html
which imply there is no difference but aren't that clear.
Consider set_target_properties() as a specialized form of set_property().
Advantages of ...
set_target_properties(...) is a convenience function because it allows to set multiple properties of multiple targets.
For example:
add_executable(a ...)
add_executable(b ...)
set_target_properties(
a
b
PROPERTIES
LINKER_LANGUAGE CXX
FOLDER "Executable"
)
set_property(TARGET ...) can APPEND to a list- or APPEND_STRING to a string-based property of targets.
For example:
add_executable(a ...)
set_property(
TARGET a
APPEND PROPERTY
INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}"
)
References
How to change the name of the output binary to not be a.out with CMake?
target_include_directories prior to 2.8.12?
Appending compiler flags to a file with CMake
The difference is that with set_property, you get to define the scope. You actually have more options with the set_property other than just specifying a target, such as specifying source files in a list to have a certain property.
For example:
set_property(SOURCE src1.cpp src2.cpp PROPERTY SKIP_AUTOMOC ...)
This will add the SKIP_AUTOMOC property to source files listed. (This is for Qt, where Moc'ing of objects occurs automatically and sometimes you don't want that).
Contrast with set_target_properties where you must specify a Target and the property and it's value.
set_target_properties(target PROPERTIES CXX_STANDARD 11 ...)
Hopefully this helps!
Note that you also have respective set_*_properties functions for some of the other types of properties: set_source_files_properties, set_directory_properties and set_tests_properties. Notably absent are setters for install and global properties.
The reason for that is that these functions predate the general set_property call, which was only introduced with CMake 2.6, together with a general overhaul of the property system to what it is today.
These days, people tend to prefer the generic set_property, as it is the more modern function and provides a few additional features. It also offers a more consistent syntax than the old functions (eg. set_directory_properties not allowing to specify the directory as a parameter, set_source_files vs set_directory, etc.).
There is not a strong technical reason for preferring set_property, but I would consider it slightly better style than using the old, specific functions.

Specifying target_link_libraries when building shared object in cmake

My project creates a shared object A that 'depends' should reference other shared objects B, C , D and E. After building the project and checking the build with the utility ldd I see no references to the shared objects B, C , D and E. However when I use the directive target_link_libraries(A , B , C , D , E ) in my build the references to the shared objects appear in A.so. My question is two fold:
Is it correct to use target_link_libraries in this way?
If so, why is this use of target_link_libraries correct considering that I'm building a shared object where linking is at runtime.
Example:
My Frobnigator project depends on a ContinuumTransfunctioner and Transmogrifier shared objects that have already been built. My question is about whether the line target_link_libraries(Frobnigator ${Libraries}) is necessary.
cmake_minimum_required(VERSION 3.0)
set(Libraries
ContinuumTransfunctioner
Transmogrifier
)
set(SourceFiles
Wrapper.cpp
Logger.cpp
)
add_library(Frobnigator SHARED ${SourceFiles})
add_library(FrobnigatorStatic STATIC ${SourceFiles})
set_target_properties(FrobnigatorStatic PROPERTIES OUTPUT_NAME Frobnigator)
#target_link_libraries(Frobnigator ${Libraries}) # Do I need this?
Yes, you need to use target_link_libraries even when create SHARED library.
While some symbol resolution is performed at runtime (loading time), there some things which should be performed at build time (linking).
The main thing is ... providing a list of libraries, which should be loading with your library. This list is "embedded" into the library file. There is no other way for dynamic loader to know, which other libraries should be loaded with your one.
Among other things performed at link time is:
Checking that all symbols needed for your library are actually available in other libraries.