cmake list append for compiler flags yields bogus results? - cmake

I need to add various flags to my C and C++ compile lines in my CMake files (CMake 2.8.10.2). I see some people use add_definitions but from what I can see that is intended for preprocessor flags (-D). I have some flags that I don't want passed to the preprocessor.
So I've been trying to modify CMAKE_C_FLAGS and CMAKE_CXX_FLAGS. I see that some people were using something like:
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -new -flags -here")
but then I read in the cmake docs that this is less efficient, and the right way to do it is to use list(APPEND ...), like this:
list(APPEND CMAKE_C_FLAGS -new -flags -here)
However, when I do this my compile line contains the flags separated by semicolons and is a syntax error. I read that this is now lists are stored internally, but I figured this would be taken care of by cmake when I used the variable. This seems so basic; am I doing something wrong? I mean, what the heck good are these lists if they can't be used unless you happen to want a semicolon-separated list of values (and who wants that, other than I guess Windows %PATH% settings or something)? Should I be using the quoted version even though the docs suggest it's less efficient/appropriate?

In CMake, a "list" is a string of items separated by semi-colons. For example:
set(FOO "a")
list(APPEND FOO "b") # now FOO="a;b"
list(APPEND FOO "c") # now FOO="a;b;c"
In CMake, a string of space-seperated items is just a string, not a list. Use the string(APPEND) command to append to it. For example:
set(FOO "a")
string(APPEND FOO " b") # now FOO="a b"
string(APPEND FOO " c") # now FOO="a b c"
On old versions of CMake that lack the string(APPEND) command, you should fallback to the set command. For example:
set(FOO "a")
set(FOO "${FOO} b")
set(FOO "${FOO} c")

In this case, you would indeed normally use the set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -new -flags -here") technique.
You're right in that in most other contexts CMake can "translate" a semi-colon separated list into something meaningful for the compiler (e.g. the list of source files in an executable), but in this case, CMake takes the flags as a single, complete string to pass to the compiler/linker.
You could if you really wanted keep the list of flags as a CMake list, but then before exiting the CMakeLists.txt, you could yourself "translate" the list into a single string value of CMAKE_C_FLAGS, but it's unusual to see this.

Related

cmake 3.15 adding JOB_POOL to add_custom_command SOMETIMES

For users that are using cmake 3.15 or later and are also using Ninja as a generator, I want to set the new JOB_POOL argument to some large add_custom_command() blocks. For other users, I want to keep my add_custom_command() the same (no JOB_POOL).
In earlier steps, I check the version and the generator and set ${JOB_POOLS} and I also set a variable such that users who should use a pool will see (something like):
For historical reasons, I leave this here, although #Tsyvarev
points out that this is the source of my problem!
The double-quotes are NOT wanted here!
set(USE_POOL "JOB_POOL pool_A")
Users that are not using a pool will not have that variable set.
Now how to leverage that variable in my custom command...?
1.) Generator expressions don't work, just including the text with the previous line...
add_custom_command(
...
$<USE_POOL>
)
2.) I can't seem to simply place the variable in the command, again just including the variable contents on the previous line. For example, when ${JOB_POOL} is set to the string "JOB_POOL pool_A", this code...
For historical reasons, I leave this here, although #Tsyvarev
points out that this is the source of my problem!
Don't use a STRING! No double-quotes!
add_custom_command(
OUTPUT foo
DEPENDS bar
# Comment line here...
${USE_POOL}
COMMAND
${CMAKE_COMMAND} -E ...
)
gives this error...
ninja: error: '../path/to/src/dir/JOB_POOL pool_A', needed by 'path/to/src/dir/foo', missing and no known rule to make it
It simply considers the ${JOB_POOL} string to be another dependency!
3.) I can't use the "APPEND" feature of add_custom_command(). It's just ignored...
if (${USE_POOL})
add_custom_command(
...
APPEND
JOB_POOL pool_A
)
endif()
The only thing that seems to work is to put an "if" around my
entire command, which offends my sensibility since I don't like to duplicate so much code...
if(${USE_POOL})
add_custom_command(
...many lines...
JOB_POOL pool_A
)
else()
add_custom_command(
...many lines...
)
endif()
Do you have a better idea...?
Here's a standalone example for #tsyvarev:
cmake_minimum_required(VERSION 3.15)
project foo
set_property(GLOBAL PROPERTY JOB_POOLS pool_A=2)
# For historical reasons, I leave this here, although #Tsyvarev
# points out that this is the source of my problem!
# Don't use a STRING! No double-quotes!
set(USE_POOL "JOB_POOL pool_A")
add_custom_command(
OUTPUT foo.out
DEPENDS foo.in
${USE_POOL}
COMMAND
${CMAKE_COMMAND} -E copy foo.in foo.out
COMMENT "Converting foo.in -> foo.out"
VERBATIM
)
add_custom_target(foo-out
DEPENDS foo.out
)
% cmake -GNinja .
% ninja foo-out
ninja: error: 'JOB_POOL pool_A', needed by 'foo.out', missing and no known rule to make it
It considers the string to be a dependency... If I move the USE_POOL to after the comment, it considers it part of the comment... If I move it to after the command, it considers it part of the command...
Your JOB_POOL option serves for the user's choice. You may create another variable which contains sequence of related parameters for add_custom_command:
if(JOB_POOL)
set(JOB_POOL_PARAMS JOB_POOL pool_A) # Will add 'JOB_POOL pool_A' sequence of params
else()
set(JOB_POOL_PARAMS) # Will add nothing
endif()
Then use new variable directly in add_custom_command call:
add_custom_command(
...
${JOB_POOL_PARAMS} # Will expand to additional parameters when needed
)

