Add dependencies to a custom target - cmake

I need a way to add additional dependencies to a custom target. I have a macro which adds resource files to a particular project, used like this:
ADD_RESOURCES( ${TARGET} some/path pattern1 pattern2 )
ADD_RESOURCES( ${TARGET} another/path pattern1 )
I create a target called ${TARGET}_ASSETS and would like to attach the generation of all these resources to the one target. add_dependencies however only accepts other targets. So if I produce a file via a add_custom_command I cannot use that as a dependency.
The workaround might be to just create a new custom taget for each call to ADD_RESOURCES and then attached that to the ASSETS target. Each target requires a unique name however, and these is no way to generate this unique name from the parameters of ADD_RESOURCES.

One work-around is to postpone the generation of the ${target}_ASSETS custom targets until all dependencies have been set up with calls to ADD_RESOURCES.
Instead of immediately adding the dependencies to the custom target, the macro ADD_RESOURCES has to record the dependencies in a global variable, whose name depends on the target:
macro (ADD_RESOURCES _targetName)
set (_dependencies ${ARGN})
...
# record depencies in a target dependency variable
if (DEFINED ${_targetName}_Dependencies)
list (APPEND ${_targetName}_Dependencies ${_dependencies})
else()
set (${_targetName}_Dependencies ${_dependencies})
endif()
endmacro()
Then add another helper macro which determines all defined target dependency variables through reflection and sets up a custom target for each target:
macro (SETUP_ASSETS_TARGETS)
get_cmake_property(_vars VARIABLES)
foreach (_var ${_vars})
if (_var MATCHES "(.+)_Dependencies")
set (_targetName ${CMAKE_MATCH_1})
set (_targetDependencies ${${_var}})
message("${_targetName} depends on ${_targetDependencies}")
add_custom_target(${_targetName}_ASSETS DEPENDS ${_targetDependencies})
endif()
endforeach()
endmacro()
In your CMakeLists.txt add all necessary dependencies with calls to ADD_RESOURCES, then call the SETUP_ASSETS_TARGETS macro to have all custom targets defined.
ADD_RESOURCES( target1 some/path pattern1 pattern2 )
ADD_RESOURCES( target1 another/path pattern1 )
ADD_RESOURCES( target2 foo/bar pattern1 )
...
...
SETUP_ASSETS_TARGETS()

I know this is a late answer, but i post my solution for everyone who searches for this problem:
function(target_resources THIS)
if (NOT TARGET ${THIS}_res)
# this is just a pseudo command which can be appended later
add_custom_command(OUTPUT ${THIS}_dep COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR})
# add a dependency with a target, as a command itself can not be a dependency
add_custom_target(${THIS}_res DEPENDS ${THIS}_dep)
add_dependencies(${THIS} ${THIS}_res)
endif ()
get_target_property(RUNTIME_OUTPUT_DIRECTORY ${THIS} RUNTIME_OUTPUT_DIRECTORY)
foreach (RES_FILE IN LISTS ARGN)
if (IS_ABSOLUTE ${RES_FILE})
file(RELATIVE_PATH PATH ${CMAKE_CURRENT_SOURCE_DIR} ${RES_FILE})
endif ()
# append the resource command with our resource
add_custom_command(OUTPUT ${THIS}_dep
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/${RES_FILE}
${RUNTIME_OUTPUT_DIRECTORY}/${RES_FILE}
APPEND)
endforeach ()
endfunction()
The benefit of this solution is that is does not rely on global variables nor the need to invoke a setup macro.

Related

How to add dependencies to a custom_target() after the fact?

