cmake 是一个跨平台的自动构建工具, cmake 是为了解决直接使用 make+Makefile 这种方式无法实现跨平台的问题,所以 cmake 是可以实现跨平台的编译工具。即:cmake 就是用来产生 Makefile 的工具,解析 CMakeLists.txt 自动生成 Makefile。

一、cmake的安装

  cmake 的官网是https://cmake.org/
  cmake 就是一个工具命令,在 Ubuntu 系统下通过 apt-get 命令可以在线安装,如下所示:

sudo apt-get install cmake

  安装完成之后可以通过 cmake --version 命令查看 cmake的版本号。

二、单一源文件

  如果只有一个main.c源文件,需要新建一个CMakeLists.txt文件,CMakeLists.txt文件会被cmake工具解析,就好比是Makefile文件会被 make 工具解析一样; CMakeLists.txt 创建完成之后,在文件中写入如下内容

project(HELLO)
add_executable(hello ./main.c)

  当前工程目录结构如下所示:

├── CMakeLists.txt
└── main.c

  在工程目录下有两个文件,源文件 main.c 和 CMakeLists.txt,接着我们在工程目录下直接执行cmake 命令,如下所示:

cmake ./

  cmake 后面携带的路径指定了 CMakeLists.txt 文件的所在路径。执行完 cmake 之后,除了源文件 main.c 和 CMakeLists.txt 之外,还生成了Makefile 文件, 有了 Makefile 之后,可以使用 make 工具编译工程。
现在来看看 CMakeLists.txt 文件中写的都是什么意思。
  第一行 project(HELLO)
  project 是一个命令, 命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号,并且通常需要我们提供参数,多个参数使用空格分隔而不是逗号“,” 。project 命令用于设置工程的名称, 括号中的参数 HELLO 便是我们要设置的工程名称。
  第二行 add_executable(hello ./main.c)
  add_executable 同样也是一个命令,用于生成一个可执行文件, 在本例中传入了两个参数,第一个参数表示生成的可执行文件对应的文件名,第二个参数表示对应的源文件; 所以 add_executable(hello ./main.c)表示需要生成一个名为 hello 的可执行文件,所需源文件为当前目录下的 main.c。
  使用 out-of-source 方式构建
  为了将构建过程生成的文件与源文件分离开来, 不让它们混杂在一起,也就是使用 out-of-source 方式构建。将 cmake 编译生成的文件清理下,然后在工程目录下创建一个 build 目录,如下所示:

├── build
├── CMakeLists.txt
└── main.c

  然后进入到 build 目录下执行 cmake:

cd build/
cmake ../
make

  这样 cmake 生成的中间文件以及 make 编译生成的可执行文件就全部在 build 目录下了,如果要清理工程,直接删除 build 目录即可。

三、多个源文件

  如果加入一个 hello.h 头文件和 hello.c 源文件,和main.c文件组成工程目录结构如下所示:

├── build //文件夹
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c

  CMakeLists.txt 文件内容为:

project(HELLO)
set(SRC_LIST main.c hello.c)
add_executable(hello ${SRC_LIST})

  进入到 build 目录下,执行 cmake、再执行 make 编译工程,最终就会得到可执行文件 hello。
  在CMakeLists.txt 文件中使用到了 set 命令, set 命令用于设置变量,如果变量不存在则创建该变量并设置它;在本例中,我们定义了一个 SRC_LIST 变量, SRC_LIST 变量是一个源文件列表, 记录生成可执行文件 hello 所需的源文件 main.c 和 hello.c,而在 add_executable 命令引用了该变量; 当然我们也可以不去定义 SRC_LIST 变量,直接将源文件列表写在 add_executable 命令中,如下:

add_executable(hello main.c hello.c)

四、生成库文件

  除了生成可执行文件 hello 之外,我们还需要将 hello.c 编译为静态库文件或者动态库文件,对 CMakeLists.txt 文件进行修改,如下所示:

project(HELLO)
add_library(libhello hello.c)
add_executable(hello main.c)
target_link_libraries(hello libhello)

  进入到 build 目录下,执行 cmake、再执行 make 编译工程,编译完成之后,在 build 目录下就会生成可执行文件 hello 和库文件。目录结构如下所示:

├── build
│ ├── hello
│ └── liblibhello.a
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c

  add_library 命令用于生成库文件,第一个参数表示库文件的名字。在 Linux 系统中,库文件的前缀是 lib,动态库文件的后缀是.so,而静态库文件的后缀是.a; 所以,意味着最终生成的库文件对应的名字会自动添加上前缀和后缀。第二个参数表示库文件对应的源文件。如果要生成动态库文件,可以这样做:

add_library(libhello SHARED hello.c) #生成动态库文件
add_library(libhello STATIC hello.c) #生成静态库文件

target_link_libraries 命令为目标指定依赖库。

五、不同源文件不同目录

  如果目录结构如下所示:

├── build #build 目录
├── CMakeLists.txt
├── libhello
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c

  在工程目录下有 src 和 libhello 目录,并将 hello.c 和 hello.h 文件移动到 libhello 目录下,将main.c 文件移动到 src 目录下,并且在顶层目录、 libhello 目录以及 src 目录下都有一个 CMakeLists.txt 文件。
  顶层 CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(HELLO)
add_subdirectory(libhello)
add_subdirectory(src)

  src 目录下的 CMakeLists.txt

include_directories(${PROJECT_SOURCE_DIR}/libhello)
add_executable(hello main.c)
target_link_libraries(hello libhello)

  libhello 目录下的 CMakeLists.txt

add_library(libhello hello.c)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

  顶层 CMakeLists.txt 中使用了 add_subdirectory 命令, 该命令告诉 cmake 去子目录中寻找新的CMakeLists.txt 文件并解析它;而在 src 的 CMakeList.txt 文件中,新增加了 include_directories 命令用来指明头文件所在的路径,并且使用到了 PROJECT_SOURCE_DIR 变量,该变量指向了一个路径,该变量表示工程源码的目录。
  进入到 build 目录下进行构建、编译,最终会得到可执行文件 hello(build/src/hello)和库文件 libhello.a(build/libhello/libhello.a):

├── build
│ ├── libhello
│ │ └── libhello.a
│ └── src
│ └── hello
├── CMakeLists.txt
├── libhello
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c