背景:第 33 轮 Code Review 的三项后续建议(EdgeScanFor 查找哈希化、 EdgePanelLayoutFor 表驱动化、wcstod 自写替身)都卡在"没有可自证的验证手段"上。 本轮先补齐基础设施,再动被建议的代码。 一、Tools/ 从忽略改为入库(只忽略各工具的 Build/ 与 Temp/ 临时产物) * `.gitignore`:删除 `Tools/` 整目录忽略,改为 `Tools/**/Build/`; 截图 / 基线 / 报告一律写 `Temp/tools/`(`Temp/` 早已忽略)。 二、新增 Tools/Makefile(统一构建入口,不碰应用本体) * 目标:`make` 构建全部、`make UnitTests` / `make OffscreenChecks` 单个、 `make clean` 清工具产物、`make help`。 * 工具链接应用本体 `../Build/Release/*.o`(排除入口 `Main.o`), 因此 ABI 开关必须与本体一致:`-fno-exceptions` / `-fno-rtti` / 链接 `-flto -flto-partition=one`。 * 工具源文件自身**不**编译成 LTO 字节码(否则 GCC 15.2.0 在 `choose_baseaddr` 处 ICE),只在链接阶段带 LTO 参数。 * `-MMD -MP` + `-include`:改头文件 / 增删源文件自动重编重链。 * 修正一个构建陷阱:`$(foreach)` 生成的工具目标会早于 `all`, make 会把第一个显式目标当默认目标 ⇒ 显式 `.DEFAULT_GOAL := all`。 三、Tools/UnitTests:纯数据层单元测试(218 项断言,17 个用例) * 极简自研框架(`Inc/UnitTestFramework.hpp`):无异常、无 RTTI、 支持布尔 / 整数 / 近似 / **逐位相等** / 宽字符串断言。 * 覆盖:`TryParseDouble` 与 libc `strtod` 逐位一致性(6 万条随机串)、 接受性语义、`FormatDecimal*` 永不科学计数法、`NiceStep/BuildTicks`、 公式语法/语义/归一化、批量求值 `FormulaProgram` 与逐点求值逐位一致、 `ApplyDrawTransform` 顺序、`TransformValueRange`、 `ComputeMovingAverageIterated` 阶数等价性、 `DataSet::FormulaTransformFor` 的值域与分组极值(逐点参考实现比对)。 四、Tools/OffscreenChecks:隐藏窗口离屏验证(5 个检查) * 窗口 `SW_HIDE`;"鼠标"用 `Application::HandleMouseMove()` 合成注入; 像素从后备缓冲区读取(不依赖窗口可见性),可保存 PNG 与计算区域 FNV 哈希。 * `smoke`:初始化 / 加载 / 渲染 / 落盘,并断言"同状态两帧逐像素相同"。 * `frames`:连续帧抓取(PNG + 哈希 + 耗时),用于问题分析。 * `panels`:7 个实验性面板全部展开后的几何不变式 + **像素基线 A/B** (`-mode record` / `-mode compare`)。 * `edgestats`:边沿统计与工具内**独立参考实现**逐位比对, 覆盖阈值变化与重复查询稳定性。 * `perf`:整帧中位数、`EdgePanelLayoutFor` 单次耗时、 198 个统计状态的整轮查询耗时(用于优化前后对比)。 五、文档 * `Tools/README.md`:目录结构、构建/运行方式、每个工具的作用、测试项与 验收标准、临时产物位置、A/B 基线用法、扩展步骤与已知限制。 实测(本机 GCC 15.2.0 UCRT / 125% DPI): * `UnitTests.exe` → `checks=218 failed=0` * `OffscreenChecks -all` → 全部 PASS;`SCATTER_TEST.csv` 整帧 ~5ms; `MF4-Record.MF4`(283 信号)198 个统计状态整轮查询 0.097 us/query。
188 lines
7.7 KiB
Makefile
188 lines
7.7 KiB
Makefile
# =============================================================================
|
||
# Tools/Makefile —— 验证工具(单元测试 / 离屏验证)的统一构建入口
|
||
# =============================================================================
|
||
#
|
||
# 职责边界(很重要,别混):
|
||
# * 根目录 `Makefile` **只负责应用本体**(`Build/Release`、`Build/Debug`)。
|
||
# 它按目录通配收集源文件,`Tools/` 不在其 `CXX_SOURCES_PATHS` 里,
|
||
# 因此**新增工具不需要改根 Makefile**,也不会把工具代码编进应用本体。
|
||
# * 本文件负责 `Tools/` 下所有工具的编译 / 链接 / 清理。
|
||
# 工具是"白盒验证":直接链接应用本体已构建好的目标文件
|
||
# (`../Build/Release/*.o`,排除入口 `Main.o`),因此**必须先构建本体**。
|
||
#
|
||
# 用法(在 `Tools/` 目录下执行,或 `make -C Tools ...`):
|
||
# make 构建全部工具
|
||
# make UnitTests 只构建单元测试
|
||
# make OffscreenChecks 只构建离屏验证工具
|
||
# make clean 删除所有工具的构建目录(**不动**应用本体)
|
||
# make help 打印帮助
|
||
#
|
||
# 目录约定:
|
||
# Tools/<Tool>/Inc 仅头文件
|
||
# Tools/<Tool>/Src 仅实现(每个 `.cpp` 都会编译)
|
||
# Tools/<Tool>/Build 产物(与 Inc/Src 同级;已被 .gitignore 忽略)
|
||
#
|
||
# ⚠️ 工具目标文件必须与应用本体使用**同一套 ABI 相关开关**,否则链接会失败:
|
||
# `-fno-exceptions`(本体 `NO_EXCEPTIONS = 1`)
|
||
# `-fno-rtti` (本体 `NO_RTTI = 1`,本仓已无 dynamic_cast)
|
||
# `-flto -flto-partition=one`(本体 `LTO = 1`,目标文件是 LTO 字节码)
|
||
# =============================================================================
|
||
|
||
# ---- 工具清单(新增工具只需在这里追加,并保证目录名 = 目标名)--------------
|
||
TOOLS := UnitTests OffscreenChecks
|
||
|
||
# ⚠️ 必须显式指定默认目标:下面用 `$(foreach)` 生成的工具目标会先于 `all` 定义,
|
||
# 而 make 会把"第一个出现的显式目标"当作默认目标 ⇒ 直接 `make` 只会建第一个工具。
|
||
.DEFAULT_GOAL := all
|
||
|
||
# 工具内构建目录名(与 Inc / Src 同级)
|
||
BUILD_DIR_NAME := Build
|
||
|
||
# ---- 工具链 ----------------------------------------------------------------
|
||
C_COMPILER := gcc
|
||
CXX_COMPILER := g++
|
||
|
||
C_STD := c17
|
||
CXX_STD := c++17
|
||
|
||
INPUT_CHAR_ENCODING := UTF-8
|
||
OUTPUT_CHAR_ENCODING := GBK
|
||
OUTPUT_WCHAR_ENCODING := UTF-16LE
|
||
|
||
# 工具自身优化等级:验证工具追求"跑得快",不参与体积红线
|
||
TOOL_OPT := -O2
|
||
TOOL_DEBUG_INFO := -g
|
||
|
||
# ---- 与被测应用本体保持一致的 ABI 开关-------------------------------------
|
||
NO_EXCEPTIONS := 1
|
||
NO_RTTI := 1
|
||
LTO := 1
|
||
|
||
# ---- 应用本体 --------------------------------------------------------------
|
||
APP_BUILD_DIR := ../Build/Release
|
||
# 排除入口 `Main.o`(它定义 `WinMain`,与工具的 `main` 不冲突但不需要)
|
||
APP_OBJECTS := $(filter-out $(APP_BUILD_DIR)/Main.o,$(wildcard $(APP_BUILD_DIR)/*.o))
|
||
|
||
# ---- 头文件目录(与应用本体一致)------------------------------------------
|
||
PROJECT_INCLUDES := \
|
||
../Utilities/Inc \
|
||
../Resources/Inc \
|
||
../Components/Main/Inc \
|
||
../Components/Shared/Inc \
|
||
../Components/Application/Inc \
|
||
../Components/TabBar/Inc \
|
||
../Components/Toolbar/Inc \
|
||
../Components/SignalPanel/Inc \
|
||
../Components/PlotView/Inc
|
||
|
||
# ---- 链接库(与应用本体一致)----------------------------------------------
|
||
LINK_LIBS := \
|
||
-lgdi32 \
|
||
-lgdiplus \
|
||
-lmsimg32 \
|
||
-lcomdlg32 \
|
||
-ld3d11 \
|
||
-ldwmapi \
|
||
-ld3dcompiler
|
||
|
||
# ---- 编译 / 链接选项 -------------------------------------------------------
|
||
# ⚠️ 这里用 `=`(递归展开)而不是 `:=`:`-MF"$(@:%.o=%.d)"` 里的 `$@`
|
||
# 必须在**展开 recipe 时**才求值(那时才确定是哪一个目标文件)。
|
||
BASE_CXXFLAGS = \
|
||
-D_UNICODE -DUNICODE \
|
||
$(addprefix -I,$(PROJECT_INCLUDES)) \
|
||
-std=$(CXX_STD) \
|
||
-finput-charset=$(INPUT_CHAR_ENCODING) \
|
||
-fexec-charset=$(OUTPUT_CHAR_ENCODING) \
|
||
-fwide-exec-charset=$(OUTPUT_WCHAR_ENCODING) \
|
||
-static \
|
||
-Wall \
|
||
$(TOOL_OPT) \
|
||
$(TOOL_DEBUG_INFO) \
|
||
-MMD -MP -MF"$(@:%.o=%.d)"
|
||
|
||
ifeq ($(strip $(NO_EXCEPTIONS)),1)
|
||
BASE_CXXFLAGS += -fno-exceptions
|
||
endif
|
||
|
||
ifeq ($(strip $(NO_RTTI)),1)
|
||
BASE_CXXFLAGS += -fno-rtti
|
||
endif
|
||
|
||
BASE_LDFLAGS =
|
||
ifeq ($(strip $(LTO)),1)
|
||
# 本体目标文件是 LTO 字节码 ⇒ **链接**必须带 `-flto -flto-partition=one`。
|
||
# ⚠️ 工具自身源文件**不要**加 `-flto`:把工具代码也交给 LTRANS 会在
|
||
# `choose_baseaddr`(i386.cc:7447)处 ICE(GCC 15.2.0 实测),
|
||
# 而工具体量很小,根本不需要跨单元内联。
|
||
BASE_LDFLAGS += -flto -flto-partition=one
|
||
endif
|
||
|
||
# ---- 每个工具的源文件 / 目标文件 / 产物 ------------------------------------
|
||
$(foreach tool,$(TOOLS),$(eval $(tool)_SRC := $(wildcard $(tool)/Src/*.cpp)))
|
||
$(foreach tool,$(TOOLS),$(eval $(tool)_OBJ := $(patsubst $(tool)/Src/%.cpp,$(tool)/$(BUILD_DIR_NAME)/%.o,$($(tool)_SRC))))
|
||
$(foreach tool,$(TOOLS),$(eval $(tool)_EXE := $(tool)/$(BUILD_DIR_NAME)/$(tool).exe))
|
||
|
||
# 本体的目标文件缺失时给出明确提示(而不是让链接器报一堆 undefined reference)
|
||
ifeq ($(strip $(APP_OBJECTS)),)
|
||
$(warning ********************************************************************)
|
||
$(warning 未找到应用本体目标文件:$(APP_BUILD_DIR)/*.o)
|
||
$(warning 请先在项目根目录构建本体,例如:make release -j8)
|
||
$(warning ********************************************************************)
|
||
endif
|
||
|
||
# ---- 每个工具的规则(模板)-------------------------------------------------
|
||
.SUFFIXES:
|
||
|
||
define TOOL_RULES
|
||
.PHONY: $(1)
|
||
$(1): $$($(1)_EXE)
|
||
|
||
$$($(1)_EXE): $$($(1)_OBJ) $(APP_OBJECTS) Makefile
|
||
@echo ====== [$(1)] All File Compiled. Now Linking... ======
|
||
$(CXX_COMPILER) $$($(1)_OBJ) $(APP_OBJECTS) -static $(TOOL_DEBUG_INFO) -o $$@ $(LINK_LIBS) $(BASE_LDFLAGS) -mconsole
|
||
@echo ====== [$(1)] Link Finished: $$@ ======
|
||
|
||
$$($(1)_OBJ): $(1)/$(BUILD_DIR_NAME)/%.o: $(1)/Src/%.cpp Makefile | $(1)/$(BUILD_DIR_NAME)
|
||
@echo ====== [$(1)] C++ Source File "$$<" Compiling... ======
|
||
$(CXX_COMPILER) -c $$(BASE_CXXFLAGS) -I$(1)/Inc $$< -o $$@
|
||
|
||
$(1)/$(BUILD_DIR_NAME):
|
||
@powershell -NoProfile -Command "New-Item -ItemType Directory -Force -Path '$$@' | Out-Null"
|
||
endef
|
||
|
||
$(foreach tool,$(TOOLS),$(eval $(call TOOL_RULES,$(tool))))
|
||
|
||
# ---- 聚合目标 --------------------------------------------------------------
|
||
.PHONY: all clean help unit offscreen
|
||
|
||
all: $(TOOLS)
|
||
@echo ====== [Tools] All Tools Built ======
|
||
|
||
# 便利别名
|
||
unit: UnitTests
|
||
offscreen: OffscreenChecks
|
||
|
||
# 各工具的构建目录(空格分隔 → PowerShell 里 `-split ' '` 成数组)
|
||
TOOL_BUILD_DIRS := $(strip $(foreach tool,$(TOOLS),$(tool)/$(BUILD_DIR_NAME)))
|
||
|
||
clean:
|
||
@echo ====== [Tools] Cleaning Build Directories... ======
|
||
@powershell -NoProfile -Command "'$(TOOL_BUILD_DIRS)' -split ' ' | ForEach-Object { if (Test-Path $$_) { Remove-Item -Recurse -Force $$_ } }"
|
||
@echo ====== [Tools] Clean Accomplished ======
|
||
|
||
help:
|
||
@echo Tools/Makefile —— 验证工具构建入口
|
||
@echo.
|
||
@echo make 构建全部工具:$(TOOLS)
|
||
@echo make UnitTests 只构建单元测试(Tools/UnitTests)
|
||
@echo make OffscreenChecks 只构建离屏验证工具(Tools/OffscreenChecks)
|
||
@echo make clean 删除各工具自己的 Build/ 目录
|
||
@echo make help 显示本帮助
|
||
@echo.
|
||
@echo 依赖:应用本体已构建(../Build/Release/*.o)
|
||
|
||
# ---- 头文件依赖(与根 Makefile 同一套 `-MMD -MP` 机制)--------------------
|
||
# ⚠️ 删掉/重命名头文件后若出现"莫名为 undefined reference",先 `make clean`。
|
||
-include $(foreach tool,$(TOOLS),$($(tool)_OBJ:.o=.d))
|