背景


以下情况可能需要在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 3RESULT_VARIABLE result_varOUTPUT_VARIABLE output_varERROR_VARIABLE error_varOUTPUT_STRIP_TRAILING_WHITESPACEERROR_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_libsCOMMAND ./bootstrap.sh --prefix=/usr/local/boostCOMMAND ./b2 link=static runtime-link=static threading=multi --with-system --with-thread --with-filesystemWORKING_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.cCOMMAND someTool -i ${CMAKE_CURRENT_SOURCE_DIR}/in.txt-o out.cDEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/in.txtVERBATIM)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_BUILDCOMMAND 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_BUILDCOMMAND 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_bootstrapCOMMAND ./bootstrap.sh --prefix=/usr/localWORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/boost/" COMMENT "begin config_bootstrap")add_custom_target(build_boost_libsCOMMAND ./b2 link=static runtime-link=static threading=multi --with-system --with-thread --with-filesystemWORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/boost/" DEPENDS config_bootstrapCOMMENT "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

CMake中执行shell命令之execute_process、add_custom_target和add_custom_command相关推荐

  1. vim中执行shell命令小结

    vim中执行shell命令,有以下几种形式 1):!command 不退出vim,并执行shell命令command,将命令输出显示在vim的命令区域,不会改变当前编辑的文件的内容 例如 :!ls - ...

  2. python调用shell命令-在Python中执行shell命令的6种方法,你都知道吗?

    原标题:在Python中执行shell命令的6种方法,你都知道吗? Python经常被称作"胶水语言",因为它能够轻易地操作其他程序,轻易地包装使用其他语言编写的库.今天我们就讲解 ...

  3. python调用shell命令-python中执行shell命令的几个方法小结

    最近有个需求就是页面上执行shell命令,第一想到的就是os.system, 复制代码 代码如下: os.system('cat /proc/cpuinfo') 但是发现页面上打印的命令执行结果 0或 ...

  4. 在 Ruby 中执行 Shell 命令的 6 种方法

    我们时常会与操作系统交互或在 Ruby 中执行 Shell 命令.Ruby为我们提供了完成该任务的诸多方法. Exec Kernel#exec 通过执行给定的命令来替换当前进程,例如: $ irb & ...

  5. SQL格式日志转为syslog格式:触发器中执行Shell命令

    很多企业网中都部署了日志中心,集中收集.分析和处理各种设备产生的日志. 但一些应用系统的日志是保存在数据库表中,如果需要提供syslog格式日志,除了进行代码改造外,一种快速的解决方法是使用SQL触发 ...

  6. python 执行shell命令行效率提升_在python脚本中执行shell命令的方法

    使用Python处理一个shell命令或一个执行一个shell脚本,一般情况下,有以下三种方法,以下我们来看: 第一种方法是使用os.system的方法 os.system(" cmd&qu ...

  7. python执行shell命令行_python执行命令行:python中执行shell命令行read结果

    +++++++++++++++++++++++++++++ python执行shell命令 1 os.system  (只有这个方法是边执行边输出,其他方法是最后一次性输出) 可以返回运行shell命 ...

  8. python中执行shell命令_python中执行shell命令的几个方法小结-阿里云开发者社区

    Python 执行 shell 命令 最近有个需求就是页面上执行shell命令,第一想到的就是os.system os.system('cat /proc/cpuinfo') 但是发现页面上打印的命令 ...

  9. python 执行shell_python学习——python中执行shell命令

    这里介绍一下python执行shell命令的四种方法: 1.os模块中的os.system()这个函数来执行shell命令>>> os.system('ls') anaconda-k ...

最新文章

  1. 求1+2+3+...+n的值。
  2. 六年级计算机应用计划,2016年小学六年级信息技术教学计划 (800字)
  3. 移动硬盘计算机无法打开硬盘,移动硬盘无法识别
  4. 【SpringMVC学习07】SpringMVC中的统一异常处理
  5. 数据库SQL语句 | 快速上手 | 面试复习
  6. springboot 微服务相关收藏
  7. GnuTLS recv error (-54): Error in the pull function(解决方法)
  8. OpenGL超级宝典(第7版)之清单的初始环境配置VS2019
  9. [原创] 我了解北京地区消费贷利息情况
  10. Apple iPhone 8G手机误升级至2.0降级破解日记
  11. BL0940电能计量 设计
  12. [附源码]JAVA+ssm交通违章举报平台(程序+Lw)
  13. memcpy、memmove、memcmp、memset函数的使用说明和模拟实现
  14. 数据结构与算法笔记:分治策略之Greatest Slice,2-Way Merge,Counting Inversions,linearSelect,Diameter,Closest Pair
  15. dof景深matlab,CG制作景深(DOF)的方法
  16. 真神奇!敲击桌子就能操控iPad或iPhone
  17. 预制发票MIR7抬头行项目检查BADi-MRM_HEADER_CHECK
  18. 个人备案的网站能放企业服务器吗,个人及企业域名备案对网站有什么影响
  19. AP模式(路由器的几种模式)
  20. 计算机网络安全毕业论文提纲,计算机网络毕业论文提纲范例.doc

热门文章

  1. 算法导论(22.1):图的表示
  2. DOA_GAN的近似复现
  3. css实现图片背景颜色变灰变白
  4. 【C语言基础学习---扫雷游戏】(包含普通版+递归炼狱版)
  5. FPS游戏通用准星修正实现自瞄
  6. 游戏中的图像资源(位图与矢量图比较)
  7. Metricbeat 指标采集工具应用示例
  8. js判断字符串下划线个数
  9. 全国计算机等级考试 备考,全国计算机等级考试该如何去备考最有效!最全备考学习方案...
  10. 春招实习之路(附面经)-Laochou