CMake中执行shell命令之execute_process、add_custom_target和add_custom_command

背景

以下情况可能需要在CMake中执行shell脚本:

  • cmake未提供的功能而实际构建中又需要时,如获取Linux发行版本
  • 项目构建时需要执行脚本才能完成,如boost构建过程

有的需要shell脚本的返回值,而有的不需要,这个关系不大。本文主要关注的是在cmake中执行shell脚本的方法。

主要涉及三个命令:execute_process、add_custom_target和add_custom_command。

execute_process

通过execute_process方法可以执行多个子进程。

原型如下:

execute_process(COMMAND <cmd1> [<arguments>]
                [COMMAND <cmd2> [<arguments>]]...
                [WORKING_DIRECTORY <directory>]
                [TIMEOUT <seconds>]
                [RESULT_VARIABLE <variable>]
                [RESULTS_VARIABLE <variable>]
                [OUTPUT_VARIABLE <variable>]
                [ERROR_VARIABLE <variable>]
                [INPUT_FILE <file>]
                [OUTPUT_FILE <file>]
                [ERROR_FILE <file>]
                [OUTPUT_QUIET]
                [ERROR_QUIET]
                [COMMAND_ECHO <where>]
                [OUTPUT_STRIP_TRAILING_WHITESPACE]
                [ERROR_STRIP_TRAILING_WHITESPACE]
                [ENCODING <name>]
                [ECHO_OUTPUT_VARIABLE]
                [ECHO_ERROR_VARIABLE]
                [COMMAND_ERROR_IS_FATAL <ANY|LAST>])

命令COMMAND会并行执行,每个子进程的标准输出映射到下一个进程的标准输入上,所有进程共用standard error管道。

各选项说明如下:

  • COMMAND: 子进程的命令行,直接使用操作系统api执行。可以提供多个command,它们会并行执行。如果需要多个命令顺序执行,可以调用execute_process多次
  • WORKING_DIRECTORY:在该目录下执行COMMAND命令
  • TIMEOUT:超时时间,过了这个时间,所有子进程会被终止,RESULT_VARIABLE会被设置为“timeout”
  • RESULT_VARIABLE:最后一个子进程的返回值(正常是0,异常是其他整数),或者描述发生错误的字符串
  • RESULTS_VARIABLE:对应于每个子进程的返回值,使用分号分割的列表
  • OUTPUT_VARIABLE:对应于standard output的内容
  • ERROR_VARIABLE:对应于standard error的内容
  • INPUT_FILE:第一个子进程的standard input
  • OUTPUT_FILE:最后一个子进程的standard output
  • ERROR_FILE:所有子进程的standard error
  • OUTPUT_QUIET/ERROR_QUIET:忽略standard output 和 standard error
  • COMMAND_ECHO:重显命令到指定的标准设备,如STDERR、STDOUT、NONE。使用CMAKE_EXECUTE_PROCESS_COMMAND_ECHO变量来修改它的行为
  • OUTPUT_STRIP_TRAILING_WHITESPACE/ERROR_STRIP_TRAILING_WHITESPACE:删除空白字符
  • ENCODING:在windows系统上指定进程输出时的解码方式,默认是utf-8,其他平台会忽略该参数
  • ECHO_OUTPUT_VARIABLE/ECHO_ERROR_VARIABLE:输出将被复制,它将被发送到配置的变量中,也会在标准输出或标准错误中,3.18版本支持
  • COMMAND_ERROR_IS_FATAL:触发致命错误并终止进程执行,方式取决于参数。ANY表示任意命令执行失败都触发,LAST表示最后一个进程执行失败才触发,3.19版本支持

示例如下:

cmake_minimum_required(VERSION 3.2)

project(cmake_test)

execute_process(COMMAND echo "hello world"
                WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
                TIMEOUT 3
                RESULT_VARIABLE result_var
                OUTPUT_VARIABLE output_var
                ERROR_VARIABLE error_var
                OUTPUT_STRIP_TRAILING_WHITESPACE
                ERROR_STRIP_TRAILING_WHITESPACE)

