CMake


友情提示,下面一大段都是废话,可以直接跳到后面的部分...

工作以来,一直从事的是基于Windows的C/C++开发工作。当然,使用最多的当数号称宇宙第一IDE的Visual Studio集成开发环境,依稀还记得大学时候用的那个Visual C++ 6.0,对于一个刚刚上了两节C++课的懵懂少年来说,难以想象居然还有如此强大的软件,点两下鼠标就能运行一个Hello World程序,简直牛逼!后来逐渐接触了VS05、08等,最近两年主要使用的版本是2015,也自认为还是有一定了解的。

也是在大学的时候,曾经有半年的时间疯狂的迷上了折腾各种各样的操作系统,Windows从2000到8.1的各个本版,几乎所有的常见Linux发行版,甚至还安装过苹果的X OS系统(解决过经典的“五国语言”错误,后来折腾到勉强能开机,但是后来还是因为各种硬件驱动问题,最终放弃),以上的各种折腾都是在实体机上面做的,因为当时的笔记本性能低下(什么酷睿2双核处理器,内存2G,硬盘256GB),虚拟机运行的体验极差。经历了各种艰难的抉择之后,最终选定了CentOS作为笔记本上唯一的一个操作系统日常使用了将近两个月的时间(后面由于坑爹的舍友强烈要求玩DOTA又安装回了Windows XP)。

扯远了,回过头来介绍CMake。我们知道在Windows平台下构建一个稍微复杂一些的C/C++项目,我们可以利用Visual Studia直接创建一个工程,在几乎完全的图形化界面下对我们的项目进行配置管理,然后进行构建之后就可以生成我们的可执行文件了。这样看起来非常方便,对于程序开发人员可以将更多的精力放在程序的逻辑和需求上去。可是,我们虽然很好的完成了任务,但却会对IDE形成很大的依赖,而且这种依赖会越来越大。一旦有一天离开这些IDE,你会发现工作起来会及其的痛苦。那么在Linux这样甚至没有图形界面的平台上我们如何创建一个复杂的工程呢,答案就是使用CMake工具(当然还有一些其他的方法,比如直接编写makefile)。


