Skip to content

Latest commit

 

History

History
335 lines (253 loc) · 18.7 KB

engineering_setup.md

File metadata and controls

335 lines (253 loc) · 18.7 KB

Spectiary 工程环境

环境与入口

仓库当前是 native shell + 多格式真实数据 loader。spectiary_native 是 Win32 + DirectX 11 executable target, 用于初始化 Dear ImGui docking、ImPlot、dock host、主图、文件区、信息/标签区、谱线区、状态栏和可选 JSONL profile sink。

本页负责工具安装、构建、测试和发布命令。相关现行合同见:

必需工具

  • Visual Studio 2022 Build Tools。
  • C++ desktop workload。
  • Windows 10/11 SDK。
  • CMake 3.24 或更新版本。
  • vcpkg。
  • Ninja,可选,用于 ninja-msvc-debug 和发布前验证使用的 ninja-msvc-release-static preset。

vcpkg

设置 VCPKG_ROOT:

[Environment]::SetEnvironmentVariable('VCPKG_ROOT', (Join-Path $env:USERPROFILE 'vcpkg'), 'User')

重新打开终端后确认:

$env:VCPKG_ROOT

vcpkg.json 使用 manifest mode,当前目标依赖为:

  • imgui[docking-experimental,win32-binding,dx11-binding]
  • implot
  • cfitsio[pthreads](default-features=false,后台并发加载要求 reentrant build)
  • yaml-cpp
  • nlohmann-json (header-only; native JSON DOM, parsing and deterministic serialization)
  • zlib

