Why variables are not accessed inside script in CMake? - 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.

Related

how can I load environment file (.env) when run cmake command?

is there any option can allow me to load key=value pairs from a dotenv file? I knew I can do -E env key=value But I have lots of values and I want to load them from the dotenv file, since other tools can load environment variables from it. I do not want to do bash tricks because I would like it to be as simple as possible and cross-platform.
There's no built-in support for dotenv files, but here's a full implementation in pure CMake. For your CMakeLists.txt, here's an example:
cmake_minimum_required(VERSION 3.21)
project(env-test NONE)
add_custom_target(
example
COMMAND
${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/env.cmake
${CMAKE_CURRENT_SOURCE_DIR}/.env # dotenv
env # command
)
Here's the contents of the .env file:
foo=bar
bears=beets
battlestar=galactica
Here's a demo of this working:
$ cmake -G Ninja -S . -B build
$ cmake --build build --target example
...
foo=bar
bears=beets
battlestar=galactica
and here's the (complicated) env.cmake script that implements this:
cmake_minimum_required(VERSION 3.21)
if (CMAKE_ARGC LESS 5)
message(FATAL_ERROR "Usage: cmake -P env.cmake <dotenv> [command...]")
endif ()
set(dotenv "${CMAKE_ARGV3}")
if (NOT EXISTS "${dotenv}")
message(FATAL_ERROR "Dot-env file not found: ${dotenv}")
endif ()
set(command "")
math(EXPR argc "${CMAKE_ARGC} - 1")
foreach (i RANGE 4 ${argc})
list(APPEND command "${CMAKE_ARGV${i}}")
endforeach ()
file(STRINGS "${dotenv}" entries)
foreach (entry IN LISTS entries)
if (entry MATCHES "^([^=]+)=(.*)$")
set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}")
else ()
message(FATAL_ERROR "Malformed dotenv entry:\n${entry}")
endif ()
endforeach ()
execute_process(COMMAND ${command} COMMAND_ERROR_IS_FATAL ANY)

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 compare files in CMake

Is there a way to compare files using cmake?
I've checked all parameters from https://cmake.org/cmake/help/latest/command/file.html
cmake executable has a tool mode, when it performs some useful actions instead of project's configuration. And compare_files is one of the commands for that mode.
For get features of the CMake command line tool mode in the CMakeLists.txt, use execute_process command:
execute_process( COMMAND ${CMAKE_COMMAND} -E compare_files <file1> <file2>
RESULT_VARIABLE compare_result
)
if( compare_result EQUAL 0)
message("The files are identical.")
elseif( compare_result EQUAL 1)
message("The files are different.")
else()
message("Error while comparing the files.")
endif()

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}")

I can not get environment at custom target shell

I can not get environment at custom target shell.
CMakeList.txt
set( ENV{TEST_VAR} "Hello" )
add_custom_target( test
COMMAND ./test.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} )
test.sh
echo test:${TEST_VAR}
when try to "make test", shell can't get ${TEST_VAR}.
Thank you.
You have to use a trick because environment variables SET in the CMakeLists.txt only take effect for cmake itself, so you cannot use this method to set an environment variable that a custom command might need:
test.cmake
set( ENV{TEST_VAR} "Hello" )
execute_process(
COMMAND ./test.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} )
CMakeLists.txt
add_custom_target( test
COMMAND ${CMAKE_COMMAND} -P test.cmake )