PPSSPP 构建与验证实战:从 b.sh 到 pspautotests 回归测试的完整构建指南
2026/9/14 9:31:49 网站建设 项目流程

PPSSPP 构建与验证实战:从 b.sh 到 pspautotests 回归测试的完整构建指南

【免费下载链接】ppssppA PSP emulator for Android, Windows, Mac, Linux and iOS, written in C++. Want to contribute? Join us on Discord at https://discord.gg/5NJB6dD or just send pull requests / issues.项目地址: https://gitcode.com/GitHub_Trending/pp/ppsspp

本文基于仓库文档 docs/building.md 及其背后的真实构建脚本、CMake 配置与测试框架源码,系统讲解 PPSSPP(C++ 编写的 PSP 模拟器)在各平台上的构建验证路径:Linux/Mac 的./b.sh快速迭代、Windows 的 Visual Studio 方案与非交互式 MSBuild 驱动、unittest/C++ 单元测试与 pspautotests 自动化回归、以及 Android NDK 与 libretro core 两条特殊构建线。读完后你可以独立完成一次改动后的构建-测试闭环,并避开文档中总结的若干"假回归"陷阱。

一、构建与验证入口:b.sh、VS 方案与 MSBuild

Linux/Mac:用 ./b.sh --debug 验证构建

文档给出的第一条基线规则是:在 Linux/Mac 上验证构建用./b.sh --debugb.sh是仓库根目录的 CMake 封装脚本,从 b.sh 源码看,它把命令行参数翻译成 CMake 选项:

  • --debug-DCMAKE_BUILD_TYPE=Debug
  • --release/--reldebugRelease/RelWithDebInfo
  • --unittest-DUNITTEST=ON(生成单元测试目标)
  • --headless-DHEADLESS=ON(生成无界面目标)
  • 其余如--ios--rpi--sanitize--clang等分别对应 iOS/树莓派工具链、ASan/UBSan、Clang 切换

脚本还会按nproc(Linux)或sysctl -n hw.physicalcpu(macOS)自动决定并行线程数,原生构建输出到build/目录,交叉构建则输出到build-<os>/。CMake 侧对应两个开关(见 CMakeLists.txt):

option(HEADLESS "Set to OFF to not generate the PPSSPPHeadless target" ${HEADLESS}) option(UNITTEST "Set to ON to generate the unittest target" ${UNITTEST})

Windows:只走 Windows/PPSSPP.sln,并用 MSBuild 非交互式驱动

文档对 Windows 的强调很明确:始终通过 Windows/PPSSPP.sln 构建,即使仓库根目录存在一个 CMake 生成的build/残留目录(例如 WSL/MSYS2 实验留下的)——那不是你该走的构建路径,其工具链可能根本没有正确接好。

为了不打开devenv图形界面,Agent/脚本可以用MSBuild.exe非交互驱动同一个解决方案。先用vswhere.exe定位 VS 安装路径,再用/t:指定要构建的项目:

$installPath = & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath $msbuild = "$installPath\MSBuild\Current\Bin\MSBuild.exe" & $msbuild "Windows\PPSSPP.sln" /t:UnitTest /p:Configuration=Debug /p:Platform=x64 /m

/t:UnitTest可换成/t:PPSSPPWindows或方案里的其他工程名;完全省略/t:则构建整个解决方案。这与 unittest/UnitTest.cpp 头部注释给出的 CMake 侧用法互为对照:

./b.sh --unittest build/unittest EscapeMenuString

小改动不必每次全量:Linux 快速重建捷径

文档最后给出了日常迭代捷径——配置过一次之后,验证小改动只需:

cd build ; make -j32; cd ..

二、C++ 单元测试(unittest/):运行方式与新增流程

除了 pspautotests 运行器(test.py),仓库还有一个独立的 C++ 单元测试程序,位于 unittest/ 子目录。文档建议在一揽子较大改动完成之后(不必每次编辑后)跑一遍:

  • Windows:构建UnitTest工程(对应 unittest/UnitTests.vcxproj),然后运行Windows/x64/Debug/UnitTest.exe all
  • Linux/Mac:配置时加-DUNITTEST=ON(即./b.sh --unittest),然后运行build/PPSSPPUnitTest all

它执行的是 unittest/UnitTest.cpp 中availableTests数组登记的全部测试。参数行为由main()实现(见 UnitTest.cpp):

  • all:跑全部测试;
  • 传一个或多个测试名(大小写不敏感):只跑指定项,如UnitTest.exe CmdLine Path Utf8;任一名字不匹配会打印Unknown test并回退到用法说明,不做静默的部分运行
  • 不带参数:仅列出所有可用测试名后退出。

availableTests实际覆盖 60 余项,按架构条件编译注册(如Arm64EmitterX64EmitterRiscVEmitterLoongArch64Emitter各带PPSSPP_ARCH宏守卫),另有VertexJitSerializer(存档序列化器往返与截断/损坏输入防护)、Utf8PathCmdLineJitMemMapThreadManager等。

单元测试为什么是"最严格的链接检查"