message(STATUS "result: ${result_var}")
message(STATUS "output: ${output_var}")
message(STATUS "error: ${error_var}")

输出如下:

-- result: 0
-- output: hello world
-- error: 

如果要执行一个shell脚本,只需要把echo命令替换为如:bash a.sh 即可。

add_custom_target

添加自定义的没有输出的目标,它总会被构建。

原型如下:

add_custom_target(Name [ALL] [command1 [args1...]]
                  [COMMAND command2 [args2...] ...]
                  [DEPENDS depend depend depend ... ]
                  [BYPRODUCTS [files...]]
                  [WORKING_DIRECTORY dir]
                  [COMMENT comment]
                  [JOB_POOL job_pool]
                  [VERBATIM] [USES_TERMINAL]
                  [COMMAND_EXPAND_LISTS]
                  [SOURCES src1 [src2...]])

目标没有输出文件,总是被认为是过时的,可以使用 add_custom_command() 命令生成依赖的文件供 DEPENDS 参数使用。

常用参数说明如下:

  • Name:目标名称
  • ALL:说明该目标需要添加到默认目标的构建中,所以命令每次都会被执行。注意Name不能是ALL
  • COMMAND:构建时执行的命令,如果指定了多个COMMAND,它们将按顺序执行,但不一定组成有状态shell或批处理脚本。(要运行完整的脚本,可以使用configure_file命令或GENERATE命令来创建它,然后指定一个command来启动它。)
  • COMMENT:注释信息,会在命令执行前打印出来
  • DEPENDS:通常以同一CMakeLists.txt文件中的add_custom_command()命令生成的文件作为依赖,目标构建后依赖会被更为最新
  • SOURCES:生成目标所需要的额外的源文件,它们会被添加到IDE项目文件中
  • WORKING_DIRECTORY:执行命令的目标,如果是相对目录,则以当前源文件目录为基准

从以上说明可以看出,可以直接使用add_custom_target执行shell命令,并且使用DEPENDS可以让各个目标之间产生关联。

通常和add_custom_command命令配合使用来产生DEPENDS。

比如我们在编译boost库时,需要执行shell命令,示例如下:

add_custom_target(build_boost_libs
	COMMAND ./bootstrap.sh --prefix=/usr/local/boost
  COMMAND ./b2 link=static runtime-link=static threading=multi --with-system --with-thread --with-filesystem
  WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/boost/" 
  COMMENT "begin build boost libs...")

这样,我们自定义了目标build_boost_libs,它是通过两行命令来构建完成的。

关于add_custom_command,下面介绍。

add_custom_command

为构建系统添加自定义的构建规则。

它有两种形式的原型,下面分别介绍。

生成文件

签名如下:

add_custom_command(OUTPUT output1 [output2 ...]
                   COMMAND command1 [ARGS] [args1...]
                   [COMMAND command2 [ARGS] [args2...] ...]
                   [MAIN_DEPENDENCY depend]
                   [DEPENDS [depends...]]
                   [BYPRODUCTS [files...]]
                   [IMPLICIT_DEPENDS <lang1> depend1
                                    [<lang2> depend2] ...]
                   [WORKING_DIRECTORY dir]
                   [COMMENT comment]
                   [DEPFILE depfile]
                   [JOB_POOL job_pool]
                   [VERBATIM] [APPEND] [USES_TERMINAL]
                   [COMMAND_EXPAND_LISTS])

使用命令生成指定的输出文件。具体参数不再说明,详情可参考后文资料。

使用示例:

add_custom_command(
  OUTPUT out.c
  COMMAND someTool -i ${CMAKE_CURRENT_SOURCE_DIR}/in.txt
                   -o out.c
  DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/in.txt
  VERBATIM)
  
add_library(myLib out.c)

这样,在生成myLib库时依赖out.c,而out.c由add_custom_command生成,每次in.txt的变动都会导致add_custom_command中命令的执行。

