mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-31 06:17:25 +00:00
CI 上 `Examples (linux/amd64)` 与 `(darwin/arm64)` 双红,只有两个失败: ✗ commondev 构建失败 ✗ editdoc 构建失败 error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本 其余 20 个示例全部成功 —— 说明不是通用问题,而是这两个被改坏了。 ## 根因(两个叠加) **① go.mod 被本机绝对路径污染** 我在上一个提交里用 `git add -A`,把本机 `go build` **自动改写**过的 go.mod 一并提交了: require github.com/JianFeeeee/homeagentsdk v1.4.0 replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0 基线(origin/main)是: require github.com/JianFeeeee/homeagentsdk v0.0.0 replace github.com/JianFeeeee/homeagentsdk => ../../ 本机那个目录真的存在 ⇒ 本地全绿;CI 没有 ⇒ 必红。 **② plg.json 声明了 `"sdk": "1.4.0"`** 一声明版本,hmapdev 就去**本地 SDK store** 找它,找不到直接拒绝构建。 全仓 20+ 示例**都不声明**,只有我改过的这两个声明了。 示例的正确依赖表达是 go.mod 的 `v0.0.0` + `replace ../../`。 ## 修法 两个文件都恢复到与其余示例一致的写法。 ## 判据(3 条,都是新加的) · `TestExampleGoModHasNoAbsoluteReplace`:任何示例 go.mod 不得含 `/root/`、`/home/`、`/Users/`、`C:\` · `TestExampleGoModUsesRelativeSDKReplace`:replace 必须是相对路径 `../../` · `TestExamplePlgJsonDoesNotPinSDKVersion`:plg.json 不得声明 sdk 变异 2/2 咬住(重新引入绝对路径 / 重新声明 sdk,都准确报出文件路径)。 ## 本地验证(模拟 CI 干净环境) 用干净 HOME 跑 `package/build-examples.sh`: · 修复前:成功 18 / 失败 4(commondev, editdoc, browser, rss) · 修复后:成功 20 / 失败 2(browser, rss —— **本机代理不通**, `proxy.golang.org` 超时;用本地 modcache 验证这两个可正常构建) 即 22/22 源码都能构建,CI 有网络不受影响。 ## 教训 `git add -A` 会把「构建工具自动改写的文件」一起提交。 提交前必须 `git status` 逐个看,尤其 go.mod / go.sum。
130 lines
4.1 KiB
Go
130 lines
4.1 KiB
Go
package main
|
||
|
||
// 示例插件的 go.mod 判据(2026-10-17 CI 事故)。
|
||
//
|
||
// ## 事故
|
||
//
|
||
// 我在一个提交里用 `git add -A` 把本机 `go build` **自动改写**过的
|
||
// go.mod 一并提交了:
|
||
//
|
||
// require github.com/JianFeeeee/homeagentsdk v1.4.0
|
||
// replace github.com/JianFeeeee/homeagentsdk => /root/.homeagent/hmapdev/sdk/v1.4.0
|
||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 本机绝对路径
|
||
//
|
||
// 后果:CI(干净环境)上 commondev / editdoc 构建**必然失败**
|
||
//
|
||
// error: 项目声明 sdk=1.4.0,但本地 SDK 存储里没有这个版本
|
||
//
|
||
// 而本机能过(那个目录真的存在)⇒ 本地全绿掩盖了问题,PR 卡在 CI 才暴露。
|
||
//
|
||
// ## 判据
|
||
//
|
||
// 1. 任何示例的 go.mod 都**不得**含本机绝对路径
|
||
// 2. replace 必须是相对路径(`../../`),这是 SDK 仓内示例的约定
|
||
// 3. plg.json 不得声明 `sdk` 版本 —— 一声明就要求本地 SDK store 有该版本,
|
||
// 而示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../
|
||
//
|
||
// 这三条合起来保证「CI 干净环境能构建全部示例」。
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"regexp"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// ★★★ 判据:示例 go.mod 不得含本机绝对路径。
|
||
func TestExampleGoModHasNoAbsoluteReplace(t *testing.T) {
|
||
for _, gm := range findExampleGoMods(t) {
|
||
b, err := os.ReadFile(gm)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
src := string(b)
|
||
for _, bad := range []string{"/root/", "/home/", "/Users/", "C:\\"} {
|
||
if strings.Contains(src, bad) {
|
||
rel := mustRel(t, gm)
|
||
t.Fatalf("★ %s 含本机绝对路径 %q ——\n"+
|
||
" CI(干净环境)会因此构建失败,而本机因为有那个目录反而通过。\n"+
|
||
" 示例必须用相对路径:replace ... => ../../", rel, bad)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ★★★ 判据:示例的 replace 指向 SDK 仓根(相对路径)。
|
||
func TestExampleGoModUsesRelativeSDKReplace(t *testing.T) {
|
||
reReplace := regexp.MustCompile(`(?m)^replace\s+\S*homeagentsdk\S*\s*=>\s*(\S+)`)
|
||
for _, gm := range findExampleGoMods(t) {
|
||
b, _ := os.ReadFile(gm)
|
||
m := reReplace.FindStringSubmatch(string(b))
|
||
if m == nil {
|
||
continue // 没有 replace 也算合法(某些示例可能不用)
|
||
}
|
||
tgt := m[1]
|
||
if filepath.IsAbs(tgt) {
|
||
t.Fatalf("★ %s 的 replace 是绝对路径 %q(必须相对)", mustRel(t, gm), tgt)
|
||
}
|
||
if tgt != "../../" {
|
||
t.Errorf("%s 的 replace 是 %q,本仓约定为 ../../(与 ai_image/calendar 等一致)",
|
||
mustRel(t, gm), tgt)
|
||
}
|
||
}
|
||
}
|
||
|
||
// ★★★ 判据:示例 plg.json 不得声明 sdk 版本。
|
||
//
|
||
// 为什么:一声明 `"sdk": "1.4.0"`,hmapdev 就会去**本地 SDK store** 找该版本,
|
||
// 找不到直接拒绝构建。而示例的正确依赖表达是 go.mod 的 v0.0.0 + replace ../../。
|
||
// 全仓 20+ 个示例都不声明,只有被我改过的两个声明了 ⇒ CI 只在这两个上红。
|
||
func TestExamplePlgJsonDoesNotPinSDKVersion(t *testing.T) {
|
||
root := repoRootForTest(t)
|
||
matches, _ := filepath.Glob(filepath.Join(root, "example", "*", "plg.json"))
|
||
reSDK := regexp.MustCompile(`"sdk"\s*:`)
|
||
for _, p := range matches {
|
||
b, err := os.ReadFile(p)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
if reSDK.Match(b) {
|
||
rel := mustRel(t, p)
|
||
t.Fatalf("★ %s 声明了 sdk 版本 ——\n"+
|
||
" 这会让 hmapdev 去本地 SDK store 找该版本,干净 CI 上必然失败。\n"+
|
||
" 示例请用 go.mod 的 `require ... v0.0.0` + `replace ... => ../../`", rel)
|
||
}
|
||
}
|
||
}
|
||
|
||
func findExampleGoMods(t *testing.T) []string {
|
||
t.Helper()
|
||
root := repoRootForTest(t)
|
||
matches, err := filepath.Glob(filepath.Join(root, "example", "*", "go.mod"))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if len(matches) == 0 {
|
||
t.Fatal("没找到任何示例 go.mod ⇒ 判据会假绿,路径写错了?")
|
||
}
|
||
return matches
|
||
}
|
||
|
||
func repoRootForTest(t *testing.T) string {
|
||
t.Helper()
|
||
wd, err := os.Getwd()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// tools/hmapdev → 上两级
|
||
return filepath.Dir(filepath.Dir(wd))
|
||
}
|
||
|
||
func mustRel(t *testing.T, p string) string {
|
||
t.Helper()
|
||
r, err := filepath.Rel(repoRootForTest(t), p)
|
||
if err != nil {
|
||
return p
|
||
}
|
||
return r
|
||
}
|