Use environment variable to set `include_directories` - cmake

I have an environment variable that contains paths to manually 'installed' header only libraries:
export INCLUDE_PATH="/some/path":"${INCLUDE_PATH}"
I want to use this in my CMakeLists.txt. But when I do:
include_directories("$ENV{INCLUDE_PATH}")
the paths appear not be properly added (no CMake error, but the compiler does not know where to look).

You can try to replace the ':' char to ';'. The ';' is the way CMake deals with lists.
string(REPLACE ":" ";" INCLUDE_LIST $ENV{INCLUDE_PATH})
include_directories(${INCLUDE_LIST})

Related

CMAKE_PREFIX_PATH separator : ':' or ';'?

When using CMAKE_PREFIX_PATH as an environnment variable, I have a weird behaviour, which I can't find in documentation.
I want to search the prefix path in two location, let's call them /path/to/prefix1 and /path/to/prefix2.
This doesn't work : export CMAKE_PREFIX_PATH="/path/to/prefix1;/path/to/prefix2"
This does work : export CMAKE_PREFIX_PATH="/path/to/prefix1:/path/to/prefix2"
Obviously in Unix PATH variable use : as a separator, but in CMake list separator is ;, like documented here. In the same link, its not written that environnment variables are an exception.
It also is explicitly written that CMAKE_PREFIX_PATH is a semicolon-separated list, which is confusing.
So what's going on here?
EDIT: It seems that when giving the argument from the command line, it does work only with ; and not :, which is the opposite as using an environnment variable : e.g. cmake .. -D CMAKE_PREFIX_PATH="/path/to/prefix1;/path/to/prefix2" works.
The normal variable and the environment variable are treated differently and are documented separately. CMake tends to treat environment variables the same way as the system treats the PATH variable. CMake normal variables are typically semicolon-separated (the most notable exceptions are the _FLAGS variables, which are space-separated for historical and backwards compatibility reasons).
For the environment variable CMAKE_PREFIX_PATH, the documentation says:
This variable may hold a single prefix or a list of prefixes separated by : on UNIX or ; on Windows (the same as the PATH environment variable convention on those platforms).
https://cmake.org/cmake/help/latest/envvar/CMAKE_PREFIX_PATH.html
Meanwhile, for the normal CMAKE_PREFIX_PATH variable, the documentation says:
Semicolon-separated list of directories specifying installation prefixes to be searched by the find_package(), find_program(), find_library(), find_file(), and find_path() commands.
https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html
which I can't find in documentation.
I found this simply by searching "CMAKE_PREFIX_PATH" in the documentation side-bar.

Cmake Error:STRING sub-command REPLACE requires at least four arguments

Cmake version:3.20.0
platform: macOS 11.1
When I tried to compiler a library with Cmake, I got the following error:
CMake Error at CMakeLists.txt:45 (STRING):
STRING sub-command REPLACE requires at least four arguments.
The corresponding code is shown below:
STRING(REPLACE "'" "\"" HYMLS_REVISION ${rev})
In there, I want to replace ' with " . I don't find any error. It should be valid.
Could anyone help with this?
Use quotes around ${rev} variable
STRING(REPLACE "'" "\"" HYMLS_REVISION "${rev}")

Do not expand CMake list variable

