How to capture CMake command line arguments? - cmake

I want to record the arguments passed to cmake in my generated scripts. E.g., "my-config.in" will be processed by cmake, it has definition like this:
config="#CMAKE_ARGS#"
After cmake, my-config will contain a line something like this:
config="-DLINUX -DUSE_FOO=y -DCMAKE_INSTALL_PREFIX=/usr"
I tried CMAKE_ARGS, CMAKE_OPTIONS, but failed. No documents mention this. :-(

I don't know of any variable which provides this information, but you can generate it yourself (with a few provisos).
Any -D arguments passed to CMake are added to the cache file CMakeCache.txt in the build directory and are reapplied during subsequent invocations without having to be specified on the command line again.
So in your example, if you first execute CMake as
cmake ../.. -DCMAKE_INSTALL_PREFIX:PATH=/usr
then you will find that subsequently running simply
cmake .
will still have CMAKE_INSTALL_PREFIX set to /usr
If what you're looking for from CMAKE_ARGS is the full list of variables defined on the command line from every invocation of CMake then the following should do the trick:
get_cmake_property(CACHE_VARS CACHE_VARIABLES)
foreach(CACHE_VAR ${CACHE_VARS})
get_property(CACHE_VAR_HELPSTRING CACHE ${CACHE_VAR} PROPERTY HELPSTRING)
if(CACHE_VAR_HELPSTRING STREQUAL "No help, variable specified on the command line.")
get_property(CACHE_VAR_TYPE CACHE ${CACHE_VAR} PROPERTY TYPE)
if(CACHE_VAR_TYPE STREQUAL "UNINITIALIZED")
set(CACHE_VAR_TYPE)
else()
set(CACHE_VAR_TYPE :${CACHE_VAR_TYPE})
endif()
set(CMAKE_ARGS "${CMAKE_ARGS} -D${CACHE_VAR}${CACHE_VAR_TYPE}=\"${${CACHE_VAR}}\"")
endif()
endforeach()
message("CMAKE_ARGS: ${CMAKE_ARGS}")
This is a bit fragile as it depends on the fact that each variable which has been set via the command line has the phrase "No help, variable specified on the command line." specified as its HELPSTRING property. If CMake changes this default HELPSTRING, you'd have to update the if statement accordingly.
If this isn't what you want CMAKE_ARGS to show, but instead only the arguments from the current execution, then I don't think there's a way to do that short of hacking CMake's source code! However, I expect this isn't what you want since all the previous command line arguments are effectively re-applied every time.

One way to store CMake command line arguments, is to have a wrapper script called ~/bin/cmake (***1) , which does 2 things:
create ./cmake_call.sh that stores the command line arguments
call the real cmake executable with the command line arguments
~/bin/cmake # code is shown below
#!/usr/bin/env bash
#
# Place this file into this location: ~/bin/cmake
# (with executable rights)
#
# This is a wrapper for cmake!
# * It calls cmake -- see last line of the script
# It also:
# * Creates a file cmake_call.sh in the current directory (build-directory)
# which stores the cmake-call with all it's cmake-flags etc.
# (It also stores successive calls to cmake, so that you have a trace of all your cmake calls)
#
# You can simply reinvoke the last cmake commandline with: ./cmake_call.sh !!!!!!!!!!
#
# cmake_call.sh is not created
# when cmake is called without any flags,
# or when it is called with flags such as --help, -E, -P, etc. (refer to NON_STORE_ARGUMENTS -- you might need to modify it to suit your needs)
SCRIPT_PATH=$(readlink -f "$BASH_SOURCE")
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
#http://stackoverflow.com/a/13864829
if [ -z ${SUDO_USER+x} ]; then
# var SUDO_USER is unset
user=$USER
else
user=$SUDO_USER
fi
#http://stackoverflow.com/a/34621068
path_append () { path_remove $1 $2; export $1="${!1}:$2"; }
path_prepend() { path_remove $1 $2; export $1="$2:${!1}"; }
path_remove () { export $1="`echo -n ${!1} | awk -v RS=: -v ORS=: '$1 != "'$2'"' | sed 's/:$//'`"; }
path_remove PATH ~/bin # when calling cmake (at the bottom of this script), do not invoke this script again!
# when called with no arguments, don't create cmake_call.sh
if [[ -z "$#" ]]; then
cmake "$#"
exit
fi
# variable NON_STORE_ARGUMENTS stores flags which, if any are present, cause cmake_call.sh to NOT be created
read -r -d '' NON_STORE_ARGUMENTS <<'EOF'
-E
--build
#-N
-P
--graphviz
--system-information
--debug-trycompile
#--debug-output
--help
-help
-usage
-h
-H
--version
-version
/V
--help-full
--help-manual
--help-manual-list
--help-command
--help-command-list
--help-commands
--help-module
--help-module-list
--help-modules
--help-policy
--help-policy-list
--help-policies
--help-property
--help-property-list
--help-properties
--help-variable
--help-variable-list
--help-variables
EOF
NON_STORE_ARGUMENTS=$(echo "$NON_STORE_ARGUMENTS" | head -c -1 `# remove last newline` | sed "s/^/^/g" `#begin every line with ^` | tr '\n' '|')
#echo "$NON_STORE_ARGUMENTS" ## for debug purposes
## store all the args
ARGS_STR=
for arg in "$#"; do
if cat <<< "$arg" | grep -E -- "$NON_STORE_ARGUMENTS" &> /dev/null; then # don't use echo "$arg" ....
# since echo "-E" does not do what you want here,
# but cat <<< "-E" does what you want (print minus E)
# do not create cmake_call.sh
cmake "$#"
exit
fi
# concatenate to ARGS_STR
ARGS_STR="${ARGS_STR}$(echo -n " \"$arg\"" | sed "s,\($(pwd)\)\(\([/ \t,:;'\"].*\)\?\)$,\$(pwd)\2,g")"
# replace $(pwd) followed by
# / or
# whitespace or
# , or
# : or
# ; or
# ' or
# "
# or nothing
# with \$(pwd)
done
if [[ ! -e $(pwd)/cmake_call.sh ]]; then
echo "#!/usr/bin/env bash" > $(pwd)/cmake_call.sh
# escaping:
# note in the HEREDOC below, \\ means \ in the output!!
# \$ means $ in the output!!
# \` means ` in the output!!
cat <<EOF >> $(pwd)/cmake_call.sh
#http://stackoverflow.com/a/34621068
path_remove () { export \$1="\`echo -n \${!1} | awk -v RS=: -v ORS=: '\$1 != "'\$2'"' | sed 's/:\$//'\`"; }
path_remove PATH ~/bin # when calling cmake (at the bottom of this script), do not invoke ~/bin/cmake but real cmake!
EOF
else
# remove bottom 2 lines from cmake_call.sh
sed -i '$ d' $(pwd)/cmake_call.sh
sed -i '$ d' $(pwd)/cmake_call.sh
fi
echo "ARGS='${ARGS_STR}'" >> $(pwd)/cmake_call.sh
echo "echo cmake \"\$ARGS\"" >> $(pwd)/cmake_call.sh
echo "eval cmake \"\$ARGS\"" >> $(pwd)/cmake_call.sh
#echo "eval which cmake" >> $(pwd)/cmake_call.sh
chmod +x $(pwd)/cmake_call.sh
chown $user: $(pwd)/cmake_call.sh
cmake "$#"
Usage:
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$(pwd)/install ..
This will create cmake_call.sh with the following content:
#!/usr/bin/env bash
#http://stackoverflow.com/a/34621068
path_remove () { export $1="`echo -n ${!1} | awk -v RS=: -v ORS=: '$1 != "'$2'"' | sed 's/:$//'`"; }
path_remove PATH ~/bin # when calling cmake (at the bottom of this script), do not invoke ~/bin/cmake but real cmake!
ARGS=' "-DCMAKE_BUILD_TYPE=Debug" "-DCMAKE_INSTALL_PREFIX=$(pwd)/install" ".."'
echo cmake "$ARGS"
eval cmake "$ARGS"
The 3rd last line stores the cmake arguments.
You can now reinvoke the exact command-line that you used by simply calling:
./cmake_call.sh
Footnotes:
(***1) ~/bin/cmake is usually in the PATH because of ~/.profile. When creating ~/bin/cmake the very 1st time, it might be necessary to log out and back in, so that .profile sees ~/bin.

