CMake入门教程

教程网址:https://subingwen.cn/cmake/CMake-primer/

1.CMake概述

CMake 允许开发者指定整个工程的编译流程,在根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需make编译即可,所以可以把CMake看成一款自动生成 Makefile的工具,其编译流程如下图:
在这里插入图片描述

  • 蓝色虚线表示使用makefile构建项目的过程
  • 红色实线表示使用cmake构建项目的过程

CMake优点:
①跨平台
②能够管理大型项目
③简化编译构建过程和编译过程
④可扩展:可以为 cmake 编写特定功能的模块,扩充 cmake 功能

2.CMake的基础使用

2.1 通过g++来编译源码的步骤

假设有几个源码文件:add.cpp、div.cpp、head.h、main.cpp、mult.cpp、sud.cpp
①g++编译可执行文件,g++ *.cpp -o app
②此时会在当前目录多一个app的可执行程序
③运行可执行文件,./app

2.2 注释

2.2.1 注释行

CMake 使用 # 进行行注释,可以放在任何位置。

# 这是一个 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.0.0)

2.2.2 注释块

CMake 使用 #[[ ]] 形式进行块注释。

#[[ 这是一个 CMakeLists.txt 文件。
这是一个 CMakeLists.txt 文件
这是一个 CMakeLists.txt 文件]]
cmake_minimum_required(VERSION 3.0.0)

2.3 CMake指令(一)

2.3.1 cmake_minimum_required

cmake_minimum_required:指定使用的 cmake 的最低版本

可选,非必须,如果不加可能会有警告
下面这样写如若版本低于指定版本,会报错

cmake_minimum_required(VERSION 3.16 FATAL_ERROR)

2.3.2 project

project:定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。

# PROJECT 指令的语法是:
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
       [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
       [DESCRIPTION <project-description-string>]
       [HOMEPAGE_URL <url-string>]
       [LANGUAGES <language-name>...])

# eg.
project(
  hello
  LANGUAGES CXX
  DESCRIPTION "hello_world"
)

2.3.3 add_executable

add_executable(可执行程序名 源文件名称)

这里的可执行程序名和project中的项目名没有任何关系
源文件名可以是一个也可以是多个,如有多个可用空格或;间隔。

# 样式1
add_executable(app add.c div.c main.c mult.c sub.c)
# 样式2
add_executable(app add.c;div.c;main.c;mult.c;sub.c)

2.3 CMake简单示例

1.文件夹内容:

$ tree
.
├── add.c
├── CMakeLists.txt
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c

0 directories, 7 files

2.把生成的这些与项目源码无关的文件统一放到一个对应的目录里边,比如将这个目录命名为build
3.cmake命令是在build目录中执行的,但是CMakeLists.txt文件是build目录的上一级目录中,所以cmake 命令后指定的路径为…,即当前目录的上一级目录。
当命令执行完毕之后,在build目录中会生成一个makefile文件。

$ mkdir build
$ cd build
$ cmake ..

2.4 CMake指令(二)

2.4.1 set

# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
  • VAR:变量名
  • VALUE:变量值
# 方式1: 各个源文件之间使用空格间隔
# set(SRC_LIST add.c  div.c   main.c  mult.c  sub.c)

# 方式2: 各个源文件之间使用分号 ; 间隔
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
add_executable(app  ${SRC_LIST})

2.4.2 指定使用的C++标准

在编写C++程序的时候,可能会用到C++11、C++14、C++17、C++20等新特性,那么就需要在编译的时候在编译命令中制定出要使用哪个标准:

g++指定C++标准

$ g++ *.cpp -std=c++11 -o app

上面的例子中通过参数-std=c++11指定出要使用c++11标准编译程序,C++标准对应有一宏叫做DCMAKE_CXX_STANDARD。

CMake中指定C++标准有两种方式

  • 在 CMakeLists.txt 中通过 set 命令指定
#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17)
  • 在执行 cmake 命令的时候指定出这个宏的值
