mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-27 12:53:35 +00:00
## 背景 `.gitignore` 第 26-45 行早已写明「外部插件与工具链维护在独立 SDK 仓 (决策 sdk_repo_only),本仓经 go.mod 的 replace 引用」,并忽略了 example/、tools/、docs/、site_build/、package/、scripts/。 但有 29 个文件**早于该规则**被跟踪,靠「已跟踪文件不受 .gitignore 影响」留着,注释还特意写了「这是有意的,不要『修』」。 本轮修 example/bili 时踩到了:那 87 行改动先落在主仓、再手动同步到 SDK 仓 —— 同一份代码两个仓各改一遍,正是这个遗留结构的成本。 ## 核实:主仓到底需要什么 - 主仓 Go 代码只 import 两个包:`homeagent-sdk/sdk`(插件入口)、 `homeagent-sdk/meta`(版本号) - `remotedevice/` 是 C 库,主仓 C 侧明确「不链接任何外部库」, 只有注释里提到它 - `tools/hmapdev/yaegi` 的 import 出现在 **SDK 仓自己的文件之间** (yaegi/interp.go → yaegi/mocksdk),不是主仓依赖; 且 tools/ 本就在 .gitignore 里,从未被跟踪 所以 29 个文件全部可以删。实际仓库负担也只有 42 个文件 / 0.6MB (工作树里那 971MB 绝大部分是未跟踪的构建产物,不是仓库体积)。 ## 改动 - 删 21 个 example/ 文件(10 个示例的 plg.json + plugin.go + qq/plugin_test.go) - 删 8 个 remotedevice/ C 文件 - 工作树里一并清掉(不受跟踪的构建产物顺带回收) 构建与全量测试均通过。 ## 判据:3 条 + 变异(internal/meta/vendored_sdk_test.go) - TestVendoredSDKHasNoExampleOrRemotedevice:防止示例代码被重新提交进来 - TestVendoredSDKKeepsRequiredPackages:**反向**保护,防止为省事把 sdk/ 与 meta/ 也删掉(上一条只防"多了",删过头要靠这条) - TestGoModStillReplacesSDKToVendoredPath:决策 A 依赖 replace 指向 vendored 路径 ★ 判据自己踩了两个坑,都靠实跑抓出来: 1. `git ls-files` 的路径参数**相对当前目录**解析,而测试跑在 internal/meta/ 下 ⇒ 就地执行返回空,表现为「必需包全都不在」的假红。 改用 `git -C <仓库根>`。 2. 把 tools/hmapdev/yaegi 当成主仓依赖写进必需清单 ⇒ 又一次假红。 根因是把 SDK 内部的引用误当成主仓依赖(它本就在 .gitignore 里)。 变异验证:塞一个 example 文件进版本控制 → 判红。
127 lines
4.9 KiB
Go
127 lines
4.9 KiB
Go
package meta_test
|
||
|
||
import (
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// ===== 主仓不再 vendoring SDK 的示例代码 =====
|
||
//
|
||
// 决策(对应 .gitignore 里的 sdk_repo_only):外部插件与工具链只在 SDK 仓
|
||
// 维护,主仓经 go.mod 的 replace 引用,**只保留编译所需的那几个包**。
|
||
//
|
||
// 曾经的状况:主仓跟踪了 29 个 SDK 示例文件(example/*/plg.json、plugin.go、
|
||
// qq/plugin_test.go,加 remotedevice/ 的 8 个 C 文件)。它们不参与主仓构建
|
||
// (唯一 import 的 SDK 包是 sdk/ 与 meta/),却要在两个仓里同步同一份代码。
|
||
// 本轮修 example/bili 时的重复劳动就是这么来的。
|
||
//
|
||
// 本测试钉住这个决策:有人再把示例代码提交进主仓时立刻变红。
|
||
// ★ 必须用 git ls-files 判断「**版本控制里有没有**」而不是看目录是否存在 ——
|
||
// 工作树里可能因为 vendoring 或本地试验而临时存在,那不算回归。
|
||
|
||
// vendoredSDKRoot 是 vendored SDK 在主仓里的位置。
|
||
const vendoredSDKRoot = "third_party/homeagent-sdk"
|
||
|
||
// repoRoot 返回主仓根目录(本测试包在 internal/meta/,往上是两层)。
|
||
func repoRoot(t *testing.T) string {
|
||
t.Helper()
|
||
abs, err := filepath.Abs(filepath.Join("..", ".."))
|
||
if err != nil {
|
||
t.Fatalf("解析仓库根: %v", err)
|
||
}
|
||
return abs
|
||
}
|
||
|
||
// gitTracked 列出 git 版本控制里该路径下的文件(相对仓库根)。
|
||
//
|
||
// ★ 必须用 `git -C <仓库根>` 而不是就地执行:git ls-files 的路径参数
|
||
//
|
||
// 是**相对当前目录**解析的,而这个测试运行在 internal/meta/ 下,
|
||
// 就地执行会返回空 —— 表现为"必需包全都不在"的假红(我第一版就这样)。
|
||
func gitTracked(t *testing.T, pathPrefix string) []string {
|
||
t.Helper()
|
||
out, err := exec.Command("git", "-C", repoRoot(t), "ls-files", pathPrefix).Output()
|
||
if err != nil {
|
||
// 不是 git 仓库 / git 不可用:跳过而不是误报。
|
||
if _, statErr := os.Stat(filepath.Join(repoRoot(t), ".git")); statErr != nil {
|
||
t.Skip("非 git 仓库,跳过结构守卫")
|
||
}
|
||
t.Fatalf("git ls-files %s: %v", pathPrefix, err)
|
||
}
|
||
var files []string
|
||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||
if line = strings.TrimSpace(line); line != "" {
|
||
files = append(files, line)
|
||
}
|
||
}
|
||
return files
|
||
}
|
||
|
||
// example/ 与 remotedevice/ 不该再被主仓跟踪。
|
||
func TestVendoredSDKHasNoExampleOrRemotedevice(t *testing.T) {
|
||
for _, sub := range []string{"example", "remotedevice"} {
|
||
files := gitTracked(t, vendoredSDKRoot+"/"+sub)
|
||
if len(files) > 0 {
|
||
t.Errorf("主仓仍在跟踪 %s/ 下的 %d 个文件(决策:示例代码只在 SDK 仓维护):\n %s\n"+
|
||
" 这些文件不参与主仓构建(唯一 import 的 SDK 包是 sdk/ 与 meta/),\n"+
|
||
" 却要在两个仓同步同一份代码 —— 本轮修 example/bili 时已因此重复劳动。\n"+
|
||
" 修复:git rm -r --cached %s/%s",
|
||
sub, len(files), strings.Join(files, "\n "), vendoredSDKRoot, sub)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 反向保护:编译真正需要的包必须在,否则删过头会编译失败。
|
||
//
|
||
// 这条不能省:上一条只防"多了",若有人为了省事把 sdk/ 或 meta/ 也删了,
|
||
// 上一条照样绿,而 go build ./... 会在 CI 里才炸。
|
||
func TestVendoredSDKKeepsRequiredPackages(t *testing.T) {
|
||
required := []string{
|
||
"sdk", // 公开契约:所有插件的入口
|
||
"sdk/plugin.go", // 具体文件:确保不是空目录
|
||
"meta", // 版本号:主仓与 SDK 版本一致性检查
|
||
"meta/meta.go",
|
||
// ★ 不列 tools/hmapdev/yaegi:.gitignore 第 33 行忽略了
|
||
// third_party/homeagent-sdk/tools/,它从来没被跟踪过。
|
||
// 而 grep 到的 yaegi 引用是 **SDK 仓自己的文件**在互相 import
|
||
// (tools/hmapdev/yaegi/interp.go → .../mocksdk),不是主仓依赖。
|
||
// 我第一版把它当主仓依赖写进来,判据直接假红。
|
||
}
|
||
tracked := map[string]bool{}
|
||
for _, f := range gitTracked(t, vendoredSDKRoot) {
|
||
tracked[f] = true
|
||
}
|
||
for _, req := range required {
|
||
// 支持两种形态:目录(下面有文件)或直接是文件
|
||
found := tracked[vendoredSDKRoot+"/"+req]
|
||
if !found {
|
||
// 目录形态:检查它下面是否有被跟踪的文件
|
||
for f := range tracked {
|
||
if strings.HasPrefix(f, vendoredSDKRoot+"/"+req+"/") {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
if !found {
|
||
t.Errorf("必需包 %s 不在主仓 vendored 范围内(删过头了):%s/%s",
|
||
req, vendoredSDKRoot, req)
|
||
}
|
||
}
|
||
}
|
||
|
||
// go.mod 的 replace 必须仍指向 vendored 路径 —— 决策 A 依赖它。
|
||
func TestGoModStillReplacesSDKToVendoredPath(t *testing.T) {
|
||
data, err := os.ReadFile(filepath.Join(repoRoot(t), "go.mod"))
|
||
if err != nil {
|
||
t.Fatalf("读 go.mod: %v", err)
|
||
}
|
||
content := string(data)
|
||
if !strings.Contains(content, vendoredSDKRoot) {
|
||
t.Errorf("go.mod 里没有 replace 到 %s:\n%s", vendoredSDKRoot, content)
|
||
}
|
||
}
|