Cmake Replacing add_compile_definitions with target_compile_definitions [duplicate] - c++

I've been told it's bad practice to do things like seting CFLAGS directly in CMake, and that, instead, I should use the target_compile_definitions() command.
Ok, but - what if I want to use similar/identical definitions for multiple (independent) targets? I don't want to repeat myself over and over again.

I see three possible ways:
The preferred one using target_compile_definitions(... INTERFACE/PUBLIC ...) which would self-propagate the compiler definitions to targets depending on it via target_link_libraries() command.
Using the set_property(TARGET target1 target2 ... APPEND PROPERTY COMPILE_DEFINITIONS ...) to set the same definitions to multiple targets.
You may still use the "old commands" of add_definitions() and remove_definitions() to modify COMPILE_DEFINITIONS directory property (which would pre-set all COMPILE_DEFINITIONS target properties in this directories scope).
References
Is Cmake set variable recursive?
CMake: Is there a difference between set_property(TARGET ...) and set_target_properties?

tl;dr: You can iterate the targets in a loop.
If you have a bunch of targets with some common/similar features, you may want to simply manipulate them all in a loop! Remember - CMake is not like GNU Make, it's a full-fledged scripting language (well, sort of). So you could write:
set(my_targets
foo
bar
baz)
foreach(TARGET ${my_targets})
add_executable(${TARGET} "${TARGET}.cu")
target_compile_options(${TARGET} PRIVATE "--some_option=some_value")
target_link_libraries(${TARGET} PRIVATE some_lib)
# and so on
set_target_properties(
${TARGET}
PROPERTIES
C_STANDARD 99
C_STANDARD_REQUIRED YES
C_EXTENSIONS NO )
endforeach(TARGET)
And you could also initialize an empty list of targets, then add to it here-and-there, and only finally apply your common options and settings to all of them, centrally.
Note: In this example I added PRIVATE compile options, but if you need some of them to propagate to targets using your targets, you can make them PUBLIC).

another neat solution is to define an interface library target (a fake target that does not produce any binaries) with all required properties and compiler definitions, then link the other existing targets against it
example:
add_library(myfakelib INTERFACE)
target_compile_definitions(myfakelib INTERFACE MY_NEEDED_DEFINITION)
add_executable(actualtarget1 main1.cpp)
add_executable(actualtarget2 main2.cpp)
set_property(
TARGET actualtarget1 actualtarget2
APPEND PROPERTY LINK_LIBRARIES myfakelib
)
refs:
https://cmake.org/cmake/help/latest/command/add_library.html#interface-libraries

Related

Access CMake's target/library name at compile time globally

We implement a logger which automatically prints out the project name of a log entry among other infos.
We recently change our build system from using native Microsoft visual c++ to cmake generated.
With native Microsoft c++ build files, we were just defining a macro "PROJECT_NAME" in a global ".props" as such:
<PreprocessorDefinitions>%(PreprocessorDefinitions);PROJECT_NAME=R"($(ProjectName))"</PreprocessorDefinitions>
However, now that we use CMake, I struggle to find a good way to access the project name at compile time. I can use configure_file or target_compile_definitions to access a CMake variable at compile time but I do not know any variable holding a string with the target name. Is there such a variable or can it be defined?
Also, can it be defined only in one place? I don't want to copy paste in every CMakeList.txt a line of the such:
target_compile_definitions(MYTARGET PRIVATE PROJECT_NAME="$mytarget_name")
If you've got multiple options to apply to all targets, you could link a INTERFACE library which allows you to inherit multiple properties of this cmake target via a single target_link_libraries use.
For using the name of the linking target, generator expressions that do not require specifying the target can be used:
add_library(OptionsForAll INTERFACE)
# note: using the target name not the cmake project name here
target_compile_definitions(OptionsForAll INTERFACE PROJECT_NAME=\"$<TARGET_PROPERTY:NAME>\")
target_link_libraries(MYTARGET PRIVATE OptionsForAll)
Another option mentioned in #Tsyvarevs comment would be to use add_compile_definitions to apply the definition for all targets defined in the current directory and subdirectory, but this makes it harder to remove it from some targets...
fabian's answer is IMO the good approach.
However, i wanted to offer an alternative based on BUILDSYSTEM_TARGETS inspired from this answer.
Thus, there is no need to modify all target's call to target_link_libraries.
function(get_all_targets var)
set(targets)
get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR})
set(${var} ${targets} PARENT_SCOPE)
endfunction()
macro(get_all_targets_recursive targets dir)
get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES)
foreach(subdir ${subdirectories})
get_all_targets_recursive(${targets} ${subdir})
endforeach()
get_property(current_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS)
list(APPEND ${targets} ${current_targets})
endmacro()
function(define_targetname all_targets)
foreach(target ${all_targets})
get_target_property(type ${target} TYPE)
if (NOT ${type} STREQUAL "INTERFACE_LIBRARY" AND NOT ${target} STREQUAL "PCHTarget") # INTERFACE library cannot target_compile_definitions && precompiled headers will redefine the MACRO
target_compile_definitions(${target} PRIVATE PROJECT_NAME="${target}")
endif()
endforeach()
endfunction()
get_all_targets(all_targets)
define_targetname("${all_targets}")

