cmake-03 CMakeLists脚本的编写
本文并不打算十分详细的介绍如何编写CMakeLists.txt脚本文件,而是先大致介绍一下其包含的几个重要方面:
- 
    变量定义 
- 
    条件控制 
- 
    循环控制 
- 
    函数 
- 
    macro 
- 
    文件包含 
- 
    target(前文已介绍) 
- 
    install 
- 
    CPack 
知道了上面这些,就基本可以编写大部分CMake编译脚本了。
这里建议在参考cmake官网文档的同时,最好从GitHub上下载一个其他人编写的CMake编译工程示例,以便我们交叉对照的学习。这里我们从GitHub上克隆gflags:
# git clone https://github.com/protocolbuffers/protobuf.git 
参看:
1. 设置变量
变量是CMake语言的基本存储单元, 其值类型均为字符串类型(ps: 尽管某些命令可能会将其解释为其他类型)。我们一般使用set或者unset命令来显式的设置、取消变量,但也有一些其他的命令具有修改变量值的语义。
一个变量有对应的作用范围:
- 
    Block scope block()命令中创建的变量具有Block scope 
- 
    Function scope function()命令中创建的变量具有Function scope 
- 
    Directory scope 源代码目录树中的每一个目录均有其自己的变量作用域绑定。在处理一个目录中的CMakeLists.txt文件之前,CMake都会拷贝其父目录中的所有变量绑定。 
- 
    Persistent Cache CMake保存着一份单独的”cache”变量集合,被称为”cache entries”,这些值可横跨CMake构建工程的多次运行。cache entries有一个单独的绑定域名,除非通过set、unset命令的’CACHE’选项加以修改。 
下面我们介绍set命令的使用,我们可以使用该命令设置三种类型变量的值:
- 
    普通变量(Normal variable) 
- 
    缓存变量(Cache variable) 
- 
    环境变量(Env variable) 
ps: 这里的环境变量是CMake环境变量, 而不是一般意义上的Linux环境变量
1.1 设置普通变量(Normal variable)
基本语法规则:
set(<variable> <value>... [PARENT_SCOPE])
用于set或unset当前函数或目录作用域中的变量:
- 
    假如指定了至少一个 <value>...的话,那么变量将会被设置为指定的值
- 
    假如并不包含 <value>,则表示unset一个变量。这等价于unset()命令 
2. 条件控制
控制条件执行一组命令,其基本的语法规则如下:
if(<condition>)
  <commands>
elseif(<condition>) # optional block, can be repeated
  <commands>
else()              # optional block
  <commands>
endif()
2.1 Condition语法
如下的语法规则适用于if、elseif、while命令的condition参数。
1)基本表达式condition
- 
    if( ) 假如constant的值为1、ON、YES、TRUE、Y、或者非零数字, 则为True;假如constant的值为0、OFF、NO、FALSE、N、IGNORE、空字符串,则为Fasle。 
- 
    if( ) 这里值得注意的是Enviroment variable不能使用此方式来测试。 
- 
    if( ) 除非字符串的值为true常量,否则其他任何字符串都为false。 
2) 逻辑操作符
- 
    if(NOT ) 
- 
    if( AND )¶ 
- 
    if( OR 
3) 存在性检查
- 
    if(COMMAND ) 用于指定的名称是否是command、macro、function,如果是则返回true,否则返回false 
- 
    if(POLICY ) 
- 
    if(TARGET ) 
4) 文件操作
- 
    if(EXISTS ) 假如指定的文件或目录存在且可读的话, 返回true 
5) 比较操作
- 
    if(<variable string> LESS <variable string>) 
- 
    if(<variable string> GREATER <variable string>) 
6)版本比较
7)路径比较
3. 循环控制
循环有两种形式:
- 
    foreach 
- 
    while 
1) foreach循环
基本语法格式:
foreach(<loop_var> <items>)
  <commands>
endforeach()
2) while循环
基本语法格式:
while(<condition>)
  <commands>
endwhile()
4. 函数
function的基本语法格式:
function(<name> [<arg1> ...])
  <commands>
endfunction()
5. macro
基本语法格式:
macro(<name> [<arg1> ...])
  <commands>
endmacro()
6. 文件包含
基本语法格式:
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>]
                      [NO_POLICY_SCOPE])
用于加载一个指定的文件或目录。
7. install
基本语法格式:
install(TARGETS <target>... [...])
install(IMPORTED_RUNTIME_ARTIFACTS <target>... [...])
install({FILES | PROGRAMS} <file>... [...])
install(DIRECTORY <dir>... [...])
install(SCRIPT <file> [...])
install(CODE <code> [...])
install(EXPORT <export-name> [...])
install(RUNTIME_DEPENDENCY_SET <set-name> [...])
8. 几个常用的命令介绍
8.1 add_custom_command与add_custom_target
在CMake构建系统中,add_custom_command和add_custom_target是两个强大的指令,它们允许我们添加自定义的构建规则。这两个指令在复杂的项目中特别有用,因为它们允许我们执行一些标准的构建步骤之外的操作。
参看:
1) 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]
                   [JOB_SERVER_AWARE <bool>]
                   [VERBATIM] [APPEND] [USES_TERMINAL]
                   [COMMAND_EXPAND_LISTS]
                   [DEPENDS_EXPLICIT_ONLY])
ps: 上面这是add_custom_command指令的一种较为常见的用法
- 
    OUTPUT:指定由命令生成的文件。这些文件将成为后续构建步骤的依赖项。 
