本文转载自yaoyuanyylyy CMake入门1——CMake与VS编译器和nmake的结合使用 CMake入门2——复杂项目的构建配置 CMake入门3——更多指令的使用 本文描述在Win10-64位系统中安装CMake、cmake-gui的简单使用及其与VS2015编译器的结合使用,以及cmake命令行与nmake的结合使用。
下载
从CMake官网上下载最新版本的安装包,本文中下载的是 cmake-3.10.1-win64-x64.msi。若不想安装,可直接下载压缩包版本cmake-3.10.1-win64-x64.zip。
安装
CMake的安装非常简单。压缩包版本直接解压即可,安装版本也可一直点击下一步进行安装。为了使用方便,可将CMake配置到环境变量中,当然,这一步也可以在安装过程中设置。另外需要注意的是为了避免不必要的麻烦,路径中最好不要包括中文字符。下面简单描述一下安装过程。
- 双击下载的安装包,进入安装界面,点击[Next]。
- 勾选同意许可后,点击[Next]。
- 可在此处选择将CMake路径添加到环境变量中,并且创建CMake GUI程序的桌面快捷方式。然后点击[Next]。
- 可在此处自定义安装目录,设置好后点击[Next]。
- 开始安装,安装完成后点击[Next]。
- 安装完成,点击[Finish],结束安装过程。
- 安装完成后,即可使用CMake了。在命令行中输入
"cmake --version"
,可看到如下图所示的输出。
例1 使用cmake-gui与VS生成器
本例使用cmake-gui和VS生成器构建出VS工程,然后用VS工具打开项目进行编译。
- 新建CMakeTest目录,在CMakeTest目录下新建demo1目录,作为测试项目的根目录。在demo1下新建main.cpp和CMakeLists.txt文件。
main.cpp文件的内容很简单,就是输出一个提示字符串:
1
2
3
4
5
6
7#include <iostream>
using namespace std;
void main()
{
cout<<"cmake test demo 1"<<endl;
getchar();
}
CMakeLists文件内容如下:
1
2
3
4
5
6# CMake最低版本要求
cmake_minimum_required(VERSION 3.10.1)
# 项目信息
project(demo1)
# 指定生成目标
add_executable(demo main.cpp)
CMakeLists.txt 的语法比较简单,由命令、注释和空格组成。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔,且命令是不区分大小写的。符号 # 后面的内容被认为是注释。 对于上面的 CMakeLists.txt 文件,依次出现了几个命令:
- cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本,如果低于3.10.1版本,则构建过程会被终止
- project:参数值是 demo1,该命令表示项目的名称是 demo1
- add_executable: 将名为 main.cpp 的源文件编译成一个名称为 demo 的可执行文件
- 打开cmake-gui程序,在上方配置两个目录:
- 第一个是CMakeLists文件目录,cmake据此开始执行
- 第二个是cmake构建后的项目目录
- 配置好点击[Configure]开始执行配置过程。首先会弹出编译器选择界面,此处使用默认的VS2015-32位编译器,直接点击[Finish],开始配置。
- 第一次配置完成后如图所示,红色的两行是新的配置值,再次点击[Configure]即可。
- 配置完成后,在build目录生成了如下的文件
- 点击[Generate],开始生成VS项目
- 生成完成后,在build目录下生成了demo1对于的VS项目
- 通过点击cmake-gui中下放的[Open Project]按钮或者直接双击build目录下的demo1.sln,即可在VS中打开工程。
- cmake生的demo1工程总共有3个项目
- ZERO_CHECK:该项目会检查生成工程的 CMake 配置文件( CMakeLists.txt )是否更新。如更新,将运行 CMake 重新生成工程文件。如果确信 CMakeLists.txt 不会被更新,或者希望手工运行 CMake 重新生成工程文件,可以在 CMakeLists.txt 配置文件中添加 set(CMAKE_SUPPRESS_REGENERATION FALSE) 命令, ZERO_CHECK 目标将不会生成。
- ALL_BUILD:该目标会导致工程中所有项目被构建,类似 Visual Studio 的 Build All 或者 make 的 make all命令。
- demo:项目本身,就是在CMakeLists.txt文件中配置的
project(demo1)
- 在VS中通过单独生成项目demo1或者直接生成项目All_BUILD后,可以在build下的相应目录看到可执行文件demo.exe已经生成。双击demo.exe后,可以看到程序输出。
- 如果不想用VS打开工程,也可以使用msbuild.exe编译demo1工程。首先需要打开VS命令行,因为cmake-gui中选择的编译器是32位的,所以此处选择”VS2015 x86 本机工具命令提示符”。
打开后在命令行中进入到demo1的build目录,执行
msbuild demo1.sln
即可编译demo1项目。编译完成后可以在debug中看到生成的demo.exe文件,双击可运行。
例2 使用cmake命令与nmake生成器 本例使用cmake命令行与nmake生成器构建Makefile,然后用nmake编译项目。
- 新建项目demo2,将demo1中的main.cpp和CMakeLists.txt文件拷贝过来。将CMakeLists.txt文件中的project项目配置为demo2。 2. 同样从VS命令行进入到demo2目录,执行命令:
1
cmake -G "NMake Makefiles" .
执行完毕后,即可看到在demo2目录中生成了一些文件:
其中重要的是Makefile文件,这是用来编译项目的。在命令行中继续执行nmake命令开始编译,编译完成后可以看到在demo2目录生成了demo.exe程序。允许后可以看到输出。
例3 PreLoad.cmake用法
在命令行下使用cmake,需要用”-G”选项指定生成器。每次都输入感觉比较麻烦,可以将其配置在”PreLoad.cmake”文件中。新建项目demo3,准备好main.cpp和CMakeLists.txt文件,然后demo3目录下新建文件”PreLoad.cmake”,输入:
1
set(CMAKE_GENERATOR "NMake Makefiles" CACHE INTERNAL "" FORCE)
然后执行"cmake ."
即可配置,然后执行"nmake"
就可以编译。
例4 多文件单目录测试
此测试项目将所有的文件都放在根目录下。
在CMakeTest目录下新建demo4目录。demo4项目文件列表如下:
其中实现了算术运算的加减乘除,每个功能分别实现在各自的文件中:
1
2
3
4int my_add(int a, int b);
int my_minus(int a, int b);
int my_multiply(int a, int b);
int my_divide(int a, int b);
然后在main.cpp中调用各个函数,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using namespace std;
void main()
{
cout<<"cmake test demo 4"<<endl;
int a=20;
int b=10;
cout<<a<<"+"<<b<<"="<<my_add(a, b)<<endl;
cout<<a<<"-"<<b<<"="<<my_minus(a, b)<<endl;
cout<<a<<"*"<<b<<"="<<my_multiply(a, b)<<endl;
cout<<a<<"/"<<b<<"="<<my_divide(a, b)<<endl;
getchar();
}
CMakeLists.txt文件内容如下:
1
2
3
4
5
6
7
8
9
10
11# CMake最低版本要求,如果低于3.10.1版本,则构建过程会被终止
cmake_minimum_required(VERSION 3.10.1)
# 项目信息
project(demo4)
# 查找当前目录下的所有源文件,并将名称保存到DIR_SRCS变量中
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(demo ${DIR_SRCS})
此配置文件中使用了一条新命令:
- aux_source_directory:该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。这样,就不用在add_executable中列出所有源文件了,而是直接引用变量即可。
从VC命令行进入到demo4目录,然后执行cmake命令和nmake命令,即可编译出执行程序。
例5 多文件单子目录测试
此测试项目将main入口函数文放在根目录下,其他文件放在独立目录下。
新建demo5目录,使用例3中的项目文件,将main.cpp放在demo5目录下,将其他的加减乘除的相关文件放在math子目录下。然后在demo5目录和math目录下分别放一个CMakeLists.txt文件。
其中demo5目录下的CMakeLists.txt文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# CMake最低版本要求,如果低于3.10.1版本,则构建过程会被终止
cmake_minimum_required(VERSION 3.10.1)
# 项目信息
project(demo5)
# 添加头文件目录
include_directories(math)
# 查找当前目录下的所有源文件,并将名称保存到DIR_SRCS变量中
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(demo ${DIR_SRCS})
# 添加math子目录
add_subdirectory(math)
# 添加链接库
target_link_libraries(demo math)
在这个配置文件中,依次出现了几条新的cmake命令:
- include_directories:头文件包含目录,因为main.cpp中使用了加减乘除函数,因此需要指明头文件目录所在之处。当然,也可以在main.cpp中包含都文件时指定目录
- add_subdirectory:添加子目录命令,表示需要编译该子目录
- target_link_libraries:添加要依赖的库文件,此处的math库是在子目录math中编译出来的 math目录下的CMakeLists.txt文件内容如下:
1
2
3
4
5
6
7
8# CMake最低版本要求,如果低于3.10.1版本,则构建过程会被终止
cmake_minimum_required(VERSION 3.10.1)
# 查找当前目录下的所有源文件,并将名称保存到DIR_SRCS变量中
aux_source_directory(. DIR_LIB_SRCS)
# 指定生成目标
add_library(math ${DIR_LIB_SRCS})
在上面这个配置文件中,出现了一条新命令:
- add_library:将指定的文件编译为库,前面是库名称,后面是源文件 进demo5目录,然后执行cmake命令和nmake命令,即可编译出执行程序。可以看到,在math子目录下生成了一个”math.lib”的库文件。
例6 多目录和多级目录测试
本例子demo6中把加减乘除四个运算分散在不同的目录下,形成了多个目录、多级目录的形式。具体的文件布局如下图所示:
多个子目录和多级目录在用cmake来构建时的一种方式是:将所有源文件按功能或目录来分别构建为库,然后在需要用到库的地方链接上所需的一个或多个库。
对于此例来说,各目录的说明和构建方式如下:
- demo6根目录下包括了math和divide两个目录;
- divide目录是除法目录,由其目录中的配置文件单独构建为一个库;
- math目录包括了add_minus和multiply两个目录;
- add_minus目录包括加法的实现文件和减法目录minus;
- 减法不单独构建为库,而是和加法一起,用add_minus中的配置文件构建为一个库;
- 乘法的库也不再它自己的目录中构建,而是在math目录下构建为单独的乘法库;
- 最后,在demo6根目录下的配置文件中构建demo.exe程序,并连接了乘法库、除法库、加减法合在一起的库;
首先,divide目录下构建除法库的配置文件比较简单,使用了
aux_source_directory
和add_library
两条指令。
1
2
3
4
5
6
7
8# CMake最低版本要求,如果低于3.10.1版本,则构建过程会被终止
cmake_minimum_required(VERSION 3.10.1)
# 查找当前目录下的所有源文件,并将名称保存到DIR_LIB_DIVIDE_SRCS变量中
aux_source_directory(. DIR_LIB_DIVIDE_SRCS)
# 指定生成目标
add_library(lib_divide ${DIR_LIB_DIVIDE_SRCS})
其次,加减法库的构建如下:
1
2
3
4
5
6
7
8
9
10# CMake最低版本要求,如果低于3.10.1版本,则构建过程会被终止
cmake_minimum_required(VERSION 3.10.1)
# 查找当前目录下的所有源文件,并将名称保存到DIR_MINUS_SRCS变量中
aux_source_directory(./minus DIR_MINUS_SRCS)
# 把要编译的文件都列在此处,存入ADD_SRCS变量中
set(ADD_SRCS add.cpp)
# 指定生成目标
add_library(lib_add_minus ${ADD_SRCS} ${DIR_MINUS_SRCS})
这个也很简单,只是有3点小改变:
- aux_source_directory指令中指定的目录为
./minus
,表示当前文件夹下的minus
子目录,也就是把minus
子目录下的减法源文件存在了DIR_MINUS_SRCS
变量中 - 使用了一条新的指令set,这也是一条用来设置变量的,不过变量放在前面,变量的值放在后面。这里指定为将当前目录下的加法实现源文件存入变量
ADD_SRCS
中。需注意的是,后面的文件可以列出多个各处的文件 - 在add_library命令将前面的两个保存了加法和减法源文件的变量一起用来生成一个库
lib_add_minus
再次,math
目录下的配置文件如下:
1
2
3
4
5
6
7
8
9
10
11# CMake最低版本要求,如果低于3.10.1版本,则构建过程会被终止
cmake_minimum_required(VERSION 3.10.1)
# 添加子目录
add_subdirectory(add_minus)
# 把要编译的文件都列在此处,存入DIR_MULTIPLY_SRC变量中
set(DIR_MULTIPLY_SRC multiply/multiply.cpp)
# 指定生成目标
add_library(lib_multiply ${DIR_MULTIPLY_SRC})
在这个配置文件中,将multiply
目录下的乘法编译成了单独的一个库,然后添加了add_minus
子目录。所用的指令都是已使用过的。
最后,根目录demo6
下的配置文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24# CMake最低版本要求,如果低于3.10.1版本,则构建过程会被终止
cmake_minimum_required(VERSION 3.10.1)
# 项目信息
project(demo6)
# 添加头文件目录
include_directories(math/add_minus math/add_minus/minus math/multiply)
include_directories(divide)
# 查找当前目录下的所有源文件,并将名称保存到DIR_SRCS变量中
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(demo ${DIR_SRCS})
# 添加math子目录
add_subdirectory(math)
# 添加divide子目录
add_subdirectory(divide)
# 添加链接库
target_link_libraries(demo lib_add_minus)
target_link_libraries(demo lib_multiply lib_divide)
在这个配置文件中,有几点小说明:
- 这个根目录下的配置文件为cmake的入口配置文件,所以在其中指定了最低版本。这样其实在子目录下的配置文件中就可以不用再用
cmake_minimum_required
指令了 - 用
include_directories
指令指明了头文件所在目录,一条命令中可以指定多个头文件包含目录。在实际项目中可将头文件提取到一个目录中,然后指定一个目录就可以了 - 用
add_subdirectory
指明了divide
和math
两个子目录 - 用
target_link_libraries
将各个库连接到目标程序”demo”中,此命令也可以一次指定多个库。 至此,所有的工作就做好了,在命令下执行cmake命令和nmake命令,就可编译出执行程序。同时可以看到,在math子目录下生成了一个”lib_multiply.lib”库,在math/add_minus目录下生成了一个lib_add_minus.lib库,在divide目录下生成了lib_divide库。
基本语法规则
从前面的几个例子中,我们使用了cmake的几条基本指令,在这里,总结一下:
- cmake由指令、注释和空白字符组成
- 以#开头,到行末尾的是注释
- 形如
指令(参数1 参数2 参数3 ...)
的是指令,参数间使用空格或者分号;
隔开 - 指令不区分大小写,但参数是区分大小写的
- cmake中可以设置变量,变量的引用方式为**${变量名}**
- cmake的构建指令为**"cmake path [参数选项]";当前我们都使用的是“cmake .”**,表示构建当前目录下的项目
内部构建与外部构建
在前面的例子中,danger构建项目后,会发现cmake构建过程中产生的中间文件和项目文件混在一起,多了就不好区分;而且cmake不能跟踪这些中间文件,所以也没有一条可以清除它们的指令。 这样的情况是因为我们使用的是cmake的内部构建,而cmake提供的另外一种称为外部构建的构建方式,可以解决这个问题。
- 内部构建:
in-source build
,在项目的入口配置文件目录下执行**“cmake .”**时,就是内部构建;这种方式下,生成的临时中间文件就放在了构建目录下,导致和源文件混在一起 - 外部构建:
out-of-source build
,在其他目录下执行cmake构建命令,然后指定入口配置文件的目录,这样就可以将临时文件存放于单独的目录中。比如我们在项目下新建一个build目录,然后在build目录下执行**“cmake ..”**,那么我们就可以看到构建后的所有临时文件都产生在build目录中,这样就方便我们管理和清理了
例7 外部构建以及**"project"和"message"**指令
首先,我们新建一个项目demo7,其中的源文件和demo6的是一样的,唯一的区别只有demo7根目录下的CMakeLists.txt文件不同,我们添加了几条**"message"**指令,这是用来在构建过程中显示消息的。 配置文件的前面部分如下:
1
2
3
4
5
6
7
8
9
10
11
12
13# CMake最低版本要求,如果低于3.10.1版本,则构建过程会被终止
cmake_minimum_required(VERSION 3.10.1)
# 项目信息
project(demo7)
message(STATUS "the BINARY dir is ${PROJECT_BINARY_DIR}")
message(STATUS "the SOURCE dir is ${PROJECT_SOURCE_DIR}")
message(STATUS "the BINARY dir is ${demo7_BINARY_DIR}")
message(STATUS "the SOURCE dir is ${demo7_SOURCE_DIR}")
# ... 后面的没有改变,此处省略不显示
因为要采用外部构建的方式,因此我们先新建一个build目录,然后在build目录下执行cmake构建指令。过程如下:
可以从以一个红框中看到外部构建的一般过程。这里我们来看看第二个红框中的输出内容,这就是**"message"**指令的输出。
**"message"**指令的语法如下:
1
message([SEND_ERROR | STATUS | FATAL_ERROR] "message" ...)
第一个参数是消息类型,后面的参数是一条或多条要显示的消息。错误类型有3种:
- SEND_ERROR:表示产生错误信息
- STATUS:表示一般的状态信息
- FATAL_ERROR:我们知道肯定是严重错误信息,cmake会立即停止执行 参数个数:
- 一条消息显示指令后可以跟上多条消息,它们会依次连在一起进行显示 我们修改一下前面的配置文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# CMake最低版本要求,如果低于3.10.1版本,则构建过程会被终止
cmake_minimum_required(VERSION 3.10.1)
# 项目信息
project(demo7)
message(STATUS "------------------------------" "this is " "another message")
message(STATUS "the BINARY dir is ${PROJECT_BINARY_DIR}")
message(STATUS "------------------------------")
message(SEND_ERROR "the SOURCE dir is ${PROJECT_SOURCE_DIR}")
message(STATUS "------------------------------")
message(STATUS "the BINARY dir is ${demo7_BINARY_DIR}")
message(STATUS "------------------------------")
message(FATAL_ERROR "the SOURCE dir is ${demo7_SOURCE_DIR}")
message(STATUS "------------------------------")
在每条消息前后我们用分割线分割开,构建后,可以看到消息显示如下:
第一个红框中的是前两条消息,其中第一条消息的多个消息是连续显示的;第二个红框中的是”SEND_ERROR”类型的错误消息,cmake会提示错误所在的行,然后项目会继续构建;最后一个红框中的是”FATAL_ERROR”类型的消息,cmake首先也输出了错误消息,然后便中止了构建过程。
下面我们来关注一下这个配置文件中在message指令中使用的几个变量:
- ${PROJECT_BINARY_DIR}
- ${PROJECT_SOURCE_DIR}
- ${demo6_BINARY_DIR}
- ${demo6_SOURCE_DIR} 首先,"project" 指令在使用后会定义两个隐式的变量 "PROJECT_BINARY_DIR" 和 "PROJECT_SOURCE_DIR"。从输出的消息我们也可以看到,前者就是我们执行构建命令时所在的目录,也是我们的中间文件存放的目录;后者就是我们的配置文件和源文件所在的目录。 同时,因为用 "project" 指令将项目名称指定为 "demo7" ,所以这两个变量中的”PROJECT”部分也可以中”demo7来代替。但是,不推荐用这种方式。 另外,当我们使用内部构建时,这两个变量的值是一样的。 最后,这两个值和我们使用cmake-gui时,要设置的两个目录是相对应的。