Detect presence of generator expressions in CMake - cmake

The use case for the following problem is macro packages which are deployed, so the input is entirely user defined.
Consider the following minimal example:
function(foo)
cmake_parse_arguments(FOO "" "POSSIBLY_RELATIVE_POSSIBLY_GENEX_PATH" "" ${ARGN})
# Chosen as an example for a necessary step which doesn't have a generator expression equivalent
get_filename_component(
ABSOLUTE_PATH
"${FOO_POSSIBLY_RELATIVE_POSSIBLY_GENEX_PATH}"
REALPATH
BASE_DIR
"${CMAKE_CURRENT_SOURCE_DIR}"
)
# Compatible with generator expressions by itself
add_custom_command(
OUTPUT
${MY_OUTPUT}
COMMAND
${MY_TOOL}
${ABSOLUTE_PATH}
)
endfunction()
add_custom_command itself supports generator expressions, but the need to go via get_filename_component to handle one class of input (relative paths, for brevity in calling code), and the use of that intermediate step being impossible for the other class of input (build type dependent generator expressions, which get scrambled by get_filename_component) clashes.
get_filename_component effectively can't be used whenever POSSIBLY_RELATIVE_POSSIBLY_GENEX_PATH had contained any genex components. get_filename_component has no generator expression equivalent either, so substitution of that logic isn't possible.
Is there a robust way to detect the presence of valid generator expressions in a CMake variable, so it can be used as a signal not to attempt any non-genex transformations on that input?

You could use command string(GENEX_STRIP) which strips generator expressions from the string, and compare its result with original string:
string(GENEX_STRIP "${FOO_POSSIBLY_RELATIVE_POSSIBLY_GENEX_PATH}" no_genex)
if(FOO_POSSIBLY_RELATIVE_POSSIBLY_GENEX_PATH STREQUAL no_genex)
# The string doesn't contain generator expressions.
# It is safe to use e.g. 'get_filename_component'
else()
# The string contains generator expressions.
# This case requires special handling.
endif()

Related

In cmake how do you use TARGET in file(GENERATE ... TARGET ...)

Docs imply that the TARGET option (new with 3.19) can be used for the generator expressions, however in the input how do you use the value input in the TARGET option?
$<TARGET_FILE:???>
tried a few things including ..., '' and just $<TARGET_FILE>. But always get Error evaluating generator expression:
--Docs
TARGET <target>
New in version 3.19.
Specify which target to use when evaluating generator expressions that require a target for evaluation (e.g. $<COMPILE_FEATURES:...>, $<TARGET_PROPERTY:prop>).
--Release notes
The file(GENERATE) command gained a new TARGET keyword to support resolving target-dependent generator expressions.

Expand variable name containing a generator expression in cmake

In the build process, I set directories where I gather the build output of different sub-projects. The directories are set as :
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_LIST_DIR}/../build/bin/debug" )
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_LIST_DIR}/../build/bin/release" )
Now, I'd like to copy some files (a directory of qt plugins) to that directory dependent on the configuration which it is built for.
I tried:
# copy qt plugins
add_custom_command( TARGET mytarget POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${QT_DIR}/../../../plugins"
"${$<UPPER_CASE:CMAKE_RUNTIME_OUTPUT_DIRECTORY_$<CONFIG> >}/plugins"
COMMAND_EXPAND_LISTS)
thus, I try to build a string that equals the variable name and then try to expand that as described here: CMake interpret string as variable. In other words: I would like to have a generator expression that evaluates to the content of CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG or CMAKE_RUNTIME_OUTPUT_DIRECTOR_RELEASE dependent on the current build configuration.
However running cmake with the statement above results in an error:
"CMakeLists.txt:112: error: Syntax error in cmake code at [..] when parsing string ${$<UPPER_CASE:CMAKE_RUNTIME_OUTPUT_DIRECTORY_$<CONFIG> >}/plugins Invalid character ('<') in a variable name: '$'
So my question is, how can I use a generator-expression to access the corresponding variable? (Bonus question: is there another/better way to achieve the same goal?)
So my question is, how can I use a generator-expression to access the corresponding variable?
You cannot. There is currently (CMake <=3.23) no way to expand a variable whose name is determined by the value of a generator expression.
Bonus question: is there another/better way to achieve the same goal?
Yes, and you are almost there! You can use $<TARGET_FILE_DIR:...>:
add_custom_command(
TARGET mytarget POST_BUILD
COMMAND
${CMAKE_COMMAND} -E copy_directory
"${QT_DIR}/../../../plugins"
"$<TARGET_FILE_DIR:mytarget>/plugins"
VERBATIM
)
This works because TARGET_FILE_DIR evaluates to the actual directory containing the executable or library file for mytarget, no matter the active configuration, property values, etc.
Docs: https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:TARGET_FILE_DIR
CMAKE_RUNTIME_OUTPUT_DIRECTORY_<CONFIG> is already relative to the binary directory so you should not try to compute the binary directory in its definition. Also, it supports generator expressions. Thus, the following will be much more robust:
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "bin/$<LOWER_CASE:$<CONFIG>>"
CACHE STRING "Common output directory for runtime artifacts")
This has a bunch of concrete benefits:
No need to set CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG or CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE
This will work for MinSizeRel and RelWithDebInfo, plus any custom configurations one might add down the line.
Since it's defined as a cache variable, it can be overridden for debugging / working around name clashes, etc.
A bit more context for (3): most CMAKE_* variables are intended to be either read-only or user-configurable (i.e. at the command line, from the GUI, etc.). Overriding their defaults via set(CACHE) is a polite compromise. A notable exception to this rule is the collection of Qt codegen flags (CMAKE_AUTO{MOC,RCC,UIC}). These must typically be set for the build to produce usable binaries.

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

