mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-20 17:08:01 +00:00
此前项目里没有任何「我要哪版 SDK」的声明:go.mod 的 require 是个 Go 模块版本, 而工具链实际用的是存储里的 current——谁改过 current 就拿谁的版本编,出错时 表现为莫名其妙的编译错误(本轮就踩过:存储里只有陈旧的 v0.8.0,模板项目 首次构建报 undefined: sdk.InjectOptions)。 - `plg.json` 新增 `sdk` 字段:本插件针对的 SDK 版本。`hmapdev init` 生成时写入 **完整版本号**(如 "1.2.0")。 - `hmapdev build` 按声明的版本在本地 SDK 存储里定位:命中则用它并把 go.mod 的 require/replace 同步到该版本;未命中则报**可执行**的错误(列出已装版本 + `hmapdev sdk install vX.Y.Z`),绝不静默退化成 current。 - **区间写法("1.2")被拒绝**并说明规矩:SDK 版本跟随内核中版本、patch 位恒为 .0, 一条内核线只有一个 SDK 版本(写区间会让人误以为同一条线里还能挑不同 SDK)。 - 产物 `plugin.json` 记录实际选中的版本(`sdk`),便于追溯「这个 .hmap 是哪版编的」。 - 显式 `--sdk-path` / `plg.json sdk_path` 优先(本机改 SDK 联调的路径),此路径下也尽力记录版本。 - 存量项目(plg.json 无 sdk 字段)行为不变,向后兼容。 顺带修一处自相矛盾:解析出 1.2.0 之外的版本时,原先只改 replace 而 require 保持旧版本, 一旦有人删掉 replace 就会静默用回旧 SDK 编译(`go list -m` 报的也是假版本)。 验证:单测 10 例(精确命中/带 v 前缀目录/区间写法被拒并说明规矩/未命中给可执行命令/ 空存储给安装指引/非法值拒绝/杂项目录不干扰)+ 反向验证(把版本比较退化成字典序, 「1.2.x 取最新」用例立刻变红,证明判据能发现缺陷)。 E2E:init → plg.json `"sdk": "1.2.0"`;build → 精确解析、go.mod require/replace 一致、 产物 plugin.json 记录 sdk;声明不存在的版本 → 可执行报错;无 sdk 字段 → 照旧构建。
136 lines
4.2 KiB
Go
136 lines
4.2 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
// sdkVersionEntry 是本地 SDK 存储里的一个版本。
|
||
//
|
||
// Dir 是存储目录名(历史上有 "v0.8.0" 与 "1.2.0" 两种写法都出现过,所以
|
||
// 目录名与规范化版本号要分开存),Version 是去掉 v 前缀的 x.y.z。
|
||
type sdkVersionEntry struct {
|
||
Dir string
|
||
Version string
|
||
}
|
||
|
||
// normalizeSDKVersion 去掉常见的前缀写法,得到 x.y.z。
|
||
func normalizeSDKVersion(v string) string {
|
||
return strings.TrimPrefix(strings.TrimSpace(v), "v")
|
||
}
|
||
|
||
// parseSDKVersion 解析 x.y.z / x.y(后者补 0)。
|
||
func parseSDKVersion(v string) (maj, min, patch int, ok bool) {
|
||
v = normalizeSDKVersion(v)
|
||
parts := strings.Split(v, ".")
|
||
if len(parts) < 2 || len(parts) > 3 {
|
||
return 0, 0, 0, false
|
||
}
|
||
nums := make([]int, 0, 3)
|
||
for _, p := range parts {
|
||
n, err := strconv.Atoi(p)
|
||
if err != nil || n < 0 {
|
||
return 0, 0, 0, false
|
||
}
|
||
nums = append(nums, n)
|
||
}
|
||
for len(nums) < 3 {
|
||
nums = append(nums, 0)
|
||
}
|
||
return nums[0], nums[1], nums[2], true
|
||
}
|
||
|
||
// compareSDKVersion 比较两个 x.y.z(a<b 返回 -1,相等 0,a>b 返回 1)。
|
||
func compareSDKVersion(a, b string) int {
|
||
amaj, amin, apat, aok := parseSDKVersion(a)
|
||
bmaj, bmin, bpat, bok := parseSDKVersion(b)
|
||
if !aok || !bok {
|
||
return strings.Compare(normalizeSDKVersion(a), normalizeSDKVersion(b))
|
||
}
|
||
for _, d := range [][2]int{{amaj, bmaj}, {amin, bmin}, {apat, bpat}} {
|
||
switch {
|
||
case d[0] < d[1]:
|
||
return -1
|
||
case d[0] > d[1]:
|
||
return 1
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// listInstalledSDKs 列出存储里已安装的 SDK,按版本升序。
|
||
func listInstalledSDKs() []sdkVersionEntry {
|
||
store := sdkStore()
|
||
entries, err := os.ReadDir(store)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
var out []sdkVersionEntry
|
||
for _, e := range entries {
|
||
if !e.IsDir() {
|
||
continue
|
||
}
|
||
name := e.Name()
|
||
if name == "current" || strings.HasPrefix(name, ".") {
|
||
continue
|
||
}
|
||
v := normalizeSDKVersion(name)
|
||
if _, _, _, ok := parseSDKVersion(v); !ok {
|
||
continue // 非版本目录(用户放别的东西进去时不误判)
|
||
}
|
||
out = append(out, sdkVersionEntry{Dir: name, Version: v})
|
||
}
|
||
sort.Slice(out, func(i, j int) bool { return compareSDKVersion(out[i].Version, out[j].Version) < 0 })
|
||
return out
|
||
}
|
||
|
||
// ResolveSDKForProject 按项目声明的 SDK 版本(plg.json 的 "sdk" 字段)在本地存储里定位 SDK。
|
||
//
|
||
// 声明必须是**完整版本号**(x.y.z,如 "1.2.0"):SDK 的版本纪律是「跟随内核中版本,
|
||
// patch 位恒为 .0」(内核的 patch 不碰公开接口 → SDK 不跟版),所以一条内核线
|
||
// 只对应一个 SDK 版本号,写 "1.2" 这种区间写法既不必要、又容易让人以为
|
||
// 「同一条线里还能挑不同 SDK」。工具链直接拒它,顺便把这条规矩说清楚。
|
||
//
|
||
// 找不到时必须报**可执行**的错误:列出已装版本 + 可直接粘贴的安装命令 ——
|
||
// 只说 "not found" 会让人以为是工具链坏了。
|
||
func ResolveSDKForProject(declared string) (dir, version string, err error) {
|
||
declared = normalizeSDKVersion(declared)
|
||
if strings.Count(declared, ".") != 2 {
|
||
return "", "", fmt.Errorf(
|
||
"plg.json 的 sdk 字段 %q 必须是完整版本号(如 \"1.2.0\")——\n"+
|
||
" SDK 版本跟随内核中版本、patch 位恒为 .0,一条内核线只有一个 SDK 版本", declared)
|
||
}
|
||
maj, min, pat, ok := parseSDKVersion(declared)
|
||
if !ok {
|
||
return "", "", fmt.Errorf("plg.json 的 sdk 字段 %q 不是合法版本号(写法:\"1.2.0\")", declared)
|
||
}
|
||
|
||
installed := listInstalledSDKs()
|
||
for i := range installed {
|
||
e := installed[i]
|
||
emaj, emin, epat, _ := parseSDKVersion(e.Version)
|
||
if emaj == maj && emin == min && epat == pat {
|
||
return filepath.Join(sdkStore(), e.Dir), e.Version, nil
|
||
}
|
||
}
|
||
|
||
// 未命中:给出可执行的下一步
|
||
var have []string
|
||
for _, e := range installed {
|
||
have = append(have, e.Version)
|
||
}
|
||
avail := "(存储里还没有任何 SDK)"
|
||
if len(have) > 0 {
|
||
avail = "已安装:" + strings.Join(have, ", ")
|
||
}
|
||
return "", "", fmt.Errorf(
|
||
"项目声明 sdk=%s,但本地 SDK 存储里没有这个版本;%s\n"+
|
||
" 安装:hmapdev sdk install v%s\n"+
|
||
" 查看:hmapdev sdk list",
|
||
declared, avail, declared)
|
||
}
|