how to set multiple element variable for CMakefile - cmake

I can pass defined variable for cmake like below(for example when I want to set PYTHON_INCLUDE_PATH=dir1).
cmake -DPYTHON_INCLUDE_PATH=dir1 ..
But what if I want to set multiple paths for this PYTHON_INCLUDE_PATH? I tried
cmake -DPYTHON_INCLUDE_PATH='dir1 dir2 dir3'
or should it be
cmake -DPYTHON_INCLUDE_PATH='dir1:dir2:dir3' (or , instead of :)
But I'm not sure it's valid. (see some other error so I am not sure yet if it's correct or not.)
I saw I can also set these defines in .cmake file like set(VARIABLE,VALUE) like below.
set(OpenCV_CUDA_VERSION 7.5)
Then what's the corresponding syntax for this set(..) form when the variable has multiple elements?

use the following syntax:
cmake -DLIST_VAR="one;two;three" ...
you can play w/ the following CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
foreach(v IN LISTS LIST_VAR)
message(STATUS "${v}")
endforeach()

Related

Debug cmake, where is variable defined?

I have very complex CMake project. Where variables are often defined like set("${scope}_${variable_name}" value..) or other complex way.
I need to find where a variable is defined, where it obtains a value.
I tried variable_watch at the beginning of the cmake, but that only gives me READ_ACCESS so I guess that setting the variable is not covered in variable_watch mechanics.
I need to find out where that variable is set, but I run out of ideas. Variable watch does not help, search sources fails due to complex variable definitions.
You can add on top of the CMakeLists:
macro(set name)
message(STATUS "defninng ${name}")
_set(${name} ${ARGV})
endmacro()
set(a b)
and print CMAKE_CURRENT_LIST_* variables.
You could do this without modifying the cmake files: Simply use grep (or windows equivalent) in combination with running the cmake configuration with the --trace-expand option.
Using this option for example
message("TGT_TYPE = ${TGT_TYPE}")
could result in console output like
/some/path/CMakeLists.txt(71): message(TGT_TYPE = UTILITY )
TGT_TYPE = UTILITY
so
cmake --trace-expand build_dir | grep -P "[sS][eE][tT]\s*\(\s*VARIABLE_NAME\s"
should provide you with the line containing the logic to set VARIABLE_NAME in the project you've set up in the directory build_dir.

Why some variables can't display by message in cmake?

I am using cmake to build my project. When I want to check some variables, I use message(${}), however I find some vars can't display using message(), giving a error "message called with incorrect number of arguments". For example, I use "message(${CMAKE_MODULE_PATH})" to check the CMAKE_MODULE_PATH,
but is gives an error "message called with incorrect number of arguments". So how do I correctly check these kinds of vars in cmake?
${VAR} expands the contents of the variable. If the variable contains spaces, it will count as multiple arguments. I think what you want is to print the value as a string by doing message("${VAR}").
As an example, my CMakeLists.txt is like this:
cmake_minimum_required(VERSION 3.17)
project(Trade)
set(CMAKE_CXX_STANDARD 11)
include_directories(/root/include)
message("${INCLUDE_DIRECTORIES}")
add_executable(Trade src/main.cpp)

With CMake, how can I set environment properties on the gtest_discover_tests --gtest_list_tests call?

I'm currently working on migrating our current build environment from MSBuild to CMake. I have a situation where I need to update the PATH variable in order for the units tests executable to run. This is not a issue for gtest_add_tests, as it uses the source to identify tests. But gtest_discover_tests, which executes the unit tests with the --gtest_list_tests flag, fails to identify any tests because a STATUS_DLL_NOT_FOUND error is encountered during the build.
For example:
add_executable(gTestExe ...)
target_include_directories(gTestExe ...)
target_compile_definitions(gTestExe ...)
target_link_libraries(gTestExe ...)
set (NEWPATH "/path/to/bin;$ENV{PATH}")
STRING(REPLACE ";" "\\;" NEWPATH "${NEWPATH}")
This works:
gtest_add_tests(TARGET gTestExe TEST_LIST allTests)
set_tests_properties(${all_tests} PROPERTIES ENVIRONMENT "PATH=${NEWPATH}")
But this does not:
#set_target_properties(gTestExe PROPERTIES ENVIRONMENT "PATH=${NEWPATH}")
#set_property(DIRECTORY PROPERTY ENVIRONMENT "PATH=${NEWPATH}")
gtest_discover_tests(gTestExe PROPERTIES ENVIRONMENT "PATH=${NEWPATH}")
Edit:
The tests themselves work when added using gtest_add_tests. The issue is the call to discover the tests, during the post build step that gtest_discover_tests registers, fails because the required libraries are not in the PATH.
I came across the same issue this morning and I found a (dirty ?) workaround. The reason why it won't work is a bit complicated, but the workaround is quite simple.
Why it won't work
gtest_discover_tests(gTestExe PROPERTIES ENVIRONMENT "PATH=${NEWPATH}")
Will not work is because the PATH contents are separated by semicolons and therefore are treated by CMake as a list value.
If you look a the GoogleTestAddTests.cmake file (located in C:\Program Files\CMake\share\cmake-3.17\Modules), it treats the PROPERTIES argument with a foreach.
The PROPERTIES value look like this for CMake at this point in the script : ENVIRONMENT;PATH=mypath;mypath2 and will treat mypath2 as a third argument instead of a value for the PATH environment variable.
CMake will then generate the following line :
set_tests_properties( mytest PROPERTIES ENVIRONMENT PATH=mypath mypath2)
Escaping the ; won't work because the list is automatically expended in add_custom_command() in GoogleTest.cmake (l. 420 in cmake 3.17.1) ignoring any form of escaping.
To prevent the cmake foreach to treat each value in the path as a list you can use a bracket argument like :
gtest_discover_tests(gTestExe PROPERTIES ENVIRONMENT "[==[PATH=${NEWPATH}]==]")
The cmake foreach will then treat your argument as one entity. Unfortunately CMake will also put a bracket in the generated code as it contains [ = and maybe spaces :
# This line
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
set(_args "${_args} [==[${_arg}]==]")
else()
set(_args "${_args} ${_arg}")
endif()
resulting in the following generated script :
set_tests_properties( mytest PROPERTIES ENVIRONMENT [==[ [==[PATH=mypath;mypath2] ]==])
And when executing the test cmake will attempt to read the value only removing the first bracket argument as they don't nest.
Possible workaround
So to do this we need CMake to not use bracket argument on our own bracket argument.
First make a local copy of GoogleTestAddTests.cmake file in your own repository (located in C:\Program Files\CMake\share\cmake-3.17\Modules).
At the beginning of your local copy of GoogleTestAddTests.cmake (l. 12) replace the function add_command by this one :
function(add_command NAME)
set(_args "")
foreach(_arg ${ARGN})
# Patch : allow us to pass a bracket arguments and escape the containing list.
if (_arg MATCHES "^\\[==\\[.*\\]==\\]$")
string(REPLACE ";" "\;" _arg "${_arg}")
set(_args "${_args} ${_arg}")
# end of patch
elseif(_arg MATCHES "[^-./:a-zA-Z0-9_]")
set(_args "${_args} [==[${_arg}]==]")
else()
set(_args "${_args} ${_arg}")
endif()
endforeach()
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
endfunction()
This will make cmake don't use bracket list on our bracket list and automatically escape the ; as set_tests_properties also treat the ; as a list.
Finally we need CMake to use our custom GoogleTestAddTests.cmake instead of the one in CMake.
After your call to include(GoogleTest) set the variable _GOOGLETEST_DISCOVER_TESTS_SCRIPT to the path to your local GoogleTestAddTests.cmake :
# Need google test
include(GoogleTest)
# Use our own version of GoogleTestAddTests.cmake
set(_GOOGLETEST_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/GoogleTestAddTests.cmake
)
Note : In my example the GoogleTestAddTests.cmake is right next to the processing cmake file.
Then a simple call to
gtest_discover_tests(my_target
PROPERTIES ENVIRONMENT "[==[PATH=${my_path};$ENV{PATH}]==]"
)
should work.

CMake set variables before defined, access them after being defined

Is it possible to set a bunch of variables before they are defined e.g., when using CUDA and I write
SET(MY_CUDA_LIBS CUDA_CUBLAS_LIBRARIES CUDA_CUFFT_LIBRARIES),
such that when are being defined by
FIND_PACKAGE(CUDA REQUIRED)
(CUDA_CUBLAS_LIBRARIES and CUDA_CUFFT_LIBRARIES can now be accessed i.e., ${CUDA_CUBLAS_LIBRARIES} and ${CUDA_CUFFT_LIBRARIES} are the paths to the corresponding library), I can access something like ${MY_CUDA_LIBS} and it returns ${CUDA_CUBLAS_LIBRARIES} ${CUDA_CUFFT_LIBRARIES}?
This obviously works, if I e.g. use CUDA_CUBLAS_LIBRARIES only and do ${${MY_CUDA_LIBS}}, but as soon as one has multiple libraries this trick does not work anymore and it returns CUDA_CUBLAS_LIBRARIESCUDA_CUFFT_LIBRARIES or "nothing".
The idea is to define the CUDA libraries in the very beginning of my CMakeLists.txt and it (far) later automatically inserts the corresponding path when accessed in TARGET_LINK_LIBRARIES.
Technically, you should treat the MY_CUDA_LIBS as a list. Then, the following is possible:
set(MANY_LIBS LIB_1 LIB_2)
set(LIB_1 "/libs/lib1")
set(LIB_2 "/libs/lib2")
list(GET MANY_LIBS 0 PATH)
message(${${PATH}})
list(GET MANY_LIBS 1 PATH)
message(${${PATH}})
resulting in the following output as predicted:
/libs/lib1
/libs/lib2
If you want it to use later in TARGET_LINK_LIBRARIES you probably can do the following after your variable are defined properly:
foreach(libname ${MANY_LIBS})
list(APPEND MANY_LIBS_PATHS ${${libname}})
endforeach(${MANY_LIBS_PATHS})
message("${MANY_LIBS_PATHS}")
after that, MANY_LIBS_PATHS can be used for linking.

CMake: show all modified variables

I would like to have a command or option to list all the modified cache variables of the current build configuration. While cmake -L[AH] is nice, it is also quite overwhelming and doesn't show which are non-default values.
There seems to be a variable property MODIFIED that sounds exactly like what I'm looking for - but the documentation is not very reassuring:
Internal management property. Do not set or get.
This is an internal cache entry property managed by CMake to track interactive user modification of entries. Ignore it.
This question also didn't help: CMAKE: Print out all accessible variables in a script
There are so many ways you could change or initialize variables in CMake (command line, environment variables, script files, etc.) that you won't be able to cover them all.
I just came up with the following script that covers the command line switches. Put the following file in your CMake project's root folder and you get the modified variables printed:
PreLoad.cmake
set(_file "${CMAKE_BINARY_DIR}/UserModifiedVars.txt")
get_directory_property(_vars CACHE_VARIABLES)
list(FIND _vars "CMAKE_BACKWARDS_COMPATIBILITY" _idx)
if (_idx EQUAL -1)
list(REMOVE_ITEM _vars "CMAKE_COMMAND" "CMAKE_CPACK_COMMAND" "CMAKE_CTEST_COMMAND" "CMAKE_ROOT")
file(WRITE "${_file}" "${_vars}")
else()
file(READ "${_file}" _vars)
endif()
foreach(_var IN LISTS _vars)
message(STATUS "User modified ${_var} = ${${_var}}")
endforeach()
This will load before anything else and therefore can relatively easily identify the user modified variables and store them into a file for later reference.
The CMAKE_BACKWARDS_COMPATIBILITY is a cached variable set by CMake at the end of a configuration run and therefor is used here to identify an already configured CMake project.
Reference
CMake: In which Order are Files parsed (Cache, Toolchain, …)?