C++大型项目管理:通用CMake框架的架构奥秘

往期本博主的 C++ 精讲优质博文可通过这篇导航进行查找:
Lemo 的C++精华博文导航:进阶、精讲、设计模式文章全收录

前言

在当今的软件开发环境中,随着 C++ 项目的不断扩大和变得越来越复杂,有效地管理这些项目成为了一个重要的挑战。 CMake 作为一个跨平台的构建系统,提供了强大的工具来帮助开发者组织和管理大型 C++ 项目。

在本文中,我们将探讨为什么使用 CMake 进行 C++ 大型项目管理以及如何使用通用 CMake 框架进行项目工程的搭建。

文章目录

      • 前言
      • 为什么用 CMake 进行 C++ 大型项目管理
      • 通用 CMake 进行 C++ 项目工程搭建的方法
        • 一个栗子
      • 总结

为什么用 CMake 进行 C++ 大型项目管理

CMake 的优势在于它的平台无关性以及能够管理复杂项目的能力。使用 CMake,开发者可以编写一次构建脚本(CMakeLists.txt 文件),然后在任何支持 CMake 的平台上生成项目。这不仅简化了构建过程,还确保了项目的可移植性。

此外,CMake 支持复杂的项目结构,包括对多个目标和库的管理,以及对依赖关系的精细控制。 CMake 能自动处理源代码文件与目标之间的依赖关系,这对于大型项目而言尤为重要,因为它们往往包含成百上千个文件和复杂的依赖链。

CMake还提供了丰富的功能,以支持现代C++项目的需要,如测试(CTest)、打包(CPack)等,这使得它成为大型项目的理想选择。

通用 CMake 进行 C++ 项目工程搭建的方法

在使用 CMake 构建大型 C++ 项目时,首先需要明确的是,良好的项目结构对项目的可维护性和可扩展性至关重要。

以下是一些常用的实践方法,旨在帮助开发者有效地使用CMake构建和管理项目。

分层项目结构: 为了有效管理项目的不同部分,推荐将项目分成多个逻辑上的层或模块,如UI层、业务逻辑层、数据访问层等。每个层都应该有自己的CMakeLists.txt文件,定义了该层所需的源文件、库依赖等。

使用全局和局部CMakeLists.txt文件: 在项目的根目录下有一个全局CMakeLists.txt文件,它不仅包含了项目的基本信息(如项目名、版本等),还负责将各个子项目/模块组织起来。而每个子目录中的CMakeLists.txt文件则负责处理该目录中的具体构建规则。这种层次化的方法有助于分离不同部分的构建逻辑,便于管理和理解。

找寻和使用第三方库: 当项目需要第三方库时,CMake的 find_package() 命令可以自动检测并配置项目中使用的依赖库。这简化了配置过程,并增加了项目的移植性。

创建可配置的构建选项: 利用CMake的 option() 命令,可以定义可在构建时配置的选项。这对于需要支持多种构建配置的大型项目特别有用。

编写可复用的CMake模块: 对于一些常用的构建逻辑,可以编写自定义的CMake模块来实现代码的复用。这些模块可以被项目中的不同部分所共享,减少了重复代码。

我们通过一个例子,来看这些实践方法是如何作用的。

一个栗子

假设我们有如下C++工程结构:

Demo|| ---- modules|        | ---- CMakeLists.txt|        | ---- Module1|        |        | ----inc|        |        | ----src|        |        | ----CMakeLists.txt|        ||        | ---- Module2|                 | ----inc|                 | ----src|                 | ----CMakeLists.txt|| ---- thirdparty|        | ----include|        | ----lib|        | ----bin|| ---- utils|        | ---- SettingUtils.cmake|| ---- CMakeLists.txt

即,我们有一个叫 Demo 的工程,里面有一个模块集 Modules 两个子模块:Module1Module2;还依赖第三方件:thirdparty;自有一个辅助文件夹 utils

我们来为这个项目设计 CMake 框架:

在 Demo 项目根目录的 CMakeLists.txt 我们通常设置一些全局信息:

cmake_minimum_required(VERSION 3.1)set(PROJECT_NAME MySolution)
project(${PROJECT_NAME})set_property(GLOBAL PROPERTY USE_FOLDERS ON)if (NOT CMAKE_BUILD_TYPE)set(CMAKE_BUILD_TYPE "Release")
endif()include(utils/SettingUtils.cmake)string(REPLACE "\\" "/" THIRDPARTY_ROOT ${THIRDPARTY_ROOT})if (NOT EXISTS ${THIRDPARTY_ROOT})message(FATAL_ERROR "Please specify the THIRDPARTY_ROOT")
endif()
message("Based on WUKONG SecDev package at ${THIRDPARTY_ROOT}")DEFAULT_CONFIGURATION_SETTINGS()include_directories("${THIRDPARTY_ROOT}/include/inc1""${THIRDPARTY_ROOT}/include/inc2")ADD_SUBDIRECTORY(modules)

在此,我们设置了该 CMake 工程的最低 cmake 版本,设置了生成解决方案的名字,依赖第三方库的项,以及一些编译链接的宏设置。

我们接下来看里面怎么写:

可复用的 CMake 代码 SettingUtils.cmake

MACRO(DEFAULT_CONFIGURATION_SETTINGS)set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "Configurations" FORCE)if (NOT CMAKE_BUILD_TYPE)set(CMAKE_BUILD_TYPE "Release")endif()
ENDMACRO(DEFAULT_CONFIGURATION_SETTINGS)MACRO(SOLUTION_DEFINITIONS_DEFAULT_SETTINGS)add_definitions(-DWIN32-DUNICODE-D_UNICODE)
ENDMACRO(SOLUTION_DEFINITIONS_DEFAULT_SETTINGS)MACRO(SOLUTION_COMPILE_OPTIONS_DEFAULT_SETTRINGS)set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_RELEASE}")set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_RELEASE}")set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")if (CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Debug"))string(REPLACE "/O2" "/Od" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")string(REPLACE "/O2" "/Od" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zi")set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Zi")add_link_options(/DEBUG)elseif (CMAKE_BUILD_TYPE AND (CMAKE_BUILD_TYPE STREQUAL "Release"))add_definitions(-DNDEBUG)# 编译器优化add_compile_options(/Ob1)add_compile_options(/Oi)add_compile_options(/Ot)add_compile_options(/GF)add_compile_options(/Gy)endif()add_compile_options(/W3)add_compile_options(/utf-8)add_compile_options(/std:c++14)add_compile_options(/wd4819)add_compile_options(/wd4275)add_compile_options(/wd4251)add_link_options(/SUBSYSTEM:WINDOWS)
ENDMACRO(SOLUTION_COMPILE_OPTIONS_DEFAULT_SETTRINGS)MACRO(IDE_DEBUG_ENVIRONMENT_DEFAULT_SETTING)IF(MSVC)SET_PROPERTY(DIRECTORY ${CMAKE_BINARY_DIR} PROPERTY VS_STARTUP_PROJECT ${startup_project})SET_TARGET_PROPERTIES(${startup_project} PROPERTIES VS_DEBUGGER_COMMAND ${THIRDPARTY_ROOT}/bin/Main.exeVS_DEBUGGER_WORKING_DIRECTORY ${THIRDPARTY_ROOT}/bin)ENDIF()
ENDMACRO(IDE_DEBUG_ENVIRONMENT_DEFAULT_SETTING)MACRO(IDE_INSTALL_ENVIRONMENT_DEFAULT_SETTING)install(DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/DESTINATION ${THIRDPARTY_ROOT}/bin/FILES_MATCHING PATTERN "*.dll")
ENDMACRO(IDE_INSTALL_ENVIRONMENT_DEFAULT_SETTING)MACRO(IDE_INSTALL_LIB_PDB_SETTING)add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${PROJECT_NAME}.lib${THIRDPARTY_ROOT}/lib/${PROJECT_NAME}.lib)IF(CMAKE_BUILD_TYPE STREQUAL "Debug")add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${PROJECT_NAME}.pdb${THIRDPARTY_ROOT}/pdb/${PROJECT_NAME}.pdb)ENDIF()
ENDMACRO(IDE_INSTALL_LIB_PDB_SETTING)MACRO(IDE_INSTALL_DEFAULT_DLL_SETTING)add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${PROJECT_NAME}.dll${THIRDPARTY_ROOT}/bin/${PROJECT_NAME}.dll)
ENDMACRO(IDE_INSTALL_DEFAULT_DLL_SETTING)MACRO(IDE_INSTALL_PLUGINS_DLL_SETTING)add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${PROJECT_NAME}.dll${THIRDPARTY_ROOT}/bin/plugins/designer/${PROJECT_NAME}.dll)
ENDMACRO(IDE_INSTALL_PLUGINS_DLL_SETTING)MACRO(IDE_INSTALL_EXE_SETTING)add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${PROJECT_NAME}.exe${THIRDPARTY_ROOT}/bin/${PROJECT_NAME}.exe)IF(CMAKE_BUILD_TYPE STREQUAL "Debug")add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${PROJECT_NAME}.pdb${THIRDPARTY_ROOT}/pdb/${PROJECT_NAME}.pdb)ENDIF()
ENDMACRO(IDE_INSTALL_EXE_SETTING)MACRO(RESOURCE_AND_CONFIG_DEFAULT_SETTING)install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../tools/xxx.batDESTINATION ${THIRDPARTY_ROOT}/bin/)install(CODE "execute_process(COMMAND cmd /c ${THIRDPARTY_ROOT}/bin/xxx.bat)")
ENDMACRO(RESOURCE_AND_CONFIG_DEFAULT_SETTING)