I'm trying to have a top level target, we'll call it sdk_extraction that is tested/built every time, and had depends on a bunch of generated files that are basically copies or minor transformations of files in the tree.
I build up the list of files to export as a property string/list, then when it's all said and done take that list and iterate through it, creating custom_commands for each one and then adding a dependency between the output file and the sdk_extraction target.
function(finalize_sdk_export destination)
file(REAL_PATH ${destination} destination )
add_custom_target(sdk_target ALL
COMMENT "Exporting SDK"
)
get_property(sdk_source GLOBAL PROPERTY SDK_SOURCE )
string(REPLACE " " ";" sdk_source "${sdk_source}")
foreach (item ${sdk_source})
string(REPLACE ${destination} ${destination}/sdk target "${item}")
add_custom_command(
OUTPUT ${target}
DEPENDS ${item}
COMMAND ${CMAKE_COMMAND} -E copy ${item} ${target}
VERBATIM
)
add_dependencies(sdk_target ${target})
endforeach()
endfunction()
This fails with an error:
CMake Error at <redacted>/build_scripts/CMakeLists.txt:209 (add_dependencies):
The dependency target
"<redacted>/sdk/CMakeLists.txt"
of target "sdk_target" does not exist.
Call Stack (most recent call first):
<redacted>/CMakeLists.txt:78 (finalize_sdk_export)
Does this not work? I WAS able to do this:
function(finalize_sdk_export destination)
file(REAL_PATH ${destination} destination )
get_property(sdk_source GLOBAL PROPERTY SDK_SOURCE )
string(REPLACE " " ";" sdk_source "${sdk_source}")
foreach (item ${sdk_source})
string(REPLACE ${destination} ${destination}/sdk target "${item}")
add_custom_command(
OUTPUT ${target}
DEPENDS ${item}
COMMAND ${CMAKE_COMMAND} -E copy ${item} ${target}
VERBATIM
)
list(APPEND sdk_files ${target})
endforeach()
add_custom_target(sdk_target ALL
COMMENT "Exporting SDK"
DEPENDS ${sdk_files}
)
endfunction()
Where I make all the custom_commands and keep a list of each ones output, then make the call to add_custom_target() with them all specified in the DEPENDS parameter.
This works for now, but I'm still just testing and I'm unsure it'll hold up if I end up having hundreds/thousands of files in the export.
So, how am I supposed to do this correctly?
(FYI, I gave up with the built in cmake install/export functionality because we really want to have control over the directory structure and look/feel of the CMake files)
All files, from which a custom target depends, should be specified in the DEPENDS option for add_custom_target() call. There is no way to add dependencies from the files afterwards.
The command add_dependencies can add only dependencies between targets.
Unlike to add_dependencies, which may connect targets creates in different directories, all generated files in DEPENDS clause should be created by add_custom_command in the same directory only. That is, it is usually not
a problem to postpone creation of the target unless all dependent files are calculated.

How to duplicate cmake target

I am writing function in my cmake project that needs to make two targets from one, and alter slightly one of them:
option(BORG_STRIP_TEST_BINARIES OFF "Strip symbols from test binaries to reduce disk space usage" )
function(add_borg_test target)
add_test(${target} ${target} --gtest_color=yes)
if(BORG_STRIP_TEST_BINARIES)
# copy target, but make it optional
duplicate_target(FROM ${target} TO ${target}_debug )
set_target_properties(${target}_debug PROPERTIES EXCLUDE_FROM_ALL TRUE)
# alter
target_link_options(${target} PRIVATE -s)
endif()
endfunction()
So this is supposed to work like this:
I create binary target that uses gtest. Set up all target_link_libraries and everything. Example name example-utests
instead add generic add_test, I use custom add_borg_test
now example-utests links with flag -s and is included in all target.
example-utests_debug is NOT included in all target but when requested explicitly, it links without -s.
How to implement duplicate_target from above code snippet?

Get full C++ compiler command line