A very Linux specific way of achieving the same objective:
if(${CMAKE_SYSTEM_NAME} STREQUAL Linux)
file(STRINGS /proc/self/status _cmake_process_status)
# Grab the PID of the parent process
string(REGEX MATCH "PPid:[ \t]*([0-9]*)" _ ${_cmake_process_status})
# Grab the absolute path of the parent process
file(READ_SYMLINK /proc/${CMAKE_MATCH_1}/exe _cmake_parent_process_path)
# Compute CMake arguments only if CMake was not invoked by the native build
# system, to avoid dropping user specified options on re-triggers.
if(NOT ${_cmake_parent_process_path} STREQUAL ${CMAKE_MAKE_PROGRAM})
execute_process(COMMAND bash -c "tr '\\0' ' ' < /proc/$PPID/cmdline"
OUTPUT_VARIABLE _cmake_args)
string(STRIP "${_cmake_args}" _cmake_args)
set(CMAKE_ARGS "${_cmake_args}"
CACHE STRING "CMake command line args (set by end user)" FORCE)
endif()
message(STATUS "User Specified CMake Arguments: ${CMAKE_ARGS}")
endif()

Related

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: unescape whitespace when echoing to a file

As part of our build process we automatically run unit tests through valgrind during the actual build (ie: it's not a separate target such as make test)
We create a sentinel file when the tests pass, so that subsequent build won't rerun the tests if not necessary.
We also save the command line and test output to a file.
Here I have built the valgrind command line:
set(VALGRIND_BIN "valgrind")
set(VALGRIND_OPTS "--leak-check=full --track-origins=yes")
set(VALGRIND_CMD "${VALGRIND_BIN} ${VALGRIND_OPTS}")
separate_arguments(VALGRIND_CMD)
These are the "passed" sentinal file, and the test output file.
set(OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}.output)
set(PASSED_FILE ${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}.passed)
Here I add a custom_command which works in the following way:
It echos the command line and saves it to the output file
It runs the test through valgrind, saving all output to the output file
If the test doesn't pass it will cat the output file and the command fails
If the test passes it will touch the passed sentinel file.
Here is the cmake source:
add_custom_command(
OUTPUT
${PASSED_FILE}
COMMAND
echo "\"${VALGRIND_BIN} ${VALGRIND_OPTS} $<TARGET_FILE:${TEST_NAME}>\"" > ${OUTPUT_FILE}
COMMAND
${VALGRIND_CMD} $<TARGET_FILE:${TEST_NAME}> >> ${OUTPUT_FILE} 2>&1 || (cat ${OUTPUT_FILE} && false)
COMMAND
${CMAKE_COMMAND} -E touch ${PASSED_FILE}
COMMENT
"Running ${ARG_NAME} tests"
DEPENDS
${TEST_NAME}
USES_TERMINAL
)
Unfortunately cmake is escaping all the whitespace in my echo of the test command line, so that the first line in the output file looks like this:
valgrind\ --leak-check=full\ --track-origins=yes\ /home/steve/src/test\
I have proven to myself the escapes aren't in the variables, as if I output a message they aren't in there.
message(STATUS "\"${VALGRIND_BIN} ${VALGRIND_OPTS} $<TARGET_FILE:${TEST_NAME}>\"")
The resulting output:
-- "valgrind --leak-check=full --track-origins=yes $<TARGET_FILE:test>"
Question:
How can I unescape the whitespace when echoing to a file?
That is, how can I have the line not be this:
valgrind\ --leak-check=full\ --track-origins=yes\ /home/steve/src/test\
but instead be this:
valgrind --leak-check=full --track-origins=yes /home/steve/src/test
You can put everything into a list, which will be expanded and the spaces will not be escaped.
Because CMake will be escaping spaces if it believes the string to be a single argument. Giving it as a list will take every element as a separate argument:
list(APPEND VALGRIND_CMD "$<TARGET_FILE:${TEST_NAME}>")
add_custom_command(
OUTPUT
${PASSED_FILE}
COMMAND
${CMAKE_COMMAND} -E echo \"${VALGRIND_CMD}\" > ${OUTPUT_FILE}
COMMAND
${VALGRIND_CMD} >> ${OUTPUT_FILE} 2>&1 || (cat ${OUTPUT_FILE} && false)
COMMAND
${CMAKE_COMMAND} -E touch ${PASSED_FILE}
COMMENT
"Running ${ARG_NAME} tests"
USES_TERMINAL
)
References
cmake: How to include literal double-quote in custom command?
cmake: when to quote variables?
As pointed by #MuertoExcobito, option VERBATIM cares about properly escaping parameters, no needs in additional double quotes escaped manually:
COMMAND
echo "${VALGRIND_BIN} ${VALGRIND_OPTS} $<TARGET_FILE:${TEST_NAME}>" > ${OUTPUT_FILE}
VERBATIM
(Outer double quotes are needed for CMake do not separate echo parameters).