我们来看下这些宏都是什么意思:

  • DEFAULT_CONFIGURATION_SETTINGS 设置默认的编译类别是哪些
  • SOLUTION_DEFINITIONS_DEFAULT_SETTINGS 设置默认的解决方案相关的宏定义
  • SOLUTION_COMPILE_OPTIONS_DEFAULT_SETTRINGS 进行默认的编译、链接选项设置
  • IDE_DEBUG_ENVIRONMENT_DEFAULT_SETTING 设置解决方案启动项
  • IDE_INSTALL_ENVIRONMENT_DEFAULT_SETTING 默认 install 时的设置
  • IDE_INSTALL_LIB_PDB_SETTING lib 和 pdb 文件的拷贝
  • IDE_INSTALL_DEFAULT_DLL_SETTING dll 文件的拷贝
  • IDE_INSTALL_PLUGINS_DLL_SETTING 插件 dll 文件的拷贝
  • IDE_INSTALL_EXE_SETTING exe 文件的拷贝
  • RESOURCE_AND_CONFIG_DEFAULT_SETTING 资源和配置文件的设置

我们接着往 modules 文件夹里面看,看这里面的 CMakeLists.txt 文件该如何编写:

ADD_SUBDIRECTORY(Module1)
ADD_SUBDIRECTORY(Module2)

这一层的 CMakeLists.txt 直接指向内层的文件夹就行。

对于里面的 Module1Module2 ,都是到了具体的模块业务,只用编写自己的 CMakeLists.txt 文件就行。

编写方式也是大同小异。我们以 Module1 的 CMakeLists.txt 来举例:

set(PROJECT_NAME Module1)
project(${PROJECT_NAME})SOLUTION_DEFINITIONS_DEFAULT_SETTINGS()add_definitions(-DXXX_MODULE  # 其中 {XXX_MODULE}参数填写 xxx_export.h 中的 define 对象-D${PROJECT_NAME}_EXPORTS   # 注意这一行参数一般填写 `library_name + _EXPORTS`
)SOLUTION_COMPILE_OPTIONS_DEFAULT_SETTRINGS()include_directories("....")link_directories("....")# 添加所需的lib文件
link_libraries(....)# Common model
set(COMMON_MODULE_INC....h)set(COMMON_MODULE_SRC.....cpp)# HiWorld example
set(HI_WORLD_INC.....h)set(HI_WORLD_SRC.....cpp) set(INC${COMMON_MODULE_INC}${HI_WORLD_INC})set(SRC${COMMON_MODULE_SRC}${HI_WORLD_SRC})add_library(${PROJECT_NAME} SHARED ${INC} ${SRC})
target_link_libraries(${PROJECT_NAME} SecDevPrjDatabase)
set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "module1")# 添加解决方案分目录存放
SOURCE_GROUP("Common\\inc"FILES${COMMON_MODULE_INC})SOURCE_GROUP("Common\\src"FILES${COMMON_MODULE_SRC})SOURCE_GROUP("HiWorld\\inc"FILES${HI_WORLD_INC})SOURCE_GROUP("HiWorld\\src"FILES${HI_WORLD_SRC})set(startup_project ${PROJECT_NAME})  # 启动项只添加到一个工程中
IDE_DEBUG_ENVIRONMENT_DEFAULT_SETTING()
IDE_INSTALL_ENVIRONMENT_DEFAULT_SETTING()# 新增 install 时,同时拷贝资源配置文件的命令。
RESOURCE_AND_CONFIG_DEFAULT_SETTING()