#增加-std=c++11
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11
#增加-std=c++14
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=14
#增加-std=c++17
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=17

# PS:在上面例子中 CMake 后的路径需要根据实际情况酌情修改。

2.4.3 指定输出的路径

在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH,它的值还是通过set命令进行设置:

set(HOME /home/robin/Linux/Sort)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)

# 第一行:定义一个变量用于存储一个绝对路径
# 第二行:将拼接好的路径值设置给EXECUTABLE_OUTPUT_PATH宏
# 如果这个路径中的子目录不存在,会自动生成,无需自己手动创建

PS:由于可执行程序是基于 cmake 命令生成的 makefile 文件然后再执行 make 命令得到的,所以如果此处指定可执行程序生成路径的时候使用的是相对路径 ./xxx/xxx,那么这个路径中的 ./ 对应的就是 makefile 文件所在的那个目录。

2.5 搜索文件

PS:如果一个项目里边的源文件很多,在编写CMakeLists.txt文件的时候不可能将项目目录的各个文件都写出来,可以使用aux_source_directory命令或者file命令。

2.5.1 aux_source_directory

在 CMake 中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命令格式为:

aux_source_directory(< dir > < variable >)
  • dir:要搜索的目录
  • variable:将从dir目录下搜索到的源文件列表存储到该变量中

eg.

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
add_executable(app  ${SRC_LIST})

2.5.2 file

file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
  • GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
  • GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。

搜索当前目录的src目录下所有的源文件,并存储到变量中

file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)

CMAKE_CURRENT_SOURCE_DIR 宏表示当前访问的 CMakeLists.txt 文件所在的路径。

关于要搜索的文件路径和类型可加双引号,也可不加:

file(GLOB MAIN_HEAD "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h")

2.6 包含头文件 include_directories

查看当前目录结构

tree -L 1

在编译项目源文件的时候,很多时候都需要将源文件(.cpp)对应的头文件(.h)路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在CMake中通过include_directories来实现。

include_directories(headpath)

在这里插入图片描述
CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALC)
set(CMAKE_CXX_STANDARD 11)
set(HOME /home/robin/Linux/calc)
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin/)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
add_executable(app  ${SRC_LIST})

PS:其中,第六行指定就是头文件的路径,PROJECT_SOURCE_DIR宏对应的值就是我们在使用cmake命令时,后面紧跟的目录,一般是工程的根目录

2.7 制作动态库或静态库

有些时候我们编写的源代码并不需要将他们编译生成可执行程序,而是生成一些静态库或动态库提供给第三方使用。

2.7.1 制作静态库

在cmake中,如果要制作静态库,需要使用的命令如下:

add_library(库名称 STATIC 源文件1 [源文件2] ...) 

在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。

在Windows中虽然库名和Linux格式不同,但也只需指定出名字即可。

在Windows系统下,动态库的后缀通常是.dll(Dynamic Link Library),而静态库的后缀通常是.lib(Library)。

下面有一个目录,需要将src目录中的源文件编译成静态库,然后再使用:

.
├── build
├── CMakeLists.txt
├── include           # 头文件目录
│   └── head.h
├── main.cpp          # 用于测试的源文件
└── src               # 源文件目录
    ├── add.cpp
    ├── div.cpp
    ├── mult.cpp
    └── sub.cpp

根据上面的目录结构,可以这样编写CMakeLists.txt文件:

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc STATIC ${SRC_LIST})

这样最终就会生成对应的静态库文件libcalc.a

2.7.2 制作动态库

在cmake中,如果要制作动态库,需要使用的命令如下:

add_library(库名称 SHARED 源文件1 [源文件2] ...) 

在Linux中,动态库名字分为三部分:lib+库名字+.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。

根据上面的目录结构,可以这样编写CMakeLists.txt文件:

cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc SHARED ${SRC_LIST})

这样最终就会生成对应的动态库文件libcalc.so

