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

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)

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

CMake and execute_process

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:

CMake not redirecting stderr with execute_process

I'm trying to redirect stdout and stderr to the same file using CMake. I'm using the execute_process option in CMake with the ERROR_FILE and OUTPUT_FILE option specified.
I'm successfully capturing the output, but the error is not there. What am I doing wrong?
File CMakeLists.txt
add_test(NAME test${ID}
COMMAND ${CMAKE_COMMAND}
-DEXE=../examples/test${exampleID}
-DID=${ID}
-DARGS=${args}
-P ${CMAKE_CURRENT_SOURCE_DIR}/Tester.cmake
)
File Tester.cmake
separate_arguments( ARGS )
# Run the test
execute_process(
COMMAND "${EXE}" ${ARGS}
ERROR_FILE test${ID}.out
OUTPUT_FILE test${ID}.out
)
Specifying the same file for both OUTPUT_FILE and ERROR_FILE has only recently been added in CMake 3.3. See release notes.
As a work-around for earlier versions, use the options OUTPUT_VARIABLE and ERROR_VARIABLE with the same variable and then write the contents of the variable to the file, e.g.:
execute_process(
COMMAND "${EXE}" ${ARGS}
ERROR_VARIABLE _testOut
OUTPUT_VARIABLE _testOut
)
file (WRITE "test${ID}.out" "${_testOut}")

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