下面将列出我自己在编写CMakeLists.txt文件的时候遇到的各种问题并给出解答,希望能够帮到你。

  1. 一些基本的东西
    • CMake工具的配置文件名称为CMakeLists.txt,错一个字符都不行,哪怕只是大小写不符;
    • 脚本语言预留了一些长得像函数一样的关键字称为命令,比如cmake_minimum_required(),奇怪的是命令却是大小写不敏感的你可以写成CMakE_MinimUM_ReqUirED()(如果你高兴的话),个人习惯能小写的尽量小写;
    • 使用#进行注释,就像C++中的//一样;
    • 一般命令可以接收不确定个数的参数,就像这样add_executable(ABC A.cpp B.cpp C.cpp),不幸的是多个参数之间并不是像我们习以为常的一样使用逗号进行分割,而是空格。一般的参数你可以使用引号引起来,当然也可以不加引号,除非你的参数中本身就包含空格;
    • 一个CMakeLists.txt总是以这样一个命令开头:cmake_minimum_required(VERSION 2.8),正如你看到的VERSION这个单词只能大写 :-( ,该命令指定了运行本配置文件所需的CMake最低版本(我们怎么才能知道最低版本是多少呢?我特么也不知道,你随便写一个吧!╮( ̄▽  ̄)╭);
    • 还有一行的最后是不需要分号的。
  2. 设置项目名称
    • project ("your project name")
    • 给你的项目取一个优雅或者霸气的名字;
  3. 打印信息
    • message("your messages")
    • 在执行cmake命令是会在终端打印指定的信息。如果你遇到了问题,希望看一下某个环境变量或自定义变量的值,那么你可以使用该命令,例如:message(${CMAKE_CXX_FLAGS})
  4. 设置变量
    • set(KCBP_PATH e:/SVN/kbsskcbp)
    • 可以定义自定义变量,也可以用来修改CMake的预定义变量,例如设置编译是Debug还是Release版本set(CMAKE_BUILD_TYPE Release),使用变量的方式是${YOUR_VARIABLE}
  5. 指定头文件目录
    • include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
    • target_include_directories( [SYSTEM] [BEFORE] [items1...] [ [items2...] ...])
    • 以上两个命令都可以用于添加包含目录,第一个命令设置的包含目录作用域是当前CMakeLists.txt文件,而第二个命令可以指定为生成的某个可执行文件或库文件单独设置包含目录。
    • 几个参数:
      • [BEFORE]:官方的解释“If BEFORE is specified, the content will be prepended to the property instead of being appended”,翻译一下“如果[BEFORE]参数被指定,包含目录将被预先设置为属性而非追加”;
      • [SYSTEM]:指定该参数,编译器将被告知该包含目录为系统的包含目录;
      • :指定包含目录的作用范围,不同的组合会修改不同的项目属性字段。
  6. 指定链接库目录
    • link_directories(directory1 directory2 ...)
    • 很好理解,没有可说的,但是官方文档有一句话 “Note that this command is rarely necessary”
  7. 链接库文件
    • link_libraries([item1 [item2 [...]]] [[debug|optimized|general] ] ...)
    • target_link_libraries( ... ... ...)
    • 以上两个命令都可以实现链接指定的库文件,link_libraries没有指定具体链接到哪一个目标,因此它适用于链接那些基础公共的,在项目中每个生成项目中都会被使用到的库;target_link_libraries可以指定具体将库链接给哪一个目标,因此它适用于仅在某个生成项目中使用到的库,另外还应该注意官方文档中有这样一句话“The target_link_libraries() command should be preferred whenever possible. Library dependencies are chained automatically, so directory-wide specification of link libraries is rarely needed.”,因此更加推荐使用target_link_libraries
    • 参数[debug|optimized|general]指定在那些配置下该链接项目生效。另外,该命令也支持PRIVATE|PUBLIC|INTERFACE参数。
  8. 生成可执行文件
    • add_executable( [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] [source1] [source2 ...])
    • 命令比较简单,添加一个名称为的,由一个或多个源文件编译链接而成的可执行文件。中间的三个可选参数可以理解为在生成时设置了相应的属性。
    • add_executable( IMPORTED [GLOBAL])该命令导入一个当前项目以外的目标,以使其在逻辑上属于当前项目,因此也就实现了引用当前项目以外资源的功能。需要注意的是,该命令仅仅是引入一个外部目标,并不会构建一个新的目标。举例:
      add_executable(generator IMPORTED)  
      set_property(TARGET generator PROPERTY IMPORTED_LOCATION "/path/to/some_generator")
      set(GENERATED_SRC ${CMAKE_CURRENT_BINARY_DIR}/generated.c)
      add_custom_command(OUTPUT ${GENERATED_SRC} COMMAND generator ${GENERATED_SRC})
      add_executable(myexe src1.c src2.c ${GENERATED_SRC})
      
    • add_executable( ALIAS )该命令用于为指定目标创建一个别名。按照个人的理解应该是类似于我们编程开发当中的引用的概念吧。具体还没有使用过,如果理解有误,万望指正。
  9. 生成库文件
    • add_library( [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [source1] [source2 ...])
    • 可选参数STATIC生成静态库、SHARED生成动态库、MODULE库是不会被链接到其他库,但可以在运行时通过dlopen-like函数动态加载的插件;EXCLUDE_FROM_ALL参数指定在构建是会添加相应的属性。
  10. 指定生成Debug还是Release版本
    • CMAKE_BUILD_TYPE该变量可被设置为Debug Release RelWithDebInfo MinSizeRel之一。当这个变量值为DebugCMake 会使用变量CMAKE_CXX_FLAGS_DEBUGCMAKE_C_FLAGS_DEBUG中的字符串作为编译选项,当这个变量值为Release时,工程会使用变量CMAKE_CXX_FLAGS_RELEASECMAKE_C_FLAGS_RELEASE作为编译选项。
  11. 指定编译选项
    • 设置变量CMAKE__FLAGS_的值,可以指定语言以及配置。
  12. 增加预处理定义
    • add_definitions(-DFOO -DBAR ...)注意预处理定义需要以 -D开头。
    • 事实上add_definitions可以用来添加任何标志,比如包含目录和编译选项,功能应该类似于Visual Studio项目配置中的命令行功能,但通常习惯上只用它来增加预处理定义。
    • CMake文档中建议使用另外三个命令来替代add_definitions
      • Use add_compile_definitions()to add preprocessor definitions
      • Use include_directories()to add include directories
      • Use add_compile_options()to add other options
  13. 添加子目录
    • add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
    • 可选参数binary_dir指定子目录生成的目标的存放位置,指定EXCLUDE_FROM_ALL参数意味着子目录的生成目标将默认不被包含在父目录所生成的目标当中,并且从IDE工程文件中排除。
    • 当然并非仅仅使用add_subdirectory命令就可以添加子目录了,你还必须为子目录编写一个CMakeLists.txt脚本文件并放置在子目录中才可以。
  14. 获取文件列表
    • aux_source_directory( )获取dir目录当中的所有源文件名称并保存在自定义变量variable当中,该命令的主要作用是避免手动写出一堆的文件名称。例如我们需要使用100个源文件构建成一个库文件或可执行文件,我们只需要将所有源文件放在一个目录中并使用该命令获取到文件列表,然后将该列表传递给add_libraryadd_executable即可。
  15. 流程控制
    • 流程控制内容非常多,基本的包括逻辑判断、循环等,等后面有时间会慢慢进行补充,需要的同学可以先参考:https://cmake.org/cmake/help/v3.12/manual/cmake-commands.7.html

CMake是一个庞大的项目,包含非常多的内容,本文仅仅是介绍了自己在项目当中使用到的一部分,对于CMake个人也是处于不断学习和自我纠正的过程中,如果文档有错误或不妥之处,还希望大家能给予批评指正,我将不胜感激。

你可能感兴趣的:(CMake)