I want to have an interface library which propagates CXX_COMPILER_LAUNCHER to another target.
As I understood the documentation (see below) something like this should work but it doesn't.
add_library(lib INTERFACE)
set_property(TARGET lib INTERFACE_CXX_COMPILER_LAUNCHER "echo")
add_executable(main)
target_link_library(main
INTERFACE lib)
but...
get_property(_VAR TARGET main PROPERTY CXX_COMPILER_LAUNCHER)
message(FATAL_ERROR "compiler launcher: ${_VAR})
What am I doing wrong?
cmake: compatible-interface-properties
cmake: transitive-usage-requirements
EDIT:
put together a minimal example and added Tsyvarevs hint. Still no success.
cmake_minimum_required(VERSION 3.18)
project(lib)
add_library(lib INTERFACE)
set_target_properties(lib PROPERTIES
INTERFACE_CXX_COMPILER_LAUNCHER "echo 'Hello, World'"
INTERFACE_C_COMPILER_LAUNCHER "echo 'Hello, World'"
)
set_property(TARGET lib APPEND PROPERTY COMPATIBLE_INTERFACE_STRING CXX_COMPILER_LAUNCHER)
set_property(TARGET lib APPEND PROPERTY COMPATIBLE_INTERFACE_STRING C_COMPILER_LAUNCHER)
add_library(main SHARED)
target_link_libraries(main
INTERFACE
lib
)
get_target_property(_VarMain main CXX_COMPILER_LAUNCHER)
get_target_property(_VarLib lib INTERFACE_CXX_COMPILER_LAUNCHER)
message(WARNING "varLib: ${_VarLib}")
message(FATAL_ERROR "varMain: ${_VarMain}")
Only a property which INTERFACE_* counterpart is listed as known property is propagated out-of-the-box.
For tell CMake to propagate other properties, you need to add those properties into one of COMPATIBLE_INTERFACE_* list-like properties. Selection of the list determines how the final value of the consumer's property is selected from its initial value (if it is explicitly set) and propagated values:
COMPATIBLE_INTERFACE_BOOL requires that all values should be equal as boolean. (E.g. TRUE and ON are compatible values).
COMPATIBLE_INTERFACE_STRING requires that all values should be equal as string. (E.g. "FOO" and "foo" are incompatible values).
COMPATIBLE_INTERFACE_NUMBER_MAX selects maximum from the numeric values.
COMPATIBLE_INTERFACE_NUMBER_MIN selects minimum from the numeric values.
The other important thing: properties are propagated only at the end of configuration process. That is, the propagated value cannot be extracted via get_property command. Instead, generator expressions should be used.
While CMake is aware about the property CXX_COMPILER_LAUNCHER, it doesn't know its INTERFACE-prefixed variant INTERFACE_CXX_COMPILER_LAUNCHER.
Since the property has string semantic, you could add it to the COMPATIBLE_INTERFACE_STRING list:
set_property(TARGET lib APPEND PROPERTY COMPATIBLE_INTERFACE_STRING CXX_COMPILER_LAUNCHER)
After that the property will be automatically propagated to the consumers of your library.
For observe the result of the propagation, you could add custom target with $<TARGET_PROPERTY> generator expression:
add_custom_target(print_prop ALL
COMMAND echo "Property for main:" $<TARGET_PROPERTY:main,CXX_COMPILER_LAUNCHER>
)
so building the project (using make) will give:
Property for main: echo
Related
I'm trying to build prusaslicer dependencies over superslicer (is not related to the question) but I'm getting an error in the FindTBB.cmake file where it says that
set_target_properties called with incorrect number of arguments.
The following code is from where if complains about the format:
set_target_properties(TBB::tbb PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}"
INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}"
INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
IMPORTED_LOCATION ${TBB_LIBRARIES})
What can I do to fix this?
set_target_properties requires the value of the property to be exactly 1 parameter, so the TBB_INCLUDE_DIRS or TBB_LIBRARIES not containing exactly one element (which given the use of plural is likely) does break things. The values need to be quoted in order to ensure the snippet works regardless of the number of elements in those (list?) variables.
set_target_properties(TBB::tbb PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}"
INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}"
INTERFACE_INCLUDE_DIRECTORIES "${TBB_INCLUDE_DIRS}"
IMPORTED_LOCATION "${TBB_LIBRARIES}")
The alternative would be to use the set_property command instead, which does allow you to specify multiple values.
set_property(TARGET TBB::tbb PROPERTY INTERFACE_COMPILE_DEFINITIONS ${TBB_DEFINITIONS})
set_property(TARGET TBB::tbb PROPERTY INTERFACE_LINK_LIBRARIES Threads::Threads ${CMAKE_DL_LIBS})
set_property(TARGET TBB::tbb PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS})
set_property(TARGET TBB::tbb PROPERTY IMPORTED_LOCATION ${TBB_LIBRARIES})
Unfortunately since this seems to be a third party library there's probably little you can to other than modifying your installation and reporting this issue to the vendor and hoping for a fix in a future update.
Btw: There's some additional weirdness going on in this snippet: The use of plural in TBB_LIBRARIES could indicate that multiple values could be specified, but the IMPORTED_LOCATION property is supposed to be set to a single path to a binary, see the documentation of the target property.
Perhaps there could be a hack for dealing with the issue that could allow you to build your project regardless of the issues mentioned: Make sure to import TBB::tbb first and as the only component of the package; If the library does not automatically add additional libraries to the TBB_LIBRARIES variable this could result in the variable containing a single value. I would't rely on this being the case in future releases though...
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.
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.
I was looking at generated PackageConfig.cmake files for external libraries I am using and I often saw things like:
set_property(TARGET Package::lib APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG)
set_target_properties(Package::lib PROPERTIES
IMPORTED_LOCATION_NOCONFIG "${CMAKE_CURRENT_LIST_DIR}/liblib.so.1.1.1"
IMPORTED_SONAME_NOCONFIG "liblib.so.1"
)
What exactly is this value used for?
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.