高效工具实战指南:从零开始编写CMakeLists

目录

1.前言

2.了解CMake

3.示例代码

4.使用CMake

5.了解CMake中的注释

6.了解三种CMake常见的命令

7.使用SET定义变量

8.使用宏指定C++标准与可执行文件输出路径

9.在CMake中搜索指定路径下的文件

10.编写CMakeList生成和使用静态库

11.编写CMakeList生成和使用动态库

12.在CMakeList中使用日志

13.鸣谢


前言

在我们浏览许多开源的项目时,都会发现其中存在一个名为CMakeLists的txt文件,其中包含了很多指令代码。而为了构建这些开源项目,我们还需要使用CMake来进行构建,那什么是CMake呢?CMake有什么用呢?CMakeLists文件又如何编写呢?这就是本篇博客所需要讲诉的东西


了解CMake

CMake是一个跨平台的自动化构建系统,它使用配置文件(称为CMakeLists.txt)来生成标准的构建文件,如Unix的Makefile或Windows的Visual Studio工程文件。CMake支持多种编程语言,包括C、C++、Java和Python等。

CMake的主要特点包括:

1.跨平台 :可以在多种操作系统上使用,如Windows、Linux、macOS等

2.模块化 :通过CMakeLists.txt文件定义项目结构和构建规则

3.生成器 :支持多种构建系统,可以根据需要生成不同的构建文件

4.依赖管理 :可以自动处理项目依赖,包括外部库和内部模块

5.可定制 :用户可以自定义构建过程,包括编译器选项、编译标志等

6.易于维护 :项目结构清晰,易于理解和维护

具体的安装教程可以参考以下博客:
高效工具实战指南:CMake构建工具-CSDN博客https://blog.csdn.net/weixin_67035108/article/details/155361963?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522bad61a17c91f12984d8dac70ab21e2b4%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=bad61a17c91f12984d8dac70ab21e2b4&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-2-155361963-null-null.nonecase&utm_term=CMake&spm=1018.2226.3001.4450


示例代码

在使用CMake前,我们需要不同的文件去构建项目,然后编写对应的CMakeLists文件再执行CMake指令。故此本篇文章的示例代码如下:

1.main.cpp

cpp 复制代码
#include <iostream>
#include "library.h"

using namespace std;

int main(){
    cout << "这一个由CMake构建管理的简单Demo" << endl;
    cout << "1 + 1 =" << addition(1, 1) << endl;
    cout << "8 / 2 =" << divsion(8, 2) << endl;
    cout << "4 * 4 =" << multiplication(4, 4) << endl;
    cout << "5 - 3 = " << subtraction(5, 3) << endl;
    cout << "构建完毕" << endl;
    system("pause");
    return 0;
}

2.library.h

cpp 复制代码
#ifndef LIBRARY_H
#define LIBRARY_H

//加法
int addition(const int& a, const int& b);

//除法
double divsion(const int& a, const int& b);

//减法
int subtraction(const int& a, const int& b);

//乘法
int multiplication(const int& a, const int& b);

#endif

3.addition.cpp

cpp 复制代码
int addition(const int& a, const int& b){
    return a + b;
}

4.divsion.cpp

cpp 复制代码
double divsion(const int& a, const int& b){
    return a / b;
}

5.multiplication.cpp

cpp 复制代码
int multiplication(const int& a, const int& b){
    return a * b;
}

6.subtraction.cpp

cpp 复制代码
int subtraction(const int& a, const int& b){
    return a - b;
}

使用CMake

CMake通常用于大型项目,因为它提供了强大的工具来管理复杂的构建过程。它也常用于开源项目,因为它允许用户在不同的平台上使用相同的构建系统。

而针对使用CMake编译项目时,在不同系统的编译流程如下:

图1.CMake编译流程(Linux)

图2.CMake编译流程(Windows)

为了使读者直观的感受到如何使用CMake命令,为此可参考以下在Windows系统中使用CMake的流程,项目代码与第二小节示例代码一致。具体的项目结构如下图:

图3.项目结构

我们打开当前项目,在当前项目的build文件夹路径输入框中输入cmd即可以当前项目的build文件夹为路径,快捷打开Windows中的命令提示符窗口。

图4.快捷打开cmd窗口

然后我们在当前窗口中执行cmake命令即可,而由于我们是在build路径下打开命令提示符的,所以还需要带上".."来表示上一级目录,具体如下图:

图5.执行CMake命令

在执行完CMake命令以后后,在当前目录会生成一些文件和文件夹,具体的这些文件的含义可以参考下表:

