构建与运行

程序入口 已经能最小调用动态库。这一章配置、编译、测试并运行这个工程。做完后,15 条棋盘用例全部通过,app 也链接到同一次构建产出的 tictactoe 动态库。构建入口是 running-build.sh。

构建脚本

准备 WSL Ubuntu,并装好 CMake、Clang、Ninja 与 libc++ 开发包。CMake 需要 3.20 或更高;工程用 -stdlib=libc++ 编译,缺这个库会在第一次编译就报错。GoogleTest 由 CMake 在首次配置时下载,机器需要能访问 GitHub;下载结果留在 build/_deps,后续配置直接复用。进入 games/tictactoe,执行脚本:

WSL Ubuntu(工程目录)
bash running-build.sh

脚本按顺序完成三项:

表 11.1: 构建脚本的三个步骤
步 做什么 由谁执行
配置与编译 读取 CMake 规则,取 GoogleTest v1.15.2,在 build/ 下生成 Ninja 文件并编译 CMake、Ninja
测试 运行 CTest 注册的 15 条棋盘用例 CTest
运行 启动 app,检查动态库调用结果 构建脚本

首次运行生成 build/,后续运行复用;这个目录由仓库根的 .gitignore 排除。脚本不跑 Release,也不跑 clang-format 与 clang-tidy;编译器警告仍然按工程设置开启。

构建规则

根 CMakeLists.txt 在 tictactoe/、apps/ 之外加入 tests/。它先调用 enable_testing(),让 CTest 接管测试目标。动态库、主程序与测试是三个 target;动态库不反向依赖另外两个。

C++ 标准由 cmake/CompileSettings.cmake 里的 CompileSettings_SetCxxStandard 统一声明:

标准函数调用
CompileSettings_SetCxxStandard(tictactoe PUBLIC c++23)
CompileSettings_SetCxxStandard(app PRIVATE c++23)

函数同时写 target_compile_features 与 CXX_STANDARD、CXX_STANDARD_REQUIRED、CXX_EXTENSIONS 三个属性。库用 PUBLIC 把标准传给链接方。CXX_STANDARD_REQUIRED ON 禁止编译器不支持时静默降级;CXX_EXTENSIONS OFF 把 -std=gnu++23 收成 -std=c++23。

警告选项收在同一份 cmake/CompileSettings.cmake,函数按等级取一组常用 flag:

警告等级调用
CompileSettings_SetWarnings(tictactoe PRIVATE LEVEL standard)
CompileSettings_SetWarnings(app PRIVATE LEVEL standard)

basic 只开 -Wall 与 -Wextra。standard 再加六项。前三项是 -Werror、-Wshadow 与 -Wold-style-cast。后三项是 -Wpedantic、-Wnon-virtual-dtor 与 -Woverloaded-virtual。项目里两处调用都用 PRIVATE,警告只作用于传入的 target。

测试目标在 tests/CMakeLists.txt 里单独声明:

tests/CMakeLists.txt
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz
  DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)

FetchContent_MakeAvailable(googletest) 只构建测试需要的那一部分。tictactoe_test 链接动态库 tictactoe 与 GTest::gtest_main,BUILD_RPATH 设为 $ORIGIN/../lib。这样从 build/bin 启动时能找到同次构建的动态库。gtest_discover_tests 在构建后把 15 条用例注册给 CTest。

GoogleTest 必须和测试目标使用同一套标准库。在 Clang 下,tests/CMakeLists.txt 只给四个 GoogleTest 目标加 -stdlib=libc++。否则 gtest 按 libstdc++ 编译、测试按 libc++ 链接,会出现一批 std:: 符号未定义。

产物落在 build/ 下:

构建产物
build/
  bin/app
  bin/tictactoe_test
  lib/libtictactoe.so

验收结果

脚本退出码为 0、CTest 报告 15 条全部通过、app 打印成功行时,这次构建通过。

核对测试输出

CTest 的最后一行给出总数:

终端输出
100% tests passed, 0 tests failed out of 15

失败时 CTest 打印用例名与断言位置;--output-on-failure 已经把失败输出带到终端。

核对程序输出

Debug 运行从 build/bin 启动 app:

终端输出
井字棋动态库调用成功:已落子 1 手

这行来自 程序入口:main 创建一局、在 (0, 0) 落一子,再用 GetHistory().size() 读回手数。它同时证明动态库被链接、Game 能构造、TryPlace 接受了一手合法落子。

手动核对链接

进入工程目录后可以直接查看动态库依赖:

WSL Ubuntu(工程目录)
ldd build/bin/app | grep tictactoe

输出中应出现 libtictactoe.so,路径指向本次构建的 build/lib。如果 ldd 显示 not found,先确认 build/lib/libtictactoe.so 存在,再检查 BUILD_RPATH 是否被覆盖。

常见错误

找不到 libc++

配置成功,但第一次编译就报找不到 <format> 或 libc++ 头文件。

  • 出错的原因:工程用 -stdlib=libc++,系统只装了 libstdc++ 开发包。
  • 正确的做法:在 WSL Ubuntu 安装 libc++ 与 libc++abi 开发包,再重新执行脚本。

GoogleTest 下载失败

配置时停在下载 v1.15.2.tar.gz。

  • 出错的原因:机器无法访问 GitHub,或代理没有覆盖 CMake 的下载请求。
  • 正确的做法:先修网络或代理;已有本地源码时,配置时加 -DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=<本地源码目录>,让 CMake 不再下载。

动态库加载失败

app 能编译,运行时却报找不到 libtictactoe.so。

  • 出错的原因:BUILD_RPATH 没有生效,或运行的是旧构建目录里的 app。
  • 正确的做法:从 build/bin/ 启动,确认 build/lib/libtictactoe.so 存在;必要时删掉构建目录重新配置和编译。

测试失败

CTest 返回非零,并打印某个用例的断言。

  • 出错的原因:棋盘行为与用例期望不一致,或测试目标链接到了旧库。
  • 正确的做法:先读失败用例名与断言行;确认 build/lib/libtictactoe.so 与测试来自同一次构建,再修改代码或用例。

自测问题

  1. 构建脚本按什么顺序执行?

    查看答案

    先配置并编译,再跑 CTest,最后启动 app。

  2. 为什么测试要排在启动程序之前?

    查看答案

    15 条棋盘用例直接检查库行为,失败能定位到函数;先跑测试,失败时不必再启动程序。

  3. 为什么 GoogleTest 要和测试目标用同一套标准库?

    查看答案

    两套标准库的 std:: 符号不通用;混用会在链接时出现一批未定义符号。

  4. CTest 里注册了多少条用例?

    查看答案

    15 条,全部来自 testing-board.cpp,由 gtest_discover_tests 注册。

本章回顾

你已经能配置、编译、跑 15 条棋盘测试并启动 app,确认它链接到了正确的 libtictactoe.so。下一章局面快照;它属于终端程序,当前构建未接入,先按设计保留。