如何捕获 CMake 命令行参数?

Posted

技术标签:

【中文标题】如何捕获 CMake 命令行参数?【英文标题】:How to capture CMake command line arguments? 【发布时间】:2012-04-29 15:55:36 【问题描述】:

我想在我生成的脚本中记录传递给 cmake 的参数。例如,“my-config.in”将由cmake处理,它的定义如下:

config="@CMAKE_ARGS@"

cmake 之后,my-config 将包含如下一行:

config="-DLINUX -DUSE_FOO=y -DCMAKE_INSTALL_PREFIX=/usr"

我尝试了CMAKE_ARGSCMAKE_OPTIONS,但失败了。没有文件提到这一点。 :-(

【问题讨论】:

相关问题:gitlab.kitware.com/cmake/cmake/-/issues/19622 【参考方案1】:

我不知道提供此信息的任何变量,但您可以自己生成它(有一些附带条件)。

传递给 CMake 的任何 -D 参数都将添加到构建目录中的缓存文件 CMakeCache.txt 中,并在后续调用期间重新应用,而无需在命令行上再次指定。

所以在你的例子中,如果你首先执行 CMake

cmake ../.. -DCMAKE_INSTALL_PREFIX:PATH=/usr

然后你会发现后面运行起来很简单

cmake .

仍会将CMAKE_INSTALL_PREFIX 设置为/usr

如果您在CMAKE_ARGS 中查找的是每次调用 CMake 在命令行上定义的完整变量列表,那么以下内容应该可以解决问题:

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

这有点脆弱,因为它取决于通过命令行设置的每个变量都有短语“No help, variable specified on the command line”。指定为其HELPSTRING 属性。如果 CMake 更改此默认 HELPSTRING,您必须相应地更新 if 语句。

如果这不是您希望 CMAKE_ARGS 显示的内容,而只是当前执行的参数,那么我认为没有办法做到这一点,而不是破解 CMake 的源代码!但是,我预计这不是您想要的,因为每次都有效地重新应用所有先前的命令行参数。

【讨论】:

它工作了一段时间,但后来停止工作。我知道这看起来很奇怪,但我确实复制/粘贴了上面的脚本并且它有效。然后它停止工作,对脚本没有任何更改。也许我快疯了,但这让我想起了 bat 文件及其不一致的行为...... 我花了一段时间才弄清楚原因。对于未来的 SOers,这不适用于 CMake 变量。 如果您只想将 cmd 参数传递给目标,请使用:***.com/a/2826069/12750911【参考方案2】:

存储 CMake 命令行参数的一种方法是使用一个名为 ~/bin/cmake (***1) 的包装脚本,它会做两件事:

创建存储命令行参数的./cmake_call.sh 使用命令行参数调用真正的cmake 可执行文件

~/bin/cmake#代码如下所示

#!/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://***.com/a/13864829
if [ -z $SUDO_USER+x ]; then
    # var SUDO_USER is unset
    user=$USER
else
    user=$SUDO_USER
fi


#http://***.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://***.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 "$@"

用法:

mkdir build
cd    build
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$(pwd)/install ..

这将创建具有以下内容的cmake_call.sh

#!/usr/bin/env bash


#http://***.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"

最后第三行存储 cmake 参数。 您现在可以通过简单地调用来重新调用您使用的确切命令行:

./cmake_call.sh

脚注:

(***1) ~/bin/cmake 通常在 PATH 中,因为 ~/.profile。第一次创建 ~/bin/cmake 时,可能需要注销并重新登录,以便 .profile 看到 ~/bin

【讨论】:

【参考方案3】:

实现相同目标的一种非常 Linux 特定的方式:

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

【讨论】:

以上是关于如何捕获 CMake 命令行参数?的主要内容,如果未能解决你的问题,请参考以下文章

Ninja 构建中的 CMake 命令行参数

如何在 MSVC 2019 中的 CMake 管理的 C++ 项目中将命令行启动参数添加到发布目标?

CMAKE 使用环境变量而不将它们作为命令行参数传递?

有没有办法在 CMake 命令行参数或 conanfile.py 中指定 VCToolsVersion?

如何不使用命令行参数编译 CMakeLists.txt 的一部分?

如何阻止我的命令行参数在 Maven 插件中被转义?