In cmake, can we avoid using find_package to import other packages?

Okay, the title is obviously weird. I hope anyone who can define my curiosity cleanly, would edit the title into proper one.
When using CMakeLists.txt, we usually import third-party packages via command find_package() macro, which looks for proper find_XXX.cmake script, and then results outputting variables such as XXX_LIBRARIES, XXX_INCLUDE_DIRECTORIES, etc.
However, recently I found some instruction guide for add_library() macro, that can include actual library binaries as its source(?), like this way:
add_library(my_target STATIC IMPORTED foo.lib)
And as I guess, as long as the name my_target is actually names a target, then we can add another compositions into that target, like any other cmake targets, like below.
target_inlcude_directories(my_target PUBLIC ${PKG_PREFIX}/include)
target_link_directories(my_target PUBLIC ${PKG_PREFIX}/lib)
target_compile_features(my_target PUBLIC std_cxx_17)
If this is possible, then wouldn't this be much cleaner way to describe exported package's compositions, rather than linking and adding those verbose *_LIBS *_DIRS variables manually for each? Like below?
add_executable(foo ${MY_SOURCES})
find_package(xxx)
# BEFORE
target_link_directories(foo PRIVATE ${XXX_LINK_DIRECTORIES}) # note: I don't remember what was it exactly.
target_link_libraries(foo PRIVATE ${XXX_LIBRARIES})
target_include_directories(foo PRIVATE ${XXX_INCLUDE_DIRECTORIES})
# AFTER
target_link_libraries(foo PRIVATE xxx::my_target)
Am I thinking something invalid?
can we avoid using find_package to import other packages?
You can, but it's the tool to be used just for that. find_package() is basically an include() with some special options.
wouldn't this be much cleaner way to describe exported package's compositions, rather than linking and adding those verbose *_LIBS *_DIRS variables manually for each? Like below?
It would be and it is used.
cmake is still ongoing relative big changes in a very short time. I think interface libraries weren't so popular or weren't available some time ago. The only (except some checks, etc) thing find_package does is it includes a file named FindXXX.cmake (or other file named differently). Only. It's completely dependent on what's inside the FindXXX.cmake file what happens.
Newer cmake FindXXX.cmake does exactly what you propose: they create an INTERFACE IMPORTED library, for example on my pc:
#/usr/share/cmake-3.20/Modules
$ grep add_library -wr --include='Find*' .
.... a lot of results ....
./FindBoost.cmake: add_library(Boost::diagnostic_definitions INTERFACE IMPORTED)
./FindBoost.cmake: add_library(Boost::disable_autolinking INTERFACE IMPORTED)
./FindBoost.cmake: add_library(Boost::dynamic_linking INTERFACE IMPORTED)
./FindThreads.cmake: add_library(Threads::Threads INTERFACE IMPORTED)
./FindHDF5.cmake: add_library(HDF5::HDF5 INTERFACE IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" INTERFACE IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" UNKNOWN IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" INTERFACE IMPORTED)
./FindHDF5.cmake: add_library("hdf5::${hdf5_target_name}" UNKNOWN IMPORTED)
./FindGTest.cmake: add_library(GTest::GTest INTERFACE IMPORTED)
./FindGTest.cmake: add_library(GTest::Main INTERFACE IMPORTED)
./FindGTest.cmake: add_library(GTest::gtest ${GTEST_LIBRARY_TYPE} IMPORTED)
./FindGTest.cmake: add_library(GTest::gtest_main ${GTEST_MAIN_LIBRARY_TYPE} IMPORTED)
./FindBZip2.cmake: add_library(BZip2::BZip2 UNKNOWN IMPORTED)
Older or "more standard" (ie. older :) indeed create multiple variables - XXX_LINK_DIRECTORIES XXX_LIBRARIES etc. Note that does variable names are not the same everywhere (XXX_LIB? XXX_INC_DIRS? etc.) and change depending on which FindXXX.cmake you call because some developers decided on different names. A lot of documentation was written in the time the find_package used variables, so it's still visible in documentation, but nowadays interface libraries clearly dominate and what you propose is already used.
Note that the imported library is not set up properly in this case. Configuration scripts (<packageName>Config.cmake) which are used as fallback for find_package or if you use the CONFIG version usually do exactly this: create one or more imported targets for you to use in your project. I strongly recommend using those instead of the *_LIBS/*_DIRS variables, if available.
Here's the correct version of importing the library:
add_library(my_target STATIC IMPORTED)
# INTERFACE "visibility" needed here;
# also quote the path concatenation to avoid issues with spaces in PKG_PREFIX
target_include_directories(my_target INTERFACE "${PKG_PREFIX}/include")
target_link_directories(my_target INTERFACE "${PKG_PREFIX}/lib")
# do you really need the linking library to use the C++ 17 standard?
target_compile_features(my_target INTERFACE std_cxx_17)
# you need specify the absolute path to the lib as IMPORTED_LOCATION target property
# you may need to adjust the location according to the exact location of the lib file
#
# variables CMAKE_CURRENT_SOURCE_DIR (if used from CMakeLists.txt) or
# CMAKE_CURRENT_LIST_DIR (if used from a script) may be helpful
#
# requires seperate treatment for different OS; this is not included here
set_target_properties(my_target PROPERTIES IMPORTED_LOCATION "${PKG_PREFIX}/lib/my_target.a")
Note that in this case the library you need to link is my_target, not xxx::my_target.

CMake: Can subdirectories inherit compile features?

I have a C++ project that consists of a main program (main.cpp), a header that defines an abstract class (algorithm.hpp), and a subdirectory (algorithms/) full of classes that implement the abstract class. I've configured CMake to build the subdirectory as an object library:
# algorithms/CMakeLists.txt
add_library(algorithms_lib OBJECT
algo1.cpp
algo2.cpp
# etc.
)
In my project's root directory, I use this object library as one of the application's sources:
# Top-level CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(MyApp CXX)
add_subdirectory(algorithms)
add_executable(my-app
main.cpp
$<TARGET_OBJECTS:algorithms_lib>
)
My application is written in C++14, so I need to tell CMake to use the correct compile options for that. I don't want to require CMake version 3.8 (since it's not packaged in some recent Linux distros that I want to support), so I can't use the cxx_std_14 compile feature; instead, I've listed a bunch of individual language features that I use:
# Top-level CMakeLists.txt (cont.)
target_compile_features(my-app PUBLIC
cxx_auto_type
cxx_constexpr
cxx_defaulted_functions
# ...14 more lines...
)
The problem is, these features only apply to the top-level my-app target, not the algorithms_lib target, so the sources in the subdirectory don't get compiled as C++14.
I know I could copy the whole big target_compile_features block into algorithms/CMakeLists.txt, but I'd rather not do that — especially since this example is simplified and I actually have nine such subdirectories, each building its own object library. That'd be a lot of duplication of boilerplate code.
Is there a way to set compile features globally for all C++ targets in the project, including subdirectories? Or would it be better to get rid of add_subdirectory and the object library, and just list all the individual subdirectory files in the top-level add_executable command? I'm new to CMake, so I don't know what the "best practices" are for this sort of thing.
It sounds like you want to put the following at the top of your CMakeLists.txt file:
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
These set defaults for the same-named target properties without the leading CMAKE_. The equivalent target properties do the following:
CXX_STANDARD yy sets the desired minimum C++ standard your code wants to use.
CXX_STANDARD_REQUIRED bb says whether CXX_STANDARD is a requirement (bb is YES or some other boolean equivalent) or whether it is just desirable (bb is NO or equivalent).
CXX_EXTENSIONS bb enables (bb = YES) or disables (bb = NO) compiler extensions.
You don't need to specify individual compiler features to achieve what you're ultimately trying to do (enable C++14 for your targets). You can find a full discussion of this whole area here.

Which variable for compiler flags of CMake's ADD_LIBRARY function?

Does exist a variable which contains the compiler flags used in some call to CMake's ADD_LIBRARY function, for example the ones used when we add a module:
ADD_LIBRARY(mylib MODULE mysrc.cpp)
Or, is there a way of getting such flags?
Turning my comments into an answer
There is not a single CMake variable to get the all compiler flags. The problem is that the CMake generator will finally put together the compiler flags (from various CMake variables and properties incl. from depending targets). So you don't have all the flags during configuration step.
I see the following possible problem/solution pairs:
CMake is a cross-platform wrapper around your compiler (that's actually what the C stands for), so no need to extract the compiler flags into an external script
If you just want to add sort of a filter to what is called by CMake you can user set "launcher" variables/properties accordingly e.g. CMAKE_CXX_COMPILER_LAUNCHER or RULE_LAUNCH_LINK
If you want the compiler calls in a machine readable JSON format you could export those by setting CMAKE_EXPORT_COMPILE_COMMANDS
If you just want to see the compiler calls incl. all the flags you could set CMAKE_VERBOSE_MAKEFILE
If you really just need the compiler flags on the output and you don't want CMake to actually compile anything, you could - at least for CMake's Makefile generators - modify CMAKE_CXX_COMPILE_OBJECT and CMAKE_CXX_CREATE_SHARED_MODULE like this:
set(CMAKE_DEPFILE_FLAGS_CXX "")
set(
CMAKE_CXX_COMPILE_OBJECT
"<CMAKE_COMMAND> -E echo <FLAGS>"
)
set(
CMAKE_CXX_CREATE_SHARED_MODULE
"<CMAKE_COMMAND> -E echo <CMAKE_SHARED_MODULE_CXX_FLAGS> <LINK_FLAGS> <CMAKE_SHARED_MODULE_CREATE_CXX_FLAGS>"
)
file(WRITE mysrc.cpp "")
add_library(mylib MODULE mysrc.cpp)
References
Is Cmake set variable recursive?
What does the "c" in cmake stand for?
How to use CMAKE_EXPORT_COMPILE_COMMANDS?
Using CMake with GNU Make: How can I see the exact commands?
Retrieve all link flags in CMake

SWIG and CMake: make use of information provided by `target_include_directories()`

With CMake 2.8+ you can avoid setting include directories redundantly by using target_include_directories().
E.g. by writing
add_libary(mylib SHARED ${SOURCES})
target_include_directories(mylib PUBLIC ./include)
.. you just have to link against mylib to add the needed include folder to your target.
But how can I make use of this information when I have to use CMake modules which don't make use of this capability yet? (in my case SWIG)
When I configure a SWIG project I currently have to hard code a lot of information:
set(SWIG_MODULE_${PYTHON_MODULE_NAME}_EXTRA_DEPS
"../long/relative/path/1/include/some/header1.h"
"../long/relative/path/1/include/some/header2.h"
"../long/relative/path/2/include/some/header1.h"
"../long/relative/path/2/include/some/header2.h")
I also have to use the old fashioned include_directories() to make the swig generator know what it needs to know:
include_directories(
"../long/relative/path/1/include
"../long/relative/path/2/include)
Otherwise the %include statements inside .i files won't work any more.
Of course I could set variables containing the paths but then I would provide the information I wanted to get rid of..
Is there a way to either extract the directory information from a target or (better of course) make the SWIG CMake module correctly use it?
My current solution:
With some (very beautiful) CMake magic you can automate listing all header files from the interface part of a library and set the include directories:
function(swig_add_library_dependencies swig_module library_names)
foreach(library_name ${library_names})
get_property(LIBRARY_INCLUDES
TARGET ${library_name}
PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
foreach(INCLUDE_PATH ${LIBRARY_INCLUDES})
include_directories(${INCLUDE_PATH})
file(GLOB_RECURSE header_files "${INCLUDE_PATH}/*.h")
list(APPEND SWIG_MODULE_${swig_module}_EXTRA_DEPS ${header_files})
# export variable to parent scope
set(SWIG_MODULE_${swig_module}_EXTRA_DEPS
${SWIG_MODULE_${swig_module}_EXTRA_DEPS} PARENT_SCOPE)
endforeach()
endforeach()
endfunction()
to be used like this:
swig_add_library_dependencies(<swig_module_name> "library1;library2")
or discretely like this:
swig_add_library_dependencies(<swig_module_name> library1)
swig_add_library_dependencies(<swig_module_name> library2)
Disadvantages:
uses GLOB_RECURSE
only works if target_include_directories had been used correctly
creates dependencies to all header files found in include directories
Have a look at the documentation for get_property:
https://cmake.org/cmake/help/v3.0/command/get_property.html?highlight=get_property
you would do something like this:
get_property(MY_INCLUDES TARGET my_target PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
to get the interface include directories from the target my_target and store them in the variable MY_INCLUDES