Dealing with the separator in CMake

I'm trying to compile some Java code with CMake (I'm aware that Java is not really the use-case for CMake) and I want to provide the class paths for the files. The compilation should work on both Unix and Windows systems. The problem I have is with separating the different class paths. Using:
set(CLASS_PATH ${PATH1} ${PATH2})
message(STATUS "${CLASS_PATH}")
prints
<PATH1>;<PATH2>
But this happens on both Unix and Windows. So I have to manually add separators. The way I'm doing it is
if(${CMAKE_HOST_WIN32})
set(SEP "\;")
elseif(${CMAKE_HOST_UNIX})
set(SEP ":")
endif(${CMAKE_HOST_WIN32})
Is this really the best way to deal with separators? I feel like I'm missing something.
Update - MCVE
To describe my thought: FILE_LIST would be contain all the java files that I want to compile. I defined a custom function which I can call on this FILE_LIST and compile the files. Maybe I'm doing something wrong with the function parameters?
cmake_minimum_required(VERSION 3.11)
set(CLASS_PATH E:/tmp/cmake/separator C:/tmp/)
set(FILE_LIST 1.txt 2.txt 3.txt)
add_custom_target(war ALL)
function(compile_java clp)
foreach(java_file ${ARGN})
add_custom_command(
TARGET war
PRE_BUILD
COMMAND echo "${clp}" ${java_file}
)
endforeach(java_file)
endfunction()
compile_java("${CLASS_PATH}" ${FILE_LIST}) # I have to pass CLASS_PATH in quotes
So, based on comments, you want the path list as a single command-line argument, with a platform-specific separator. You can achieve this using string operations:
function(compile_java clp)
if(NOT CMAKE_HOST_WIN32)
string(REPLACE ";" ":" clp "${clp}")
endif()
foreach(java_file ${ARGN})
add_custom_command(
TARGET war
PRE_BUILD
COMMAND echo "${clp}" ${java_file}
)
endforeach(java_file)
endfunction()

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

Proper way to use platform specific separators in cmake

I wonder if there is a better solution to my problem. I am working on a platform independent software project and want to add python-based unittests to cmake. The issue that I encountered now is that when configuring the ctest tests and setting up the correct PYTHONPATH environment variable for running my test, I end up with a lot of boilerplate code for each test:
add_test(my_awesome_test ${PYTHON_EXECUTABLE} my_awesome_test.py)
if("${CMAKE_HOST_SYSTEM}" MATCHES ".*Windows.*")
set_tests_properties(my_awesome_test PROPERTIES ENVIRONMENT "PYTHONPATH=somepath\;anotherpath")
else() # e.g. Linux
set_tests_properties(my_awesome_test PROPERTIES ENVIRONMENT "PYTHONPATH=somepath:anotherpath")
endif()
# more tests like this after this...
The problem here is the branching, that is required only because of the platform dependent list separators.
Is there some neater way to accomplish this?
Is there a constant which specifies the platform separator or a function that allows me to construct these lists?
If there is no "proper" answer, I also wanted to share my obvious, but not-so-nice solution:
if("${CMAKE_HOST_SYSTEM}" MATCHES ".*Windows.*")
set(SEP "\\;")
else() # e.g. Linux
set(SEP ":")
endif()
add_test(my_awesome_test ${PYTHON_EXECUTABLE} my_awesome_test.py)
set_tests_properties(my_awesome_test PROPERTIES ENVIRONMENT "PYTHONPATH=somepath${SEP}anotherpath")
# more tests like this after this...
On Windows the ";" must be double escaped because otherwise it it is substituted later in the add_test line as a single ";" again, which is then in turn interpreted as the cmake-list separator leading to wrong results. However, having cmake report which character should be used as list separator would still be nicer...
Unfortunately file(TO_NATIVE_PATH ... does not convert ; to : on Unix platforms. I think the easiest way is therefore something like this:
set(PATH "/usr/bin" "/bin")
if (UNIX)
string(REPLACE ";" ":" PATH "${PATH}")
endif()
This works because CMake lists are really ;-separated strings, so on Windows you can use them for path lists as-is. It won't work for paths containing ; or : but that will trip up 99% of Unix software anyway so I wouldn't worry about it.
You can use the file function as follow:
file(TO_CMAKE_PATH "<path>" <variable>)
file(TO_NATIVE_PATH "<path>" <variable>)
https://cmake.org/cmake/help/v3.9/command/file.html
You may define function/macro which transforms some choosen separator into platform-specific one. E.g., function below transforms CMake list into platform-specific path list:
function(to_path_list var path1)
if("${CMAKE_HOST_SYSTEM}" MATCHES ".*Windows.*")
set(sep "\\;")
else()
set(sep ":")
endif()
set(result "${path1}") # First element doesn't require separator at all...
foreach(path ${ARGN})
set(result "${result}${sep}${path}") # .. but other elements do.
endforeach()
set(${var} "${result}" PARENT_SCOPE)
endfunction()
Usage:
to_path_list(pythonpath "somepath" "anotherpath")
set_tests_properties(my_awesome_test PROPERTIES ENVIRONMENT "PYTHONPATH=${pythonpath}")

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)