add_custom_command指定的DEPENDS可以是某个target(通过add_library/add_executable/add_custom_target创建),或者直接是某个文件。

如果add_custom_command命令不指定DEPENDS的话,那么只要没有这个OUTPUT的文件,都会生成自己并执行command。

构建事件

为库、可执行文件等目标添加自定义命令,可以在构建目标前或者构建目标后执行一些命令。

要执行的命令会成为目标的一部分,并且只在目标构建时执行,如果目标已经构建完成,这些命令也不会执行。

原型:

add_custom_command(TARGET <target>
                   PRE_BUILD | PRE_LINK | POST_BUILD
                   COMMAND command1 [ARGS] [args1...]
                   [COMMAND command2 [ARGS] [args2...] ...]
                   [BYPRODUCTS [files...]]
                   [WORKING_DIRECTORY dir]
                   [COMMENT comment]
                   [VERBATIM] [USES_TERMINAL]
                   [COMMAND_EXPAND_LISTS])

这样就为指定的target关联了要执行的命令,target必须在当前目录里定义。

命令执行的时机:

  • PRE_BUILD:在所有规则执行前执行
  • PRE_LINK:在源文件编译后且链接前执行
  • POST_BUILD:在所有规则执行后执行命令

其他参数不再说明。

示例:

# 在目标构建完成后执行一些操作
add_executable(myExe myExe.c)
add_custom_command(
  TARGET myExe POST_BUILD
  COMMAND someHasher -i "$<TARGET_FILE:myExe>"
                     -o "$<TARGET_FILE:myExe>.hash"
  VERBATIM)
add_custom_target vs add_custom_command

add_custom_target有依赖文件时,经常和add_custom_command的生成文件模式搭配使用。

它们之间的关系比较暧昧,这里说明一下。

当add_custom_target所要生成的target依赖add_custom_command所生成的文件时,这个文件就是一个纽带。

add_custom_command命令输出的OUTPUT文件和命令里的command之间的关系是:每当这个文件需要被重新生成时,都会执行这段command。

这个文件会不会被生成,取决于构建的target是否depends这个output文件。

这个文件会不会被重新生成,取决于这个output文件depends的东西变了没。

上面也有点绕,举例说明一下:

add_custom_command(OUTPUT config_bootstrap
  COMMAND ./bootstrap.sh --prefix=/usr/local
  WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/boost/" 
  COMMENT "begin config_bootstrap")

add_custom_target(build_boost_libs
  COMMAND ./b2 link=static runtime-link=static threading=multi --with-system --with-thread --with-filesystem
  WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/boost/" 
  DEPENDS config_bootstrap
  COMMENT "begin build_boost_libs")

执行流程为:

  1. 开始构建目标build_boost_libs,发现它依赖config_bootstrap,cmake会根据该依赖和缓存决定是否重新构建该目标
  2. config_bootstrap是由add_custom_command生成的,它是否需要重新生成,取决于它自己的depends,该例中它没有depends,则只要没有config_bootstrap,就重新生成
  3. 只要config_bootstrap需要构建,add_custom_command中的命令就会被执行

这个流程与MakeFile决定是否重新编译目标是一个道理,它会自动识别模块间的依赖关系,并自己构建需要构建的模块。

写cmake的过程,也是告诉cmake模块间依赖关系的过程。

小结

cmake提供了对执行自定义命令的支持,可以很方便地使用它们执行shell命令。

但它们使用上有一些区别,比如有的会无条件每次执行,有的则依赖于依赖文件是否被更新。

具体使用哪种模式,就看需求了。

参考资料