2.7.3 指定输出的路径

  • 方式1 - 适用于动态库
    对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的,所以可以按照生成可执行程序的方式去指定它生成的目录:
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(calc SHARED ${SRC_LIST})

对于这种方式来说,其实就是通过set命令给EXECUTABLE_OUTPUT_PATH宏设置了一个路径,这个路径就是可执行文件生成的路径。

  • 方式2 - 都适用
    由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,而应该使用LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})

2.8 包含库文件

Linux 静态库和动态库

在编写程序的过程中,可能会用到一些系统提供的动态库或者自己制作出的动态库或者静态库文件,cmake中也提供了相关的加载动态库的命令。

2.8.1 链接静态库

src
├── add.cpp
├── div.cpp
├── main.cpp
├── mult.cpp
└── sub.cpp

现在我们把上面src目录中的add.cpp、div.cpp、mult.cpp、sub.cpp编译成一个静态库文件libcalc.a。通过命令制作并使用静态链接库

测试目录结构如下:

$ tree 
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h
├── lib
│   └── libcalc.a     # 制作出的静态库的名字
└── src
    └── main.cpp

4 directories, 4 files

在cmake中,链接静态库的命令如下:

link_libraries(<static lib> [<static lib>...])
  • 参数1:指定出要链接的静态库的名字
    可以是全名 libxxx.a
    也可以是掐头(lib)去尾(.a)之后的名字 xxx
  • 参数2-N:要链接的其它静态库的名字

如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:

link_directories(<lib path>)

这样,修改之后的CMakeLists.txt文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(app ${SRC_LIST})

添加了第8行的代码,就可以根据参数指定的路径找到这个静态库了。

2.8.2 链接动态库

  • 在cmake中链接动态库的命令如下:
target_link_libraries(
    <target> 
    <PRIVATE|PUBLIC|INTERFACE> <item>... 
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
  • target:指定要加载动态库的文件的名字
    该文件可能是一个源文件
    该文件可能是一个动态库文件
    该文件可能是一个可执行文件
    PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认PUBLIC

如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。

  • 动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
target_link_libraries(A B C)
target_link_libraries(D A)

PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。

a.链接系统动态库

动态库的链接和静态库是完全不同的:

静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。
动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存

因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后:

cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC_LIST})
# 指定可执行程序要链接的动态库名字
target_link_libraries(app pthread)

target_link_libraries(app pthread)中:
app: 对应的是最终生成的可执行程序的名字
pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)

b.链接第三方动态库

现在,自己生成了一个动态库,对应的目录结构如下:

$ tree 
.
├── build
├── CMakeLists.txt
├── include
│   └── head.h            # 动态库对应的头文件
├── lib
│   └── libcalc.so        # 自己制作的动态库文件
└── main.cpp              # 测试用的源文件

3 directories, 4 files

假设在测试文件main.cpp中既使用了自己制作的动态库libcalc.so又使用了系统提供的线程库,此时CMakeLists.txt文件可以这样写:

cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(app ${SRC_LIST})
target_link_libraries(app pthread calc)

在第六行中,pthread、calc都是可执行程序app要链接的动态库的名字。当可执行程序app生成之后并执行该文件,会提示有如下错误信息:

$ ./app 
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

这是因为可执行程序启动之后,去加载calc这个动态库,但是不知道这个动态库被放到了什么位置解决动态库无法加载的问题,所以就加载失败了,在 CMake 中可以在生成可执行程序之前,通过命令指定出要链接的动态库的位置,指定静态库位置使用的也是这个命令:

link_directories(path)

所以修改之后的CMakeLists.txt文件应该是这样的:

cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 指定源文件或者动态库对应的头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 添加并生成一个可执行程序
add_executable(app ${SRC_LIST})
# 指定要链接的动态库

target_link_libraries(app pthread calc)
通过link_directories指定了动态库的路径之后,在执行生成的可执行程序的时候,就不会出现找不到动态库的问题了。

温馨提示:使用 target_link_libraries 命令就可以链接动态库,也可以链接静态库文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值