In CMake, the flags for the C++ compiler can be influenced in various ways: setting CMAKE_CXX_FLAGS manually, using add_definitions(), forcing a certain C++ standard, and so forth.
In order to compile a target in the same project with different rules (a precompiled header, in my case), I need to reproduce the exact command that is used to compile files added by a command like add_executable() in this directory.
Reading CMAKE_CXX_FLAGS only returns the value set to it explicitly, CMAKE_CXX_FLAGS_DEBUG and siblings only list default Debug/Release options. There is a special functions to retrieve the flags from add_definitions() and add_compiler_options(), but none seem to be able to return the final command line.
How can I get all flags passed to the compiler into a CMake variable?
To answer my own question: It seems like the only way of getting all compiler flags is to reconstruct them from the various sources. The code I'm working with now is the following (for GCC):
macro (GET_COMPILER_FLAGS TARGET VAR)
if (CMAKE_COMPILER_IS_GNUCXX)
set(COMPILER_FLAGS "")
# Get flags form add_definitions, re-escape quotes
get_target_property(TARGET_DEFS ${TARGET} COMPILE_DEFINITIONS)
get_directory_property(DIRECTORY_DEFS COMPILE_DEFINITIONS)
foreach (DEF ${TARGET_DEFS} ${DIRECTORY_DEFS})
if (DEF)
string(REPLACE "\"" "\\\"" DEF "${DEF}")
list(APPEND COMPILER_FLAGS "-D${DEF}")
endif ()
endforeach ()
# Get flags form include_directories()
get_target_property(TARGET_INCLUDEDIRS ${TARGET} INCLUDE_DIRECTORIES)
foreach (DIR ${TARGET_INCLUDEDIRS})
if (DIR)
list(APPEND COMPILER_FLAGS "-I${DIR}")
endif ()
endforeach ()
# Get build-type specific flags
string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE_SUFFIX)
separate_arguments(GLOBAL_FLAGS UNIX_COMMAND
"${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_TYPE_SUFFIX}}")
list(APPEND COMPILER_FLAGS ${GLOBAL_FLAGS})
# Add -std= flag if appropriate
get_target_property(STANDARD ${TARGET} CXX_STANDARD)
if ((NOT "${STANDARD}" STREQUAL NOTFOUND) AND (NOT "${STANDARD}" STREQUAL ""))
list(APPEND COMPILER_FLAGS "-std=gnu++${STANDARD}")
endif ()
endif ()
set(${VAR} "${COMPILER_FLAGS}")
endmacro ()
This could be extended to also include options induced by add_compiler_options() and more.
Easiest way is to use make VERBOSE=1 when compiling.
cd my-build-dir
cmake path-to-my-sources
make VERBOSE=1
This will do a single-threaded build, and make will print every shell command it runs just before it runs it. So you'll see output like:
[ 0%] Building CXX object Whatever.cpp.o
<huge scary build command it used to build Whatever.cpp>
There actually is a fairly clean way to do this at compile time using CXX_COMPILER_LAUNCHER:
If you have a script print_args.py
#!/usr/bin/env python
import sys
import argparse
print(" ".join(sys.argv[1:]))
# we need to produce an output file so that the link step does not fail
p = argparse.ArgumentParser()
p.add_argument("-o")
args, _ = p.parse_known_args()
with open(args.o, "w") as f:
f.write("")
You can set the target's properties as follows:
add_library(${TARGET_NAME} ${SOURCES})
set_target_properties(${TARGET_NAME} PROPERTIES
CXX_COMPILER_LAUNCHER
${CMAKE_CURRENT_SOURCE_DIR}/print_args.py
)
# this tells the linker to not actually link. Which would fail because output file is empty
set_target_properties(${TARGET_NAME} PROPERTIES
LINK_FLAGS
-E
)
This will print the exact compilation command at compile time.
Short answer
It's not possible to assign final value of compiler command line to variable in CMake script, working in all use cases.
Long answer
Unfortunately, even solution accepted as answer still not gets all compiler flags. As gets noted in comments, there are Transitive Usage Requirements. It's a modern and proper way to write CMake files, getting more and more popular. Also you may have some compile options defined using generator expressions (they look like variable references but will not expand when needed).
Consider having following example:
add_executable(myexe ...);
target_compile_definitions(myexe PRIVATE "PLATFORM_$<PLATFORM_ID>");
add_library(mylib ...);
target_compile_definitions(mylib INTERFACE USING_MY_LIB);
target_link_libraries(myexe PUBLIC mylib);
If you try to call proposed GET_COMPILER_FLAGS macro with myexe target, you will get resulting output -DPLATFORM_$<PLATFORM_ID> instead of expected -DPLATFORM_Linux -DUSING_MY_LIB.
This is because there are two stages between invoking CMake and getting build system generated:
Processing. At this stage CMake reads and executes commands from cmake script(s), particularly, variable values getting evaluated and assigned. At this moment CMake just collecting all required info and being prepared to generate build system (makefiles).
Generating. CMake uses values of special variables and properties, being left at end of processed scripts to finally decide and form generated output. This is where it constructs final command line for compiler according to its internal algorithm, not avaliable for scripting.
Target properties which might be retrieved at processing stage with get_target_property(...) or get_property(... TARGET ...) aren't complete (even when invoked at the end of script). At generating stage CMake walks through each target dependency tree (recursively) and appends properties values according to transitive usage requirements (PUBLIC and INTERFACE tagged values gets propagated).
Although, there are workarounds, depending on what final result you aiming to achieve. This is possible by applying generator expressions, which allows use final values of properties of any target (defined at processing stage)... but later!
Two general possibilites are avaliable:
Generate any output file based on template, which content contains variable references and/or generator expressions, and defined as either string variable value, or input file. It's not flexible due to very limited support of conditional logic (i.e. you cannot use complex concatenations available only with nested foreach() loops), but has advantages, that no further actions required and content described in platform-independent way. Use file(GENERATE ...) command variant. Note, that it behaves differently from file (WRITE ...) variant.
Add custom target (and/or custom command) which implements further usage of expanded value. It's platform dependent and requires user to additionally invoke make (either with some special target, or include to all target), but has advantage, that it's flexible enough because you may implement shell script (but without executable bit).
Example demonstrating solution with combining these options:
set(target_name "myexe")
file(GENERATE OUTPUT script.sh CONTENT "#!/bin/sh\n echo \"${target_name} compile definitions: $<TARGET_PROPERTY:${target_name},COMPILE_DEFINITIONS>\"")
add_custom_target(mycustomtarget
COMMAND echo "\"Platform: $<PLATFORM_ID>\""
COMMAND /bin/sh -s < script.sh
)
After calling CMake build directory will contain file script.sh and invoking make mycustomtarget will print to console:
Platform: Linux
myexe compile definitions: PLATFORM_Linux USING_MY_LIB
Use
set(CMAKE_EXPORT_COMPILE_COMMANDS true)
and get compile_commands.json

