网易首页 > 网易号 > 正文 申请入驻

“5 分钟 CMake 使用指南,解决我的 C++ 打包问题!”

0
分享至

在软件开发的世界里,构建系统扮演着至关重要的角色,它不仅决定了项目的构建效率,还直接影响到团队协作的流畅度。对于许多 C++ 开发者而言,CMake 因其强大的功能和广泛的兼容性成为了构建自动化流程的首选工具。

原文链接:https://journal.hexmos.com/cmake-survial-guide/

声明:未经允许,禁止转载。

作 者 | Shrijith Venkatramana

翻译 | 郑丽媛

出品 | CSDN(ID:CSDNnews)

最近我一直在用 C++ 处理一些编程挑战,其中管理 C++ 项目的一个重要方面就是依赖管理。

如今,我们在很多编程生态系统中享受着即时包管理器的便利:

● 在 Node.js/JavaScript 中使用 npm

● 在 Rust 中使用 cargo

● 在 Python 中使用 pip

而在 C++ 中,尽管有像 Conan 这样的包管理器,但处理实际项目时,你通常会发现 CMake 是绕不开的选择。因此如果你想在 C++ 生态系统中工作,学习如何使用 CMake 就不是可选项,而是必修课。

CMake 到底是什么,为什么要学它?

CMake 是一个跨平台的构建系统生成器。跨平台这一点非常重要,因为 CMake 能够在一定程度上抽象出不同平台之间的差异。

例如在类 Unix 系统上,CMake 会生成 makefile 文件,然后用这些文件来构建项目。而在 Windows 系统中,CMake 会生成 Visual Studio 项目文件,随后用于构建项目。

需要注意的是,不同平台通常都有各自的编译和调试工具链:Unix 使用 gcc,macOS 使用clang 等等。

在 C++ 生态系统中,另一个重要方面是能同时处理可执行文件和库。

可执行文件可基于以下不同因素:

● 目标 CPU 架构

● 目标操作系统

● 其他因素

对于库来说,链接方式也有不同的选择(链接是指在代码中使用另一个代码库的功能,而无需了解其具体实现):

● 静态链接

● 动态链接

我曾在一些内部原型项目中,需要调用底层操作系统 API 来执行某些任务,唯一可行的高效方法就是基于一些 C++ 库来进行构建。

CMake 是如何工作的:三个阶段

1. 配置阶段

CMake 会读取所有的 CMakeLists.txt 文件,并创建一个中间结构来确定后续步骤(如列出源文件、收集要链接的库等)。

2. 生成阶段

基于配置阶段的中间输出,CMake 会生成特定平台的构建文件(如在 Unix 系统上生成 makefiles 等)。

3. 构建阶段

使用特定平台的工具(如 make 或 ninja)来构建可执行文件或库文件。

一个简单的 CMake 项目示例(Hello World!)

假设你有一个用于计算数字平方根的 C++ 源文件。

tutorial.cxx

// A simple program that computes the square root of a number
#include
#include // TODO 5: Remove this line
#include
#include

// TODO 11: Include TutorialConfig.h

int main(int argc, char* argv[])
{
if (argc < 2) {
// TODO 12: Create a print statement using Tutorial_VERSION_MAJOR
// and Tutorial_VERSION_MINOR
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}

// convert input to double
// TODO 4: Replace atof(argv[1]) with std::stod(argv[1])
const double inputValue = atof(argv[1]);

// calculate square root
const double outputValue = sqrt(inputValue);
std::cout << "The square root of " << inputValue << " is " << outputValue
<< std::endl;
return 0;
}

CMakeLists.txt

project(Tutorial)
add_executable(tutorial tutorial.cxx)

上述两行是生成一个可执行文件所需的最少指令。理论上,我们还应该指定 CMake 的最低版本号,省略 CMake 会默认使用某个版本(暂时跳过这部分)。

严格来说,project 指令并非必需,但我们还是保留它。所以最重要的代码行是:

add_executable(tutorial tutorial.cxx)

这行代码指定了目标二进制文件 tutorial 以及源文件 tutorial.cxx。

如何构建

以下是一组用于构建项目和测试二进制文件的命令,稍后会详细解释:

mkdir build
cd build/
cmake ..
ls -l # inspect generated build files
cmake --build .
./tutorial 10 # test the binary

从上面的步骤可以看到,整个构建过程大约涉及 5-6 个步骤。

首先,在 CMake 中,我们应该将构建相关的内容与源代码分开,所以先创建一个构建目录:

mkdir build

然后我们可以在构建目录中进行所有与构建相关的操作:

cd build

从这一步开始,我们将执行多个构建相关的任务。

先是生成配置文件:

cmake ..

在这一步中,CMake 会生成平台特定的配置文件。在我的 Ubuntu 系统中,我看到了生成的makefile,这些文件相当冗长,但目前我不需要担心它们。