CMake generator expression is not being evaluated

Due to the following warning:
CMake Error at test/CMakeLists.txt:29 (get_target_property):
The LOCATION property may not be read from target "my_exe". Use the
target name directly with add_custom_command, or use the generator
expression $<TARGET_FILE>, as appropriate.
which is the result from lines like this:
get_target_property(my_exe_path my_exe LOCATION)
Like recommended in the docs, I tried to use a generator expression like this:
add_executable(my_exe_path main.cpp)
message("path to executable: $<TARGET_FILE:my_exe_path>")
But TARGET_FILE is not being evaluated
path to executable: $<TARGET_FILE:my_exe>
I'm using CMake 3.4 and added cmake_minimum_required(VERSION 3.4) to my CMakeLists.txt so what am I doing wrong?
Here is a quick and easy way to print the value of a generator expression:
add_custom_target(print
${CMAKE_COMMAND} -E echo $<1:hello> $<0:world>
)
In this example, if you run cmake . and then make print, you will see "hello" (without the quotation marks) in the output.
However, if you just use message($<1:hello> $<0:world>), you will see "$<1:hello> $<0:world>" as output (again, without the quotation marks).
While generator expression is stored at configuration stage (when corresponded CMake command is executed),
evaluation of generator expressions is performed at build stage.
This is why message() command prints generator expression in non-dereferenced form: value denoted by the generator expression is not known at this stage.
Moreover, CMake never dereferences generator expressions by itself. Instead, it generates appropriate string in the build file, which is then interpreted by build utility (make, Visual Studio, etc.).
Note, that not every CMake command accepts generator expressions. Each possible usage of generator expressions is explicitely described in documentation for specific command. Moreover, different CMake command flows or different options have different policy about using of generator expressions.
For example, command flow
add_test(NAME <name> COMMAND <executable>)
accepts generator expressions for COMMAND option,
but command flow
add_test(<name> <executable>)
doesn't!
Another example of policies difference:
install(DIRECTORY <dir> DESTINATION <dest>)
In this command flow generator expressions are allowed for DESTINATION, but not for DIRECTORY option.
Again, read documentation carefully.

CMake generator expression is not evaluated

I cannot understand what I'm doing wrong.
I'm always getting the string "$<TARGET_FILE:tgt1>" instead of the path to the library.
I've created the dummy project.
Here is my root CMakeLists.txt
cmake_minimum_required (VERSION 3.0) # also tried 2.8 with the same result
set(PROJECT_NAME CMP0026)
add_subdirectory(src)
set(TGT_PATH $<TARGET_FILE:tgt1>)
message(STATUS "${TGT_PATH}")
Here is my src/CMakeLists.txt
add_library(tgt1 a.c)
File a.c is created and is empty
I've tried the following generators: Visual Studio 2013 Win64, Ninja and MingW Makefile. I've used Android toolchain for the last two, downloaded from here
I expect that the last message(STATUS command would print full path to the library. However, all variants print the string $<TARGET_FILE:tgt1>.
Generator expressions are not evaluated at configure time (when CMake is parsing CMakeLists, executing commands like add_target() or message() etc.). At this time, a generator expression is just a literal string - the character $ followed by <, then T, then ...
Evaluation of generator expressions happens at generate time (that's why they are called "generator expressions"). Generate time occurs after all CMake code is parsed and processed, and CMake is starting to act on the data therein to produce buildsystem files. Only then does it have all the information necessary to evaluate generator expressions.
So you can only really use generator expressions for things which occur at generate time or later (such as build time). A contrived example would be this:
add_custom_target(
GenexDemo
COMMAND ${CMAKE_COMMAND} -E echo "$<TARGET_FILE:tgt1>"
VERBATIM
)
At configure time, CMake will record the literal string $<TARGET_FILE:tgt1> as the argument of COMMAND. Then at generate time (when the location of tgt1 is known for each configuration and guaranteed not to change any more), it will substitute it for the generator expression.