|----------------|------------------------|-------------------------------------------|
| 名称 | 类型 | 含义说明 |
| CMakeCache.txt | 文本文档 | CMake配置缓存文件,保存所有检测到的系统信息、编译器路径、项目选项等变量 |
| Demo.sln | Visual Studio Solution | 主解决方案文件,可用Visual Studio直接打开,包含整个项目的组织结构 |
| CMakeFiles | 文件夹 | CMake配置过程的核心文件夹,包含编译器检测、依赖检查、构建规则和日志等中间文件 |

表1.生成的主要文件含义

在执行完CMake命令以后,我们需要执行"CMake --build ."命令生成可执行文件,生成的可执行文件在bin文件夹下,具体如下图:

图6.使用CMake --build命令生成可执行文件

PS:此处只讲解大概怎么使用CMake命令生成可执行文件,具体的CMakeList文件的内容不作讲解,后续学习到CMake的命令的时候才会编写CMakeList文件


了解CMake中的注释

在编写CMakeLists文件时,如果需要注释则需要使用" # "代表行注释,具体使用如下:

bash 复制代码
#这是一个行注释

而在复杂的编程环境中,不只是需要行注释,还需要使用" #[[]] "进行块注释。具体使用方法如下:

bash 复制代码
#[[
    这是一个块注释
]]

图7.注释示例


了解三种CMake常见的命令

1.cmake_minimum_required:指定编译使用的cmake的最低版本

bash 复制代码
cmake_minimum_required(VERSION 版本号)

示例:cmake_minimum_required(VERSION 3.30.2)

2.project:指定编译项目的名称,版本、工项目描述、主页地址、支持的编译语言

bash 复制代码
project(<PROJECT-NAME> [<language-name>...])

示例:project(MyProject VERSION 1.0.0 LANGUAGES CXX)

3.add_executable:定义项目生成可执行程序的名称

bash 复制代码
add_executable(可执行程序名 源文件)

示例:add_executable(Demo addition.cpp division.cpp multiplication.cpp subtraction.cpp main.cpp)

在了解了这三种指令后,我们发现其中的名称都是以空格分隔,当然也可以使用" ; "号进行分割。了解了这些后我们就可以在博客的示例代码文件夹下编写CMakeLists.txt文件:

bash 复制代码
#指定编译的cmake最低的版本号
cmake_minimum_required(VERSION 3.30.2)
#[[
    以下定义的数据为:
        1.项目名称
        2.可执行文件名称
]]
project("Demo")
add_executable(Demo addition.cpp;division.cpp;multiplication.cpp;subtraction.cpp;main.cpp)

使用SET定义变量

在CMake中所有的变量值都为字符串,在我们编写CMakeLists文件时,往往会存在多个.cpp文件需要构建,这时如果每次编译生成可执行文件都写上一大串的文件名,这对于我们维护CMakeLists文件和对项目做调整时是极为不方便的,此时我们则需要使用SET来定义变量并对其赋值,方便我们后续维护。其指令使用方法如下:

bash 复制代码
SET(变量名 变量值)

例如:SET(Path addition.cpp division.cpp multiplication.cpp subtraction.cpp main.cpp)

在使用SET定义变量后,我们要对变量进行使用,此时则需要使用 "${变量名}" 来对其进行取值,具体如下:

cpp 复制代码
${变量名}

例如:add_executable(Demo  ${Path})

在了解了SET定义变量和取值后,我们需要对先前编写的CMakeLists文件使用SET进行优化,方便后续维护项目:

cpp 复制代码
cmake_minimum_required(VERSION 3.30.2)  #指定CMake的最小版本,这里为3.10.2
project("Demo")                         #项目名称
set(Path addition.cpp division.cpp multiplication.cpp subtraction.cpp main.cpp) #使用SET定义变量
add_executable(Demo ${Path})     #添加可执行文件,第一个参数为可执行文件名称,第二个参数为源文件列表

如果想使用CMake指令构建上述的CMakeLists文件则需要保证文件夹中结构和文件为以下情况:

图8.文件夹结构

运行Cmake指令后,可以在debug目录下找到可执行文件:

图9.使用SET命令定义变量并使用


使用宏指定C++标准与可执行文件输出路径

在了解了SET的使用后,我们发现可执行文件生成的路径不是自己指定的路径,而且当使用C++比较新的标准,如C++11时会导致构建项目失败。这主要是因为我们并没有指定C++标准,一般默认使用的是C++98的标准,该小节辨识针对这两个问题进行讲解:

1.使用CMAKE_CXX_STANDARD指定C++标准:

cpp 复制代码
set(CMAKE_CXX_STANDARD 标准号)

例如:set(CMAKE_CXX_STANDARD 11)

2.使用构建指令指定C++标准:

bash 复制代码
-std=c++11

例如:g++ *.cpp -std=c++11 -o Demo

3.使用Cmake指令指定C++标准:

bash 复制代码
-DCMAKE_CXX_STANDARD=11

