跳到主要内容

CMake 实战案例三:复杂项目

这一篇给出一个“更接近生产”的项目骨架:多目录、库与可执行文件分离、可选特性开关、单元测试、依赖管理(示例用 FetchContent),以及安装。

目标

  • 多目录组织:src/include/apps/tests/
  • 以库为中心:应用与测试都链接同一个库
  • 提供一个可选功能开关 ENABLE_FEATURE_X
  • 集成 CTest + GoogleTest(示例)

目录结构(建议模板)

complex-demo/
├── CMakeLists.txt
├── cmake/
│ └── options.cmake
├── include/
│ └── demo/
│ └── calc.h
├── src/
│ ├── CMakeLists.txt
│ └── calc.c
├── apps/
│ ├── CMakeLists.txt
│ └── demo-cli.c
└── tests/
├── CMakeLists.txt
└── test_calc.cpp

顶层 CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(complex_demo VERSION 0.1.0 LANGUAGES C CXX)

include(CTest)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(options)

add_subdirectory(src)
add_subdirectory(apps)

if(BUILD_TESTING)
add_subdirectory(tests)
endif()

cmake/options.cmake

option(ENABLE_FEATURE_X "Enable feature X" OFF)

src/:库目标(核心)

src/CMakeLists.txt

add_library(demo
calc.c
)

target_include_directories(demo
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../include
)

target_compile_definitions(demo
PUBLIC
$<$<BOOL:${ENABLE_FEATURE_X}>:DEMO_ENABLE_FEATURE_X>
)

include/demo/calc.h

#pragma once

int demo_add(int a, int b);

src/calc.c

#include "demo/calc.h"

int demo_add(int a, int b) {
return a + b;
}

apps/:应用目标

apps/CMakeLists.txt

add_executable(demo-cli demo-cli.c)
target_link_libraries(demo-cli PRIVATE demo)

apps/demo-cli.c

#include <stdio.h>
#include "demo/calc.h"

int main(void) {
printf("1+2=%d\n", demo_add(1, 2));
return 0;
}

tests/:GoogleTest(示例)

tests/CMakeLists.txt

include(FetchContent)

FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.14.0
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

add_executable(test_calc test_calc.cpp)
target_link_libraries(test_calc PRIVATE demo GTest::gtest_main)

include(GoogleTest)
gtest_discover_tests(test_calc)

tests/test_calc.cpp

#include <gtest/gtest.h>
#include "demo/calc.h"

TEST(calc, add) {
EXPECT_EQ(demo_add(1, 2), 3);
}

构建、运行与测试

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j

./build/apps/demo-cli

ctest --test-dir build --output-on-failure

启用可选特性:

cmake -S . -B build -DENABLE_FEATURE_X=ON
cmake --build build -j

安装(可选)

当你希望把 demo-cli 安装到系统(或某个前缀),可以在顶层或 apps/ 添加:

include(GNUInstallDirs)
install(TARGETS demo-cli RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

这个骨架的关键点

  • 库是中心:应用与测试都链接库,避免重复编译/复制源码。
  • 目录内聚:每个子目录有自己的 CMakeLists.txt,顶层只做“组装”。
  • 可选特性用 compile definitions 表达:让特性对依赖方可见/不可见由 PUBLIC/PRIVATE 控制。
  • 测试在 BUILD_TESTING 开关下启用:与 CMake/CTest 生态一致。

小结

这套结构足以支撑中型项目,并能自然扩展到依赖管理、安装打包、交叉编译与 CI。你可以把它作为后续章节(依赖/测试/安装/工具链)的“贯穿示例工程”。