接下来,我根据新生成的文件触发构建:

cmake --build .

这一步使用生成的构建文件,生成目标二进制文件 tutorial。

最后,我可以通过以下命令验证二进制文件是否如预期运行:

./tutorial 16

我得到了预期的答案,这说明构建过程运行正常!

在 C++ 项目中注入变量

CMake 通过 Config.h.in 提供了一种机制,允许你在 CMakeLists.txt 中指定变量,这些变量可以在你的 .cpp 文件中使用。

下面是一个示例,我们在 CMakeLists.txt 中定义了项目的版本号,并在程序中使用。

Config.h.in

在这个文件中,来自 CMakeLists.txt 的变量将以 @VAR_NAME@ 的形式出现。

#pragma once

#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR#@spiderLineBreak#define AUTHOR_NAME "@AUTHOR_NAME@"

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(Tutorial)

# Define configuration variables
set(PROJECT_VERSION_MAJOR 1)
set(PROJECT_VERSION_MINOR 0)
set(AUTHOR_NAME "Jith")

# Configure the header file
configure_file(Config.h.in Config.h)

# Add the executable
add_executable(tutorial tutorial.cxx)

# Include the directory where the generated header file is located
target_include_directories(tutorial PRIVATE "${CMAKE_BINARY_DIR}")

请注意,我们添加了 cmake_minimum_required 来指定所需的最低 CMake 版本,这是编写 CMakeLists.txt 文件时的一个良好习惯。

然后,我们使用多个 set() 语句来定义所需的变量名。接着,指定配置文件 Config.h.in,通过该文件来使用上述设置的变量。

最后,CMake 会在变量占位被填充后生成头文件,这些动态生成的头文件需要被包含到项目中。

在我们的示例中,Config.h 文件将被放置在 ${CMAKE_BINARY_DIR} 目录中,所以我们只需指定该路径即可。

你可能会对以下这一行的 PRIVATE 标签感到好奇:

target_include_directories(tutorial PRIVATE "${CMAKE_BINARY_DIR}")

理解 CMake 的两个关键概念:可见性修饰符和目标

在 CMake 中,有三个可见性修饰符:PRIVATE、PUBLIC、INTERFACE。

这些修饰符可以在命令中使用,例如:target_include_directories 和 target_link_libraries 等。

这些修饰符是在目标(Targets)的上下文中指定的。目标是 CMake 中的一种抽象概念,表示某种类型的输出:

● 可执行目标(通过 add_executable)生成二进制文件

● 库目标(通过 add_library)生成库文件

● 自定义目标(通过 add_custom_target)通过脚本等生成任意文件

所有上述的目标都会产生具体的文件或工件作为输出。库目标的一个特殊情况是接口目标(Interface Target)。接口目标的定义如下:

add_library(my_interface_lib INTERFACE)
target_include_directories(my_interface_lib INTERFACE include/)

在这里,my_interface_lib 并不会立即生成任何文件。但在后续阶段,一些具体的目标可能会依赖于 my_interface_lib。这意味着,接口目标中指定的 include 目录也会被依赖。因此,INTERFACE 库可以看作是构建依赖关系树的一种便利机制。

理解了目标和依赖的概念之后,我们就回到可见性修饰符的概念。

PRIVATE 可见性

1target_include_directories(tutorial PRIVATE "${CMAKE_BINARY_DIR}")

PRIVATE 表示目标 tutorial 将使用指定的包含目录。但如果在后续阶段其他目标链接到 tutorial,包含目录将不会传递给那些依赖项。

PUBLIC 可见性

1target_include_directories(tutorial PUBLIC "${CMAKE_BINARY_DIR}")

使用 PUBLIC 修饰符意味着目标 tutorial 需要使用该包含目录,并且任何依赖于 tutorial 的其他目标也会继承这个包含目录。

INTERFACE 可见性

1target_include_directories(tutorial INTERFACE "${CMAKE_BINARY_DIR}")

INTERFACE 修饰符表示 tutorial 本身不需要该包含目录,但任何依赖于 tutorial 的其他目标会继承这个包含目录。

简单总结,可见性修饰符的工作原理如下:

● PRIVATE:源文件和依赖关系只传递给当前目标;

● PUBLIC:源文件和依赖关系传递给当前目标及其依赖的目标;

INTERFACE:源文件和依赖关系不传递给当前目标,但会传递给依赖于它的目标。

将项目构建划分为库和目录

随着项目规模不断增长,通常需要模块化来组织项目并管理复杂性。

在 CMake 中,可以用子目录来指定独立的模块及其自定义的构建流程。我们可以拥有一个主 CMake 配置,它能触发多个库(子目录)的构建,最后将所有模块链接在一起。

