本文通过两个完全独立的 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_sort、selection_sort、insertion_sort、merge_sort、quick_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.txt 和 src/utils/CMakeLists.txt 负责进入下一级目录;真正
定义目标的是 message/CMakeLists.txt 和 sort/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
三条命令分别完成:
- 使用
MinGW Makefiles配置 Debug 构建,并生成
build/mingw/compile_commands.json。 - 编译
message_utils和sort_utils静态库。 - 安装静态归档、头文件和 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.dll、libsort_utils.dll 或相应的 .dll.a 导入库。
6. 使用 Ninja/Clang 构建
生产端还提供 ninja-clang 和 clangbuild:
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_utils 和 sort_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_utils 或 sort_utils 项目 DLL;按部署环境处理 MinGW 运行库 |
二、在另一个 CMake 项目中使用静态库
本文说明 cmake-use-static-lib 如何查找并链接 cmake-static-lib 安装的两个
静态库,以及如何通过 compile_commands.json 为 clangd 提供语法、补全和
跳转支持。
1. 消费关系概览
消费流程分为三个阶段:
cmake-static-lib构建并安装头文件、静态归档和 CMake 包配置。cmake-use-static-lib通过find_package(cmake-static-lib)加载导入目标。- 链接器从两个静态归档中按需取出目标代码,写入最终可执行文件。
项目使用以下两个导入目标:
| 导入目标 | 提供的功能 |
|---|---|
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_utils 和 sort_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 应指向安装根目录,而不是 include、lib 或具体
.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.a 和 libsort_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. 使用自定义安装前缀
自定义前缀时,需要同步更新三个位置:
- 安装静态库时的
--prefix - 消费端配置时的
CMAKE_PREFIX_PATH .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.dll 或 libsort_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 选项,程序仍可能依赖系统或工具链运行库。