Execute git describe in custom_target

I would like to write output of git describe as a string to a file, so I can embed the information in my binary (C++). This has to work across platforms.
The best I can yet come up with was:
add_custom_target( SubmarineGitVersion
COMMAND cmd /c "${CMAKE_EXECUTABLE}" echo czstring GIT_VERSION = STRINGIFY\( > "${CMAKE_CURRENT_BINARY_DIR}/GitVersion.hpp"
COMMAND cmd /c "${GIT_EXECUTABLE}" describe --tags --always >> "${CMAKE_CURRENT_BINARY_DIR}/GitVersion.hpp"
COMMAND cmd /c "${CMAKE_EXECUTABLE}" echo \) >> "${CMAKE_CURRENT_BINARY_DIR}/GitVersion.hpp"
)
This roughly works on Windows (is missing a ; at the end):
czstring GIT_VERSION = STRINGIFY(
tag-343434
)
Is there any better/more cross-platform way of doing this?
Common way for create "version" files is using configure_file command. Such way file will be created at configure stage:
GitVersion.hpp.in:
czstring GIT_VERSION = STRINGIFY(
${GIT_REPO_VERSION}
)
CMakeLists.txt:
# Store version into variable
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always
OUTPUT_VARIABLE GIT_REPO_VERSION)
# The variable will be used when file is configured
configure_file("GitVersion.hpp.in" "GitVersion.hpp")
If you want to create version file on build stage, move above cmake commands into some file, and execute this file in CMake script mode:
generate_version.cmake:
# Git executable is extracted from parameters.
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always
OUTPUT_VARIABLE GIT_REPO_VERSION)
# Input and output files are extracted from parameters.
configure_file(${INPUT_FILE} ${OUTPUT_FILE})
CMakeLists.txt:
add_custom_target( SubmarineGitVersion
COMMAND ${CMAKE_COMMAND}
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/GitVersion.hpp.in
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/GitVersion.hpp
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
)

