1. 第1例

现代CMake构建多目标C++工程——使用CMake组织一个包含静态库、动态库和可执行程序的多目标C++工程,配置目标级依赖、包含目录和编译特性,并验证构建产物可被外部工程通过find_package方式引用。 学习卡片

工程目标与目录结构

本案例构建一个名为 mathkit 的多目标 C++ 工程,包含三个构建目标:静态库 mathkit_static、动态库 mathkit_shared 和可执行程序 calc_app。静态库与动态库共享同一份源码,可执行程序链接静态库并调用其接口。工程还需导出 CMake 包配置,使外部工程能通过 find_package(mathkit) 引用。

目录结构如下:

mathkit/
├── CMakeLists.txt
├── include/
│   └── mathkit/
│       └── mathkit.h
├── src/
│   └── mathkit.cpp
├── app/
│   └── main.cpp
└── cmake/
    └── mathkitConfig.cmake.in

核心 CMakeLists.txt 实现

顶层 CMakeLists.txt 使用现代 CMake 的目标级命令组织依赖。关键点:target_include_directories 使用 PUBLIC 让链接者自动获得头文件路径;target_compile_features 声明 C++17 要求;install(EXPORT) 生成包配置文件。

cmake_minimum_required(VERSION 3.20)
project(mathkit VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 公共接口头文件目录
set(MATHKIT_PUBLIC_HEADERS
    ${CMAKE_CURRENT_SOURCE_DIR}/include)

# 静态库目标
add_library(mathkit_static STATIC src/mathkit.cpp)
target_include_directories(mathkit_static PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>)
target_compile_features(mathkit_static PUBLIC cxx_std_17)
set_target_properties(mathkit_static PROPERTIES
    OUTPUT_NAME mathkit
    POSITION_INDEPENDENT_CODE ON)

# 动态库目标(复用同一源码)
add_library(mathkit_shared SHARED src/mathkit.cpp)
target_include_directories(mathkit_shared PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>)
target_compile_features(mathkit_shared PUBLIC cxx_std_17)
set_target_properties(mathkit_shared PROPERTIES
    OUTPUT_NAME mathkit
    VERSION ${PROJECT_VERSION}
    SOVERSION ${PROJECT_VERSION_MAJOR})

# 可执行程序:链接静态库
add_executable(calc_app app/main.cpp)
target_link_libraries(calc_app PRIVATE mathkit_static)

# 安装规则
include(GNUInstallDirs)
install(TARGETS mathkit_static mathkit_shared calc_app
    EXPORT mathkitTargets
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# 导出包配置
install(EXPORT mathkitTargets
    FILE mathkitTargets.cmake
    NAMESPACE mathkit::
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathkit)

include(CMakePackageConfigHelpers)
configure_package_config_file(
    cmake/mathkitConfig.cmake.in
    ${CMAKE_CURRENT_BINARY_DIR}/mathkitConfig.cmake
    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathkit)
write_basic_package_version_file(
    ${CMAKE_CURRENT_BINARY_DIR}/mathkitConfigVersion.cmake
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion)
install(FILES
    ${CMAKE_CURRENT_BINARY_DIR}/mathkitConfig.cmake
    ${CMAKE_CURRENT_BINARY_DIR}/mathkitConfigVersion.cmake
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mathkit)

源码与包配置文件

include/mathkit/mathkit.h 声明接口,src/mathkit.cpp 实现,app/main.cpp 调用。包配置模板 cmake/mathkitConfig.cmake.in 内容为:

@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/mathkitTargets.cmake")
check_required_components(mathkit)

头文件示例:

#pragma once
namespace mathkit {
int add(int a, int b);
int multiply(int a, int b);
}

实现文件示例:

#include "mathkit/mathkit.h"
namespace mathkit {
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
}

主程序示例:

#include <cstdio>
#include "mathkit/mathkit.h"
int main() {
    std::printf("add=%d\n", mathkit::add(2, 3));
    std::printf("mul=%d\n", mathkit::multiply(4, 5));
    return 0;
}

构建与验证

构建并安装到本地前缀:

cmake -S . -B build -DCMAKE_INSTALL_PREFIX=./install
cmake --build build
cmake --install build

验证产物:install/lib/libmathkit.a、install/lib/libmathkit.so、install/bin/calc_app、install/lib/cmake/mathkit/ 下的配置文件。运行 ./install/bin/calc_app 应输出 add=5 与 mul=20。

外部工程验证 find_package:新建 consumer/CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(consumer LANGUAGES CXX)
find_package(mathkit 1.0 REQUIRED)
add_executable(use_mathkit main.cpp)
target_link_libraries(use_mathkit PRIVATE mathkit::mathkit_static)

配置时指定 -DCMAKE_PREFIX_PATH=../mathkit/install,若配置成功且链接运行正常,说明包导出与目标命名空间均正确。

静态库与动态库使用相同 OUTPUT_NAME 时,在 Windows 上会因导入库与静态库同名冲突,建议为动态库设置不同 OUTPUT_NAME 或使用 ARCHIVE_OUTPUT_NAME 区分。
mathkit 多目标构建与包导出关系 src/mathkit.cpp 共享实现源码 include/mathkit/mathkit.h PUBLIC 接口头文件 app/main.cpp 可执行入口 mathkit_static STATIC 库目标 mathkit_shared SHARED 库目标 calc_app 可执行目标 同源 PRIVATE 链接 install(EXPORT mathkitTargets NAMESPACE mathkit::) 生成 mathkitTargets.cmake + mathkitConfig.cmake + mathkitConfigVersion.cmake 安装至 lib/cmake/mathkit/ 外部工程: find_package(mathkit 1.0 REQUIRED) 链接 mathkit::mathkit_static libmathkit.a libmathkit.so bin/calc_app cmake/mathkit/

关键配置要点

配置项作用本案例取值
target_include_directories传递头文件搜索路径PUBLIC + 生成器表达式区分构建/安装接口
target_compile_features声明语言标准要求PUBLIC cxx_std_17
POSITION_INDEPENDENT_CODE静态库可被动态库链接ON
install(EXPORT)导出目标供外部引用命名空间 mathkit::
configure_package_config_file生成包配置入口模板含 @PACKAGE_INIT@
验证闭环:构建 → 安装 → 检查产物 → 外部工程 find_package 配置成功 → 链接运行输出正确结果。任一环节失败,优先检查 INSTALL_INTERFACE 路径与导出目标命名空间是否匹配。