这是一个经过简化后的示例。我们将创建一个名为 MathFunctions 的模块/库,它将构建为一个静态库(在 Unix 系统上生成 MathFunctions.a),最后再把它链接到我们的主程序中。

首先是源文件部分(代码较为简单):

MathFunctions.h

#pragma once

namespace mathfunctions {
double sqrt(double x);
}

MathFunctions.cxx

#include "MathFunctions.h"
#include "mysqrt.h"

namespace mathfunctions {
double sqrt(double x)
{
return detail::mysqrt(x);
}
}

mysqrt.h

#pragma once

namespace mathfunctions {
namespace detail {
double mysqrt(double x);
}
}

mysqrt.cxx

#include "mysqrt.h"

#include

namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}

double result = x;

// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
}
}

以上这些代码片段,引入了一个名为 mathfunctions 的命名空间,其中包含了一个自定义的 sqrt 函数实现。这样我们就可以在项目中定义自己的平方根函数,而不会与其他版本的 sqrt 冲突。

接下来,如何将该文件夹构建为 Unix 二进制文件?我们需要为该模块/库创建一个自定义的 CMake 子配置:

MathFunctions/CMakeLists.txt

add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)

通过这条简单的 add_library 指令,我们指定了需要编译的 .cxx 文件来生成库文件。

但这还不够,解决方案的核心在于如何将这个子目录或库链接到我们的主项目中:

tutorial.cxx(使用库/模块版本)

#include "Config.h"
#include "MathFunctions.h"
#include
#include
#include
#include

int main(int argc, char* argv[])
{
std::cout << "Project Version: " << PROJECT_VERSION_MAJOR << "." << PROJECT_VERSION_MINOR << std::endl;
std::cout << "Author: " << AUTHOR_NAME << std::endl;

if (argc < 2) {
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}

const double inputValue = atof(argv[1]);

// use library function
const double outputValue = mathfunctions::sqrt(inputValue);
std::cout << "The square root of " << inputValue << " is " << outputValue
<< std::endl;
return 0;
}

在这个文件中,我们导入了 MathFunctions.h,并使用命名空间 mathfunctions 来调用自定义的 sqrt 函数。我们都知道 MathFunctions.h 位于子目录中,但可以直接引用它,就像它在根目录中似的,这是怎么做到的?答案在于修订后的主 CMake 配置文件中:

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(Tutorial)

# Define configuration variables
set(PROJECT_VERSION_MAJOR 1)
set(PROJECT_VERSION_MINOR 0)
set(AUTHOR_NAME "Jith")

# Configure the header file
configure_file(Config.h.in Config.h)

add_subdirectory(MathFunctions)

add_executable(tutorial tutorial.cxx)


target_include_directories(tutorial PUBLIC "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/MathFunctions")

target_link_libraries(tutorial PUBLIC MathFunctions)

这里有几条新命令:

● add_subdirectory 指定了一个子目录构建,CMake 将负责处理该子目录中的构建任务。

● target_include_directories 告诉 CMake MathFunctions 文件夹的路径,这样我们可以在 tutorial.cxx 中直接引用 MathFunctions.h。

● target_link_libraries 将 MathFunctions 库链接到主程序 tutorial 中。

当我在 Linux 上构建这个项目时,我看到 build/MathFunctions 目录下生成了 libMathFunctions.a 文件,这是一个静态链接的库文件,它已经成为主程序的一部分。

现在,我们还可以随意移动生成的 tutorial 可执行文件,它将继续正常运行,因为 libMathFunctions.a 已经被静态链接进主程序中。

下一步是什么?

学习 CMake 的基本工作原理和如何用它完成一些基本任务确实很有意思。

CMake 解决了我现在在 C++ 打包方面遇到的大部分问题。同时,探索 Conan 和 vcpkg 以简化 C++ 中的依赖管理也是一件有趣的事情。未来有机会的话,我应该会进一步了解和尝试这些工具。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
26国对伊朗下通牒!武力护航霍尔木兹,全球耐心耗尽

26国对伊朗下通牒!武力护航霍尔木兹,全球耐心耗尽

凤眼论
2026-05-15 21:45:41
方媛,为何要来《桃花坞6》没苦硬吃?

方媛,为何要来《桃花坞6》没苦硬吃?

娱乐圈笔娱君
2026-05-15 14:19:34
终于拍桌子了!荷兰为阿斯麦硬刚美国:不允许对中国进一步限制

终于拍桌子了!荷兰为阿斯麦硬刚美国:不允许对中国进一步限制

小鹿姐姐情感说
2026-05-16 06:08:12
中国有宴会厅,美国也要修,特朗普迫不及待宣布要修园子

中国有宴会厅,美国也要修,特朗普迫不及待宣布要修园子

三叔的装备空间
2026-05-15 22:43:59
中国做出两个承诺!特朗普亲口证实:不提供军武、帮忙通海峡

