按:

这个系列与 cmake 有关的记录也好,笔记也好,成书也好,目前暂且归属在 cmake-hello 标记之下。

cmake 的历史已有多年。

cmake 有关的各类文档无分中英也可以搜索得到很多资源。

cmake 的中文资料,基本上找不到成系列,不过时的内容。随着 cmake 不断发行新版本,这两年我突然发现,我早年使用cmake的方法已经过时,很多用法已经被视为不恰当,由此,更新自己的遗留的认知竟然成为有必要的待办事项了。

所以我考虑是不是应该在这方法下点功夫。

由于尚未真的针对这些情况进行思考,所以当下暂且先以笔记的形式进行记录和罗列,并对已有的记忆重新做一次梳理。而 cmake 相当庞大,所以估计这个笔记也会成为一个系列,不断追加。

Install CMake

我很服气 cmake 当前的维护者(Kitware),是个狼灭,每天更新超多的。当然这也造成了目前 cmake 的版本迭代非常要命,多而且密集。所以没有正确的视角的话,你对 cmake 所做的衍生文档很可能很快就已无效错漏。

也就想到 CSDN 或者自媒体了,21世纪开端30年都是垃圾制造者狂欢的盛宴呐。

macOS

可以使用 HomeBrew 安装

1
brew install cmake

或者,安装 CLion 则内置 cmake。

Debian,RedHat

Debian 系或者 RedHat 等 Linux 发行版多半采用包管理器进行安装:

1
2
apt install cmake
yum install cmake

安装 KDevelop 或者 Qt 相关 IDEs 时往往已经自动安装了 cmake,所以你应该首先

Windows

TODO

最新的 Visual Studio 好像是已经内置了 cmake,由于它太巨大了(好像是有 Community 版本可以免费下载),而我的本本已经坏了,所以暂时不得而知。

MSYS,TODO

cygwin,TODO

小节

如果你对这里略过的话题感兴趣,请参考 Installing - CMake ,他们家讲得略完整,也包括从源码构建的有关说明。或者看看 2.1. CMake Installation — CGold 0.1 documentation ,这是另一家人关于安装 cmake 提到的内容。

开始使用

对于 CMake 来说,我们首先有一个顶级的项目文件夹 ProjectDir,该文件夹中包含一个 CMakeLists.txt 文件,其中定义了顶级的 CMake 操作。

在这个 CMakeLists.txt 中,我们可以通过 CMake 宏指令包含任何子目录,只要该子目录中也有一个 CMakeLists.txt 实行了 CMake 指令描述。通过这样的嵌套结构,我们可以建立多级的项目、子项目结构。

在每个 CMakeLists.txt 文件中,我们可以定义一个(子)项目,并加入若干个构建目标(Target),当然也可以什么都不做。

一个构建目标,通常是可执行文件,动态库,静态库等等。

First Hello

首先我们尝试建立一个控制台应用程序 hello。

这是一个极简的例子,只需要在 Linux/macOS 终端中就可以建立:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mkdir hello-1 && cd $_

cat >main.cc<<EOF
#include <iostream>

int main() {
  std::cout << "Hello CMake!\n";
}
EOF

cat >CMakeLists.txt<<EOF
project (hello)

set(SRC_LIST main.cc)

message(STATUS "This is BINARY dir " \${hello_BINARY_DIR})
message(STATUS "This is SOURCE dir " \${hello_SOURCE_DIR})

add_executable(hello \${SRC_LIST})
EOF

而且,也只需要在终端中就可以完成构建任务并运行可执行文件:

Now, the hello project was created and ready for building.

We can follow the standard cmake buiding instruments and build hello:

1
2
3
4
mkdir build && cd $_
cmake ..
cmake --build .
./hello

这个例子的完整结构可以在 z01-hello-1 中被找到。

早期版本

在 CMake 的早期版本中,使用的是 make 来完成构建动作:

1
2
3
4
5
mkdir build && cd $_
cmake ..
# make && make install    # Or: make && sudo make install
make  # 在开发过程中一般不要 make install 将构建好的目标复制到安装的目的地
./hello

