How to capture CMake command line arguments?

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

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.

Tags:

Cmake