Linux C++ 开发5 - 一文了解CMake构建

news/2024/9/20 7:16:06/文章来源:https://www.cnblogs.com/luoweifu/p/18381439
  • 1. 什么是CMake?
    • 1.1. CMake的定义
    • 1.2. CMake有哪些优势?
    • 1.3. CMake 的特点
    • 1.4. Cmake 、CMakeLists.txt 、Make 、Makefile 之间的关系
  • 2. 应用案例
    • 2.1. 项目概述
    • 2.2. CMakeLists.txt
      • 2.2.1. 基本用法
      • 2.2.2. 完整内容
      • 2.2.3. 构建执行

上一篇《Linux C++ 开发4 - 入门makefile一篇文章就够了》我们讲解了通过Makefile来编译 包含多个.cpp和多个.h文件 的复杂C++项目。这种方式用来构建中小型的Linux(或类Unix系统)C++项目,是没有问题的。但如果是跨平台项目或者大型项目,Makefile就显得力不从心了;因为Makefile不具备良好的跨平台性,大型项目的编译规则和依赖项也是比较复杂的,Makefile的编写和维护成本都比较高。这时,CMake就可以派上用场了。

1. 什么是CMake?

1.1. CMake的定义

CMake是一个跨平台的开源构建系统生成器。它能够生成各种构建系统文件,如MakefileVisual Studio 项目文件等。CMake通过读取一个或多个CMakeLists.txt文件来配置项目的构建过程。

1.2. CMake有哪些优势?

相较于Makefile,CMake有以下优势。

  1. 跨平台支持: CMake支持多种操作系统和编译器,使得项目能够在不同平台上进行构建。
  2. 简化构建过程: 通过CMake,开发者可以编写一次构建脚本,然后在不同平台上生成相应的构建文件。
  3. 模块化: CMake支持模块化开发,可以方便地管理项目的依赖关系。

1.3. CMake 的特点

  1. 跨平台支持

    • CMake 支持多种操作系统,包括 Linux、Windows、macOS 等。
    • 它能够生成适用于不同编译器的构建文件,如 MakefileNinjaVisual Studio 项目文件等。
  2. 简化构建过程

    • 通过 CMake,开发者可以编写一次构建脚本(CMakeLists.txt),然后在不同平台上生成相应的构建文件,简化了构建过程。
    • CMake 提供了丰富的命令和选项,使得构建配置更加灵活和高效。
  3. 模块化

    • CMake 支持模块化开发,可以方便地管理项目的依赖关系。
    • 通过 add_subdirectory 命令,可以将大型项目拆分为多个子项目,每个子项目都有自己的 CMakeLists.txt 文件。
  4. 可扩展性

    • CMake 提供了丰富的模块和函数,可以方便地扩展其功能。
    • 开发者可以编写自定义的 CMake 模块和函数,以满足特定项目的需求。
  5. 依赖管理

    • CMake 支持外部依赖的管理,可以通过 find_package 命令查找和链接外部库。
    • 它还支持通过 FetchContent 模块下载和集成第三方库。

1.4. Cmake 、CMakeLists.txt 、Make 、Makefile 之间的关系

  • CMakeLists.txtCMake 的配置文件,定义了项目的源文件、构建规则和依赖关系。
  • CMake 是一个构建系统生成器,负责读取一个或多个 CMakeLists.txt 文件并生成相应的构建文件(如 MakefileVisual Studio 项目文件等)。
  • MakefileMake 工具的配置文件,它包含了一系列规则和指令,定义了如何编译和链接源代码。
  • Make 是一个构建工具,负责读取 Makefile 文件并执行编译和构建过程,生成最终的构建产物。

他们之间的关系可以用下面这张图来表示。

file

2. 应用案例

《Linux C++ 开发4 - 入门makefile一篇文章就够了》一文中,我们用Makefile编译了Iterator项目。现在我们任然以这个项目为例,将其改成通过CMake来构建。

2.1. 项目概述

一个公司有多个部门,每个部门有多个人组成,这些人中有开发人员,有测试人员,和与项目相关的其它人员,其结构如下图片。

file

现在要遍历这个公司的所有开发人员,遍历这个公司的所有测试人员。

在项目的源代码中,我们用迭代器模式实现了这个需求,类的结构图是这样的:

file

详细代码参见: https://gitee.com/spencer_luo/iterator/tree/cmake/

现在我们就以这个项目为例,看看这个项目的CMakeLists.txt需要怎么写?

2.2. CMakeLists.txt

2.2.1. 基本用法

设置 cmake的最低版本号:

cmake_minimum_required(VERSION 3.28.3)

注意:这一项要放在CMakeLists.txt的第一行,否则可能会报错。

设置 项目名称、版本、语言:

project(Iterator VERSION 1.0.0 LANGUAGES CXX)

这里CXX表示C++语言。

设置 C/C++ 的标准:

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)

可以根据自己的需求设置编译时使用的C++版本,如:98/11/14/17/20。(注意:你的编译也要能支持你设置的C++版本)