文档点出一个跨平台差异,值得展开:单元测试常常是某个函数在自身.cpp之外第一次被调用的地方,因此 Android 遗留构建里的ppsspp_unittest目标(android/jni/Android.mk)成为仓库里最严格的检查——MSVC 会把定义在 .cpp 里的inline函数照样链接进目标,而 clang 会正确地拒绝。后果是:一个测试可以在 Windows 上构建并通过,却只在 Android CI 上因未定义符号链接失败,报错指向某个头文件行。文档给出的修复方向明确:去掉定义上多余的inline,而不是绕开对该函数的调用

另外注意文档提醒:unittest/UnitTest.cpp 与 headless/ 目录各自持有独立的main,并桩(stub)掉了大部分System_平台函数(源码注释直接说明"copied from Headless"),做跨平台改动时要同时照顾这两套构建。新增测试的完整流程是:

  1. 在 unittest/UnitTest.cpp 的availableTests中登记新测试(大测试可单独放unittest/下的新文件,如TestUtf8式的独立 .cpp);
  2. 同时更新 CMakeLists.txt 与 Visual Studio 工程两处文件清单。

三、陈旧二进制陷阱:stash 循环、bisect 与 LNK1168

这部分是文档中最"事故复盘"性质的经验,直接关系到你是否相信自己的测试结果:

1. 触碰头文件后的git stash/git stash pop必须/t:Rebuildstash 重写文件后时间戳可能导致对象文件"看起来比所依据的头文件更新",于是增量构建放行,结果是各翻译单元对对象布局产生分歧(比如增删了一个类成员)。典型症状:UnitTest.exe在打印任何东西之前就段错误,且所有测试都挂——看起来像灾难性代码 bug,其实不是。文档的处方是:stash 循环之后构建莫名崩溃时,先重建再调查其他任何东西。

2. bisect 行为变化之前,先确认二进制真的变了。检查可执行文件 mtime,或让你新加的代码打一行可 grep 的日志。陈旧二进制的回归和真回归无法区分,且它"持续说谎",在陈旧二进制上做的 bisect 会给出自信而完全虚构的结论。文档记述了一次真实耗时数小时的案例:一次静默失败的链接把旧的PPSSPPHeadless.exe留在原地,此后每一步"revert 再测"都报同样的失败,最终把一个清白改动背了黑锅,直到干净重建才澄清。

3.LNK1168: cannot open ... for writing是上述问题的最常见来源——通常是还有一个正在运行的实例占着 exe。这就是文档建议无条件在构建前先杀掉残留进程(而不只是构建抱怨时再杀)的原因,详见 docs/debugging.md。

