CMake 实战:创建并使用静态库(MinGW + clangd)

本文通过两个完全独立的 CMake 项目,演示如何在 Windows + PowerShell 7 + MinGW 环境下创建、安装、导出并消费静态库,同时配置 clangd 的代码补全与跳转支持。

示例已经完成实际构建和运行验证。生产端是 cmake-static-lib,消费端是 cmake-use-static-lib;两个项目使用独立包名、命名空间和安装前缀,不会与动态库示例混淆。

一、创建并导出静态库

本文说明 cmake-static-lib 如何生成、安装并导出静态库,使其他 CMake

项目能够通过 find_package() 使用它们。

1. 项目概览

cmake-static-lib 使用 C++17,生成两个彼此独立的静态库:

CMake 目标 职责 公开接口
message_utils 构造示例提示信息 std::string build_message()
sort_utils 返回排序后的整数副本 bubble_sortselection_sortinsertion_sortmerge_sortquick_sort

排序接口位于 sort_algorithms 命名空间中,参数和返回值均为

std::vector<int>

本项目与动态库示例完全独立:

  • 包名:cmake-static-lib
  • 默认安装前缀:C:/install/cmake-static-lib
  • 安装头文件目录:include/cmake-static-lib
  • 导入目标:cmake-static-lib::message_utils
    cmake-static-lib::sort_utils

因此它可以与动态库包安装在不同前缀中,不会让

find_package(cmake-static-lib) 错误加载动态库包。

项目的主要结构如下:

text 复制代码
cmake-static-lib/
├── CMakeLists.txt
├── CMakePresets.json
├── cmake-static-lib-config.cmake.in
├── create_static_lib.md
├── .vscode/
│   └── tasks.json
└── src/
    ├── CMakeLists.txt
    └── utils/
        ├── CMakeLists.txt
        ├── message/
        │   ├── CMakeLists.txt
        │   ├── message.h
        │   └── message.cpp
        └── sort/
            ├── CMakeLists.txt
            ├── sort_algorithms.h
            └── sort_algorithms.cpp