查找要编译的.cpp文件:

file(GLOB SRC_FILES${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
)
message(DEBUG "COMMON_UTIL_SRC:" ${SRC_FILES})
  • CMAKE_CURRENT_SOURCE_DIR是CMake的内置变量,表示当前CMakeLists.txt文件所在的目录,通过$(CMAKE_CURRENT_SOURCE_DIR)方式来使用该变量,更多内置变量参见官方文档《cmake-variables》。
  • GLOB命令会搜索当前目录下所有.cpp文件,并将它们添加到SRC_FILES变量中。你也可以使用GLOB_RECURSE,与GLOB相比,它不仅会搜索当前目录,还会递归搜索所有子目录。
  • message是CMake的内置命令,用于输出构建相关的信息。第一个参数表示消息的类型,可以是以下这些值(按优先级大小排序):FATAL_ERROR(致命错误消息) > SEND_ERROR(错误消息) > AUTHOR_WARNING(警告消息) > NOTICE(重要消息) > STATUS(状态消息) > DEBUG(调试消息) > TRACE(跟踪消息)。

构建可执行文件:

add_executable(${PROJECT_NAME} ${SRC_FILES})
  • 表示:要将所有.cpp文件(${SRC_FILES})编译并链接成可执行的二进制文件,可执行文件名为项目名。
  • 如果要编译链接成静态库,可以替换成add_library(${PROJECT_NAME} STATIC ${SRC_FILES})
  • 如果要编译链接成动态库,可以替换成add_library(${PROJECT_NAME} SHARED ${SRC_FILES})

根据不同的编译模式添加不同的编译选项:

# 设置构建类型: Debug/Release
set(CMAKE_BUILD_TYPE Debug)
# 设置编译选项
if(CMAKE_BUILD_TYPE STREQUAL "Debug")# Debug模式,编译是需要保留调试符号表set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -pg")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")# Release模式,编译时优化代码,优化选项:-O2set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
endif()

这里通过 if(CMAKE_BUILD_TYPE STREQUAL "Debug") 来判断是Debug模式还是Release模式,然后设置不同的编译选项,Debug模式下编译时保留调试符号表,Release模式下编译时会优化代码。

2.2.2. 完整内容

# 要求的cmake的最低版本号
cmake_minimum_required(VERSION 3.28.3)# 项目名称、版本、语言
project(Iterator VERSION 1.0.0 LANGUAGES CXX)# C/C++ 的标准: C11/C++11
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)# 查找要编译的.cpp文件
file(GLOB SRC_FILES${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
)
message(DEBUG "COMMON_UTIL_SRC:" ${SRC_FILES})# 构建可执行文件
add_executable(${PROJECT_NAME} ${SRC_FILES})

2.2.3. 构建执行

构建项目:

# 开始构建项目,生成Makefile构建系统文件
cmake -B ./build -S ./
-- The CXX compiler identification is GNU 13.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.5s)
-- Generating done (0.1s)
-- Build files have been written to: /mnt/d/workspace/iterator/build
➜  iterator git:(cmake) ✗

cmake -B ./build -S ./说明:

  • -B: 指定构建目录,这里是./build
  • -S: 指定源码目录,这里是./。 这一参数可以不写,不写时表示:源码目录就是当前目录。
  • --log-level: 可以指定CMakeLists.txtmessage输出的日志级别,如可以使用这个命令来构建: cmake -B ./build --log-level DEBUG

开始编译链接:

# 进入 build 目录
cd build
# 查看 build 目录有哪些文件
ls
CMakeCache.txt  CMakeFiles  Makefile  cmake_install.cmake
# 执行make,开始编译链接
make
[ 25%] Building CXX object CMakeFiles/Iterator.dir/Company.cpp.o
[ 50%] Building CXX object CMakeFiles/Iterator.dir/Iterator.cpp.o
[ 75%] Building CXX object CMakeFiles/Iterator.dir/Person.cpp.o
[100%] Linking CXX executable Iterator
[100%] Built target Iterator

执行编译结果:

# 进入 build 目录。此时会发现多了一个可执行文件 Iterator
ls
CMakeCache.txt  CMakeFiles  Iterator  Makefile  cmake_install.cmake
# 执行 Iterator 可执行文件,查看输出结果
./Iterator 
遍历所有开发者:
员工:1-Developer11 开发工程师,擅长语言:C++,负责项目:智慧城市
员工:2-Developer12 开发工程师,擅长语言:Java,负责项目:智慧城市
员工:3-Developer13 开发工程师,擅长语言:JavaScript,负责项目:智慧城市
员工:6-Developer21 开发工程师,擅长语言:IOS,负责项目:智能语音
员工:7-Developer22 开发工程师,擅长语言:Android,负责项目:智能语音
员工:9-Developer31 开发工程师,擅长语言:C++,负责项目:电子书内核
遍历所有测试人员:
员工:4-Tester15 测试工程师,测试类型:LoadRunner
员工:5-Tester16 测试工程师,测试类型:黑盒测试
员工:8-Tester24 测试工程师,测试类型:TestIn
员工:10-Tester35 测试工程师,测试类型:LoadRunner
遍历公司所有员工:
员工:1-Developer11 开发工程师,擅长语言:C++,负责项目:智慧城市
员工:2-Developer12 开发工程师,擅长语言:Java,负责项目:智慧城市
员工:3-Developer13 开发工程师,擅长语言:JavaScript,负责项目:智慧城市
员工:4-Tester15 测试工程师,测试类型:LoadRunner
员工:5-Tester16 测试工程师,测试类型:黑盒测试
员工:6-Developer21 开发工程师,擅长语言:IOS,负责项目:智能语音
员工:7-Developer22 开发工程师,擅长语言:Android,负责项目:智能语音
员工:8-Tester24 测试工程师,测试类型:TestIn
员工:9-Developer31 开发工程师,擅长语言:C++,负责项目:电子书内核
员工:10-Tester35 测试工程师,测试类型:LoadRunner

