Cmake

  1. 缘起
    1. 由于 C 和 C++ 比较糟糕的库的链接设计的历史遗留问题,当编译源代码的时候,一般使用 Make 和 Makefile 来指定文件之间的依赖关系,这样可以带来两个好处:一是让编译工作尽量自动化,一条 Makefile 指令可以管一片;二是可以让编译工作加速,当只有少数几个文件中的代码出现变动时,则只需要编译与该代码有依赖或关联的部分文件即可,不需要将整个项目都重新编译,节省时间;
    2. 尽管已经有了 Make 这么好用的工具,仍然存在一个问题,即 Makefile 的编译工作跟操作系统有一定的依赖关系,即不同的操作系统,Makefile 的内容需要有所不同,才能正确编译代码;为了解决这个平台依赖的问题,引入了 CMake 工具,它的目标是只需写一次 CMakeLists,就可以根据所要编译的平台环境,自动生成对应的 Makefile,这样一来就可以极大的减少针对不同平台编写 Makefile 的工作量;
    3. CMake 有自己的语法,可以算是一种 DSL 了,按王垠说法,绝大部分 DSL 都设计的很糟糕,我也有同感,据说可能是因为它们都是由非 PL 专业的人员设计的,所以总是不如一门图灵完备的语言那么强大和高表达性;
      1. 后来,在 CMake 教程学完后,发现了另外一个以 python 作为作为语法表达的工具 SCons,估计这个工具就要强大多了,据说得到了很多名家的推荐,包括 Eric S. Raymond、Timothee Besset、Zed A. Shaw 等;有待日后抽空学习了解一下;
    4. 升级步骤
      1. 到官网下载源代码包,解压,进入解压后的文件夹
      2. cmake .
      3. make
      4. make install (注:此步可选)
  2. 语法
    1. 说明
      1. 由指令+指令参数组成;
      2. 指令的名称一般使用小写字母表示(实际并不区分大小写),指令的参数部分一般用圆括号括起来,例如 cmake_minimum_required (VERSION 2.8)
      3. 参数部分中的变量名称一般使用大写字母表示,例如 VERSION,引用变量时的格式为美元符加花括号,例如 ${VERSION}
      4. 注释使用 # 符号;间隔使用空格
      5. 后来我发现 CMake 有很多自己的全局变量;包括
        1. CMAKE_BINARY_DIR,顶级二进制文件目录
        2. CMAKE_SOURCE_DIR,顶级源文件目录
        3. CMAKE_CURRENT_BINARY_DIR,当前二进制文件目录
        4. CMAKE_CURRENT_SOURCE_DIR,当前源文件目录
        5. PROJECT_NAME,项目名称
    2. 具体指令
      1. cmake_minimum_required
        1. 用来指定版本要求,例如:cmake_minimum_required (VERSION 2.8)
      2. project
        1. 用来指定项目名称,例如 project (Demo)
      3. add_executable
        1. 用来指定要生成的目标,以及目标所依赖的源文件,例如 add_executable(Demo main.cc)
        2. 其他源文件支持多个;当有很多时,可以使用变量来替换表示;
      4. aux_source_directory
        1. 用来查找指定目录下的所有源文件,首参数为目录路径,第二个参数是变量名,用来存储找到的结果;
          1. 例如 aux_source_directory(. DIR_SRCS)
      5. add_subdirectory
        1. 用来添加子目录,这样子目录下的 CMakeLists 也会进行处理;
        2. 子目录的 CMakeLists 用来处理子目录里面的内容,例如生成库文件;
      6. include_directories
        1. 指定头文件的搜索目录
      7. link_directories
        1. 指定要链接的库文件的搜索目录
      8. link_libraries
        1. 指定要链接的库文件的名称和路径(绝对地址)
      9. target_link_libraries
        1. 指定要链接的库文件的名称,第一个参数表示主文件,第二个参数表示主文件需要链接的库文件;
          1. 例如 target_link_libraries(Demo MathFunctions)
          2. 如果有多个库,估计也支持多参数,以及使用变量来表示多个库文件;
      10. add_library
        1. 用来指定要生成的库文件,第一个参数表示要生成的库文件,第二个参数所用到的源文件;
          1. 例如 add_library (MathFunctions ${DIR_LIB_SRCS})
          2. 第二个参数也同样支持用变量表示多个文件;
        2. 格式
          1. add_library( [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [source1] [source2 …])
      11. target_include_directories
        1. 指定编译 target 时要使用的 include 目录;其中的 target 需要由 add_library 或者 add_executable 指定;
        2. 格式
          1. target_include_directories(
            1. [SYSTEM] [BEFORE]
            2. <INTERFACE | PUBLIC | PRIVATE> [items1…]
            3. [<INTERFACE | PUBLIC | PRIVATE> [items2…] …])
      12. configure_file
        1. 根据输入的文件,替换其中的变量,生成另外一个文件;第一个参数表示输入的文件,第二个参数表示要生成的文件
        2. 输入文件中的变量定义格式为: #CMakedefine VAR …
        3. 输出文件中的变量会被替换为:#define VAR … 或者 /* #undef VAR */ (表示此变量被注释掉了,不启用)
        4. 例如: configure_file ( “${PROJECT_SOURCE_DIR}/config.h.in” “${PROJECT_BINARY_DIR}/config.h” )
        5. 在输入的文件中,可以引用在 CMakeLists 文件中定义的变量
        6. 输出的文件可以作为源代码文件的一部分引用;
      13. options
        1. 用来定义可供用户选择的配置项开关,第一个参数是选项变量名,第二个参数是选项提示信息,第三个参数是默认初始值(可选,未注明则默认为 OFF )
        2. 选项只有两种默认值,ON 或者 OFF
        3. 例如 option (USE_MYMATH “Use custom math implementation” ON)
      14. if
        1. 用来设置条件判断,
          1. 格式为
            1. if ()
              1. # command 可由多行组成,每行建议缩进以方便阅读
            2. elseif()
            3. else()
            4. endif()
          2. 示例
            1. if (USE_MATH)
              1. include_directories (“${PROJECT_SOURCE_DIR}/math”)
              2. add_subdirectory (math)
              3. set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
            2. endif ()
        2. 一般源文件中也会有基于全局条件变量的部分代码,例如
          1. #ifdef USE_MYMATH
            1. #include “math/MathFuncitons.h”
          2. #else
            1. #include
          3. #endif
      15. set
        1. 用来设置变量及其初始值,第一个参数为变量名,第二个参数为变量值,第三个参数是父级作用域 [PARENT_SCOPE](可选)
          1. 格式:set ( … [PARENT_SCOPE])
          2. 我们在 CMakeLists 中设置的变量,是可以在 config.h.in 文件中引用的;
        2. 用来设置 CACHE,它用来将某个变量值存储到缓存中,重新运行 cmake 时,会使用缓存值,除非使用 [FORCE] 选项显式的重新赋值;
          1. 格式:set ( … CACHE [FORCE])
          2. 其中 type 表示要缓存的变量类型,支持以下五种
            1. BOOL,布尔值,ON/OFF 两个值;
            2. PATH,文件夹路径
            3. FILEPATH,文件名路径;
            4. STRING,字符串
            5. INTERNAL,同 STRING,也是字符串;据说是隐式的 FORCE,暂时不知道使用场景;
          3. 用来对缓存的变量做简单说明,仅支持字符串格式;
        3. 用来设置环境变量
          1. 格式:set (ENV{} [])
      16. install
        1. 用来设置安装规则,DESTINATION 用来表示要安装的目标路径,FILES 用来表示文件安装规则,TARGETS 用来表示target 目标的安装规则
        2. 示例
          1. install (TARGETS Demo DESTINATION bin)
          2. install (FILES “${PROJECT_BINARY_DIR}/conifig” DESTINATION include)
      17. add_test
        1. 用来添加测试用例,格式为 add_test (NAME COMMAND […])
          1. 第一个参数表示测试用例的名称,第二个名称表示要执行的测试命令,第三个参数表示测试选项
          2. 示例:add_test (test_5_2 Demo 5 2)
      18. enable_testing
        1. 用来表示启用测试,可以没有参数
          1. 示例:enable_testing( )
      19. set_tests_properties
        1. 用为设置测试的参数,一般用来验证测试结果是否正确,格式 set_tests_properties (test1 [test2…] PROPERTIES prop1 value1 prop2 value2)
          1. 其中 test1, test2 等表示测试用例的名称,通过 add_test 指令设定;
        2. 示例
          1. set_tests_properties (test_5_2 PROPERTIES PASS_REGULAR_EXPRESSION “is 25”)
      20. include
        1. 加载指定的CMake文件或模块,以便加载并运行文件或模块中的 CMake 代码,格式为 include (<file | module> [OPTIONAL])
        2. 示例
          1. include (${CMAKE_ROOT}/Modules/CheckFunctinExists.cmake)
        3. 问题:Modules 是哪来的?CMake 自带的;
      21. check_function_exists
        1. 用来检查某个 C 函数是否可以被链接,格式为:check_function_exists( )
          1. 第一个参数 function 表示系统标准库提供的函数名,第二个参数是用来存储结果的变量 variable (临时创建,存储在缓存中)
        2. 示例
          1. include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake) # 需要先引入 CMake 自带的宏文件
          2. check_function_exists (pow HAVE_POW)
      22. find_package
        1. 用来查找并导入外包的库
        2. 格式: find_package( [version] [EXACT] [QUIET] [MODULE] [REQUIRED] [[COMPONENTS] [components…]] [OPTIONAL_COMPONENTS components…] [NO_POLICY_SCOPE])
          1. REQUIRED 选项,表示该库文件是必须的,没有找到会终止 CMake 执行并退出;
        3. 示例:find_package( OpenCV REQUIRED )
        4. 这个指令执行完毕后,会有一些内置变量保存查询结果,包括
          1. _FOUND,
      23. foreach
        1. 对列表中的元素进行遍历,并对每个元素执行一遍指令
        2. 格式
          1. foreach( )
          2. endforeach()
        3. 这个命令可以用来执行多个子项目的编译;
      24. 添加编译选项
        1. SET (GCC_COMPLIE_FLAGS “-lrt”)
        2. add_definitions(${GCC_COMPLIE_FLAGS})
    3. 场景
      1. 构建安装包
        1. CMakeLists 配置
          1. include (InstallRequiredSystemLibraries) # 导入包安装的模块
          2. set (CPACK_RESOURCE_FILE_LICENSE “${CMAKE_CURRENT_SOURCE_DIR}/License.txt”) # 设置版本声明变量
          3. set (CPACK_PACKAGE_VERSION_MAJOR “${Demo_VERSION_MAJOR}”) # 设置大版本号
          4. set (CPACK_PACKAGE_VERSION_MINOR “${Demo_VERSION_MINOR}”) # 设置小版本号
          5. include (CPack) # 导入CPack 包;
        2. 命令行执行的命令
          1. cpack -C CPackConfig.cmake # 生成二进制安装包
            1. 或者 cpack -C CPackSourceConfig.cmake # 生成源码安装包

Cmake
https://ccw1078.github.io/2019/05/17/Cmake/
作者
ccw
发布于
2019年5月17日
许可协议