From 7efcab5dfea9f28559bc4c26fe47cd0cb3a297f4 Mon Sep 17 00:00:00 2001 From: ila Date: Thu, 16 Jul 2026 21:27:52 +0800 Subject: [PATCH] Isolate modern and Win7 workspaces (T-604) --- docs/00-ai-start-here.md | 20 ++-- docs/03-tech-stack.md | 13 ++- docs/current-state.md | 12 +-- docs/tasks/T-604.md | 15 ++- go.work | 1 - go.work.sum | 2 - init.ps1 | 13 ++- init.sh | 4 +- scripts/check_go_versions.py | 179 +++++++++++++++++++++++++++++++++-- scripts/verify_phase0.ps1 | 11 ++- scripts/verify_phase0.sh | 7 +- 11 files changed, 234 insertions(+), 43 deletions(-) diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md index 3a7586e..c570618 100644 --- a/docs/00-ai-start-here.md +++ b/docs/00-ai-start-here.md @@ -45,21 +45,21 @@ SoftBox 软件盒子是一个使用 Go + Gio 开发的 Windows 桌面客户端, ## 当前阶段 -当前项目处于:**M2 已完成(清单验签、ZIP 安全解压、原子切换/回滚三项高风险原型已验证),准备进入 Phase 2 清单与软件列表**。 +当前项目已完成 Phase 0~2、T-301 与首个交叉审核整改 `T-604`。下一步按审核定稿落成状态模型收口任务,随后处理依赖可复现性和 Runtime 并发契约,再恢复 T-302。 优先路径: 1. 已完成 Phase 0:monorepo 骨架 + 双目标编译 + 空 Gio 窗口。 2. 已完成 Phase 1:清单验签、ZIP 安全解压、原子切换回滚原型。 -3. 下一步 Phase 2-3:清单/列表 → 下载/安装。 -4. Phase 4-5:启动/更新/自更新 → 授权。 -5. Phase 6:Win7 加固与双通道发布。 +3. 已完成 Phase 2 与 T-301:清单/列表/详情/图标缓存 + 可恢复下载队列。 +4. 已完成 T-604:modern/Win7 workspace 与 Gio 版本解析彻底隔离。 +5. 继续串行完成其余审核整改,再恢复 T-302/T-303 和 Phase 4-6。 ## 领取任务规则 任务以「一任务一文件」存放在 `docs/tasks/`(约定见 [`tasks/README.md`](tasks/README.md)): -- 每个 agent 只领取一个 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件,取编号最靠前的;项目可并行多个 `write_paths` 互不重叠的任务。 +- 单 Agent 只领取一个 frontmatter `status: TODO` 且依赖均 `DONE` 的任务文件,取编号最靠前的;项目同一时间只保留一个活跃任务。 - 若 `docs/tasks/` 暂无可领任务,先按 [`06-tasks.md`](06-tasks.md) 路线图把下一个建议任务落成任务文件,再领取。 - 开始前在独立分支 / worktree 把该文件 frontmatter 的 `status` 改为 `DOING`。 - 本轮只完成这一个任务;验收通过后改为 `DONE`。 @@ -133,17 +133,17 @@ MVP 不做: ## 验证命令 -统一启动与验证入口收敛到根目录 `./init.sh` / `./init.ps1`;脚本会同步工作区依赖并运行完整 Phase 0 闸门。也可直接运行 `bash scripts/verify_phase0.sh` / `./scripts/verify_phase0.ps1`。标准分项命令: +统一启动与验证入口收敛到根目录 `./init.sh` / `./init.ps1`;脚本会分别同步根 modern/core workspace 与嵌套 win7/core workspace,再运行完整 Phase 0 闸门。也可直接运行 `bash scripts/verify_phase0.sh` / `./scripts/verify_phase0.ps1`。标准分项命令: ```bash # core 测试(Linux 无头可跑) cd core && go vet ./... && go test -count=1 ./... -# 现代版构建 -cd app-modern && GOTOOLCHAIN=go1.25.0 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-H=windowsgui" ./cmd/softbox +# 现代版构建(从仓库根执行;GOWORK 必须是绝对路径) +GOTOOLCHAIN=go1.25.0 GOWORK="$(pwd)/go.work" CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go -C app-modern build -trimpath -ldflags="-H=windowsgui" ./cmd/softbox -# Win7 版构建(强制 Go 1.20) -cd app-win7 && GOTOOLCHAIN=go1.20.14 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-H=windowsgui" ./cmd/softbox +# Win7 版构建(从仓库根执行;强制 Go 1.20) +GOTOOLCHAIN=go1.20.14 GOWORK="$(pwd)/app-win7/go.work" CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go -C app-win7 build -trimpath -ldflags="-H=windowsgui" ./cmd/softbox ``` 说明: diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md index bcf78e1..1ec9563 100644 --- a/docs/03-tech-stack.md +++ b/docs/03-tech-stack.md @@ -11,7 +11,7 @@ | UI 框架 | Gio(纯 Go 立即模式 GUI) | 已定 | 无 CGO、单 EXE、跨版本可锁定;仅限 UI 层 | | 现代版工具链 | Go 1.25(锁定已验证版本) + Gio v0.10.1 | 已定 | 当前受支持工具链;支持 Win10/Win11 x64 | | Win7 版工具链 | Go 1.20.14(最终补丁) + Gio v0.6.0 | 已定 | Go 1.20 是最后支持 Win7 的版本;Gio v0.6.0 可由 Go 1.20 构建 | -| 模块结构 | go.work + 三个 go.mod(core / app-modern / app-win7) | 已定 | 共享核心,双构建互不污染工具链要求 | +| 模块结构 | 两个 go.work + 三个 go.mod(core / app-modern / app-win7) | 已定 | 根 workspace 只含 modern/core;嵌套 Win7 workspace 只含 win7/core | | 依赖管理 | 版本锁定 + vendor(Gio 及相关依赖) | 已定 | 防止 Win7 构建被隐式升级到要求 Go 1.21+ 的依赖 | | 存储 | JSON 文件 + 临时文件原子替换 | 已定 | 数百款软件规模足够;SQLite 仅在历史/搜索/事务需求出现后评估 | | 软件包格式 | ZIP(标准软件包协议 v1) | 已定 | 避免 UnRAR.dll;协议见 [api.md](api.md) | @@ -41,14 +41,17 @@ | --- | --- | | Phase 0 完整闸门(Unix) | `bash scripts/verify_phase0.sh` | | Phase 0 完整闸门(Windows) | `./scripts/verify_phase0.ps1` | -| 同步工作区依赖 | `go work sync` | +| 同步根 workspace | `GOTOOLCHAIN=go1.25.0 GOWORK=/go.work go work sync` | +| 同步 Win7 workspace | `GOTOOLCHAIN=go1.25.0 GOWORK=/app-win7/go.work go work sync`(同步元数据后仍由 Go 1.20.14 校验/构建) | | core 测试 | `cd core && go test -count=1 ./...` | | core 静态检查 | `cd core && go vet ./...` | -| 现代版构建 | `cd app-modern && GOTOOLCHAIN=go1.25.0 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-H=windowsgui" -o ../dist/SoftBox.exe ./cmd/softbox` | -| Win7 版构建 | `cd app-win7 && GOTOOLCHAIN=go1.20.14 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-H=windowsgui" -o ../dist/SoftBox-win7.exe ./cmd/softbox` | +| 现代版构建 | `GOTOOLCHAIN=go1.25.0 GOWORK=/go.work CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go -C app-modern build -trimpath -ldflags="-H=windowsgui" -o ../dist/SoftBox.exe ./cmd/softbox` | +| Win7 版构建 | `GOTOOLCHAIN=go1.20.14 GOWORK=/app-win7/go.work CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go -C app-win7 build -trimpath -ldflags="-H=windowsgui" -o ../dist/SoftBox-win7.exe ./cmd/softbox` | | 本地运行(开发) | Windows 下直接 `go run ./cmd/softbox`(WSL 中构建 EXE 后到 Windows 侧运行) | -根 `go.work` 纳入三个模块并使用 modern 所需的 Go 1.25 workspace 版本;`app-win7/go.work` 是就近的 Go 1.20 workspace,只纳入 `app-win7` 与 `core`,确保 Go 1.20.14 不需要解析 modern 模块。 +根 `go.work` 使用 Go 1.25.0,只纳入 `app-modern` 与 `core`;`app-win7/go.work` 使用 Go 1.20,只纳入 `app-win7` 与 `core`。同步、测试和构建始终显式指定对应 `GOWORK`,任何 workspace 都不会同时解析 Gio v0.10.1 与 v0.6.0。 + +两个 workspace 的元数据同步统一使用 Go 1.25.0。原因是 Go 1.20.14 的 `go work sync` 会忽略 workspace 级本地 replace 并尝试联网解析占位模块 `softbox.local/core@v0.0.0`;不为规避该工具缺陷把本机相对路径写进 go.mod。Win7 的依赖图、测试和构建仍全部强制使用 Go 1.20.14,并由版本检查拒绝 Go 1.21+ 依赖。 工具链矩阵: diff --git a/docs/current-state.md b/docs/current-state.md index c65b387..f5af101 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -13,22 +13,22 @@ ## 当前快照 - 日期:2026-07-16 -- 阶段:Phase 2 已完成(T-201~T-204);Phase 3 的 T-301 可恢复下载队列已完成;Phase 0 交叉审核整改从 T-604 开始,T-302 暂后置 -- 技术栈:根 Go workspace 纳入 core/app-modern/app-win7 三模块;`app-win7/go.work` 隔离 Go 1.20.14 构建;modern Gio v0.10.1 与 win7 Gio v0.6.0 已实际接入 +- 阶段:Phase 2 已完成(T-201~T-204);Phase 3 的 T-301 可恢复下载队列已完成;Phase 0 首个交叉审核整改 T-604 已完成,T-302 继续暂后置 +- 技术栈:根 Go 1.25 workspace 只纳入 core/app-modern,`app-win7/go.work` 独立纳入 core/app-win7;版本闸门证明 modern Gio v0.10.1 与 win7 Gio v0.6.0 不交叉解析 - 生产代码:core 已有 Catalog/本地状态/存储、无 IO 软件列表模型、可信图标缓存和默认并发 2 的持久可恢复下载队列;modern/win7 AppShell 已实现搜索/分类/视图、惰性列表、详情右栏与内存图标 - 测试:core 覆盖 Catalog、SemVer/12 状态、本地安装记录、列表/图标、下载并发/暂停/取消/重试/Range/断连/恢复/事件失败与文件身份替换;两个 app 覆盖 500 项虚拟列表、ID 控件稳定性、详情/ApplyIcon 与平台 stub;ZIP/安装恢复矩阵保持通过 - 数据:`schemas/` 已有 manifest/app.json/installed-app.json/download-task.json v1 Schema;`testdata/catalog/` 有公开虚构清单样例;`testdata/zip/` 与 `testdata/download/` 记录运行时生成的攻击/传输矩阵 - 标准启动路径:`./init.sh` / `./init.ps1`(同步依赖、执行完整 Phase 0 闸门、打印双目标构建命令) - 标准验证路径:`bash scripts/verify_phase0.sh` / `./scripts/verify_phase0.ps1` - 版本管理:git 已初始化,main 分支,远端 origin 为 Gitea `opc/soft_quay`;harness 文档已提交 -- 当前 blocker:无;下一个任务是 T-604 workspace 隔离加固,完成审核整改后再恢复 T-302 安装流程整合 +- 当前 blocker:无;下一步按审核定稿落成状态模型收口任务,其余审核整改完成后再恢复 T-302 安装流程整合 ## 当前目录要点 | 路径 | 状态 | 说明 | | --- | --- | --- | | `docs/` | 已有 | harness coding 文档集(本次初始化完成) | -| `docs/tasks/` | 已有 | Phase 0~2 与 T-301 已完成;T-604 审核整改已落成并待领取;T-302 暂后置 | +| `docs/tasks/` | 已有 | Phase 0~2、T-301 与 T-604 已完成;下一审核整改任务尚未落成;T-302 暂后置 | | `scripts/` | 已有 | harness 治理、core 边界、Go 版本检查与 Phase 0 双平台验证入口 | | `core/` | 已建 | Go 1.20 兼容;已有正式 Catalog、本地状态/存储、列表模型、图标缓存、可恢复下载队列与 Phase 1 安装安全原型 | | `app-modern/` | 已建 | Go 1.25.0 + Gio v0.10.1;Modern AppShell 已接入虚拟列表、详情和内存图标 | @@ -40,9 +40,9 @@ 任务状态以 `docs/tasks/` 各任务文件 frontmatter 的 `status` 为准。本节只写项目级摘要: -- 已完成:Phase 0 的 `T-001`~`T-004`;Phase 1 的 `T-101`、`T-102`、`T-103`;Phase 2 的 `T-201`~`T-204`;Phase 3 的 `T-301`。 +- 已完成:Phase 0 的 `T-001`~`T-004`;Phase 1 的 `T-101`、`T-102`、`T-103`;Phase 2 的 `T-201`~`T-204`;Phase 3 的 `T-301`;审核整改 `T-604`。 - 正在进行:无。 -- 下一个可领取任务:`T-604 隔离 modern 与 Win7 workspace`;完成并提交后继续按审核定稿拆分下一整改任务。 +- 下一个可领取任务:无;先按 `docs/review/phase0-skeleton-review.md` 落成下一状态模型收口任务。 ## 当前可运行内容 diff --git a/docs/tasks/T-604.md b/docs/tasks/T-604.md index 7ea4f8f..34d5d48 100644 --- a/docs/tasks/T-604.md +++ b/docs/tasks/T-604.md @@ -3,12 +3,12 @@ id: T-604 title: 隔离 modern 与 Win7 workspace phase: 0 deps: [T-004] -status: TODO +status: DONE created: 2026-07-16 issue: null -context_ref: null +context_ref: e4a9295cbf94d645370d2583866b7ca91c0c5271 claim_branch: null -work_branch: null +work_branch: agent/codex/T-604 write_paths: - docs/tasks/T-604.md - go.work @@ -36,7 +36,7 @@ Phase 0 交叉审核确认根 `go.work` 同时纳入 `app-modern`、`app-win7` 1. 根 `go.work` 只纳入 `app-modern` 与 `core`;`app-win7` 只由 `app-win7/go.work` 纳入并继续与 `core` 共享本地替换。 2. `init.sh`、`init.ps1`、`verify_phase0.sh` 与 `verify_phase0.ps1` 分别同步两个 workspace: - Go 1.25.0 同步根 workspace。 - - Go 1.20.14 + 显式 `GOWORK=app-win7/go.work` 同步 Win7 workspace。 + - Go 1.25.0 + 显式 `GOWORK=app-win7/go.work` 同步 Win7 workspace 元数据;随后必须用 Go 1.20.14 校验实际依赖图、测试和构建。 3. 加固 `scripts/check_go_versions.py`: - 校验根 workspace 只包含 modern/core。 - 校验 Win7 workspace 只包含 win7/core。 @@ -50,7 +50,7 @@ Phase 0 交叉审核确认根 `go.work` 同时纳入 `app-modern`、`app-win7` - `go work edit -json` 显示根 workspace 只有 `./app-modern`、`./core`;`app-win7/go.work` 只有 `.`、`../core`。 - 根 workspace 执行 `go list -m gioui.org` 得到 v0.10.1;Win7 workspace 使用 Go 1.20.14 执行同命令得到 v0.6.0。 - 根 workspace 不再把 `./app-win7/...` 当作自身 package pattern 构建;Win7 test/build 必须显式使用嵌套 workspace。 -- 两个 workspace 的 sync 都由 `init` 与 `verify_phase0` 执行;sync 后 `core/go.mod`、`app-modern/go.mod`、`app-win7/go.mod` 无意外漂移。 +- 两个 workspace 的 sync 都由 `init` 与 `verify_phase0` 执行;Win7 sync 使用 Go 1.25.0 规避 Go 1.20.14 对 workspace replace 的已验证限制,但 Win7 依赖图/test/build 仍强制 Go 1.20.14;sync 后三个 go.mod 无意外漂移。 - `python scripts/check_go_versions.py` 能在 workspace 成员或 Gio 解析版本错误时失败。 - gopls/IDE 打开仓库后,Win7 文件使用 Gio v0.6.0 语义;若当前环境没有 gopls,执行记录必须写明限制并至少提供嵌套 workspace 的 `go list/test` 证据。 - `./scripts/verify_phase0.ps1` 与 `bash scripts/verify_phase0.sh` 全绿,modern/Win7 EXE 均重新产出。 @@ -74,3 +74,8 @@ Phase 0 交叉审核确认根 `go.work` 同时纳入 `app-modern`、`app-win7` ## 执行记录 - 2026-07-16:根据 `docs/review/phase0-skeleton-review.md` 的交叉复核定稿落成首个整改任务;编号按路线图与现有任务最大编号 `T-603 + 1` 取 `T-604`。 +- 2026-07-16:在 `agent/codex/T-604` 分支领取任务;基线为 `e4a9295cbf94d645370d2583866b7ca91c0c5271`。当前环境未安装 gopls,后续以嵌套 workspace 的实际 `go list/test/build` 作为自动化证据,并在收尾记录该环境限制。 +- 2026-07-16:真实执行发现 Go 1.20.14 `go work sync` 虽能用 workspace replace 完成 `go list`,却会在 sync 时联网解析 `softbox.local/core@v0.0.0` 并失败;Go 1.25.0 对同一 Win7 workspace sync 成功且三个 go.mod 哈希不变。决定用 Go 1.25.0 同步两套 workspace 元数据,继续用 Go 1.20.14 强制验证和构建 Win7,不向 go.mod 写本地 replace。 +- 2026-07-16:根 `go.work` 移除 `app-win7`,清理根 `go.work.sum` 中 Win7 专属旧校验和;`init`/`verify_phase0` 分别同步两套 workspace并显式传递绝对 `GOWORK`。`check_go_versions.py` 现校验 workspace 成员、实际 Gio 解析版本、Go 1.20.14 工具链与 core/win7 完整模块图,并带错误分支 self-check。 +- 2026-07-16:隔离证据通过:根 `go work edit -json` 仅含 `app-modern/core`,Gio 为 v0.10.1;Win7 workspace 仅含 `app-win7/core`,Go 1.20.14 解析 Gio v0.6.0;根 workspace 的 `go list ./app-win7/...` 按预期拒绝;三个 go.mod 前后哈希完全一致。当前环境未安装 gopls,已按验收规则记录限制并用实际 list/test/build 替代自动化证据。 +- 2026-07-16:验证通过:`python scripts/check_go_versions.py`;`./scripts/verify_phase0.ps1`;`C:\Program Files\Git\bin\bash.exe scripts/verify_phase0.sh`;`./init.ps1`(含 `RUN_START_COMMAND=1` 的显式双 workspace build);`C:\Program Files\Git\bin\bash.exe ./init.sh`;治理/边界检查、core Go 1.20.14 vet/test、modern Go 1.25.0 与 Win7 Go 1.20.14 adapter test/GUI build 全绿。 diff --git a/go.work b/go.work index 6abf496..f650f3b 100644 --- a/go.work +++ b/go.work @@ -2,7 +2,6 @@ go 1.25.0 use ( ./app-modern - ./app-win7 ./core ) diff --git a/go.work.sum b/go.work.sum index 4964092..cdec701 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,8 +1,6 @@ eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA= -gioui.org v0.6.0/go.mod h1:eUvGo6FAzA7jUqeSu5a+M1W03yc9r1nanIBS8A5+Nng= gioui.org v0.10.1 h1:Dvp6iDk9RKuZk19jxhOmb4p673CLVvb656LyMxQ+uO0= gioui.org v0.10.1/go.mod h1:MZJZsdEPkTBzChdqeE8CiiQhreUQBj43qusDxQNDf7k= -gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA= gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU= diff --git a/init.ps1 b/init.ps1 index bd91302..14ce4ea 100644 --- a/init.ps1 +++ b/init.ps1 @@ -12,17 +12,23 @@ $Utf8 = [System.Text.UTF8Encoding]::new($false) $OutputEncoding = $Utf8 Set-Location -Path $PSScriptRoot -$InstallCmd = "go work sync" +$InstallCmd = '$env:GOTOOLCHAIN="go1.25.0"; $env:GOWORK=(Resolve-Path "go.work").Path; go work sync; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; $env:GOWORK=(Resolve-Path "app-win7/go.work").Path; go work sync' $VerifyCmd = "./scripts/verify_phase0.ps1" -$StartCmd = '$env:CGO_ENABLED="0"; $env:GOOS="windows"; $env:GOARCH="amd64"; $env:GOTOOLCHAIN="go1.25.0"; go build -C app-modern -trimpath "-ldflags=-H=windowsgui" -o ../dist/SoftBox.exe ./cmd/softbox; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; $env:GOTOOLCHAIN="go1.20.14"; go build -C app-win7 -trimpath "-ldflags=-H=windowsgui" -o ../dist/SoftBox-win7.exe ./cmd/softbox' +$StartCmd = '$env:CGO_ENABLED="0"; $env:GOOS="windows"; $env:GOARCH="amd64"; $env:GOTOOLCHAIN="go1.25.0"; $env:GOWORK=(Resolve-Path "go.work").Path; go build -C app-modern -trimpath "-ldflags=-H=windowsgui" -o ../dist/SoftBox.exe ./cmd/softbox; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; $env:GOTOOLCHAIN="go1.20.14"; $env:GOWORK=(Resolve-Path "app-win7/go.work").Path; go build -C app-win7 -trimpath "-ldflags=-H=windowsgui" -o ../dist/SoftBox-win7.exe ./cmd/softbox' Write-Host "==> 当前目录: $($PWD.Path)" Write-Host "==> 同步依赖" Invoke-Expression $InstallCmd +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} Write-Host "==> 运行基础验证" Invoke-Expression $VerifyCmd +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} Write-Host "==> 启动命令" Write-Host " $StartCmd" @@ -30,6 +36,9 @@ Write-Host " $StartCmd" if ($env:RUN_START_COMMAND -eq "1") { Write-Host "==> 启动应用" Invoke-Expression $StartCmd + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } } else { Write-Host "如果希望 init.ps1 直接启动应用,请设置环境变量 RUN_START_COMMAND=1。" Write-Host "如果基础验证失败,先修复基线状态,不要在坏的起点上继续叠新功能。" diff --git a/init.sh b/init.sh index d645ebf..cf53660 100755 --- a/init.sh +++ b/init.sh @@ -11,9 +11,9 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ROOT_DIR" -INSTALL_CMD="go work sync" +INSTALL_CMD="GOTOOLCHAIN=go1.25.0 GOWORK=\"$ROOT_DIR/go.work\" go work sync && GOTOOLCHAIN=go1.25.0 GOWORK=\"$ROOT_DIR/app-win7/go.work\" go work sync" VERIFY_CMD="bash scripts/verify_phase0.sh" -START_CMD="GOTOOLCHAIN=go1.25.0 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go -C app-modern build -trimpath -ldflags=-H=windowsgui -o ../dist/SoftBox.exe ./cmd/softbox && GOTOOLCHAIN=go1.20.14 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go -C app-win7 build -trimpath -ldflags=-H=windowsgui -o ../dist/SoftBox-win7.exe ./cmd/softbox" +START_CMD="GOTOOLCHAIN=go1.25.0 GOWORK=\"$ROOT_DIR/go.work\" CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go -C app-modern build -trimpath -ldflags=-H=windowsgui -o ../dist/SoftBox.exe ./cmd/softbox && GOTOOLCHAIN=go1.20.14 GOWORK=\"$ROOT_DIR/app-win7/go.work\" CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go -C app-win7 build -trimpath -ldflags=-H=windowsgui -o ../dist/SoftBox-win7.exe ./cmd/softbox" echo "==> 当前目录: $PWD" diff --git a/scripts/check_go_versions.py b/scripts/check_go_versions.py index 4127f64..eec69ee 100644 --- a/scripts/check_go_versions.py +++ b/scripts/check_go_versions.py @@ -10,6 +10,8 @@ import sys MAX_LEGACY_GO_VERSION = (1, 20) +MODERN_TOOLCHAIN = "go1.25.0" +LEGACY_TOOLCHAIN = "go1.20.14" def version_tuple(value): @@ -52,9 +54,10 @@ def required_version(path, module_path): return match.group(1) -def run_go(repo_root, cwd, arguments, go_work): +def run_go(cwd, arguments, go_work, toolchain): environment = os.environ.copy() environment["GOWORK"] = str(go_work) if go_work else "off" + environment["GOTOOLCHAIN"] = toolchain result = subprocess.run( ["go"] + arguments, cwd=str(cwd), @@ -70,6 +73,130 @@ def run_go(repo_root, cwd, arguments, go_work): return result.stdout +def workspace_modules(workspace, toolchain): + document = json.loads( + run_go( + workspace.parent, + ["work", "edit", "-json"], + workspace, + toolchain, + ) + ) + return { + (workspace.parent / entry["DiskPath"]).resolve() + for entry in document.get("Use", []) + } + + +def display_path(path, repo_root): + try: + return str(path.relative_to(repo_root)) + except ValueError: + return str(path) + + +def workspace_layout_violation(workspace, actual, expected, repo_root): + if actual == expected: + return None + actual_names = sorted(display_path(path, repo_root) for path in actual) + expected_names = sorted(display_path(path, repo_root) for path in expected) + return "{} contains {}, want {}".format( + display_path(workspace, repo_root), + actual_names, + expected_names, + ) + + +def validate_workspace_layout(repo_root): + root_workspace = repo_root / "go.work" + win7_workspace = repo_root / "app-win7" / "go.work" + expected = { + root_workspace: { + (repo_root / "app-modern").resolve(), + (repo_root / "core").resolve(), + }, + win7_workspace: { + (repo_root / "app-win7").resolve(), + (repo_root / "core").resolve(), + }, + } + toolchains = { + root_workspace: MODERN_TOOLCHAIN, + win7_workspace: LEGACY_TOOLCHAIN, + } + violations = [] + for workspace, expected_modules in expected.items(): + actual_modules = workspace_modules(workspace, toolchains[workspace]) + violation = workspace_layout_violation( + workspace, + actual_modules, + expected_modules, + repo_root, + ) + if violation: + violations.append(violation) + return violations + + +def resolved_module_version(cwd, workspace, toolchain, module_path): + document = json.loads( + run_go( + cwd, + ["list", "-m", "-json", module_path], + workspace, + toolchain, + ) + ) + return document.get("Version", "") + + +def module_version_violation(label, module_path, actual, expected): + if actual == expected: + return None + return "{} workspace resolves {} {}, want {}".format( + label, + module_path, + actual, + expected, + ) + + +def validate_workspace_gio_versions(repo_root): + checks = ( + ( + "modern", + repo_root / "app-modern", + repo_root / "go.work", + MODERN_TOOLCHAIN, + "v0.10.1", + ), + ( + "win7", + repo_root / "app-win7", + repo_root / "app-win7" / "go.work", + LEGACY_TOOLCHAIN, + "v0.6.0", + ), + ) + violations = [] + for label, cwd, workspace, toolchain, expected in checks: + actual = resolved_module_version( + cwd, + workspace, + toolchain, + "gioui.org", + ) + violation = module_version_violation( + label, + "gioui.org", + actual, + expected, + ) + if violation: + violations.append(violation) + return violations + + def validate_pins(repo_root): expected_go_directives = { repo_root / "go.work": "1.25.0", @@ -106,7 +233,12 @@ def validate_pins(repo_root): def validate_go20_toolchain(repo_root): - output = run_go(repo_root, repo_root, ["version"], None).strip() + output = run_go( + repo_root, + ["version"], + None, + LEGACY_TOOLCHAIN, + ).strip() if "go1.20.14" not in output: return ["compatibility scan uses {!r}, want go1.20.14".format(output)] return [] @@ -114,22 +246,23 @@ def validate_go20_toolchain(repo_root): def validate_module_versions(repo_root): module_sets = ( - ("core", repo_root / "core", None), + ("core", repo_root / "core", None, LEGACY_TOOLCHAIN), ( "win7", repo_root / "app-win7", repo_root / "app-win7" / "go.work", + LEGACY_TOOLCHAIN, ), ) violations = [] checked = 0 - for label, cwd, go_work in module_sets: + for label, cwd, go_work, toolchain in module_sets: output = run_go( - repo_root, cwd, ["list", "-m", "-json", "all"], go_work, + toolchain, ) for module in decode_json_stream(output): checked += 1 @@ -153,12 +286,45 @@ def self_check(): raise AssertionError("Go 1.20.14 should be accepted") if not version_tuple("1.21")[:2] > MAX_LEGACY_GO_VERSION: raise AssertionError("Go 1.21 should be rejected") + fake_root = pathlib.Path("/repo") + fake_workspace = fake_root / "go.work" + fake_expected = {fake_root / "core", fake_root / "app-modern"} + if workspace_layout_violation( + fake_workspace, + fake_expected, + fake_expected, + fake_root, + ): + raise AssertionError("matching workspace layout should be accepted") + if not workspace_layout_violation( + fake_workspace, + fake_expected | {fake_root / "app-win7"}, + fake_expected, + fake_root, + ): + raise AssertionError("unexpected workspace module should be rejected") + if module_version_violation( + "win7", + "gioui.org", + "v0.6.0", + "v0.6.0", + ): + raise AssertionError("matching Gio version should be accepted") + if not module_version_violation( + "win7", + "gioui.org", + "v0.10.1", + "v0.6.0", + ): + raise AssertionError("mismatched Gio version should be rejected") def main(): self_check() repo_root = pathlib.Path(__file__).resolve().parents[1] violations = validate_pins(repo_root) + violations.extend(validate_workspace_layout(repo_root)) + violations.extend(validate_workspace_gio_versions(repo_root)) violations.extend(validate_go20_toolchain(repo_root)) module_violations, checked = validate_module_versions(repo_root) violations.extend(module_violations) @@ -169,7 +335,8 @@ def main(): raise SystemExit(1) print( - "Go version check passed: pins valid; {} module records are Go 1.20-compatible.".format( + "Go version check passed: workspace isolation and pins valid; " + "{} module records are Go 1.20-compatible.".format( checked ) ) diff --git a/scripts/verify_phase0.ps1 b/scripts/verify_phase0.ps1 index 7f6698a..3665065 100644 --- a/scripts/verify_phase0.ps1 +++ b/scripts/verify_phase0.ps1 @@ -35,7 +35,13 @@ New-Item -ItemType Directory -Force -Path (Join-Path $Root "dist") | Out-Null Invoke-Step "Sync root workspace" { $env:GOTOOLCHAIN = "go1.25.0" - Remove-Item Env:GOWORK -ErrorAction SilentlyContinue + $env:GOWORK = (Resolve-Path "go.work").Path + go work sync +} + +Invoke-Step "Sync Win7 workspace" { + $env:GOTOOLCHAIN = "go1.25.0" + $env:GOWORK = (Resolve-Path "app-win7/go.work").Path go work sync } @@ -54,7 +60,8 @@ Invoke-Step "Validate core architecture boundary" { } Invoke-Step "Validate Go and dependency pins" { - $env:GOTOOLCHAIN = "go1.20.14" + Remove-Item Env:GOTOOLCHAIN -ErrorAction SilentlyContinue + Remove-Item Env:GOWORK -ErrorAction SilentlyContinue python scripts/check_go_versions.py } diff --git a/scripts/verify_phase0.sh b/scripts/verify_phase0.sh index 5a5e931..9108441 100644 --- a/scripts/verify_phase0.sh +++ b/scripts/verify_phase0.sh @@ -26,7 +26,10 @@ fi mkdir -p dist echo "==> Sync root workspace" -GOTOOLCHAIN=go1.25.0 go work sync +GOTOOLCHAIN=go1.25.0 GOWORK="$ROOT_DIR/go.work" go work sync + +echo "==> Sync Win7 workspace" +GOTOOLCHAIN=go1.25.0 GOWORK="$ROOT_DIR/app-win7/go.work" go work sync echo "==> Validate harness governance" "$PYTHON" scripts/validate_agent_context.py @@ -38,7 +41,7 @@ echo "==> Validate core architecture boundary" "$PYTHON" scripts/check_core_boundaries.py echo "==> Validate Go and dependency pins" -GOTOOLCHAIN=go1.20.14 "$PYTHON" scripts/check_go_versions.py +"$PYTHON" scripts/check_go_versions.py echo "==> Vet and test core with Go 1.20.14" GOTOOLCHAIN=go1.20.14 GOWORK=off go -C core vet ./...