第三节:CMake 工程实践场景笔记

第三节:CMake 工程实践场景笔记

1. 基础场景:可执行文件 (Executable)

这是最简单的场景,目标是编译出一个可以直接运行的程序(如 hello.exehello),并把它安装到系统目录。

1.1 核心流程

  1. 定义版本与项目:规定最低版本,给项目起名。
  2. 生成目标:告诉 CMake 我要造一个可执行程序。
  3. 安装 :定义编译好的程序要去哪里(比如 /usr/local/bin)。

1.2 关键代码模板

cmake 复制代码
cmake_minimum_required(VERSION 3.18)
project(InstallHello VERSION 1.0.0 LANGUAGES CXX)

# 1. 生成可执行文件
# 语法:add_executable(名字 源文件)
add_executable(hello main.cpp)

# 2. 安装配置 (重要!)
# 引入 GNU 标准安装目录模块,方便跨平台兼容
include(GNUInstallDirs)

# 语法:install(TARGETS 目标名 ...)
# 这里的 hello 就是上面定义的目标
install(TARGETS hello) 
# 默认安装位置:Linux 下通常是 /usr/local/bin

1.3 常用命令与变量笔记

  • cmake --install .:这是 CMake 3.15+ 推荐的安装命令,比传统的 make install 更通用。
  • CMAKE_INSTALL_PREFIX:控制安装的根目录。
    • 默认是 /usr/local
    • 修改方法:cmake -DCMAKE_INSTALL_PREFIX=/my/custom/path ..
  • message(STATUS "内容"):类似 printf,用于构建过程中打印日志,调试变量很有用。

2. 进阶场景:静态库 (Static Library)

在大型项目中,我们通常把通用功能(如数学计算、网络请求)封装成库,供主程序调用。静态库在编译时会将代码"拷贝"进主程序,生成的可执行文件比较大,但运行时不需要依赖外部文件。

2.1 目录结构管理 (add_subdirectory)

不要把所有文件堆在一个目录。通常做法是:

  • app/:放主程序代码 (main.cpp)
  • my_lib/:放库代码 (add.cpp, sub.cpp)

在根目录的 CMakeLists.txt 中使用 add_subdirectory(my_lib),CMake 就会自动进入子目录处理那个文件夹下的 CMakeLists.txt

2.2 生成静态库

my_lib/CMakeLists.txt 中:

cmake 复制代码
# 收集源文件
file(GLOB SRC_LISTS "src/*.cpp") 

# 生成静态库
# 语法:add_library(库名 STATIC 源文件)
# STATIC 表示静态库 (Linux下生成 .a, Windows下生成 .lib)
add_library(MyMath STATIC ${SRC_LISTS})

2.3 链接静态库 (核心难点)

这是 CMake 的精髓------Target-based(基于目标) 的管理方式。

app/CMakeLists.txt 中:

cmake 复制代码
add_executable(main main.cpp)

# 链接库:告诉 main 程序,你需要用到 MyMath 这个库
target_link_libraries(main PRIVATE MyMath)

问题 :编译器怎么知道库的头文件在哪里?
旧方法 :用 include_directories(全局污染,不推荐)。
新方法:让库自己告诉使用者,"我的头文件在哪里"。

在库的 CMakeLists.txt 中添加:

