CMake入门学习笔记

说明

本文是学习哔哩哔哩网站原子之音的“现代C++:CMake简明教程”视频课程所做的一点笔记。
后文中的编译命令都是如下两条:

cmake -B build -G "MinGW Makefiles"
cmake --build build

项目工程

图1 项目工程文件

cat.h

#ifndef __CAT_H_
#define __CAT_H_
#include <string>

class Cat {
public:
    std::string Barking();
};

#endif

cat.cpp

#include "cat.h"

std::string Cat::Barking()
{
    return "cat: mi mi";
}

dog.h

#ifndef __DOG_H_
#define __DOG_H_
#include <string>

class Dog {
public:
    std::string Barking();
};

#endif

dog.cpp

#include "dog.h"

std::string Dog::Barking()
{
    return "dog: wang wang";
}

hello.cpp

#include <iostream>
#include "Animal/dog.h"
#include "Animal/cat.h"

int main()
{
    Dog dog;
    Cat cat;
    std::cout << "hello world" << std::endl;
    std::cout << dog.Barking() << std::endl;
    std::cout << cat.Barking() << std::endl;
    return 0;
}

两种方式

方法一:直接写入源码路径,一个CMakeLists.txt搞定

\color{blue}{提示:这种方式只适合小型工程。}

直接在最外层源文件所在目录(本例就是hello.cpp所在目录)添加一个CMakeLists.txt文件,其内容如下:

cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_executable(Hello hello.cpp Animal/dog.cpp Animal/cat.cpp)

\color{red}{这种写法有两点需要注意:}
\color{red}{(1)add_-executable里面的cpp路径必须是相对路径;}
\color{red}{(2)源文件里面include头文件(非当前源文件同目录时)是也必须是相对路径;}

最终项目工程如下:


图2 一个CmakeLists.txt的项目工程

改进一:使用一个CMakeLists.txt加多个.cmake文件配合

如果项目稍微大一点,使用上上述方法,则add_executable里面的参数会很多、很长,此时可以把一个目录下的源文件单独放在一个.cmake文件里面。

步骤:
1、在对应目录下添加.cmake文件。该例中是在Animal目录下新增一个animal.cmake,其内容如下:

set(animal_source Animal/dog.cpp Animal/cat.cpp)

2、在CMakeLists.txt里面新增include自带,include上述.cmake文件,在add_executable命令里面引用上述.cmake文件里面的源文件变量。

cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
include(Animal/animal.cmake)
add_executable(Hello hello.cpp ${animal_source})

\color{red}{两点需要注意:}
\color{red}{(1).cmake文件可以放在与CMakeLists.txt同级目录,也可以放在对应的目录}
\color{red}{下,建议使用后者,比较清晰。.cmake里面源文件路径是工程的相对路径,而不是}
\color{red}{相对于它放的位置的路径;}
\color{red}{(2)CMakeLists.txt里面include这个.cmake文件也要使用相对位置。}

图3 CMakeLists.txt配合.cmake文件的项目工程

改进二:源文件include时不需要写相对路径

上述方法一及其改进方法,都要求在写源代码includ非系统头文件和非当前目录头文件时,必须使用相对路径,很麻烦。比如hello.cpp里面:

#include "Animal/dog.h"
#include "Animal/cat.h"'

通常我们写代码的时候都是直接include的,而不用可以去关注他们的路径,路径在编译的时候使用target_include_directories指定即可。如下:

cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
include(Animal/animal.cmake)
add_executable(Hello hello.cpp ${animal_source})
target_include_directories(Hello PUBLIC "${PROJECT_SOURCE_DIR}/Animal")

如此,include的时候就清爽多了,hello.cpp里面include如下:

#include "dog.h"
#include "cat.h"'

方法二:使用CMakeLists.txt嵌套

\color{blue}{提示:这种方式适合中大型工程。}

步骤:
1、在子目录下新增CMakeLists.txt文件,此例是在Animal目录下新增:

add_library(AnimalLib cat.cpp dog.cpp) # 不指定STATIC或SHARED的时候,默认生成静态库

2、修改最外层的CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal) # 添加子目录并构建该子目录,子目录下必须有CMakeLists.txt
add_executable(Hello hello.cpp )
target_link_libraries(Hello PUBLIC AnimalLib)
target_include_directories(Hello PUBLIC "${PROJECT_SOURCE_DIR}/Animal")

\color{blue}{提示:这种方法其实是生成了一个静态库。}

图4 CMakeLists.txt嵌套的项目工程

除了静态库,还有一种Object Library的方法。仍然以上面的目录结构为例,修改Animal目录下CMakeLists.txt,如下:

add_library(AnimalLib OBJECT cat.cpp dog.cpp) # 生成OBJECT
target_include_directories(AnimalLib PUBLIC .)

有需要的话,也可以分开写:

add_library(cat OBJECT cat.cpp) # 生成OBJECT
target_include_directories(cat PUBLIC .)
add_library(dog OBJECT dog.cpp) # 生成OBJECT
target_include_directories(dog PUBLIC .)

外层的MakeLists.txt改动如下:

cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal) # 添加子目录并构建该子目录,子目录下必须有CMakeLists.txt
add_executable(Hello hello.cpp )
target_link_libraries(Hello PUBLIC AnimalLib)

如果子目录的CMakeList.txt里面的OBJECT是分开写的话,那么外层的CMakeLists也要做相应的改动:

cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal) # 添加子目录并构建该子目录,子目录下必须有CMakeLists.txt
add_executable(Hello hello.cpp )
target_link_libraries(Hello PUBLIC cat dog)

可以看到OBJECT与静态库的区别就是:在子目录CMakeLists.txt里面add_library时指定生成OBJECT,并且把外层CMakeLists.txt里面的target_include_directories命令移到子目录的CMakeLists.txt里面。

\color{red}{OBJECT注意事项:}
\color{red}{1、Object Library是一种特殊的库类型,它将目标文件编译成一个库,}
\color{red}{但不会生成最终的链接文件。这意味着你可以在后续的add_-library或}
\color{red}{add_-executable命令中将Object Library作为源文件进行链接,}
\color{red}{从而生成最终的可执行文件;}
\color{red}{2、需将最外层的target_-include_-directories命令移到子目录的CMakeLists.txt里面}
\color{red}{3、OBJECT库对CMake的最低版本要求是3.12。}

改进方法:向标准项目迈进,inc和src分开

一个稍微大点的项目会有多个模块,每个模块有多个头文件和.cpp文件,通常的做法是为每个独立的模块创建一个独立的文件夹,然后在文件夹里面创建inc和src分别存放.h文件和.cpp文件,如下:


图5 标准化的项目工程

这样做的好处是,不仅代码结构更加清晰,也便于将不同的模块编译成静态或者动态库。
Animal目录下的CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.20)
project(ANIMAL VERSION 1.0)
file(GLOB src ${PROJECT_SOURCE_DIR}/src/*.cpp) # 可以使用aux_source_directory命令实现与file命令相同的功能
include_directories(${PROJECT_SOURCE_DIR}/inc)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(animal STATIC ${src}) # 动态库写法一样,只是把STATIC换成SHARED

在外层的CMakeLists.txt根据调用的是静态库还是动态库会有所不同。

\color{green}{调用静态库的流程:}
\color{green}{1、引入头文件}
\color{green}{2、声明库目录}
\color{green}{3、链接静态库}
\color{green}{4、生成可执行二进制文件}

调用静态库的方式:

cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal)
include_directories(${PROJECT_SOURCE_DIR}/Animal/inc)
link_directories(${PROJECT_SOURCE_DIR}/Animal/lib)
link_libraries(animal)
add_executable(Hello hello.cpp)

\color{green}{调用动态库的流程:}
\color{green}{1、引入头文件}
\color{green}{2、声明库目录}
\color{green}{3、生成可执行二进制文件}
\color{green}{4、链接动态库}

调用动态库的方式:

cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal)
include_directories(${PROJECT_SOURCE_DIR}/Animal/inc)
link_directories(${PROJECT_SOURCE_DIR}/Animal/lib)
add_executable(Hello hello.cpp)
target_link_libraries(Hello PUBLIC animal)

\color{red}{说明:}
\color{red}{link_-libraries用在add_-executable命令之前,只能用来链接静态库,} \color{red}{target_-link_-libraries用在add_-executable命令之后,可以链接静态库和动态库。}

include_directories、link_directories、、link_libraries等命令都有target_前缀的版本,target_前缀的命令都必须写在add_executable或者add_library等产生target对象的命令之后。

由于调用静态库和动态库也就链接库文件和生成二进制文件这一步顺序不一样,并且target_前缀的命令需要在add_executable命令之后,那么是不是如果都使用target_前缀的命令,就可以不用关注顺序了?如下:

cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal)
add_executable(Hello hello.cpp) # add_executable后才有对象给target_命令使用
target_include_directories(Hello PUBLIC ${PROJECT_SOURCE_DIR}/Animal/inc)
target_link_directories(Hello PUBLIC ${PROJECT_SOURCE_DIR}/Animal/lib)
target_link_libraries(Hello PUBLIC animal)

\color{red}{事实证明确实如此,建议采用这种方法。}

\color{blue}{提示:}
\color{blue}{用上述调用动态库方法编译的二进制可执行文件,}
\color{blue}{可能是无法直接执行,}
\color{blue}{因为二进制执行过程中需要去链接该动态库,}
\color{blue}{所以如果动态库所在目录不在系统环境变量里面,}
\color{blue}{可以把动态库拷贝到二进制可执行文件的同级目录,就可以执行了}

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

推荐阅读更多精彩内容