CMake and execute_process - cmake

Having a little trouble with cmake. I'm working in a weird mode where I need cmake to call an external cmake script to execute multiple commands as part of a test. I've boiled it down to this example.
test.cmake:
message("CMD: " ${CMD})
message("ARG: " ${ARG})
execute_process(COMMAND ${CMD} ${ARG}
RESULT_VARIABLE result
OUTPUT_VARIABLE output
)
message("RESULT: " ${result})
message("OUTPUT: " ${output})
mytest:
cmake -DCMD="cmake" -DARG="-E sleep 10" -V -P ./test.cmake
output:
CMD: cmake
ARG: -E sleep 10
CMake Error: The source directory "/Users/user/-E sleep 10" does not exist.
Specify --help for usage, or press the help button on the CMake GUI.
RESULT: 1
It works fine for all other CMD settings besides CMD=cmake. Any thoughts?
Passing ARG as "-E;sleep;10" works but my higher level project looks like:
project( test NONE)
cmake_minimum_required(VERSION 3.0)
enable_testing()
set(ARG "-E;sleep;-10")
# set(ARG "-E;sleep -10")
# set(ARG "-E sleep -10")
add_test( NAME test
COMMAND ${CMAKE_COMMAND} -DCMD=cmake -DARG=${ARG} -P test.cmake)
And this fails :/
Tony

To address this, you would have to use a different list separator. For example: ^^ would do it.
Note that updating a list with such a separator is also easy in newer version of CMake:
list(JOIN ${ARGS} "^^" ARG)
Calling test.cmake would the be done using:
cmake -DCMD="cmake" -DARG="-E^^sleep^^1" -V -P ./test.cmake
and the test.cmake would be like this:
message("ARG: ${ARG}")
string(REPLACE "^^" ";" ARGS ${ARG})
message("CMD: ${CMD}")
message("ARGS:")
foreach(argument IN LISTS ARGS)
message(STATUS " ${argument}")
endforeach()
execute_process(COMMAND ${CMD} ${ARGS}
RESULT_VARIABLE result
OUTPUT_VARIABLE output
)
message("RESULT: ${result}")
message("OUTPUT: ${output}")
the output looks like this:
ARG: -E^^sleep^^1
CMD: cmake
ARGS:
-- -E
-- sleep
-- 1
RESULT: 0
OUTPUT:

Related

Passing environment variable to the COMMAND in CMake execute_process