2. 环境要求

  • Windows 与 PowerShell 7(pwsh
  • CMake 3.25 或更高版本
  • MinGW 的 g++mingw32-make 已加入 PATH

CMakeLists.txt 声明的最低版本是 3.16,但

CMakePresets.json 使用 schema version 6;通过本文的 preset 命令构建时,

实际需要 CMake 3.25 或更高版本。

项目还提供 Ninja/Clang preset。无论选择哪套工具链,生产静态库和消费静态库

的工程都应使用 ABI 兼容的编译器、C++ 标准库、目标架构和构建配置。

3. CMake 如何生成静态库

3.1 根目录与三级 CMake 结构

CMakeLists.txt 定义项目版本和 C++17 标准,并在进入源码目录前加载

GNUInstallDirs

cmake 复制代码
cmake_minimum_required(VERSION 3.16)
project(cmake-static-lib VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

include(GNUInstallDirs)
add_subdirectory(src)

提前加载 GNUInstallDirs,可以保证叶子目录配置

${CMAKE_INSTALL_INCLUDEDIR} 时,该变量已经有确定值。

src/CMakeLists.txtsrc/utils/CMakeLists.txt 负责进入下一级目录;真正

定义目标的是 message/CMakeLists.txtsort/CMakeLists.txt。这形成

"根工程 → 功能集合 → 具体库目标"的三级结构。

3.2 显式创建 STATIC 目标

两个叶子目录都显式使用 STATIC,因此目标类型不受

BUILD_SHARED_LIBS 的值影响:

cmake 复制代码
add_library(message_utils STATIC
    message.cpp
    message.h
)

add_library(sort_utils STATIC
    sort_algorithms.cpp
    sort_algorithms.h
)

目标的公开头文件路径同时覆盖构建树和安装树:

cmake 复制代码
target_include_directories(message_utils
    PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
  • BUILD_INTERFACE 让同一构建树中的调用方从源码目录找到头文件。
  • INSTALL_INTERFACE 让安装后的导入目标公开 <prefix>/include
  • sort_utils 使用相同配置。

4. 安装与 CMake 包导出

4.1 安装头文件

公开头文件安装到:

text 复制代码
<prefix>/include/cmake-static-lib/message.h
<prefix>/include/cmake-static-lib/sort_algorithms.h

消费端因此使用:

cpp 复制代码
#include <cmake-static-lib/message.h>
#include <cmake-static-lib/sort_algorithms.h>
4.2 只安装静态归档

两个静态目标加入 cmake-static-lib-targets 导出集,并通过

ARCHIVE DESTINATION 安装:

cmake 复制代码
install(
    TARGETS message_utils sort_utils
    EXPORT cmake-static-lib-targets
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

在 MinGW 下,安装产物通常是:

text 复制代码
lib/libmessage_utils.a
lib/libsort_utils.a

这里的 .a 包含库的目标代码,是静态归档;它不是动态库对应的

.dll.a 导入库。使用 MSVC 时,对应静态归档通常使用 .lib 扩展名。

本项目的安装规则不需要为两个库设置 RUNTIME 目的地,也不会生成或安装

项目 DLL。静态库代码会在链接阶段按需合入最终程序。

4.3 导出带命名空间的目标

导出集安装到 <prefix>/lib/cmake/cmake-static-lib,并添加独立命名空间:

cmake 复制代码
install(
    EXPORT cmake-static-lib-targets
    FILE cmake-static-lib-targets.cmake
    NAMESPACE cmake-static-lib::
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cmake-static-lib
)

这会为消费端创建:

cmake 复制代码
cmake-static-lib::message_utils
cmake-static-lib::sort_utils

导入目标携带静态归档位置和公开 include 路径。消费端无需硬编码 .a

.lib 文件的绝对路径。

4.4 包配置和版本文件

cmake-static-lib-config.cmake.in 加载导出的 targets 文件:

cmake 复制代码
@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/cmake-static-lib-targets.cmake")

check_required_components(cmake-static-lib)

安装时还会生成 cmake-static-lib-config-version.cmake。消费端通过下面一行

同时加载包配置和两个导入目标:

cmake 复制代码
find_package(cmake-static-lib REQUIRED)

5. 使用 MinGW 构建和安装

cmake-static-lib 目录中运行:

powershell 复制代码
cmake --preset mingw
cmake --build --preset mingwbuild
cmake --install build/mingw --prefix C:/install/cmake-static-lib

三条命令分别完成:

  1. 使用 MinGW Makefiles 配置 Debug 构建,并生成
    build/mingw/compile_commands.json
  2. 编译 message_utilssort_utils 静态库。
  3. 安装静态归档、头文件和 CMake package 文件。

MinGW 构建树中的库通常位于:

text 复制代码
build/mingw/src/utils/message/libmessage_utils.a
build/mingw/src/utils/sort/libsort_utils.a

安装后的典型结构如下:

text 复制代码
C:/install/cmake-static-lib/
├── include/
│   └── cmake-static-lib/
│       ├── message.h
│       └── sort_algorithms.h
└── lib/
    ├── libmessage_utils.a
    ├── libsort_utils.a
    └── cmake/
        └── cmake-static-lib/
            ├── cmake-static-lib-config.cmake
            ├── cmake-static-lib-config-version.cmake
            ├── cmake-static-lib-targets.cmake
            └── cmake-static-lib-targets-debug.cmake

该安装树不需要保存本项目的 bin 目录,也不应包含

libmessage_utils.dlllibsort_utils.dll 或相应的 .dll.a 导入库。

6. 使用 Ninja/Clang 构建

生产端还提供 ninja-clangclangbuild

powershell 复制代码
cmake --preset ninja-clang
cmake --build --preset clangbuild
cmake --install build/ninja-clang --prefix C:/install/cmake-static-lib-clang

该流程要求 clang++ninja 已加入 PATH。当前消费示例以

MinGW 为主,因此不要直接把 Clang 生成的静态归档交给 MinGW 消费。示例使用

独立安装前缀,避免覆盖 MinGW 产物。

7. 静态库与动态库的区别

项目 静态库 动态库
MinGW 链接输入 .a 静态归档 .dll.a 导入库
项目运行时文件 库代码已合入可执行文件 还需加载 .dll
项目 DLL 搜索路径 不需要为这两个库配置 PATH 通常需把安装目录的 bin 加入 PATH
更新库 重新链接程序后生效 可在 ABI 兼容时替换 DLL

"链接本项目的静态库"不等于"生成完全静态的可执行文件"。本工程没有添加

MinGW 的 -static 选项;程序仍可能依赖 libstdc++libgcc、线程库和

Windows 系统 DLL。将程序复制到其他机器时,应使用依赖检查工具确认实际运行

时依赖。

两个项目内部仍使用通用目标名 message_utilssort_utils。不要把共享版

和静态版同时通过 add_subdirectory() 加入同一顶层构建,否则会发生目标

重名;安装后应分别通过各自的包名和命名空间消费。

8. 常见问题

现象 检查方法
找不到 g++mingw32-make 确认 MinGW 工具目录已加入 PATH
preset 无法读取 使用 CMake 3.25 或更高版本
安装目录没有 .a 先成功构建,再执行 cmake --install
安装结果出现 .dll.dll.a 检查叶子目标是否确实使用 STATIC,并确认安装的是本项目的构建目录
消费端找不到包 检查 <prefix>/lib/cmake/cmake-static-lib/cmake-static-lib-config.cmake 是否存在
链接时报未定义符号或文件格式错误 确认生产端和消费端的编译器、C++ 标准库、位数及构建配置兼容
程序在其他机器上仍提示缺少运行库 DLL 这是工具链运行时依赖,不是 message_utilssort_utils 项目 DLL;按部署环境处理 MinGW 运行库

二、在另一个 CMake 项目中使用静态库

本文说明 cmake-use-static-lib 如何查找并链接 cmake-static-lib 安装的两个

静态库,以及如何通过 compile_commands.json 为 clangd 提供语法、补全和

跳转支持。

1. 消费关系概览

消费流程分为三个阶段:

  1. cmake-static-lib 构建并安装头文件、静态归档和 CMake 包配置。
  2. cmake-use-static-lib 通过 find_package(cmake-static-lib) 加载导入目标。
  3. 链接器从两个静态归档中按需取出目标代码,写入最终可执行文件。

项目使用以下两个导入目标:

导入目标 提供的功能
cmake-static-lib::message_utils build_message()
cmake-static-lib::sort_utils 五种返回排序副本的排序函数

默认安装前缀是 C:/install/cmake-static-lib。包名、命名空间、头文件目录和

安装前缀均与动态库示例分开。

2. 环境要求

  • Windows 与 PowerShell 7(pwsh
  • CMake 3.25 或更高版本
  • MinGW 的 g++mingw32-make 已加入 PATH
  • 使用兼容 MinGW 工具链构建并安装的 cmake-static-lib
  • 如需代码补全和跳转:clangd 可执行文件及 VS Code clangd 扩展

两个项目使用 C++17。其 CMakePresets.json 使用 schema version 6,因此

本文的 preset 命令实际要求 CMake 3.25 或更高版本。

3. 完整构建和运行流程

以下命令从仓库根目录开始执行。

3.1 构建并安装静态库
powershell 复制代码
Set-Location .\cmake-static-lib
cmake --preset mingw
cmake --build --preset mingwbuild
cmake --install build/mingw --prefix C:/install/cmake-static-lib

安装完成后,至少应存在:

text 复制代码
C:/install/cmake-static-lib/
├── include/
│   └── cmake-static-lib/
│       ├── message.h
│       └── sort_algorithms.h
└── lib/
    ├── libmessage_utils.a
    ├── libsort_utils.a
    └── cmake/
        └── cmake-static-lib/
            ├── cmake-static-lib-config.cmake
            ├── cmake-static-lib-config-version.cmake
            ├── cmake-static-lib-targets.cmake
            └── cmake-static-lib-targets-debug.cmake

MinGW 下的 .a 是包含目标代码的静态归档,不是动态库使用的 .dll.a

导入库。

3.2 配置并构建消费程序
powershell 复制代码
Set-Location ..\cmake-use-static-lib
cmake --fresh --preset mingw
cmake --build --preset mingwbuild

mingw preset 默认设置:

json 复制代码
"CMAKE_PREFIX_PATH": "C:/install/cmake-static-lib"

因此配置阶段会在该前缀中查找 cmake-static-lib 包。这里使用 --fresh

重新生成 CMake 缓存,避免之前的 cmake-static-lib_DIR 或自定义安装前缀

继续生效。

3.3 直接运行
powershell 复制代码
.\build\mingw\cmake-use-static-lib.exe

运行前不需要把 C:/install/cmake-static-lib/bin 加入 PATH,因为

message_utilssort_utils 的代码已经在链接时写入可执行文件。

程序的预期输出为:

text 复制代码
Hello from a CMake subdirectory project built with MinGW.
原始数组: 5 3 8 1 9 2 7 4 6
快速排序: 1 2 3 4 5 6 7 8 9
冒泡排序: 1 2 3 4 5 6 7 8 9
归并排序: 1 2 3 4 5 6 7 8 9

不需要本项目的 DLL,不代表可执行文件完全静态。项目没有添加 MinGW 的

-static 选项,程序仍可能依赖 MinGW C++ 运行库、线程库和 Windows 系统

DLL。

4. CMake 如何找到并链接静态库

4.1 CMAKE_PREFIX_PATH 指向安装前缀

CMAKE_PREFIX_PATH 应指向安装根目录,而不是 includelib 或具体

.cmake 文件:

text 复制代码
CMAKE_PREFIX_PATH
└── C:/install/cmake-static-lib
    └── lib/cmake/cmake-static-lib/cmake-static-lib-config.cmake
4.2 find_package() 加载导入目标

消费端调用:

cmake 复制代码
find_package(cmake-static-lib REQUIRED)

包配置会加载 cmake-static-lib-targets.cmake,从而创建:

cmake 复制代码
cmake-static-lib::message_utils
cmake-static-lib::sort_utils
4.3 使用 PRIVATE 链接

消费端直接链接带命名空间的导入目标:

cmake 复制代码
add_executable(cmake-use-static-lib src/main.cpp)

target_link_libraries(cmake-use-static-lib PRIVATE
    cmake-static-lib::message_utils
    cmake-static-lib::sort_utils
)

导入目标把安装前缀下的 include 目录和 .a 文件位置传递给链接目标。由于

最终目标是可执行程序,使用 PRIVATE 即可,不需要把依赖继续传播给下游。

源码使用安装后的公开头文件:

cpp 复制代码
#include <cmake-static-lib/message.h>
#include <cmake-static-lib/sort_algorithms.h>

可以在生成的链接命令或安装导出文件中确认,两个导入目标的实际位置指向

libmessage_utils.alibsort_utils.a,而不是 .dll.a

5. clangd 语法支持

5.1 生成编译数据库

消费端 preset 开启:

json 复制代码
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"

成功执行配置命令后:

powershell 复制代码
cmake --fresh --preset mingw

CMake 会生成:

text 复制代码
cmake-use-static-lib/build/mingw/compile_commands.json

该文件记录真实编译器、C++17 参数和导入目标提供的 include 路径。更换安装

前缀、编译器或 CMake 配置后,应重新运行配置命令。

5.2 .clangd 配置

项目根目录的 .clangd 指定编译数据库,并为默认安装头文件提供补充路径:

yaml 复制代码
CompileFlags:
  CompilationDatabase: build/mingw
  Add:
    - "-IC:/install/cmake-static-lib/include"

Index:
  Background: Build
  • CompilationDatabase 让 clangd 读取 build/mingw/compile_commands.json
  • Add 是默认安装目录的兜底 include 路径。
  • 成功加载导入目标后,正确的 include 路径也会出现在编译数据库中。

本项目不抑制 pp_file_not_found。如果依赖没有安装、CMake 配置失败或路径

错误,clangd 应保留头文件缺失诊断,便于及时发现问题。

.vscode/settings.json 只禁用 Microsoft C/C++ 扩展的重复 IntelliSense:

json 复制代码
{
    "C_Cpp.intelliSenseEngine": "disabled"
}

该设置不会安装 clangd,也不会配置 clangd 可执行文件路径;这些仍属于本机

前置条件。

6. 使用自定义安装前缀

自定义前缀时,需要同步更新三个位置:

  1. 安装静态库时的 --prefix
  2. 消费端配置时的 CMAKE_PREFIX_PATH
  3. .clangd 中附加的 -I<prefix>/include

例如使用 D:/sdk/cmake-static-lib,以下命令从仓库根目录开始执行:

powershell 复制代码
$staticLibPrefix = "D:/sdk/cmake-static-lib"

Set-Location .\cmake-static-lib
cmake --preset mingw
cmake --build --preset mingwbuild
cmake --install build/mingw --prefix $staticLibPrefix

Set-Location ..\cmake-use-static-lib
cmake --fresh --preset mingw "-DCMAKE_PREFIX_PATH=$staticLibPrefix"
cmake --build --preset mingwbuild
.\build\mingw\cmake-use-static-lib.exe

同时把 .clangd 更新为:

yaml 复制代码
CompileFlags:
  CompilationDatabase: build/mingw
  Add:
    - "-ID:/sdk/cmake-static-lib/include"

--fresh 很重要:CMake 会缓存找到的包目录。若只修改 preset 或命令行前缀

而沿用旧缓存,find_package() 仍可能加载之前安装的静态库。自定义前缀不

需要加入运行时 PATH,因为本项目不安装运行时 DLL。

7. 常见问题

现象 原因与处理
CMake 提示找不到 cmake-static-libConfig.cmake 先安装静态库,确认 CMAKE_PREFIX_PATH 指向安装根目录,并用 --fresh 清除旧的包目录缓存
#include <cmake-static-lib/...> 飘红 确认头文件已安装,重新配置 CMake,并核对 .clangd-I 路径
没有 compile_commands.json CMake 配置尚未成功,或查看了错误的 build/mingw 目录
修改前缀后 clangd 仍跳转到旧头文件 --fresh 重新配置、同步更新 .clangd,然后重启 clangd language server
链接命令出现 .dll.a 很可能加载了动态库包或旧缓存;核对包名、命名空间、安装前缀并重新配置
链接时报未定义符号或文件格式错误 确认静态库和程序使用兼容的 MinGW 工具链、架构及构建配置
运行时提示缺少 libmessage_utils.dlllibsort_utils.dll 当前程序可能链接了动态版或使用了旧构建缓存;静态版不需要这两个 DLL
运行时提示缺少 libstdc++libgcc 或线程库 DLL 这属于 MinGW 运行时依赖;链接项目静态库不等于完全静态链接工具链运行库

三、验证结果

消费程序可以在不配置项目库运行时 PATH 的情况下直接启动,三种排序算法均得到相同结果:

text 复制代码
Hello from a CMake subdirectory project built with MinGW.
原始数组: 5 3 8 1 9 2 7 4 6
快速排序: 1 2 3 4 5 6 7 8 9
冒泡排序: 1 2 3 4 5 6 7 8 9
归并排序: 1 2 3 4 5 6 7 8 9

需要特别注意:链接本项目静态库不等于把整个程序做成完全静态程序。本文没有添加 MinGW 运行库的 -static 选项,程序仍可能依赖系统或工具链运行库。

相关推荐
Javenwww21 小时前
CMake入门——基本语法规则
cmake
郝学胜-神的一滴3 天前
中级OpenGL教程 023:Assimp模型加载全解——从源码到架构的骈文探秘
c++·unity·游戏引擎·cmake·unreal engine·opengl
习惯就好zz5 天前
让 Neovim 的 clang-tidy 诊断和命令行完全一致
cpp·clangd·nvim·clang-tidy·lazyvim·nvim-lint
atsec7 天前
PCI取证调查 – 高效应对支付产业潜在的数据泄露
cpp·atsec·pci·数据泄露·pfi
橙色阳光五月天10 天前
CMake 构建配置文件解析
c++·cmake·plugin
blueman888812 天前
Qt5通过vcpkg中调用时,在debug模式下调试时总是调用release的plugins文件夹中的dll
c++·qt·cmake
郝学胜-神的一滴19 天前
CMake 038:OBJECT目标复用编译+Linux动态库版本自动化管理
linux·c++·程序人生·软件工程·软件构建·cmake·工程配置
郝学胜_神的一滴21 天前
CMake 037:宏传递流转机制与C++编译特性跨平台适配指南
c++·cmake
bu_shuo21 天前
计算机二级基础知识-数据结构学习
cpp·计算机二级