Using awk to extract a specific text in a Makefile

I have a config file with this format
foo=bar
fie=boo
..
..
and there is a Makefile, I want to extract a line of config file that have a string 'disk_size' then extract value that is assigned to the variable
this is the line I've used in Makefile
fallocate -l $(shell awk -F= '/disk_size/ { print $2 }' $(conf)) $#
but I receive this error, (the whole line was extracted.)
fallocate -l disk_size=268435456 disk.img
fallocate: invalid length value specified
the awk command work in terminal but it doesn't work in Makefile, why?
tnx
You probably just need to escape the $:
fallocate -l $(shell awk -F= '/disk_size/ { print $$2 }' $(conf)) $#
Make is trying to use the variable $2 rather than passing the string $2 to awk.
If your config file is really this format:
foo = bar
disk_size = 1234
You can directly include it in the Makefile:
# Include configuration file
include $(conf)
target:
fallocate -l $(disk_size)
You can also use the - operator to ignore error of the include command and assign default value if there is no config file.
# Include configuration file
-include $(conf)
# Set default size
disk_size ?= 5678
target:
fallocate -l $(disk_size)

script to run a certain program with input from a given directory

So I need to run a bunch of (maven) tests with testfiles being supplied as an argument to a maven task.
Something like this:
mvn clean test -Dtest=<filename>
And the test files are usually organized into different directories. So I'm trying to write a script which would execute the above 'command' and automatically feed the name of all files in a given dir to the -Dtest.
So I started out with a shellscript called 'run_test':
#!/bin/sh
if test $# -lt 2; then
echo "$0: insufficient arguments on the command line." >&1
echo "usage: $0 run_test dirctory" >&1
exit 1
fi
for file in allFiles <<<<<<< what should I put here? Can I somehow iterate thru the list of all files' name in the given directory put the file name here?
do mvn clean test -Dtest= $file
exit $?
The part where I got stuck is how to get a list of filenames.
Thanks,
Assuming $1 contains the directory name (validation of the user input is a separate issue), then
for file in $1/*
do
[[ -f $file ]] && mvn clean test -Dtest=$file
done
will run the comand on all files. If you want to recurse into subdirectories then you need to use the find command
for file in $(find $1 -type f)
do
etc...
done
#! /bin/sh
# Set IFS to newline to minimise problems with whitespace in file/directory
# names. If we also need to deal with newlines, we will need to use
# find -print0 | xargs -0 instead of a for loop.
IFS="
"
if ! [[ -d "${1}" ]]; then
echo "Please supply a directory name" > &2
exit 1
else
# We use find rather than glob expansion in case there are nested directories.
# We sort the filenames so that we execute the tests in a predictable order.
for pathname in $(find "${1}" -type f | LC_ALL=C sort) do
mvn clean test -Dtest="${pathname}" || break
done
fi
# exit $? would be superfluous (it is the default)