【CMake 教程】基础使用教程(一)

一、准备

1. CMake 官方下载网址
2. 文件目录结构
MySimpleProject
├── CMakeLists.txt	# CMake 配置文件
└── main.cpp		# 主程序源码

我们在 MySimpleProject 目录中创建 CMakeLists.txtmain.cpp 两个空白文件。

二、CMake 简单概念

要学会使用 CMake,首先要弄清楚 CMake 项目的基本结构。

大体上,可以分为两个主要层级:

  • 项目(Project)
    通常是一个独立的工程,对应一个整体代码库,通常包含多个目标或子模块。
  • 目标(Target)
    核心概念之一,分为可执行文件(Executable)动态/静态库(Library)

此外,CMake支持创建更复杂的层级,比如 子项目子目录(Subdirectory),这些后面会介绍。

提示:如果你熟悉 Visual Studio,可以简单理解:Visual Studio 的 解决方案(Solution) 类似于 CMake 的根项目,而其中的 项目(Project) 则类似于 CMake 中的单个目标。

三、CMakeLists.txt 结构拆解

以下是最简单的 CMakeLists.txt 结构,逐步拆解说明:

# 声明项目名称和版本
cmake_minimum_required(VERSION 3.21)
project(MySimpleProject VERSION 1.0)

# 添加可执行目标
add_executable(MyApp main.cpp)
  1. 指定 CMake 的最低版本要求,确保脚本兼容性:
cmake_minimum_required(VERSION 3.21)
  • 如果项目使用了低于 3.21 版本的 CMake 进行构建,会报错。
  • 最好根据项目需求调整版本,保持兼容的同时尽量使用最新功能。
  1. 定义项目名称和版本,类似一个工程的“名片”:
project(MySimpleProject VERSION 1.0)
  • MySimpleProject 是项目名称,不一定是最终生成的文件名(比如可执行文件名)。
  • 可在 project 后添加描述、语言等选项,便于构建配置:
project(MySimpleProject VERSION 1.0 LANGUAGES CXX)
  1. 定义目标,生成可执行文件,参数依次为目标名源码文件
add_executable(MyApp main.cpp)
  • MyApp目标名,最终生成程序的文件名会以此为基础(具体格式取决于平台,如 Windows 的 MyApp.exe
  • main.cpp源码文件路径,支持多个文件:
add_executable(MyApp main.cpp utils.cpp)

如果我们希望项目名目标名一致怎么做呢:

add_executable(${PROJECT_NAME} main.cpp)

${PROJECT_NAME} 指的就是 MySimpleProject。更多细节,后面会详细介绍。

四、构建、编译项目

我们先把 main.cpp 补充完整:

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello World!" << endl;
    return 0;
}

接下来,我们打开命令行,进入项目的根目录,然后执行构建命令

cmake .

接着再执行编译命令

cmake --build .

Linux 下:

在这里插入图片描述

目录结构:

在这里插入图片描述

Windows 下:

在这里插入图片描述

Windows 这里默认用了 msvc 编译器,后面我们再说怎么自定义

目录结构:

在这里插入图片描述

可以看到 sln 文件也出现了,它是执行 cmake . 时生成的

五、中间文件介绍

不同的操作系统,在不同的编译器下,生成的中间文件也是不同的。但我们能发现总有几个相同的文件(或目录),我们一起看看:

1. CMakeCache.txt

保存了 CMake 配置过程中得出的缓存变量的值,比如编译器路径构建类型、依赖的路径等信息。

  • 用途:一般用于确保多次运行 CMake 时可以重复使用之前的配置结果,避免重复计算。
  • 提示不要手动改它,如果发现里面的变量与预期不一样,应该考虑怎么修改 CMake 脚本,而不是来更改这个文件!

2. CMakeFiles目录(通常没必要了解)

CMake 生成的内部的构建工具文件。通常包含一些辅助的构建文件和依赖关系。

3. cmake_install.cmake

CMake 自动生成的脚本文件,描述了如何将构建产物(如可执行文件、库、头文件等)从构建目录安装到目标安装路径。

当你在项目根目录执行 make install(或类似的安装命令,例如 Ninja 的 ninja install,Visual Studio 的 INSTALL 项目)时,系统会调用该文件中的指令完成安装操作

详细作用后面会说到。

六、拓展:指定 include 目录

我们在根目录中创建一个 include 目录,再在里面创建 unit.hpp 文件。目录结构如下:

MySimpleProject
├── CMakeLists.txt
├── include
│   └── unit.hpp	# 新增
└── main.cpp

unit.hpp 内容如下:

inline int sum(int a, int b)
{
    return a + b;
}

这时候如果我们要在 main.cpp 使用这个头文件,像这样:

#include "include/unit.hpp"

但我是想这么用的:

#include "unit.hpp"

接下来,我们修改一下 CMakeLists.txt 文件:

# 声明项目名称和版本
cmake_minimum_required(VERSION 3.21)
project(MySimpleProject VERSION 1.0)

# 添加可执行目标
add_executable(MyApp main.cpp)