中国做出两个承诺!特朗普亲口证实:不提供军武、帮忙通海峡

子桑说
2026-05-15 16:12:39
医学发现:只要血糖在这个范围,不会引发并发症,不要自己害自己

医学发现:只要血糖在这个范围,不会引发并发症,不要自己害自己

冷眼看世界728
2026-05-14 21:45:01
马斯克表示:他绝对能建造出比中国任何公共交通系统都更好的系统

马斯克表示:他绝对能建造出比中国任何公共交通系统都更好的系统

华史谈
2026-04-14 13:00:13
又一新能源爆燃,车门无法打开!施救者徒手掰车,车企曝光引争议

又一新能源爆燃,车门无法打开!施救者徒手掰车,车企曝光引争议

史料布籍
2026-05-14 15:22:13
上海的老破小,涨了!

上海的老破小,涨了!

魔都财观
2026-05-15 07:40:54
人伦之乱,正在悄悄毁掉无数家庭!看完一身冷汗

人伦之乱,正在悄悄毁掉无数家庭!看完一身冷汗

三农老历
2026-05-08 19:20:12
一夜降价2000元!库克来中国后深夜送上大礼,国产机真慌了?

一夜降价2000元!库克来中国后深夜送上大礼,国产机真慌了?

科技专家
2026-05-15 14:28:20
为什么航母速度都在30节左右,30节换成汽车的速度是多快?

为什么航母速度都在30节左右,30节换成汽车的速度是多快?

吴王旅行ing
2026-05-12 23:36:17
争议!法国仅带5中场踢世界杯 主帅弃用皇马5千万巨星:让他恨我

争议!法国仅带5中场踢世界杯 主帅弃用皇马5千万巨星:让他恨我

我爱英超
2026-05-15 07:45:16
你们都是什么时候对男女之事开窍的?网友:果然还是拦不住有心人

你们都是什么时候对男女之事开窍的?网友:果然还是拦不住有心人

夜深爱杂谈
2026-02-21 21:37:02
张雪峰猝死不到2月,小沈阳被紧急送往就医,已是10天内第二次

张雪峰猝死不到2月,小沈阳被紧急送往就医,已是10天内第二次

情感大头说说
2026-05-16 00:59:38
跨界大瓜!特罗萨德夜店狂欢,竟与安妮海瑟薇同框?

跨界大瓜!特罗萨德夜店狂欢,竟与安妮海瑟薇同框?

仰卧撑FTUer
2026-05-15 12:05:11
国际足联秘书长回应央视世界杯版权费用:不能透露细节,“只能说我们对协议非常满意,很高兴能继续合作,将足球赛事带给所有中国的球迷”

国际足联秘书长回应央视世界杯版权费用:不能透露细节,“只能说我们对协议非常满意,很高兴能继续合作,将足球赛事带给所有中国的球迷”

大风新闻
2026-05-15 19:27:07
看中国仪仗队,特朗普直接敬礼,对比美国250年阅兵,差距太悬殊

看中国仪仗队,特朗普直接敬礼,对比美国250年阅兵,差距太悬殊

胖福的小木屋
2026-05-15 16:21:52
1972年谢富治病逝,毛主席问曾山,周总理心领神会

1972年谢富治病逝,毛主席问曾山,周总理心领神会

奇怪的鲨鱼们
2026-05-16 03:19:50
美媒曝湖人续约投票排名,八村塁斯玛特里夫斯居前詹姆斯仅第5

美媒曝湖人续约投票排名,八村塁斯玛特里夫斯居前詹姆斯仅第5

格斗社
2026-05-16 05:56:52
2026-05-16 06:35:00
CSDN incentive-icons
CSDN
成就一亿技术人
26548文章数 242288关注度
往期回顾 全部

科技要闻

直降千元起步!苹果华为率先开启618让利

头条要闻

黄仁勋在北京喝豆汁痛苦皱眉 问“这是什么东西”

头条要闻

黄仁勋在北京喝豆汁痛苦皱眉 问“这是什么东西”

体育要闻

德约科维奇买的球队,从第6级联赛升入法甲

娱乐要闻

方媛为何要来《桃花坞6》没苦硬吃?

财经要闻

腾讯掉队,马化腾戳破真相

汽车要闻

高尔夫GTI刷新纽北纪录 ID. Polo GTI迎全球首秀

态度原创

教育
数码
手机
旅游
公开课

教育要闻

老师掌掴多名学生后续,系一名书法教师,当地公布处罚结果

数码要闻

联想发布ThinkPad T14 Gen 7 支持LPCAMM2可更换内存

手机要闻

iPhone 17系列全系跳水,最高立减2500!

旅游要闻

藏在沈阳闹市的金色秘境!2 万㎡油菜花全开,地铁直达还免费

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版