大家好,我是陌尘。

IT从业10年+, 北漂过也深漂过,目前暂定居于杭州,未来不知还会飘向何方。

搞了8年C++,也干过2年前端;用Python写过书,也玩过一点PHP,未来还会折腾更多东西,不死不休。

感谢大家的关注,期待与你一起成长。



【SunLogging】
扫码二维码,关注微信公众号,阅读更多精彩内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/787632.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BLE 广播报文格式

广播报文结构 一个完整的BLE广播报文由四部分组成,分别是前导码、接入地址、协议数据单元和CRC校验码。Preamble 前导 Access address(接入设备) PDU CRC校验1 Bytes 4 Bytes 2-37 Bytes 3 Bytes前导码:用来同步时序,可以是0x55或者0xAA,由接入地址的第一个比特决定。如果接…

[JLOI2015] 骗我呢——一类经典反射容斥

加载解析界面 数字变化跳跃反射容斥 一层反射:有一条线 \(y=x+b\) 不能碰到。 从第一次碰到直线开始,将后面的部分沿直线翻折,最终一定会到达 \((n-b,n+b)\),因为 \(b\ne 0\),所以构成双射。答案即为 \(\binom{2n}{n}-\binom{2n}{n-b}\)。 注意,如果最终到达的位置是 \((…

南沙区信息学奥林匹克竞赛(信奥赛)介绍

​信息学奥林匹克竞赛(International Olympiad in Informatics,IOI)是一项旨在选拔和培养信息技术和计算机科学人才的国际性竞赛。该竞赛始于1989年,每年举办一次,由不同的国家轮流承办。参加比赛的选手来自全球各国,都是信息技术和计算机科学领域的尖子生。信息学奥林匹…

英文单词字母大小写在线转换工具html代码

这是一个简单而实用的在线大小写转换工具。它允许用户输入任意文本,并提供三种转换选项:转换为全大写、全小写或首字母大写。 使用这个工具非常简单快捷。用户只需要在输入框中输入想要转换的文本,选择合适的转换类型,然后点击"转换"按钮即可。转换结果会立即显示在输…

TCP的调试助手开发笔记

动图:1 先利用VS自带的socket类来写好TCP_CORE: 类目录如下:点击查看代码 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.RegularExp…

Why Transformers Need Adam: A Hessian Perspective

目录概符号说明所有参数的 Hessian 矩阵Block-wise Hessian代码Zhang Y., Chen C., Ding T., Li Z., Sun R. and Luo Z. Why transformers need adam: a hessian perspective. arXiv preprint, 2024.概 本文从 Hessian 矩阵的角度回答为什么 Adam 相较于其它方法, 比如 SGD 在 …

VL24 边沿检测

这个就是需要对a 进行打一拍last_a<=a; 需要理解的点是打一拍的last_a是落后a一个时钟周期的,也就是对当前时刻使用a时候,此时的last_a是a的上一时刻的值。`timescale 1ns/1ns module edge_detect(input clk,input rst_n,input a,output reg rise,output reg down ); reg …

RE入门第三天---TEA算法

OK,老规矩,先复习一下昨天的内容 ..... 几分钟就复习了,直接开干今天的内容 先找大佬的wp 来源: TEA系列加密解密 | Gruges Blog (g2uge.github.io) 逆向算法之TEA算法 - Sk2rw - 博客园 (cnblogs.com) 一.TEA加密解密简介 在密码学中,微型加密算法(Tiny Encryption Algo…

vue3 控制el-dialog 双向绑定显示隐藏

父组件<Contact v-model:isView="isView" /> 子组件<template><div><el-dialogwidth="400"title="微信二维码":model-value="props.isView"@closed="handleClose"><div class="dialog-div…

Typora使用PicGo自动上传图片

Gitee配置PicGo图床 简介 由于我们使用Markdown写博客时需要上传一些图片,以便于理解。但是md文件不像Word文件一样能承载图片传输,所以我们使用md文件进行多设备协作,或者传输发给其他人的时候,图片的传输成了很大的问题。一般情况下我们可以搭建一个文件服务器,但是这样…