manifest 固定 builtin-baseline,避免依赖版本跟随本机 VCPKG_ROOT checkout 漂移。 vcpkg-configuration.json 为所有 manifest 构建启用 ports/implot overlay, 以阻止轴范围菜单把 NaN 转成 0(#140)。补丁范围、升级和移除条件见 ports/implot/README.md。

DirectX 11 来自 Windows SDK;spectiary_renderer 封装 DX11/DXGI presentation,spectiary_native 负责 Win32/DWM shell。 FITS container 解析使用 vcpkg 提供的 CFITSIO。Debug preset 使用 x64-windows,允许 vcpkg 依赖以 DLL 形式存在;当前正式 Release preset 统一继承 x64-windows-static,因此 CFITSIO 链入 Spectiary.exe,Portable 包不携带 cfitsio.dll。这是当前 CFITSIO/Release 的具体选择,不是“所有依赖 必须静态”的全局规则;依赖链接策略仍按组件和分发需求分别决定。 yaml-cpp 用于 production ASDF sample-labeling 文档的受限 YAML metadata 解析;zlib 用于该 codec 的固定压缩 profile,以及受限 .fits.gz 单光谱读取路径。

内存安全与静态分析

Issue #131 的 hardening 使用两个独立 MSVC/Ninja preset,默认构建和发布配置不启用它们。 需要 Visual Studio Installer 中的 C++ AddressSanitizer 组件,以及 MSVC 原生代码分析工具。 在仓库根目录运行:

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run-hardening.ps1 -Mode Asan
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run-hardening.ps1 -Mode Analyze

入口始终调用 build-ninja-msvc-debug.ps1,保留其工具链发现、日志和超时回收机制; 受限 agent 环境需要与普通 Ninja/MSVC 构建相同的 sandbox escalation。 可用 -VcvarsPath 指定工具链,-ArtifactsDirectory 指定诊断目录。 默认日志位于 logs/hardening/Asan 和 logs/hardening/Analyze。

  • ninja-msvc-asan 使用独立 Debug 目录,移除 /RTC1、禁用增量链接, 对仓库 C++ 编译单元启用 /fsanitize=address,保留完整调试符号。 spectiary_asan_targets 与 CTest asan 标签共用显式测试名单,覆盖 FITS/NPY 加载、 CSV sample annotation、ASDF codec/store/mutation/property、sample navigation、 后台加载队列及 native/headless automation。测试串行执行,每项最多 180 秒;空集合失败。 启动脚本从该配置的编译器路径定位 ASan runtime DLL,并固定失败退出选项。 检测探针先执行合法访问,再在独立子进程中故意越界;只有报告 heap-buffer-overflow 且非零退出才通过,普通崩溃、缺失 DLL 或未插桩都不能冒充检测成功。
  • ninja-msvc-analyze 对 core、sessions、automation、desktop UI、renderer 和 ImGui layout 等自有生产库启用 /analyze;config/native-analysis.ruleset 将 C6001(未初始化读取)、C6011(空指针解引用)、C6385/C6386(越界读写)设为错误。 对应 /we 参数确保命令行编译器返回失败,而不只输出诊断。 配置阶段故意解引用空指针,验证 C6011 确实使编译失败。 规则集有意限定为这四类缺陷,后续扩展需先检查实际诊断。

repository-verification.yml 的独立 hardening matrix 在每次手动运行中执行两条通道, 失败时保留构建日志及 ASan CTest/JUnit 报告;不会修改任何 workflow 触发器。 分析针对自有生产代码,/analyze:external- 跳过外部头文件,但外部模板实例化仍可能报告诊断。 预编译 vcpkg 库不重新插桩,因此不保证检测其内部全部内存访问;不使用全局第三方告警抑制。 预编译 imgui_stdlib 的 STL annotation ABI 与 ASan 默认值不一致(LNK2038), 因此仅 ASan 配置统一定义 _DISABLE_VECTOR_ANNOTATION / _DISABLE_STRING_ANNOTATION。 这会失去 vector/string 的 size 与 capacity 之间的越界检测;普通堆、栈访问插桩仍然启用。 该明确取舍避免为本通道维护一套第三方重编译 triplet。 alloc_dealloc_mismatch=0 沿用 Windows 默认值:未插桩 yaml-cpp DLL 中正确的 operator delete 会转调 free,额外开启该检查会将跨模块分配误报为 new/free 不匹配。 这项检查暂不覆盖;use-after-free、普通越界和 double-free 检查继续启用。 ASan 不替代线程竞争、Win32 handle、COM 或 GPU 资源生命周期验证,也不运行资源用量基准。

参数限制见 MSVC ASan 文档 和 MSVC 静态分析文档。

CMake Presets

Jump List 的原生 shell 与真实 GUI 回归使用测试程序的隐式 shell identity/临时 Portable 目录, 不会改动用户的 Files roster;后者会短暂打开并关闭测试窗口,其任务栏分组与 Jump List 生命周期由 Windows 决定,不承诺按配置目录隔离。构建后可显式运行:

ctest --test-dir build/ninja-msvc-debug -R '^spectiary_win32_jump_list_(native|gui_routing)_tests$' --output-on-failure

它们标记为 extended;real-gui;gui-integration,不随 headless extended preset 运行;确定性的发布协议测试属于 fast。启动和 shell identity 合同见 ADR 0017。

GUI 回归也验证首次无来源启动恢复、后续无来源启动不激活来源、无操作关闭不覆盖会话, 以及显式 Jump List 来源仍保留完整 Files 清单。真实 Windows 手势另做人工验收: 保存来源后退出全部实例,从开始菜单普通启动确认恢复;在其运行时用任务栏 Shift-click 或 Win+Shift+数字确认新窗口的 Files 清单保留、无激活来源且主图为空。再退出全部实例,测试冷启动 Start Menu + Shift; 如果 Windows 产生两次 invocation,确认首次恢复 saved active、后续只恢复 Files。该手势时序不由进程启动测试替代; 现有 endpoint 只在 GUI 初始化后注册,不为未复现的并发启动增加存在性锁。

共享配置写在 CMakePresets.json。本地个人配置写在 CMakeUserPresets.json,不要提交。

Ninja configure check:

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build-ninja-msvc-debug.ps1 -Configure
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build-ninja-msvc-debug.ps1 -Preset ninja-msvc-release-static -Configure

Visual Studio configure check:

cmake --preset vs2022-x64-debug

Configure success 验证依赖和生成文件,build success 验证 native shell target。

Build native shell:

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build-ninja-msvc-debug.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build-ninja-msvc-debug.ps1 -Preset ninja-msvc-release-static

CTest 验证层级

完成 ninja-msvc-debug configure 并通过上面的包装器构建所需测试 target 后,日常开发的标准 edit/test loop 是从仓库根目录运行:

ctest --preset fast

fast 是确定性、低成本的日常开发反馈层。需要在提交前执行更完整但仍为 headless 的开发验证时,运行:

ctest --preset extended

两个 preset 为未单独声明超时的测试提供 120 秒默认上限;测试自己的 CTest TIMEOUT 属性仍优先。这是卡住时的退出边界,不是整套测试的耗时预算。

已测耗时与适用边界见 2026-09-02 CI 范围与 fast tier 记录。

extended 会运行较慢的 headless 测试,但不会代替需要专门环境或目的的验证。验证入口分工如下:

入口 用途 选择边界
fast preset 日常开发 快速、确定性的 unit、component 和廉价 contract 测试
extended preset 更完整 headless 较慢的 headless 开发验证;排除下列专门验证 labels
real-gui label 专门 GUI 验证 需要交互式 Windows desktop;gui-integration 也按同一专门边界处理
release label 专门发布验证 release artifact、packaging、configured-build 等发布合同
periodic label 专门周期验证 显式启用的资源稳定性 smoke/soak 工作负载
asdf-pinned-oracle label 专门 ASDF oracle 验证 需要 dedicated ASDF preset、固定 Python ASDF 版本和 oracle 依赖

fast / extended 是每项测试恰好选择一个的执行成本 tier;专门 labels 是与之正交的验证责任。 因此廉价 component test 可以同时属于 fast 和 release,但完整 release suite 仍必须通过 release label 显式运行。extended preset 会排除 real-gui、gui-integration、release、periodic 和 asdf-pinned-oracle,不会把这些专门验证静默计入普通 headless 结果。未限定的 aggregate ctest 不是日常开发入口。

专门验证应在满足相应 build、desktop、数据或 oracle 前置条件后,以对应 label 显式选择:

ctest --test-dir <build-directory> --output-on-failure --no-tests=error -L '^<label>$'

真实 GUI 的 runner 与证据规则见自动化 CI,release 构建与 artifact 合同见 Release artifacts,periodic 资源稳定性 tier 见响应速度测试流程, ASDF pinned oracle 的环境与命令见ASDF labeling spike 运行说明。

Ninja/MSVC 卡住排查

ninja-msvc-debug 和 ninja-msvc-release-static preset 依赖 MSVC developer environment 与 vcpkg manifest mode。不要在普通 PowerShell 里裸跑 ninja 或 直接执行对应的 cmake --build --preset ...;cl.exe 可能找不到标准库头,例如 cstddef。

在 Codex 或其它只允许写仓库目录的受限环境里,configure/build 需要用同一套 vcvars64.bat 命令形态并允许写 workspace 外缓存。这两个 Ninja preset 的 configure 都会调用 vcpkg,并可能写入 $env:VCPKG_ROOT\buildtrees\0.vcpkg_dep_info.cmake、buildtrees/、packages/、下载缓存或 MSVC 工具链缓存;如果沙箱拦住这些 workspace 外写入,表现可能是 configure 失败或后续 build 看起来卡住。

受限环境中的正确处理方式是把 configure 和 build 都作为需要外部工具链/cache 写入权限的命令执行,并优先使用 scripts/build-ninja-msvc-debug.ps1。这个脚本会加载 vcvars64.bat、记录 stdout/stderr 到 logs/build/, 并把构建进程放进 Windows Job Object。启动真正的 CMake 之前,脚本会先做一次 process-kill preflight: 创建一个短生命周期探针进程、放进同类 job,然后验证当前 shell 能用 taskkill /T /F 清掉这棵进程树。 如果 preflight 失败,脚本会在进入 CMake 前直接失败;在 Codex 里这表示普通沙箱不能自救,需要用提权/非沙箱 方式重跑,而不是重复等待同一个命令。

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build-ninja-msvc-debug.ps1 -Configure -TimeoutSec 180
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build-ninja-msvc-debug.ps1 -Target spectiary_source_collection_session_tests -TimeoutSec 60 -Explain

如果看到类似 This shell cannot kill job-assigned process trees with taskkill /T /F 的 preflight 错误, 不要继续在同一个普通受限 shell 里重试。对 Codex agent 来说,这个命令必须通过 sandbox escalation 执行, 因为 vcpkg/MSVC 会写 workspace 外缓存,且超时清理需要能终止 job-assigned cmd/cmake/ninja 进程树。

如果一次构建被中断,后续命令可能卡在 Ninja lock。先查是否有残留构建进程:

Get-Process | Where-Object { $_.ProcessName -match 'cmake|ninja|cl|link|ctest|msbuild' } |
  Select-Object Id,ProcessName,CPU,StartTime,Path

确认是旧的 cmake.exe/ninja.exe 持锁后,只终止对应进程,再重跑上面的 vcvars64.bat configure/build 命令:

Stop-Process -Id <cmakeId>,<ninjaId> -Force

生成程序位于:

build/ninja-msvc-debug/Spectiary.exe
build/ninja-msvc-release-static/Spectiary.exe

普通构建输出的 spectiary_metadata.json 不含 deployment,因此 EXE 作为 Standalone 运行,ImGui layout、 panel 显示状态、profile 设置和默认日志分别写入 %LOCALAPPDATA%\Spectiary 下按 config/、state/、logs/ 分工的文件或目录。 Release 程序可在 Settings > Diagnostics 开始/停止性能诊断录制,并可选择 profile 输出目录。 设置 SPECTIARY_PROFILE=1 则从启动阶段自动录制;SPECTIARY_PROFILE_DIR 仍可为自动化流程覆盖 UI 设置。 录制器使用有界异步写入,单次 5 分钟或 100 MiB 自动停止;分析前检查 profile_recorder_summary.dropped_events == 0。scripts/analyze-profile.ps1 默认强制检查 summary 位于日志 末尾、停止原因有效且没有丢事件;旧格式日志只有显式传入 -AllowLegacyIncompleteRecording 才可继续分析。

Portable release

第一版 portable 是 no-launcher 包:zip 根目录包含 Spectiary.exe、 spectiary_metadata.json 和 config/、state/、logs/、unsaved/。第三方声明与数据来源内嵌在所有分发形式 共用的 EXE 中,可从 About 阅读。直接从当前工作区文件构建:

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build-portable.ps1

严格忽略 tracked dirty 和 untracked 文件、从构建开始时的本地完整 HEAD object ID 构建:

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build-portable-from-head.ps1

HEAD 脚本先解析并冻结完整 HEAD object ID,再用 git archive 将该精确 revision 展开到经过校验的临时目录,并调用快照内同一份 build-portable.ps1。成功或失败都会 清理临时源码和 build 目录,不修改当前工作区或 Git index。

当前工作区 builder 直接调用 CMake 和当前统一的 Visual Studio release preset。HEAD 包装器不把工作区的 preset 或 configuration 默认值下传到快照;这些默认值以及 metadata 和包内容校验均由快照内 builder 按其自身代际负责。外层只确认子进程成功且 package 目录、ZIP 和 SHA-256 文件存在,避免工作区脚本解释旧代 schema。两个入口都预期在正常 开发 shell 或已批准的非沙箱 agent 运行中执行;它们不复用 Ninja debug wrapper 的日志、 timeout 和 preflight 形态。

Working-tree 默认输出位于 dist\Spectiary-portable,包名来自 identity 契约中的 artifact_basename;HEAD 入口目前仍默认输出到 dist\head\Spectiary-portable。 各自的 ZIP 和 .sha256 位于对应的 dist\ 或 dist\head\ 目录,HEAD 构建不会删除或覆盖 working-tree 包。 共同脚本的 source mode/revision 参数是两个正式入口之间的内部契约;为避免 dirty checkout 被误标为 HEAD,它在源码根仍包含 .git 时拒绝 head 模式。

包内容、metadata 字段、依赖声明、校验顺序与失败行为统一见 Release artifacts。 运行身份和数据根选择见 运行时数据边界。

仓库卫生

文本换行由 .gitattributes 固定为 LF,编辑器的缩进与文件末尾换行由 .editorconfig 约定。Markdown 保留有意义的行末空格;ASDF fixture 和图标 保持原始字节,尤其不得重新编码或格式化带 manifest 校验和的 ASDF 文件。

以下内容不进入提交:

  • build/
  • out/
  • .vs/
  • vcpkg_installed/
  • CMakeUserPresets.json
  • 运行包中的 managed role 目录 config/、state/、logs/、unsaved/ 及其运行时数据;不包括源码仓库中包含 project_identity.json 等正式输入的 config/。
  • imgui.ini
  • logs/
  • 本地光谱数据。
  • .scratch/ 中的旧 demo 和实验。

如果后续需要测试数据,只提交小型、明确授权的 fixture,并放在专门的 fixture 路径中。

本地构建备份和实验材料集中保留在已忽略的 .scratch/,采样日志保留在 logs/。这些目录不属于 Git 提交范围,也不因一次卫生整理而自动删除。 归档或清理前应先确认相关任务不再需要该份构建,且报告引用的原始采样、 EXE/PDB 和哈希仍有可追溯副本;不要用 git clean -xfd 批量清除证据。

后续实现检查

继续实现前先确认:

  • 新输入是否继续从 producer 侧产出 SpectrumSnapshotHandle,不让 UI/plot 解析 loader 细节。
  • profile JSONL schema 是否覆盖本次要判断的交互路径。
  • 真实性能判断是否使用真实 .npy 数据和新生成日志。
  • 真实数据路径是否仍留在仓库外。

ImGui widget regression tests

See ImGui widget regression tests for the headless real-widget harness, focused commands, and its boundary with business automation and presentation diagnostics.

Live-resize 的 instrumentation event、字段和机器验收见 presentation telemetry。这些诊断不改变 resize/presentation policy。#56 的范围、实验结论与下一步统一见 调查入口。