cmake初识(一)

前言

cmake是一个构建工具,它有如下特点:
1、基于类BSD许可协议发布,http://cmake.org/HTML/Copyright.html
2、垮平台的,通过它可生成native编译配置文件,在Linux/Unix 平台,生成makefile ,在苹果平台,可以生成xcode ,在Windows平台,可以生成MSVC的工程文件。

它的缺点:
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 3.24)
# 项目信息 名字随便写
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 3.24)和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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容