解释

在上面的终端命令行中,我们手工构造了 CMakeLists.txt

1
2
3
4
5
6
7
8
project(hello)

set(SRC_LIST main.cc)

message(STATUS "This is BINARY dir " ${hello_BINARY_DIR})
message(STATUS "This is SOURCE dir " ${hello_SOURCE_DIR})

add_executable(hello ${SRC_LIST})
project

PROJECT(projectname [C] [CXX] [JAVA]) 指令用于定义一个项目。原则上说每个 CMakeLists.txt 可以定义一个项目,当使用 add_subdirectory 宏时可以包含子目录中的子项目。然而实际上,如果你愿意,是可以在同一个文件中定义多个项目的,每一条 PROJECT 指令开始一段作用域,直到遇到下一条 PROJECT 为止。同时,所有的构建目标定义(例如 add_executable),以及所有的其它 CMake 脚本指令,都归属到相应的 PROJECT 作用域中。

PROJECT 指令在开始 projectname 项目的作用域的同时,也隐含地定义 <projectname>_BINARY_DIR<projectname>_SOURCE_DIR 变量。同时 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR 变量也会被定义,且与 <projectname>_BINARY_DIR<projectname>_SOURCE_DIR 变量相同。使用哪一套变量取决于你的喜好,但为了省力,我们推荐 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR 这一套。

add_executable

add_executable 指令用于指定一个可执行文件类型的构建目标,该可执行文件的文件名将会是 hello,归属于同名的项目之下(但并非一定要同名)。

set, list

SET 指令可以定义一个变量,以后通过 ${VariableName} 的语法可以展开它。

但是,如果你在 IF (VariableName) 指令中检测一个变量时,不要使用 `${} 语法。其原因在于你想用 IF 检测的是该变量。

SET 指令的语法格式为 SET(VAR [Value] [Cache Type DocString [FORCE]])

对于 SET(VARNAME a b c d) 来说,SET 指令为变量名 VARNAME 指定一个字符串 a b c d 作为其值 Value。

CMake 指令语法

指令(参数1 参数2 ...)

CMake 指令采用上述的通用语法结构。其中,参数之间使用空格或者分号间隔。所以 ADD_EXECUTABLE(hello main.c func.c)ADD_EXECUTABLE(hello main.c;func.c) 是等价的。

在很多场合值当中的空格(或者分号)隐含着切分字符串为数组的功能。当我们用强类型视角来观察时,你也可以将它们组合起来看成一个单个的字符串。

注意,对于 SET(VARNAME "a b" "c" "d") 来说,引号的作用是排除不必要的空格歧义,但最终 VARNAME 的值将会是将三个字符串片段用单个空格 Join 之后的字符串值。

SET 中,值的定义、切分、组合的特性,实际上和 Bash 的数组是较为相似的,并且这个特点在任何需要值定义的地方都是相同的,例如 MESSAGE(STATUS text) 中的 text 值定义部分。

如果你想要定义一个数组类型的变量,可以使用 LIST(APPEND VAR ...) 指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
list(APPEND SC14R_INCS
     ${PROJECT_SOURCE_DIR}
     ${PROJECT_SOURCE_DIR}/include
     ${PROJECT_SOURCE_DIR}/src
     ${PROJECT_SOURCE_DIR}/src/include
     ${CMAKE_CURRENT_SOURCE_DIR}/include

     # Generated files
     ${CMAKE_BINARY_DIR}/generated

     # Generated includes
     ${CMAKE_BINARY_DIR}
     ${CMAKE_BINARY_DIR}/src
     )
message

MESSAGE([STATUS|SEND_ERROR|FATAL_ERROR] text) 指令会将 text 输出到标准输出设备,这是 CMake 的调试特性。

小节

第一篇笔记,就介绍一个开始就够,不过我认为个人的理解反而有必要看看,内容并非纯粹的 getting started,还是尽力描述了完完全全的初学者所想要知道的内容。

稍后再考虑这组笔记单独列举,因为那样的话在线阅读和检索可能会更方便一些。再说吧。

🔚