例如:cmake CMakeLists.txt -DCMAKE_CXX_STANDARD=11

4.使用EXECUTABLE_OUTPUT_PATH指定可执行文件输出路径:

bash 复制代码
set(EXECUTABLE_OUTPUT_PATH 路径)

例如:set(EXECUTABLE_OUTPUT_PATH E:\\demo_Code\\CMakeDemo\\Debug)

在了解了这些方法后,我们的CMakeList文件也需要对其进行调整,如下:

bash 复制代码
cmake_minimum_required(VERSION 3.30.2)  #指定CMake的最小版本,这里为3.10.2
project("Demo")                         #项目名称
set(outputPath ./bin)   #输出路径
set(CMAKE_CXX_STANDARD 11)  #指定C++版本
set(EXECUTABLE_OUTPUT_PATH ${outputPath})   #指定可执行文件输出路径
set(Path addition.cpp division.cpp multiplication.cpp subtraction.cpp main.cpp) #使用SET定义变量
add_executable(Demo ${Path})     #添加可执行文件,第一个参数为可执行文件名称,第二个参数为源文件列表

当前小节的文件结构与《使用SET定义变量》小节中的文件夹结构一致,只需要修改CMakeLists文件即可,生成的项目如下:

图9.使用宏指定C++标准与可执行文件输出路径


在CMake中搜索指定路径下的文件

在我们使用set进行变量定义和赋值的时候会发现还是需要先输入一连串的文件名来给变量赋值,而本小节就是为了简化该操作而写的。在了解如何简化该操作之前,我们需要先了解两个宏定义:

1.**PROJECT_SOURCE_DIR:**该宏定义用于代表执行CMake指令所指向的路径

bash 复制代码
执行cmake .时,此时PROJECT_SOURCE_DIR指向当前路径
执行cmake ./build时,此时PROJECT_SOURCE_DIR指向当前路径下的build

2.**CMAKE_CURRENT_SOURCE_DIR:**该宏定义指向所执行的CMakeLists文件的路径

了解了以上两个宏定义后,下面两个指令将配合这些宏定义进行使用,搜索指定路径下的文件:

1.使用aux_source_directory指令搜索指定路径下的源文件

bash 复制代码
aux_source_directory(路径 变量名)

例如:aux_source_directory(${PROJECT_SOURCE_DIR} Path)
代表将宏定义PROJECT_SOURCE_DIR路径下的源文件赋值给变量Path

2.使用file指令搜索指定路径下的指定文件

bash 复制代码
file(GLOB/GLOB_RECURSE 变量名 文件路径及文件类型)

例如:file(GLOB Path ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
代表搜索CMakeList路径下的.cpp文件(但不搜索子文件夹),赋值给变量Path

file(GLOB_RECURSE Path ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
代表搜索CMakeList路径下的.cpp文件(搜索子文件夹),赋值给变量Path

在使用file指令时,我们发现还使用了两个定义值分别为GLOB和GLOB_RECURSE,以下为他们的含义:

1.GLOB:指定路径下满足条件的文件

2.GLOB_RECURSE :递归搜索指定路径下满足条件的文件

以上便是搜索文件的指令使用,对此我们的文件结构和CMakeLists文件都需要做对应的调整,具体如下:

bash 复制代码
cmake_minimum_required(VERSION 3.30.2)  #指定CMake的最小版本,这里为3.10.2
project("Demo")                         #项目名称
set(outputPath ./bin)   #输出路径
set(CMAKE_CXX_STANDARD 11)  #指定C++版本
set(EXECUTABLE_OUTPUT_PATH ${outputPath})   # 指定可执行文件输出路径
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src cppPath)   #收集 src 目录下的所有 .cpp 文件
#file(GLOB_RECURSE headPath ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h) #收集 include 目录下的所有 .h 文件
set(Path ${cppPath} ${headPath} main.cpp) #合并路径  
add_executable(Demo ${Path})    #添加可执行文件,确保头文件不作为源文件
target_include_directories(Demo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)

如果想使用CMake指令构建上述的CMakeLists文件则需要保证文件夹中结构和文件为以下情况:

图10.文件路径

在build文件夹中执行CMake ..命令生成.sln解决方案文件后,再使用CMake --build ..命令调用编译器编译链接源代码生成可执行文件,最后生成的可执行文件如下图:

图11.使用指令搜索指定路径下的文件并生成可执行文件


编写CMakeList生成和使用 静态库

有些时候我们编写的源代码并不需要将他们编译生成可执行程序,而是生成静态库或动态库提供给第三方使用。而在CMake中,如果要制作静态库,需要使用的命令如下:

bash 复制代码
add_library(库名称 STATIC 源文件1 [源文件2] ...) 

接下来,我们需要编写CMakeList进行文件的生成,具体内容如下:

bash 复制代码
cmake_minimum_required(VERSION 3.30.2)  #指定CMake的最小版本,这里为3.10.2
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc STATIC ${SRC_LIST})