Query headers added to cmake project

I would like to get all headers added to cmake project. The use case is that I'd get this list of headers and call some custom validation on them. I would really love this to be a query mechanism to mitigate errors in oversight.
I am not interested in globbing the file system as headers may exist that are not appropriate for every platform. It's also bad.
This is what I would like the usage to look like.
add_library(example_lib
foo.h
foo.cpp
bar.h
bar.cpp
)
add_executable(example main_example.cpp)
target_link_libraries(example example_lib)
# this is the feature I am interested in
get_target_headers(example_header example)
# alternatively
get_target_headers(example_header example example_lib)
do_custom_thing("${example_header}")
A more manual way of doing this would be something like the below. I'd just reuse the example_header variable to do the custom validation.
set(example_header
foo.h
bar.h
)
set(example_source
foo.cpp
bar.cpp
)
add_library(example_lib
${example_header}
${example_source}
)
add_executable(example main_example.cpp)
target_link_libraries(example example_lib)
do_custom_thing("${example_header}")
This is what I'm doing now and it works, I am just wondering if there is a better way.
If all your headers have a ".h" suffix, you could use something like:
function(get_target_headers Headers MainTarget)
# Gather list of MainTarget's dependencies
get_target_property(Dependencies ${MainTarget} LINK_LIBRARIES)
set(AllTargets ${MainTarget})
foreach(Dependency ${Dependencies})
# If this is a CMake target, add it to the list
if(TARGET ${Dependency})
list(APPEND AllTargets ${Dependency})
endif()
endforeach()
# Gather each target's list of source files ending in ".h"
foreach(Target ${AllTargets})
get_target_property(Sources ${Target} SOURCES)
foreach(Source ${Sources})
string(REGEX MATCH "^.*\\.h$" Header ${Source})
if(Header)
list(APPEND AllHeaders ${Header})
endif()
endforeach()
endforeach()
# Since functions have their own scope, set the list in the parent scope
set(${Headers} ${AllHeaders} PARENT_SCOPE)
endfunction()
and invoke it using your first choice:
get_target_headers(example_header example)

