前言
cmake是一个构建工具,它有如下特点:
1、基于类BSD许可协议发布,http://cmake.org/HTML/Copyright.html
2、垮平台的,通过它可生成native编译配置文件,在Linux/Unix 平台,生成makefile ,在苹果平台,可以生成xcode ,在Windows平台,可以生成MSVC的工程文件。
3、简化编译构建过程和编译过程,Cmake 的工具链非常简单:cmake+make 。
它的缺点:
1、cmake的构建需要CmakeLists.txt文件的支持,需要重新学习cmake编程语法,有新的学习成本
2、cmake 跟已有体系的配合并不是特别理想,比如pkgconfig
3、如果使用的是C/C++/Java之外的语言,请不要使用cmake( 至少目前是这样)
安装
cmake 目前已经成为各大Linux发行版提供的组件,如果系统中没有cmake工具可以访问官网下载源码以及针对各种不同操作系统的二进制程序进行安装,官网地址:
http://www.cmake.org/HTML/Download.html
这里以Mac OS X为例,安装cmake工具:
1、首先安装Homebrew
ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"
2、然后安装cmake
brew install cmake
我这里使用的环境为:
Mac os x 10.14.4
cmake 3.15.3
本文目标
1、构建一个简单的工程
2、构建包含多个源文件的工程
3、构建包含多个目录的工程
4、外部构建
1、构建一个简单的工程
本例子在目录sample1下。
首先创建一个main.cpp文件和CMakeLists.txt(大小写敏感)文件,main.cpp中实现一个指数函数,并根据输入求出结果,最终目录如下:
sample1
|
+--- main.cpp
|
+--- CMakeLists.txt
main.cpp文件内容:
#include <stdio.h>
#include <stdlib.h>
double mypower(double base, int exp)
{
int result = base;
int i;
if (exp == 0) {
return 1;
}
for(i = 1; i < exp; i++){
result = result * base;
}
return result;
}
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exp \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exp = atoi(argv[2]);
double result = power(base, exp);
printf("%g ^ %d is %g\n", base, exp, result);
return 0;
}
CMakeLists.txt文件内容如下:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息 名字随便写
project (sample1)
message(STATUS "This is BINARY dir " ${sample1_BINARY_DIR})
message(FATAL_ERROR "This is SOURCE dir "${sample1_SOURCE_DIR})
# 定义源文件列表
set(SRC_LIST main.cpp)
# 指定生成目标 目标名字随便写,和project指定的名字没有必然联系;${SRC_LIST}代表前面定义的源文件列表变量
add_executable(sample1 ${SRC_LIST})
根目录下的CMakeLists.txt文件必须要有cmake_minimum_required (VERSION 2.8)和project (sample1)两项,子目录下的可以省略
在sample1目录执行命令 cmake .
注意:cmake命令后面的 . 号,代表基于当前目录下的CMakeLists.txt进行构建,如果执行cmake命令时与CMakeLists.txt不在同一个目录,则这里的.号要换成CMakeLists.txt所在路径)
默认将会在当前目录生成如下中间文件和文件夹
cmake_install.cmake
CMakeCache.txt
CMakeFiles
Makefile
接着执行命令 make,会在当前目录下生成sample1的可执行程序
编译的中间文件main.o在CMakeFiles目录下的DemoProject.dir中
这种直接在CMakeLists.txt所在目录执行cmake命令的构建方式称为内部构建(in-source build)
2、构建包含多个源文件的工程
现在把 power 函数单独写进一个名为 Math.cpp 的源文件里,使得这个工程变成如下的形式:
./sample2
|
+--- main.cpp
|
+--- Math.cpp
|
+--- Math.h
main.cpp文件内容如下:
#include <stdio.h>
#include <stdlib.h>
#include "Math.h"
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
double result = power(base, exponent);
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
CMakeLists.txt如下:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 2.8)
# 项目信息,随便取名
project(MyProject)
message(STATUS "This is BINARY dir " ${MyProject_BINARY_DIR})
message(STATUS "This is SOURCE dir " ${MyProject_SOURCE_DIR})
# 可执行文件名
add_executable(DemoProject main.cpp Math.cpp)
如果源文件很多,那add_executable()里面将要写一长串,可以用另外一种方式实现,用aux_source_directory()内建函数指定源文件路径,此函数将自动读取这个路径下的所有.c,.cpp,.mm等结尾的文件作为源文件,如下:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 2.8)
# 项目信息,随便取名
project(MyProject)
message(STATUS "This is BINARY dir " ${MyProject_BINARY_DIR})
message(STATUS "This is SOURCE dir " ${MyProject_SOURCE_DIR})
# 查找当前目录下的所有源文件 并将名称列表保存到DIR_SRC 变量中
# (它会查找目录下的.c,.cpp ,.mm,.cc 等等C/C++语言后缀的文件名)
aux_source_directory(. DIR_SRC)
# 可执行文件名 ${DIR_SRC}代表前面定义的源文件列表
add_executable(DemoProject ${DIR_SRC})
3、构建包含多个目录的工程
现在将Math.h和Math.cpp放入Math目录下,目录如下:
./sample3
|
+--- main.cpp
|
+--- Math/
|
+--- Math.cpp
|
+--- Math.h
如果main.cpp文件中依然是#include"Math.h"方式引入头文件肯定会出现找不到头文件的错误,两种解决办法,一种是在main.cpp中通过#include "Math/Math.h"指定具体路径 另外一种可以在CMakeLists.txt中通过include_directories()指定头文件的搜索路径
那么CMakeLists.txt就要如下写了:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 2.8)
# 项目信息,随便取名
project(MyProject)
message(STATUS "This is BINARY dir " ${MyProject_BINARY_DIR})
message(STATUS "This is SOURCE dir " ${MyProject_SOURCE_DIR})
set(ROOT_DIR ${CMAKE_SOURCE_DIR})
# 定义头文件搜索路径
include_directories(${ROOT_DIR}/Math)
# 查找当前目录下的所有源文件 并将名称列表保存到DIR_SRC 变量中
# (它会查找目录下的.c,.cpp ,.mm,.cc 等等C/C++语言后缀的文件名)
aux_source_directory(. DIR_SRC)
# 查找其它目录下的源文件
aux_source_directory(Math DIR_SRC2)
# 可执行文件名 ${DIR_SRC} ${DIR_SRC2} 代表前面定义的源文件列表
add_executable(DemoProject ${DIR_SRC} ${DIR_SRC2})
4、外部构建
前面在执行cmake指令时都是在当前目录(源码所在目录),这样构建的中间文件(cmake_install.cmake,CMakeCache.txt,CMakeFiles,Makefile等等)就对源码造成了污染,这种构建方式也称为内部构建(in-of-sourcebuild)
外部构建(out-of-sourcebuild)
创建sample4,创建外部构建目录build(也可以是其它目录,不一定非得在工程目录中创建),如下:
./sample4
|
+--- main.cpp
|
+--- CMakeLists.txt
+--- build/
然后进入build目录,依次执行命令 cmake ..;make
可以看到构建的中间文件以及可执行程序都再build目录下了,这种构建方式就称为外部构建
外部构建时cmake xxx 这里xxx表示外部源码根目录下CMakeLists.txt的路径,上面..代表父目录
5、构建时自带参数
通过-DKEY_XXX=val 的形式自带构建时的参数,它的作用等价于set(KEY_XXX val),KEY_XXX既可以是内建cmake变量也可以是自定义的变量,如果是自定义变量,那么CMakeLists.txt内可以直接使用
创建如下CMakeLists.txt内容,直接输出cmake构建时传入的变量:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息 名字随便写
project (sample1)
message(STATUS "This is BINARY dir " ${sample1_BINARY_DIR})
message(STATUS "This is SOURCE dir "${sample1_SOURCE_DIR})
# CUSTOM_BUILD_ARGcmake构建时自带变量
message(STATUS "This is build arg "${CUSTOM_BUILD_ARG})
# 定义源文件列表
set(SRC_LIST main.cpp)
# 指定生成目标 目标名字随便写,和project指定的名字没有必然联系;${SRC_LIST}代表前面定义的源文件列表变量
add_executable(sample1 ${SRC_LIST})
执行构建命令 cmake . -DCUSTOM_BUILD_ARG=arg 则输出
-- This is BINARY dir /Users/apple/devoloper/mine/cmake/sample1/build
-- This is SOURCE dir /Users/apple/devoloper/mine/cmake/sample1
-- This is build arg
涉及的cmake语法讲解
首先CmakeLists.txt文件是大小写相关的,而指令是大小写无关的,比如
cmake_minimum_required(VERSION 2.8)和CMAKE_MINIMUM_REQUIRED(VERSION 2.8)都可以
上面三个例子涉及到的cmake语法指令有:
1、CMAKE_MINIMUM_REQUIRED(VERSION XXX)
表示构建此工程所需要的最低的cmake的版本
2、PROJECT(projectname [CXX] [C] [Java])
定义工程名和指定工程支持的语言,默认支持所有语言。工程名会影响内建变量_BINARY_DIR和_SOURCE_DIR,他们分别表示生产的可执行程序路径和源代码路径,如上的 MyProject_BINARY_DIR和MyProject_SOURCE_DIR,对于内部构建(in-of-source),这两个路径是同一个,对于外部构建(out-of-source)他们不一样
此外cmake也提供了一个更方便的内建变量PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR分别表示编译后的二进制路径和源代码路径,即使修改了工程名称,也不会影响这两个变量
3、MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)
这个指令用于打印相关信息;
- SEND_ERROR,产生错误,生成过程被跳过。
- SATUS,输出相关信息信息。
- FATAL_ERROR,立即终止所有 cmake 过程。
4、SET(VAR [VALUE] [[CACHE <type> <docstring> [FORCE]] | PARENT_SCOPE])
用来显式的定义变量,比如SET(SRC_LIST main.cpp Math.cpp)定义多个源文件列表,不同文件之间用空格分开或者分号分开
如果指定了PARENT_SCOPE选项,变量<variable>将会被设置为当前作用域之上的作用域中。每一个新的路径或者函数都可以创建一个新作用域。该命令将会把一个变量的值设置到父路径或者调用函数中(或者任何类似的可用的情形中。)
5、INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
定义头文件搜索路径
6、aux_source_directory(Math DIR_SRC2)
查找当前目录下的所有源文件 并将名称列表保存到DIR_SRC 变量中,
(它会查找目录下的.c,.cpp ,.mm,.cc 等等C/C++语言后缀的文件名)
7、ADD_EXECUTABLE(DemoProject ${SRC_LIST})
代表构建一个名为DemoProject的可执行文件,其源文件列表为变量SRC_LIST所指向的文件列表,在本例我们使用了${}来引用变量,这是cmake的变量应用方式,但是,有一些例外,比如在IF控制语句,变量是直接使用变量名引用,而不需要${}。如果使用了${}去引用变量,其实IF会去判断名为${}所代表的值的变量,那当然是不存在的了。
8、CMAKE_SOURCE_DIR
代表工程根目录CMakeLists.txt文件所在目录
9、使用变量
对于用set()函数定义的变量,在除了IF()语句的地方 使用${变量名}引用,IF语句中直接使用变量名来引用IF(变量名)
总结
1、cmake 的语法还是比较灵活而且考虑到各种情况,比如SET(SRC_LIST main.c) 也可以写成SET(SRC_LIST “main.c”) 是没有区别的,但是假设一个源文件的文件名是fu nc.c( 文件名中间包含了空格)。这时候就必须使用双引号
2、跟经典的autotools 系列工具一样,运行:make clean即可清理make编译产生的中间编译文件,但是无法清除cmake产生的中间编译文件;make distclean 指令也无效
3、所以cmake构建工程时尽量用外部构建方式
4、可以通过 cmake -GXXX指定 cmake要生成的构建文件,如果不指定 那么将构建 unix makefile,如果为-GXcode 那么将生成Xcode平台用的文件,如果为-GNinja 那么将生成build.ninja文件(比如android studio使用的文件)
参考文章
https://www.hahack.com/codes/cmake/
https://www.kancloud.cn/itfanr/cmake-practice/82983
https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android-with-the-ndk
网友评论