# 添加头文件目录
target_include_directories(MyApp PRIVATE include)	# 新增

接下来我们就可以愉快的使用 #include "unit.hpp" 了。


target_include_directories 简单介绍

target_include_directories 是 CMake 中推荐的现代用法,用于为特定目标设置包含目录。其格式为:

target_include_directories(<target> [INTERFACE|PUBLIC|PRIVATE] <DIRECTORY>...)

简单介绍参数:

  1. 目标名,如例子中的 MyApp
  2. [INTERFACE|PUBLIC|PRIVATE]
    • PRIVATE:头文件目录只对当前目标生效。
    • PUBLIC:适用于当前目标及依赖它的目标(同时对自己和下游生效)。
    • INTERFACE:仅对依赖它的目标生效(即仅下游生效,对自己不生效)。

INTERFACEPUBLIC 目前可以不用管,等后面写“库”的时候再聊。

在这里使用 PRIVATE 是因为 unit.hpp 仅供 MyApp 使用,不需要暴露给其他目标。


既然有现代用法,那肯定就有古代用法。

include_directories 是早期全局作用的 API,一旦指定会对所有目标生效,容易引入不必要的依赖,维护成本高。(说人话,就是如果你有两个目标,写成 include_directories(include),会使这两个目标都包含 include 目录)。

七、拓展:添加依赖库

我们先清理掉刚刚那些文件,只留下 CMakeLists.txtmain.cpp

注意,我是在 Linux 下测试的,方法在 Windows 通用。

假设我的项目需要依赖 zlib(msvc 没有自带这个库,别试了,换个库测试吧),main.cpp 代码如下:

#include <iostream>
#include <string>
#include <zlib.h>  // 引入 zlib 头文件

void compressString(const std::string &str)
{
    uLong srcLen  = str.size();
    uLong destLen = compressBound(srcLen);  // 计算压缩缓冲区最大可能大小
    char *dest    = new char[destLen];      // 创建缓冲区

    // 调用 zlib 压缩函数
    if (compress(reinterpret_cast<Bytef *>(dest), &destLen,
                 reinterpret_cast<const Bytef *>(str.c_str()), srcLen)
        == Z_OK) {
        std::cout << "原始数据大小: " << srcLen << " 字节" << std::endl;
        std::cout << "压缩后数据大小: " << destLen << " 字节" << std::endl;
    }
    else {
        std::cerr << "压缩失败!" << std::endl;
    }

    delete[] dest;  // 释放内存
}

int main()
{
    std::string data = "这是一段将会被 zlib 压缩的字符串。";
    compressString(data);
    return 0;
}

这时候执行 cmake .,一切正常。

但执行 cmake --build . 的时候,就会发现报错了:

在这里插入图片描述

因为没有添加 zlib 依赖库。我们在 CMakeLists.txt 文件中加上下面这段代码:

target_link_libraries(MyApp PRIVATE z)	# 别问我为什么是 z 而不是 zlib,这个问题……

这里的 PRIVATE 意义和上面的 target_include_directories 是一样的。

其实就相当于执行了 g++ main.cpp -o MyAppC -lz

重新 cmake . cmake --build .,成功。


target_link_libraries 简单介绍

有了上面 target_include_directories 的经验,我们快速过一下。

target_link_libraries(<target> [INTERFACE|PUBLIC|PRIVATE] <library>...)

同样,它也有古代写法:

link_libraries(<library>...)

于是,聪明的我们想到了 CMakeLists.txt

# 声明项目名称和版本
cmake_minimum_required(VERSION 3.21)
project(MySimpleProject VERSION 1.0)

# 添加可执行目标
add_executable(MyApp main.cpp)

# 添加头文件目录
target_include_directories(MyApp PRIVATE include)

# 添加链接库
link_libraries(z)	# 新增

然后构建、编译,然后报错:

在这里插入图片描述

为什么?因为 MyApp 目标不知道你加了 zlib 库!

正确应该这样:

# 声明项目名称和版本
cmake_minimum_required(VERSION 3.21)
project(MySimpleProject VERSION 1.0)

link_libraries(z)	# 放到目标前添加

# 添加可执行目标
add_executable(MyApp main.cpp)

# 添加头文件目录
target_include_directories(MyApp PRIVATE include)

想必也不用解释了~

八、源码、中间文件分离

我们发现,执行 cmake . 时,中间文件会出现在根目录中,污染了我们的代码文件。

我们可以在根目录中创建一个 build 目录,接下来有两种做法:

1. 在根目录执行:

cmake -S . -B build
cmake --build build

其中,-S . 指的是源码文件在当前目录,-B build 指的是构建到 build 目录中去;--build build 指的是构建目录是 build 目录,CMake 就知道要在 build 目录中编译了。

2. 在 build 目录中执行:

cmake -S .. -B .
cmake --build .

看第1点的介绍,这里应该很好理解。


【CMake 教程】基础使用教程(一) 至此完毕,希望大家提提意见,欢迎指正!还请大家点点赞,给我点动力~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

指针去哪儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值