CMake: Print out all accessible variables in a script

I'm wondering if there is a way to print out all accessible variables in CMake. I'm not interested in the CMake variables - as in the --help-variables option. I'm talking about my variables that I defined, or the variables defined by included scripts.
I'm currently including:
INCLUDE (${CMAKE_ROOT}/Modules/CMakeBackwardCompatibilityCXX.cmake)
And I was hoping that I could just print out all the variables that are here, instead of having to go through all the files and read what was available - I may find some variables I didn't know about that may be useful. It would be good to aid learning & discovery. It is strictly for debugging/development.
This is similar to the question in Print all local variables accessible to the current scope in Lua, but for CMake!
Has anyone done this?
Using the get_cmake_property function, the following loop will print out all CMake variables defined and their values:
get_cmake_property(_variableNames VARIABLES)
list (SORT _variableNames)
foreach (_variableName ${_variableNames})
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()
This can also be embedded in a convenience function which can optionally use a regular expression to print only a subset of variables with matching names
function(dump_cmake_variables)
get_cmake_property(_variableNames VARIABLES)
list (SORT _variableNames)
foreach (_variableName ${_variableNames})
if (ARGV0)
unset(MATCHED)
string(REGEX MATCH ${ARGV0} MATCHED ${_variableName})
if (NOT MATCHED)
continue()
endif()
endif()
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()
endfunction()
To print environment variables, use CMake's command mode:
execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "environment")
Another way is to simply use:
cmake -LAH
From the manpage:
-L[A][H]
List non-advanced cached variables.
List cache variables will run CMake and list all the variables from the CMake cache that are not marked as INTERNAL or ADVANCED. This will effectively display current CMake settings [...].
If A is specified, then it will display also advanced variables.
If H is specified, it will also display help for each variable.
ccmake is a good interactive option to interactively inspect cached variables (option( or set( CACHE:
sudo apt-get install cmake-curses-gui
mkdir build
cd build
cmake ..
ccmake ..
Another way to view all cmake's internal variables, is by executing cmake with the --trace-expand option.
This will give you a trace of all .cmake files executed and variables set on each line.
based on #sakra
function(dump_cmake_variables)
get_cmake_property(_variableNames VARIABLES)
list (SORT _variableNames)
foreach (_variableName ${_variableNames})
if (ARGV0)
unset(MATCHED)
#case sensitive match
# string(REGEX MATCH ${ARGV0} MATCHED ${_variableName})
#
#case insenstitive match
string( TOLOWER "${ARGV0}" ARGV0_lower )
string( TOLOWER "${_variableName}" _variableName_lower )
string(REGEX MATCH ${ARGV0_lower} MATCHED ${_variableName_lower})
if (NOT MATCHED)
continue()
endif()
endif()
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()
endfunction()
dump_cmake_variables("^Boost")
variable names are case sensitive
btw if you are interested in boost, it is Boost_INCLUDE_DIRS not BOOST_INCLUDE_DIRS, and it is Boost_LIBRARIES not BOOST_LIBRARIES, and by mistake I had BOOST_LIBRARIES instead of Boost_LIBRARIES, https://cmake.org/cmake/help/v3.0/module/FindBoost.html , better example for boost:
set(Boost_USE_STATIC_LIBS ON)
find_package(Boost REQUIRED COMPONENTS RANDOM)
include_directories(${Boost_INCLUDE_DIRS})
target_link_libraries(myfile PRIVATE
${Boost_LIBRARIES}
)
You can use message :
message([STATUS] "SUB_SOURCES : ${SUB_SOURCES}")
None of the current answers allowed me to see the variables in my project subdirectory. Here's a solution:
function(print_directory_variables dir)
# Dump variables:
get_property(_variableNames DIRECTORY ${dir} PROPERTY VARIABLES)
list (SORT _variableNames)
foreach (_variableName ${_variableNames})
get_directory_property(_variableValue DIRECTORY ${dir} DEFINITION ${_variableName})
message(STATUS "DIR ${dir}: ${_variableName}=${_variableValue}")
endforeach()
endfunction(print_directory_variables)
# for example
print_directory_variables(.)
print_directory_variables(ui/qt)