I have a CMake script that runs some tests via add_test(), running under Windows (Server 2008, don't ask) in CMake 3.15. When these tests are called, the PYTHONPATH environment variable in the environment they run in seems to get reset to the environment default, and doesn't contain some paths that it needs to.
I therefore need to set PYTHONPATH when the tests are run to the value of the $ENV{PYTHONPATH} variable when CMake runs. This has a number of semicolon-separated paths, so CMake thinks it's a list and tries to expand it into a number of space-separated strings, which obviously ends badly.
I cannot work out how to stop CMake doing this. From everything I can see, you should be able to do just surround with quotes:
add_test(
NAME mytest
COMMAND cmake -E env PYTHONPATH="$ENV{PYTHONPATH}"
run_test_here)
...but it always does the expansion. I also tried setting with set_tests_properties:
set_tests_properties(mytest PROPERTIES
ENVIRONMENT PYTHONPATH="$ENV{PYTHONPATH}")
...but that didn't appear to do anything at all - PYTHONPATH at test time wasn't altered. I thought it was because it's an environment variable, but using a regular CMake variable via set() makes no difference, so I'm doing something wrong. Help please!
The following should work:
COMMAND cmake -E env "PYTHONPATH=$ENV{PYTHONPATH}"
You need to quote the full part of the command line, to make properly expanded message.
Tested with:
set(tmp "C:\\Python27\\Scripts;E:\\JenkinsMIDEBLD\\workspace\\...;...")
add_test(NAME MYtest1 COMMAND cmake -S . -E env "tmp=${tmp}")
add_test(NAME MYtest2 COMMAND cmake -S . -E env tmp="${tmp}")
After running ctest I get:
1: Test command: /bin/cmake "-S" "." "-E" "env" "tmp=C:\Python27\Scripts;E:\JenkinsMIDEBLD\workspace\...;..."
2: Test command: /bin/cmake "-S" "." "-E" "env" "tmp="C:\Python27\Scripts" "E:\JenkinsMIDEBLD\workspace\..." "...""
The first test has proper ; passed to var, while the second one passes space separated list.
This is how cmake parses quoted arguments. An argument is either fully quoted or not quoted at all - partial quotes are interpreted as a literal ". So assumnig that:
set(var a;b;c)
The following:
var="$var"
Is not a quoted argument and " are taken literally! It expands the $var list into space separated list and the " stay, there is one " between = and a, and there is additional " on the end. The var="$var" is equal to:
var=\"a b c\"
^^ ^^ - the quotes stay!
^^^^^^^ ^ ^^^ - these are 3 arguments, the last one is `c"`
Without quotes is:
var=$var
is equal to (notice the missing quotes):
var=a c c
To quotes argument you have to quote it all, with first and last character of the element beeing ":
"var=$var"
will expand to:
"var=a;b;c"
You can make this work with the ENVIRONMENT test property, but there's a catch:
Semicolon separates different environment variables to set; you need to escape semicolons in your environment variable. For example instead of
set_tests_properties(mytest PROPERTIES
ENVIRONMENT "PYTHONPATH=foo;bar")
you need to use
set_tests_properties(mytest PROPERTIES
ENVIRONMENT "PYTHONPATH=foo\\;bar")
The fact that an environment variable may contain semicolons makes some transformation necessary: since ; is used to separate list elements you can simply use list(JOIN) to replace those with "\\;".
The following example works with PATH, not PYTHONPATH, since I don't have python installed:
CMakeLists.txt
cmake_minimum_required(VERSION 3.12.4) # required for list(JOIN)
project(TestProject)
# get a version of the PATH environment var that can be used in the ENVIRONMENT test property
set(_PATH $ENV{PATH})
list(JOIN _PATH "\\;" _PATH_CLEAN)
# just use a cmake script so we don't need to require any program able to retrieve environment vars
add_test(NAME Test1 COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_SOURCE_DIR}/test_script.cmake")
# we add another simpler var to test in the cmake script
set_tests_properties(Test1 PROPERTIES ENVIRONMENT "PATH=${_PATH_CLEAN};FOO=foo")
enable_testing()
test_script.cmake
message(STATUS "PATH=$ENV{PATH}")
if (NOT "$ENV{FOO}" STREQUAL "foo")
# the following command results in a non-0 exit code, if executed
message(FATAL_ERROR "FOO environment var should contain \"foo\" but contains \"$ENV{FOO}\"")
endif()

Problem using Qt4 with find_package of CMake, inside a macro

I have defined the following macro in CMake (version 3.10):
macro(configureQt4 requiredVersion selectedPackages)
message(STATUS "selectedPackages: ${selectedPackages}")
find_package(Qt4 ${requiredVersion} COMPONENTS ${selectedPackages} REQUIRED )
endmacro()
Now, when I tried to call the macro in the following way, I get an error:
set(SelectedQt4Packages "QtCore QtNetwork")
configureQt4( 4.8 ${SelectedQt4Packages})
The error reported is:
CMake Error at /usr/share/cmake-3.10/Modules/FindPackageHandleStandardArgs.cmake:137 (message):
Could NOT find Qt4 (missing: QT_QTCORE QTNETWORK_INCLUDE_DIR QT_QTCORE
QTNETWORK_LIBRARY) (found suitable version "4.8.7", minimum required is
"4.8")
If I call find_package() in the following way inside the macro, it works!
find_package(Qt4 ${requiredVersion} COMPONENTS QtCore QtNetwork REQUIRED )
But I need to use it by setting a variable as discussed earlier. How can I resolve this issue?
If you want to set a list variable in CMake, you can achieve this by excluding the quotes:
set(SelectedQt4Packages QtCore QtNetwork)
Using quotes like this "QtCore QtNetwork" simply creates a string with a space between the two component names, which is likely not what you intend.
Now, you can pass the SelectedQt4Packages list variable to your macro, but be sure to surround it with quotes (as suggested in this answer):
set(SelectedQt4Packages QtCore QtNetwork)
configureQt4( 4.8 "${SelectedQt4Packages}")
This is because CMake expects a list of components. That is, a string where each item is separated by a ;. If you instead do set(SelectedQt4Packages "QtCore;QtNetwork") and change the call to configureQt4( 4.8 "${SelectedQt4Packages}") (note the double quotes), it should work as expected.
Edit: A cleaner solution would be to simply convert the argument to a list inside the macro:
# Now we can set selectedPackages to either "QtCore QtNetwork" or "QtCore;QtNetwork", both will work.
macro(configureQt4 requiredVersion selectedPackages)
message(STATUS "selectedPackages: ${selectedPackages}")
string(REPLACE " " ";" _selectedQtPackages ${selectedPackages})
find_package(Qt4 ${requiredVersion} COMPONENTS ${_selectedQtPackages} REQUIRED )
endmacro()

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.