然后我们在CMD命令窗口运行cmake ..命令和cmake --build .命令即可生成静态库文件,具体如下图:

图12.使用add_library命令生成静态库文件

而如果我们需要编写CMakeList来使用静态库的话则需要使用以下命令:

bash 复制代码
link_libraries(静态库名称1.a 静态库名称2.a)

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

bash 复制代码
link_directories(静态库路径)

在使用link_directories命令和linke_libraries命令来使用静态库的时候,我们需要在项目中调整一下文件,目录如下:

图13.使用静态链接库时文件夹目录

在明确新的文件夹目录以后,我们编写CMakeList进行文件的生成调用静态链接库的可执行文件,具体的CMakeList内容如下:

bash 复制代码
cmake_minimum_required(VERSION 3.30.2)  #指定CMake的最小版本,这里为3.10.2
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})

在build文件夹中执行CMake ..命令生成.sln解决方案文件后,再使用CMake --build ..命令调用编译器编译链接源代码生成可执行文件,最后生成的可执行文件如下图:

图14.使用静态链接库


编写CMakeList生成和使用动态库

在Cmake中如果要制作动态库,需要使用的与静态库的命令一致。但是生成静态库的参数为STATIC,而动态库的参数为SHARED,具体命令如下:

bash 复制代码
add_library(库名称 SHARED 源文件1 [源文件2] ...) 

所以如果我们需要生成的是动态库的话,只需要将add_library命令中的STATIC参数修改为SHARED即可,具体命令如下:

bash 复制代码
cmake_minimum_required(VERSION 3.30.2)  #指定CMake的最小版本,这里为3.10.2
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
add_library(calc SHARED ${SRC_LIST})

然后我们在CMD命令窗口运行cmake ..命令和cmake --build .命令即可生成动态库文件,具体如下图:

图15.使用add_library命令生成动态库文件

在CMakLlist为了使用动态库,我们有两种方式,具体如下:
1.使用target_link_libraries命令:

bash 复制代码
target_link_libraries(
    <target>  // 指定要加载的库的文件的名字
    <PRIVATE|PUBLIC|INTERFACE> <item>...     // 动态库的访问权限,默认为PUBLIC
    [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

2.使用link_libraries命令

因为在《编写CMakeList生成和使用静态库》小节中我们已经介绍了如何使用link_libraries命令和link_directories命令来查找和使用静态库,所以本小节就不再讲解,而是重点讲解target_link_linraries命令。在使用动态链接库时,新的项目目录如下:

图16.使用动态库项目目录

在明确新的文件夹目录以后,我们编写CMakeList进行文件的生成调用动态链接库的可执行文件,具体的CMakeList内容如下:

bash 复制代码
cmake_minimum_required(VERSION 3.30.2)  #指定CMake的最小版本,这里为3.10.2
project(TEST)

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

在build文件夹中执行CMake ..命令生成.sln解决方案文件后,再使用CMake --build ..命令调用编译器编译链接源代码生成可执行文件,最后生成的可执行文件如下图:

图17.使用动态链接库


在CMakeList中使用日志

在CMake中可以用用户显示一条消息,该命令如下:

bash 复制代码
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "日志信息" ...)

(无) :重要消息
STATUS :非重要消息
WARNING:CMake 警告, 会继续执行
AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR:CMake 错误, 终止所有处理过程

CMake的命令行工具会在stdout上显示STATUS消息,在stderr上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息。具体使用如下:

bash 复制代码
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")

鸣谢

本篇文章在草稿中躺了很久,已经烂尾。后续的许多内容参考了博主大炳的文章。具体链接如下:
CMake 保姆级教程(上) | 爱编程的大丙https://subingwen.cn/cmake/CMake-primer/

相关推荐
kpl_203 小时前
智能指针(C++)
c++·c++11·智能指针
Darkwanderor4 小时前
高精度计算——基础模板整理
c++·算法·高精度计算
Tanecious.4 小时前
蓝桥杯备赛:Day5-P1036 选数
c++·蓝桥杯
mmz12075 小时前
深度优先搜索DFS(c++)
c++·算法·深度优先
憧憬从前6 小时前
算法学习记录DAY1
c++·学习
A.A呐6 小时前
【C++第二十四章】异常
开发语言·c++
xiaoye-duck6 小时前
《算法题讲解指南:动态规划算法--子序列问题》--29.最长递增子序列的个数,30.最长数对链,31.最长定差子序列
c++·算法·动态规划
森G6 小时前
39、拓展知识---------事件系统
c++·qt
tankeven6 小时前
HJ164 太阳系DISCO
c++·算法