execute_process
add_custom_target
add_custom_command

  • 8
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: `add_custom_command` 是 CMake 的一个命令,用于添加自定义命令。它可以用于在生成目标文件或其他输出文件之前或之后执行一些自定义命令,例如编译代码生成器、生成一些间文件、拷贝文件等等。 `add_custom_command` 的语法如下: ```cmake add_custom_command(TARGET target POST_BUILD|PRE_BUILD|PRE_LINK COMMAND command1 [ARGS] [args1...] [COMMAND command2 [ARGS] [args2...] ...] [WORKING_DIRECTORY dir] [COMMENT comment] [VERBATIM]) ``` 其,`TARGET` 参数指定了添加自定义命令的目标文件或输出文件。`POST_BUILD`、`PRE_BUILD` 和 `PRE_LINK` 参数用于指定命令在生成目标文件之前、之后或者在链接之前执行。`COMMAND` 参数指定了需要执行命令,可以是任何可执行程序或脚本。`ARGS` 参数用于指定命令的参数,`WORKING_DIRECTORY` 参数用于指定执行命令时的工作目录,`COMMENT` 参数用于添加注释,`VERBATIM` 参数用于告诉 CMake 不要对命令进行任何转义或处理。 下面是一个示例,演示如何使用 `add_custom_command` 命令来编译 Protobuf 文件: ```cmake # 设置 Protobuf 文件的路径 set(PROTO_FILE ${CMAKE_CURRENT_SOURCE_DIR}/myproto.proto) # 生成对应的 .pb.cc 和 .pb.h 文件 add_custom_command( OUTPUT myproto.pb.cc myproto.pb.h COMMAND protoc --cpp_out=${CMAKE_CURRENT_BINARY_DIR} ${PROTO_FILE} DEPENDS ${PROTO_FILE} ) # 将生成的 .pb.cc 文件添加到可执行文件的源文件 add_executable(myapp main.cpp myproto.pb.cc) ``` 在这个例子,我们使用 `add_custom_command` 命令来生成 Protobuf 文件对应的 `.pb.cc` 和 `.pb.h` 文件。我们指定了需要执行命令是 `protoc --cpp_out=${CMAKE_CURRENT_BINARY_DIR} ${PROTO_FILE}`,其 `${CMAKE_CURRENT_BINARY_DIR}` 是 CMake 的一个变量,表示当前的二进制目录,`${PROTO_FILE}` 是我们在前面设置的 Protobuf 文件的路径。我们还指定了这个自定义命令的输出文件是 `myproto.pb.cc` 和 `myproto.pb.h`,并且这个自定义命令依赖于 `PROTO_FILE` 文件。最后,我们将生成的 `.pb.cc` 文件添加到可执行文件的源文件列表。 ### 回答2: 在CMakeadd_custom_command函数用于向构建系统添加自定义命令。这允许我们在构建过程执行一些自定义的操作。 add_custom_command函数的语法如下: add_custom_command(TARGET target POST_BUILD COMMAND command1 [ARGS] [args1...] [COMMAND command2 [ARGS] [args2...] ...] [WORKING_DIRECTORY dir] [COMMENT comment] [VERBATIM] [USES_TERMINAL]) 选项TARGET指定要应用自定义命令的目标。POST_BUILD表示将在构建目标之后执行自定义命令COMMAND用于指定要执行命令,可以是一个可执行文件或脚本文件,还可以带有参数。 WORKING_DIRECTORY用于指定命令的工作目录。 COMMENT用于添加描述性的注释。 VERBATIM表示命令参数不会被分析或扩展,原样传递给命令。 USES_TERMINAL用于表示是否应将命令在终端执行。 使用add_custom_command的一个典型示例是在构建目标之后复制生成的文件到指定目录。例如: add_custom_command(TARGET myTarget POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:myTarget> ${CMAKE_CURRENT_BINARY_DIR}/output/) 上述命令将在构建myTarget目标之后执行,它调用CMake命令行工具的-E选项来执行复制动作,将生成的myTarget目标文件复制到output目录。 在CMakeLists.txt文件使用add_custom_command函数可以实现更复杂的自定义构建操作,例如生成文档、运行测试等。这使得CMake具有很大的灵活性,可以满足各种项目的构建需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值