4. 已知环境特异性问题:Jit测试在个别沙箱中挂死。unittest/JitHarness.cpp 对应的Jit测试在至少一个沙箱化开发环境中会在CPUCore::JIT_IR阶段无限挂起——在未修改的检出上同样复现,且与源码改动无关、也不是内存访问故障(从未进入Memory::HandleFault)。根因未进一步定位(那个环境无法挂原生调试器),但从"CI 在多个平台每个提交都跑等价的UnitTest.exe all且无异常"来看,很可能是该沙箱自身的问题而非 PPSSPP 真 bug。文档给出的规避方案:若all/Jit在你的环境挂死,按名字逐个跑其余测试(跳过Jit以保住真实覆盖。

四、pspautotests:像 CI 一样跑回归

pspautotests 是针对 PSP 系统 API 面的大型测试集,因此实际压测的是 PPSSPP 的 HLE 实现。文档要求检查回归时必须完全按 CI 的方式运行(参考 .github/workflows/build.yml):

python test.py -g --graphics=software

-g是关键。从 test.py 源码看,脚本维护两个列表:

  • tests_good(test.py 起):回归集——这些测试当前通过且必须保持通过,约 314 个;
  • tests_next(test.py 起):进行中的测试,预期失败,相当于待办清单。

参数语义与 test.py 的main()一致:

参数行为
-g只跑tests_good(回归检查用这个)
-b只跑tests_next
-m <前缀>对当前选中的列表做前缀过滤,如-m cpu/
-g/-btests_next + tests_good,约有一百个失败,不代表出了问题

文档特别警告:不要无标志运行然后去追那一百来个失败,更不要把它们当回归上报——-g模式下唯一有意义的结果就是0 tests failed。其余参数(如--graphics=software)会被run_tests()原样透传给 PPSSPPHeadless(脚本以--compare --timeout=5 @-方式从 stdin 逐行喂测试文件名,见 test.py)。

另一个容易误判的输出:Windows Debug 构建下,runner 在汇总行之后会打印调试 CRT 的 "Detected memory leaks!" 转储。这是正常的,不是测试失败——要看的是在它之前N tests passed, N tests failed行。运行器按可执行文件 mtime 自动挑选最新的PPSSPPHeadless(查找路径清单见 test.py,覆盖Windows/x64/Debug/build*/等位置),所以再次呼应了第三节的"先确认二进制已更新"原则。更多基于 pspautotests 改进 PPSSPP 的工作流见 docs/pspautotests.md。

五、遗留 Android NDK 构建(android/jni)

android/下除了 gradle 构建,还有一条遗留的裸 NDK 构建线(android/jni/Android.mk +ndk-build),两者相互独立。它挂在 CI 上(见 .github/workflows/build.yml 的android矩阵条目),适合做快速测试构建(可以构建ppsspp_headless和 Android 版单元测试)。默认不需要构建它,如需本地测试构建:

  • NDK 路径在 Windows 下硬编码于 android/ab.cmd,POSIX 下通过环境变量NDK传给 android/ab.sh。它应与 android/build.gradle.kts 的ndkVersion = "29.0.14206865"保持一致;
  • 脚本先拷贝资源,再以由机器核数(nproc/%NUMBER_OF_PROCESSORS%)推导的-j并行度运行ndk-build(见 android/ab.sh:$NDK/ndk-build -j$(nproc ...) $*);
  • POSIX 示例:
cd android && NDK=/path/to/ndk ./ab.sh APP_ABI=arm64-v8a HEADLESS=1
  • 产物ppsspp_headless落在android/libs/<abi>/

六、libretro core 构建(Windows)

规范说明在 libretro/README_WINDOWS.txt,文档对其做了摘要并补充了 Agent 相关的坑。要点:libretro core(ppsspp_libretro.dll)在 Windows 上也是用真正的 GNU Make 构建的,不是 VS 方案——编译器/链接器是cl.exe/link.exe(经由platform=windows_msvc2019_desktop_x64,其中 "2019" 无实际含义,MSVC 2022 同样可用),但由运行在 MSYS2 shell 里的 GNU Make 编排(是纯 MSYS2 安装,不是 "Git Bash",典型位置C:\msys64,需要pacman -S make):

cd libretro make DEBUG=1 platform=windows_msvc2019_desktop_x64 -j32

去掉DEBUG=1即 release 构建;-j不必精确等于逻辑核数。验证方式是把ppsspp_libretro.*拷到本地 RetroArch 读取 core 的目录(如cores/),在 RetroArch 内加载。注意 README 还提醒:不带platform的裸make不可行(g++ 链不上 D3D11 部分)。

Agent 非交互式驱动时的两个环境坑:

  1. 应直接以子进程方式调用C:\msys64\usr\bin\bash.exe -lc "..."——-l(login shell)标志是关键,它才让 MSYS2 自身的PATHmakecygpath等正确就位;
  2. 在沙箱化调用中(区别于人交互打开的 MSYS2 终端),Makefile 的 VS 探测逻辑依赖的两个 Windows 环境变量可能没被子进程继承:COMSPEC(破坏cmd //c "bash VSWhere.sh ..."这一定位 VS 的调用)和ProgramFiles(x86)VSWhere.sh自己找vswhere.exe需要它)。

若自动探测因此失败,可在make命令行上直接覆盖VsInstallRoot跳过探测——GNU Make 的命令行变量优先于 Makefile 内对同名变量的:=赋值:

make VsInstallRoot="/c/Program Files/Microsoft Visual Studio/<year>/<edition>" DEBUG=1 platform=windows_msvc2019_desktop_x64 -j32

路径要用 MSYS2/cygpath 的 POSIX 形式而非裸 Windows 路径;不确定<year>/<edition>时用vswhere -latest -property installationPath查真实值。

最后文档强调:这是一次真实的完整编译+链接,应优先于用独立的cl.exe /Zs对 libretro 相关文件做语法检查——后者可能漏掉真实 bug,例如 include 顺序问题导致VK_USE_PLATFORM_WIN32_KHR这类平台宏在vulkan.h首次(带 include guard 的)包含前未定义,因为语法检查用的更窄的手动 include 路径/宏集未必能复现真实构建步骤的顺序。

七、适用前提与限制小结

  • Windows 构建的唯一受支持路径是Windows/PPSSPP.sln;根目录残留的 CMakebuild/不可用于 Windows。
  • Linux/Mac 的"配置+构建"一步到位可用./b.sh --debug(加--unittest获得PPSSPPUnitTest);日常小改动用cd build ; make -j32
  • 回归判定以python test.py -g --graphics=software0 tests failed为准;tests_next的失败是预期状态而非回归。
  • Android NDK 遗留构建仅用于快速测试构建,且需 NDK 版本与 android/build.gradle.kts 的ndkVersion对齐。
  • 所有"改了行为却测出同样结果"的场景,先验证二进制已更新(mtime 或新增日志),再下结论。

本文内容以 docs/building.md 为主体骨架,构建脚本行为、测试注册表与运行器参数均已在 b.sh、CMakeLists.txt、unittest/UnitTest.cpp、test.py、android/ab.sh 与 libretro/README_WINDOWS.txt 中逐一核对。

【免费下载链接】ppssppA PSP emulator for Android, Windows, Mac, Linux and iOS, written in C++. Want to contribute? Join us on Discord at https://discord.gg/5NJB6dD or just send pull requests / issues.项目地址: https://gitcode.com/GitHub_Trending/pp/ppsspp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询