# =============================================================================
# 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))