- 
    COMMAND:要执行的命令。这可以是任何可以在命令行中运行的命令。 
- 
    MAIN_DEPENDENCY:可选参数,指定主要依赖项。这通常是一个源文件,当该文件更改时,将重新运行命令。 
- 
    DEPENDS:其他依赖项列表。当这些文件更改时,也将重新运行命令。 
- 
    IMPLICIT_DEPENDS:隐式依赖项。这允许你指定命令对哪些文件有隐式依赖。 
- 
    VERBATIM:如果设置,命令将不会通过CMake的命令行解释器,而是直接传递给构建系统。 
- 
    WORKING_DIRECTORY:指定命令的工作目录。 
- 
    COMMENT:为构建系统提供的注释,通常用于描述命令的目的。 
- 
    PREBUILDS或POSTBUILDS:指定命令是在目标构建之前还是之后运行。 
- 
    BYPRODUCTS:指定命令生成的副产品文件。这些文件不会触发重新构建,但如果它们不存在,构建将被视为失败。 
2) add_custom_target
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]
                  [JOB_SERVER_AWARE <bool>]
                  [VERBATIM] [USES_TERMINAL]
                  [COMMAND_EXPAND_LISTS]
                  [SOURCES src1 [src2...]])
- 
    target_name:自定义目标的名称。 
- 
    ALL:可选参数,如果设置,该目标将被添加到默认构建目标中,即执行make或cmake –build时会自动构建。 
- 
    DEPENDS:其他依赖项列表。当这些目标或文件更改时,该目标将被重新构建。 
- 
    WORKING_DIRECTORY、COMMAND、VERBATIM、IMPLICIT_DEPENDS和BYPRODUCTS的参数与add_custom_command中的相同。 
3) 实际应用
在实际项目中,add_custom_command和add_custom_target可以非常有用。例如,你可能需要:
- 
    使用add_custom_command生成由源代码生成的头文件,如使用protobuf工具生成C++头文件。 
- 
    使用add_custom_target运行测试套件,确保代码质量。 
- 
    使用add_custom_target清理构建过程中生成的文件,以保持工作区的整洁。 
通过合理使用这两个指令,你可以极大地扩展CMake的构建能力,使其适应各种复杂的构建需求。
4) 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>])
cmake在配置阶段阶段使用该指令来执行命令;而add_custom_command与add_custom_target是在构建阶段执行相应命令。
8.2 文件操作指令
file指令用于操作文件或路径。基本语法规则如下:
Reading
  file(READ <filename> <out-var> [...])
  file(STRINGS <filename> <out-var> [...])
  file(<HASH> <filename> <out-var>)
  file(TIMESTAMP <filename> <out-var> [...])
  file(GET_RUNTIME_DEPENDENCIES [...])
Writing
  file({WRITE | APPEND} <filename> <content>...)
  file({TOUCH | TOUCH_NOCREATE} <file>...)
  file(GENERATE OUTPUT <output-file> [...])
  file(CONFIGURE OUTPUT <output-file> CONTENT <content> [...])
Filesystem
  file({GLOB | GLOB_RECURSE} <out-var> [...] <globbing-expr>...)
  file(MAKE_DIRECTORY <directories>...)
  file({REMOVE | REMOVE_RECURSE } <files>...)
  file(RENAME <oldname> <newname> [...])
  file(COPY_FILE <oldname> <newname> [...])
  file({COPY | INSTALL} <file>... DESTINATION <dir> [...])
  file(SIZE <filename> <out-var>)
  file(READ_SYMLINK <linkname> <out-var>)
  file(CREATE_LINK <original> <linkname> [...])
  file(CHMOD <files>... <directories>... PERMISSIONS <permissions>... [...])
  file(CHMOD_RECURSE <files>... <directories>... PERMISSIONS <permissions>... [...])
Path Conversion
  file(REAL_PATH <path> <out-var> [BASE_DIRECTORY <dir>] [EXPAND_TILDE])
  file(RELATIVE_PATH <out-var> <directory> <file>)
  file({TO_CMAKE_PATH | TO_NATIVE_PATH} <path> <out-var>)
Transfer
  file(DOWNLOAD <url> [<file>] [...])
  file(UPLOAD <file> <url> [...])
Locking
  file(LOCK <path> [...])
Archiving
  file(ARCHIVE_CREATE OUTPUT <archive> PATHS <paths>... [...])
  file(ARCHIVE_EXTRACT INPUT <archive> [...])
比如我们使用如下的命令来匹配出src目录下的所有cpp文件:
file(GLOB SOURCES "src/*.cpp")
如下给出一个示例,用于遍历目录下的所有.proto文件,使用protoc生成.cpp:
cmake_minimum_required(VERSION 3.10)
project(protobuf_generation LANGUAGES CXX)
 
find_package(Protobuf REQUIRED)
 
include_directories(${CMAKE_CURRENT_BINARY_DIR})
 
file(GLOB_RECURSE PROTO_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.proto")
 
foreach(PROTO_FILE ${PROTO_FILES})
    get_filename_component(PROTO_DIR ${PROTO_FILE} DIRECTORY)
    set(PROTO_SRC_DIR "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_DIR}")
 
    execute_process(
        COMMAND ${PROTOBUF_PROTOC_EXECUTABLE}
        --cpp_out=${PROTO_SRC_DIR}
        -I ${CMAKE_CURRENT_SOURCE_DIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/${PROTO_FILE})
endforeach()
 
add_executable(protobuf_example main.cpp)
target_link_libraries(protobuf_example protobuf)