至此,Cmake 的初始框架就基本搭建完成,后续的模块增加,无非是在这个框架基础上进行完善和填充。

总结

CMake 提供了一套强大的工具,能够帮助开发人员有效地管理和构建大型 C++ 项目。通过采用良好的组织结构,利用 CMake 的强大功能,如自动依赖处理、第三方库的集成等,可以极大地提高项目的可维护性和可扩展性。同时,CMake 的跨平台特性确保了项目可以在不同环境中轻松构建,从而促进了项目的可移植性。

通过本文的讨论,我们看到了使用 CMake 对于大型 C++ 项目管理的重要性,以及实施通用 CMake 框架的基本方法。希望这些信息可以帮助 C++ 开发者更好地理解和运用 CMake,从而提升其项目管理的效率和质量。

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

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

相关文章

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第35课-3D互动教材

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第35课-3D互动教材 使用dtns.network德塔世界(开源的智体世界引擎),策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界引擎&am…

Zero Infinity原理

如上图,一次加载一个layer的一个weights分片(一层的1/DP的参数量),Broadcast至所有rank,计算各自的梯度,再Reduce至其中一个负责的rank,offload存放至CPU Memory,释放GPU里的weights…

i.MX8MP平台开发分享(TMU驱动及用户接口篇)

概念 thermal zone 温度控制区域。sensor 获取温度。trip points 温度跳变点,或者是温度阈值。cooling device Thermal Cooling Device 是可以降温设备的抽象,能降温的设备比如风扇,这些好理解,但是像CPU、GPU 这些 Cooling devi…

探究Spring中的Controller:单例、多例及其并发安全性

1. Spring框架的简介 Spring是一个开源的Java平台,用来简化企业级应用程序的开发。Spring框架提供了一整套统一的编程模型,使得开发人员能够更加专注于业务逻辑,而不必去处理复杂的技术细节。Spring包含多个模块,其中最常使用的就…

cesium Material的理解与使用