I have the following CMake snippet that runs COMMAND in WORKING_DIRECTORY. I tried different ways to pass the environment variable (MBEDTLS_INCLUDE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/../mbedtls/mbedtls/include) but without success.
The snippet that works (without env variable):
set(BUILD_CMD cargo build --features parsec-client/no-fs-permission-check)
set(WORKING_DIR "${CMAKE_CURRENT_SOURCE_DIR}/parsec_se_driver")
execute_process( COMMAND ${BUILD_CMD}
RESULT_VARIABLE CMD_ERROR
WORKING_DIRECTORY ${WORKING_DIR} )
if(NOT ${CMD_ERROR} MATCHES "0")
MESSAGE(SEND_ERROR "BUILD_CMD STATUS:" ${CMD_ERROR})
endif()
How can I pass the env variable to the execute_process?
If I write something like this:
execute_process( COMMAND MBEDTLS_INCLUDE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/../mbedtls/mbedtls/include cargo build --features parsec-client/no-fs-permission-check
RESULT_VARIABLE CMD_ERROR
WORKING_DIRECTORY ${WORKING_DIR} )
or taking different parts to variables, or adding quotes, I get:
BUILD_CMD STATUS:No such file or directory
As recommended in the CMake mailing list here, your solution using set(ENV ...) is perfectly valid:
set(ENV{MBEDTLS_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../mbedtls/mbedtls/include)
execute_process(
COMMAND ${BUILD_CMD}
RESULT_VARIABLE CMD_ERROR
WORKING_DIRECTORY ${WORKING_DIR}
)
You could also use CMake's command line utility to run the command in a modified environment using cmake -E env:
execute_process(
COMMAND ${CMAKE_COMMAND} -E env
MBEDTLS_INCLUDE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../mbedtls/mbedtls/include" ${BUILD_CMD}
RESULT_VARIABLE CMD_ERROR
WORKING_DIRECTORY ${WORKING_DIR}
)

How to append command to add_custom_target in CMake

Suppose I have a custom target in CMake for unit tests like the below
add_custom_target(
test
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ATest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/BTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/CTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/DTest)
but I want to add an additional test to the target based on whether an external dependency is found. Currently, I did it with
if(EXTERNAL_FOUND)
add_custom_target(
test
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ATest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/BTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/CTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/DTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ETest)
else()
add_custom_target(
test
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ATest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/BTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/CTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/DTest)
endif()
This is not very elegant and it quickly becomes unmanageable when there are multiple conditions. Is there something like append to custom target so we can write the below instead?
add_custom_target(
test
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ATest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/BTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/CTest
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/DTest)
if(EXTERNAL_FOUND)
# I can't seem to find something like this
append_custom_target(test COMMAND ${CMAKE_CURRENT_BINARY_DIR}/ETest)
else()
Or is there a better way to do this?
You can use add_custom_command and use it as dependency to your target. With the custom command you can APPEND commands with same OUTPUT:
add_custom_target(
test
DEPENDS test-cmd
)
add_custom_command(
OUTPUT test-cmd
COMMAND ${CMAKE_COMMAND} -E echo "ATest"
COMMAND ${CMAKE_COMMAND} -E echo "BTest"
COMMAND ${CMAKE_COMMAND} -E echo "CTest"
COMMAND ${CMAKE_COMMAND} -E echo "DTest"
)
if(EXTERNAL_FOUND)
add_custom_command(
OUTPUT test-cmd APPEND
COMMAND ${CMAKE_COMMAND} -E echo "ETest"
)
endif()
# test-cmd is not actually generated so set it to symbolic
set_source_files_properties(test-cmd PROPERTIES SYMBOLIC "true")
See SYMBOLIC for the artifical source file property.

Why variables are not accessed inside script in CMake?

I have a script called "install_copy_dlls.cmake", which is called to execute from top level cmake file as shown below.
INSTALL(SCRIPT "install_copy_dlls.cmake")
And, I have a variable named "USE_OSG_STATIC" which is set to ON if I use Statically compiled OpenSceneGraph and set of OFF if I use Dynamically compiled OpenSceneGraph.
I need to use this variable inside install_copy_dlls.cmake script.
so, here is how install_copy_dlls.cmake file should look like.
copy other required dlls...
if(NOT USE_OSG_STATIC) //if dynamic OSG
copy osg dlls
here, I try to use "message" to print USE_OSG_STATIC variable and it doesn't print anything.
Can anyone explain me why I can not use variables in Script file?
Can anyone explain me why I can not use variables in Script file?
install(SCRIPT ...) command works like cmake -P. So there is no variables forwarded
from parent script to child (until you explicitly define one):
> cat run.cmake
if(A)
message("A: ${A}")
else()
message("A is empty")
endif()
> cmake -P run.cmake
A is empty
> cmake -DA=15 -P run.cmake
A: 15
Using CMakeLists.txt:
> cat CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
set(A 43)
execute_process(COMMAND ${CMAKE_COMMAND} -P run.cmake)
> cmake -H. -B_builds
A is empty
Forward to child process:
> cat CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
set(A 43)
execute_process(COMMAND ${CMAKE_COMMAND} -DA=${A} -P run.cmake)
> cmake -H. -B_builds
A: 43
Solution #1 (forwarding)
Using install(CODE ...) command you can define variable for run.cmake script:
> cat CMakeLists.txt
install(
CODE
"execute_process(
COMMAND
${CMAKE_COMMAND}
-DA=${A}
-P
${CMAKE_CURRENT_LIST_DIR}/run.cmake
)"
)
> cmake -H. -B_builds -DA=554
> cmake --build _builds --target install
Install the project...
-- Install configuration: ""
A: 554
Solution #2 (configuring)
You can configure install script using configure_file command:
> cat run.cmake.in
set(A #A#)
if(A)
message("A: ${A}")
else()
message("A is empty")
endif()
> cat CMakeLists.txt
set(custom_script ${PROJECT_BINARY_DIR}/custom_install_scripts/run.cmake)
configure_file(run.cmake.in ${custom_script} #ONLY)
install(SCRIPT ${custom_script})
> cmake -H. -B_builds -DA=42
> cmake --build _builds --target install
Install the project...
-- Install configuration: ""
A: 42
I found a simpler solution: set the variable in a preceding install() call:
install(CODE "set(A \"${A}\")")
install(SCRIPT cmake/custom_script.cmake)
This ends up rendered into the cmake_install script roughly as:
set(A "Avalue")
include(/path/to/cmake/custom_script.cmake)
which is exactly what you need.

CMake file in script mode inheriting variables

How can I call to a cmake file in script mode (-P) from other cmake file, so this "cmake child" knows all variable of its parent? Because, if I have a lot of variables the child needs, I have to write many -D options, and I want to avoid it.
Example:
// CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
set(teststr "Hello World!")
add_custom_command(test
${CMAKE_COMMAND} -Dteststr=${teststr} -P test.cmake
)
// test.cmake
message("${teststr}")
$ cmake .
$ make test
Hello world!
Built target test
Works fine!. But, without "-Dteststr":
// CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
set(teststr "Hello World!")
add_custom_command(test
${CMAKE_COMMAND} -P test.cmake
)
// test.cmake
message("${teststr}")
$ cmake .
$ make test
Built target test
Of course, without -D option, the "teststr" variable, in test.cmake, is unset, and thus, the output is empty.
Any option to call test.cmake in "heritage mode", or something like that?
You can pass arguments to a script with cmake -P.
If you call:
cmake -P <script-file> <arg3> <arg4> <arg5> ...
then the variables CMAKE_ARGC, CMAKE_ARGV0, CMAKE_ARGV1, ... will be available for the script.
See documentation for CMAKE_ARGC and CMAKE_ARGV0.
The other way is to define variables, just like with the non-script cmake command. However there's one thing to be aware of: you need to define the variables before -P:
cmake -DVAR=VALUE -DFOO=BAR -P <script-file> <arg5> <arg6> ...
Now in the cmake VAR and FOO will be available.
Also, note that the numbering of the args after the script-file will be shifted accordingly.
There's no particularly easy way to do this that I know of.
You could write all the current variables in the parent CMakeLists.txt to a separate file and then include this in your test.cmake:
# CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
set(teststr "Hello World!")
set(CacheForScript ${CMAKE_BINARY_DIR}/CMakeCacheForScript.cmake)
file(WRITE ${CacheForScript} "")
get_cmake_property(Vars VARIABLES)
foreach(Var ${Vars})
if(${Var})
string(REPLACE "\\" "\\\\" ${Var} ${${Var}})
endif()
file(APPEND ${CacheForScript} "set(${Var} \"${${Var}}\")\n")
endforeach()
add_custom_target(test ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/test.cmake)
# test.cmake
include(${CMAKE_BINARY_DIR}/CMakeCacheForScript.cmake)
message("${teststr}")

How do I initialize a CMake variable with the result of a shell command

Is there a way to set a variable in a CMake script to the output of a shell command?
Something like SET(FOO COMMAND "echo bar") would come to mind
You want the execute_process command.
In your case, on Windows:
execute_process(COMMAND CMD /c echo bar OUTPUT_VARIABLE FOO)
or on Linux, simply:
execute_process(COMMAND echo bar OUTPUT_VARIABLE FOO)
In this particular case, CMake offers a cross-platform solution. CMake can itself be used to run commands that can be used on all systems, one of which is echo. To do this, CMake should be passed the command line arg -E. For the full list of such commands, run cmake -E help
Inside a CMake script, the CMake executable is referred to by ${CMAKE_COMMAND}, so the script needs to do:
execute_process(COMMAND ${CMAKE_COMMAND} -E echo bar OUTPUT_VARIABLE FOO)