cmake 复制代码
# INTERFACE 意思是:我自己编译不需要这个路径,但谁链接我,谁就得包含这个路径
target_include_directories(MyMath PUBLIC 
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

这样,只要 main 链接了 MyMath,CMake 会自动把 MyMath 的头文件路径加给 main


3. 核心概念:Public, Private, Interface

这三个关键字决定了属性(头文件路径、编译选项、宏定义)的传播范围。通俗理解:

  • PRIVATE (私有)
    • 含义:我自己用,不告诉别人。
    • 例子:我做面包(构建库)需要用烤箱,但吃面包的人(链接库的人)不需要知道烤箱的存在。
  • INTERFACE (接口)
    • 含义:我自己不用,但别人用我时必须得有。
    • 例子:我卖给你个电器,我自己不插电,但你用的时候必须找个插座(头文件)。纯头文件库常用这个。
  • PUBLIC (公开)
    • 含义:我自己用,别人用我时也得用。
    • 例子:传染病。我有,接触我的人也会有。

4. 进阶场景:动态库 (Shared Library)

动态库(Shared Library)在程序运行时才被加载。Linux 下是 .so,Windows 下是 .dll

4.1 生成动态库

只需将 STATIC 改为 SHARED

cmake 复制代码
add_library(MyMath SHARED ${SRC_LISTS})

4.2 动态库的坑:RPATH

编译能通过,运行却报错 cannot open shared object file

这是因为程序在运行时 找不到 .so 文件在哪里。静态库都塞进 exe 里了所以没这问题。

解决方法 :需要设置 RPATH (Run-time Search Path)。

CMake 默认在 build 目录下会自动设置 RPATH,但在安装(install)后会由系统把 RPATH 抹除。如果需要发布,通常要配置 RPATH 相关的变量。

4.3 查看链接情况

  • ldd 命令ldd ./main
    • 查看可执行程序依赖了哪些动态库,以及是否找到(not found)。
  • readelf 命令readelf -d ./main
    • 查看二进制文件头部的 RPATH 信息。

5. 库的安装与导出 (Export)

如果要让自己写的库像 OpenCV 或 Boost 一样,能被别人的项目通过 find_package(MyMath) 找到,需要做"导出"工作。这部分比较繁琐,是专业库开发的标准动作。

5.1 安装流程

  1. 安装文件:库文件 (.a/.so) 和 头文件 (.h)。
  2. 生成导出文件install(EXPORT ...)。这会生成一个 .cmake 文件,里面记录了库的绝对路径、依赖关系等信息。
  3. 生成版本配置write_basic_package_version_file。让别人能检查版本兼容性。
  4. 生成核心配置configure_package_config_file。生成 MyMathConfig.cmake

别人使用时:

cmake 复制代码
# 在别人的 CMakeLists.txt 中
find_package(MyMath REQUIRED) # CMake 会去系统目录找 MyMathConfig.cmake
target_link_libraries(UserApp PRIVATE MyMath::MyMath)

6. 常用辅助命令笔记

6.1 file 命令

用于文件操作。

  • file(GLOB 变量 "src/*.cpp"):傻瓜式搜索目录下所有 cpp 文件赋值给变量。
    • 缺点:如果新建了文件,不重新运行 cmake 这种通配符可能扫描不到。

6.2 set_target_properties

这是 CMake 的"万能修改器"。可以修改目标的各种属性。

  • 修改输出目录ARCHIVE_OUTPUT_DIRECTORY (静态库位置), LIBRARY_OUTPUT_DIRECTORY (动态库位置), RUNTIME_OUTPUT_DIRECTORY (exe位置)。
  • 修改输出名字OUTPUT_NAME (比如把 libMyMath.so 改名为 libmath.so)。
  • 设置版本VERSIONSOVERSION (生成 libfoo.so.1.2 这种带版本号的库)。

6.3 Generator Expressions (生成器表达式)

长得像 $<...> 的东西。

  • 作用 :在 配置阶段 不知道具体值,等到 生成构建系统阶段 才计算出的值。
  • 常用$<TARGET_FILE:MyMath>
    • 含义:我不关心 MyMath 到底编译到哪个文件夹了,也不关心它是 .a 还是 .so,总之把那个最终文件的绝对路径填在这里。

7. 总结回顾

  1. 编译 exeadd_executable
  2. 编译库add_library (分 STATIC 和 SHARED)。
  3. 建立依赖 永远用 target_link_libraries
  4. 管理头文件 永远用 target_include_directories
  5. Scope 很重要:Private 自己用,Public 大家用,Interface 别人用。
  6. 安装与导出install 只是复制文件,但配合 EXPORT 可以生成专业的 SDK 配置,支持 find_package
相关推荐
肆悟先生3 小时前
3.16 含有可变参数的函数
c++·算法
郝学胜-神的一滴3 小时前
封装OpenGL的Shader相关类:从理论到实践
开发语言·c++·程序人生·游戏·图形渲染
Bruce_kaizy3 小时前
c++图论————最短路之Floyd&Dijkstra算法
c++·算法·图论
WBluuue3 小时前
AtCoder Beginner Contest 437(ABCDEF)
c++·算法
郝学胜-神的一滴3 小时前
Linux 下循环创建多线程:深入解析与实践指南
linux·服务器·c++·程序人生·算法·设计模式
superman超哥3 小时前
仓颉语言中异常处理入门的深度剖析与工程实践
c语言·开发语言·c++·python·仓颉
leiming64 小时前
C++ 类模板对象做函数参数
开发语言·c++·算法
王老师青少年编程4 小时前
csp信奥赛C++标准模板库STL案例应用1
c++·算法·stl·标准模板库·csp·信奥赛·binary_search
moonquakeTT5 小时前
C++:智能指针
开发语言·c++