1.简介 材质Material可以是比较简单的,比如直接将一张图片赋予表面,或者使用条纹状、棋盘状的图案;也可以使用Fabric和GLSL,重新创建一个新的材质或者组合现有的材质。例如,我们可以通过程序生成的纹理(procedural bri…

el-input实现后缀图标和clearable的兼容,调整el-input clearable与自定义图标展示位置问题

背景:常见的输入框存在两个图标的展示效果都是清空在前搜索或其他图标在后 常见以及最终实现效果(清空图标在前,搜索图标在后) BUG以及el-input默认效果 问题排查 通过控制台审查元素能够发现,默认的效果是自定义图标…

pyautogui模拟鼠标拖动选中文字的基本知识(附Demo)

目录 前言1. Demo1.1 特定窗口点击拖动1.2 屏幕中间点击拖动 2. 基本知识 前言 相关知识推荐阅读: 详细分析Python中的Pyautogui库(附Demo)详细分析PyAutoGUI中的locate函数(附Demo) 1. Demo 先给出一部分代码展示…

银河麒麟操作系统安装conda

参考: https://blog.csdn.net/Andy_shenzl/article/details/136294743 查看系统版本 uname -m 下载对应版本 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/?CM&OD 安装 bash Anaconda3-2023.09-0-Linux-aarch64.sh 刷新环境 source ~/.bashrc 查看…

基于C#的计算机与安捷伦34970A通信方法

概述 安捷伦34970A采集数据,34970A支持RS232接口,但是如果直接用winform自带的seriaport类基本是没必要使用的,安捷伦等仪表通讯需要用到VISA的库。 库的获取 1. 是德科技的IO Library. 2. NI下载NI-VISA. 两者用法接近. 代码如下 using…

WLAN基础-WLAN安全

目录 一、引言二、WLAN安全威胁三、WLAN安全防御机制四、WLAN常用接入认证方式五、总结 一、引言 随着无线网络的广泛应用,WLAN(无线局域网)因其灵活性和便利性成为越来越多用户和企业首选的接入方式。然而,由于无线通信开放的传…

【动手学深度学习】多层感知机之暂退法研究详情

目录 🌊1. 研究目的 🌊2. 研究准备 🌊3. 研究内容 🌍3.1 多层感知机暂退法 🌍3.2 基础练习 🌊4. 研究体会 🌊1. 研究目的 防止过拟合:权重衰减和暂退法都是用来控制模型的复杂…

基于springboot实现疫情信息管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现疫情信息管理系统演示 摘要 近年来,信息化管理行业的不断兴起,使得人们的日常生活越来越离不开计算机和互联网技术。首先,根据收集到的用户需求分析,对设计系统有一个初步的认识与了解,确定疫情信息…

【C++题解】1392 - 回文偶数?

问题:1392 - 回文偶数? 类型:for循环 题目描述: 小明发现有一类数非常有趣,他们正过来读和反过来读是一样的,比如:121、202、383 等,小明给这类数起了一个名字,叫做回文…

【Python】 将日期转换为 datetime 对象在 Python 中

基本原理 在 Python 中,处理日期和时间的库是 datetime,它提供了广泛的功能来处理日期和时间。datetime 模块中有一个 datetime 类,它可以用来表示日期和时间。有时,我们可能会遇到需要将日期字符串转换为 datetime 对象的情况&a…

《绝区零》测试开启,揭开了米哈游海外战略意图

原标题:《绝区零》公测,米哈游希望用一个成熟平台迎接《绝区零》的诞生 易采游戏网6月5日消息:随着《绝区零》公测日期的逐渐迫近,身为米哈游的忠实拥趸,对于这款新作怀揣着无尽期待。作为中国二次元游戏行业的领军企业…

一文读懂银行承兑汇票:从申请到使用全攻略

银行承兑汇票(Banks Acceptance Bill,BA)是商业汇票的一种。它是由在承兑银行开立存款账户的存款人出票,向开户银行申请并经银行审查同意承兑的,保证在指定日期无条件支付确定的金额给收款人或持票人的票据。银行承兑汇…

qt c++类继承QWidget和不继承有什么区别

class CheckBoxSetting {Q_OBJECT public:CheckBoxSetting(); };和 class CheckBoxSettingsEditor : public QWidget {Q_OBJECTpublic:explicit CheckBoxSettingsEditor(QWidget *parent 0);~CheckBoxSettingsEditor();有什么区别? 这两个类 CheckBoxSetting 和 C…

Klipper安装

安装必要软件 Klipper相关的软件需要Python等软件才能运行,且需要git来同步最新的Klipper等软件源代码,所以需要使用sudo apt install build-essential python3 python3-pip git来安装必要的软件。 安装好pip后,运行pip config set global.i…

Linux系统Docker部署Apache Superset并实现远程访问详细流程

目录 前言 1. 使用Docker部署Apache Superset 1.1 第一步安装docker 、docker compose 1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网穿透,实现公网访问 3. 设置固定连接公网地址 前言 作者简介: 懒大王敲代码&#xff0…

校园外卖系统的技术架构与实现方案

随着校园生活的日益现代化,外卖需求在高校学生群体中迅速增长。为了满足这一需求,校园外卖系统应运而生。本文将详细探讨校园外卖系统的技术架构及其实现方案,帮助读者了解这一系统的核心技术与实现路径。 一、系统概述 校园外卖系统主要包…