mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-20 08:58:03 +00:00
refactor(toolchain)!: 工具链 plugindev 更名为 hmapdev,module path 改回 gitcode
- 目录 tools/plugindev → tools/hmapdev,可执行文件名/平台产物名同步
(hmapdev_linux_amd64 等;包格式仍叫 .hmap)
- module path github.com/JianFeeeee/homeagent-sdk/tools/... → gitcode.com/...
(与仓库实际托管一致;核心仓不依赖该 path,改动无外部影响)
- SDK 存储目录 ~/.homeagent/plugindev/sdk → ~/.homeagent/hmapdev/sdk
新目录不存在而旧目录存在时沿用旧目录 → 已装 SDK 版本不会丢失
- 命令表/usage/--help/生成项目 README/示例 README/NSIS 安装器/
package/build.sh/build-examples.sh 全部同步;PLUGINDEV 环境变量保留兼容
- sdk/ 目录零改动(公开接口不变)
验证:
- go build ./... ok;go test ./tools/hmapdev/ ok(含模板接线守卫 TestProcTemplate_CoversAllCoreMethods)
- bash -n package/{build,build-examples}.sh ok
- 端到端:hmapdev init demo && hmapdev build → dist/demo_bundle.hmap(linux+darwin)
- 本机安装 /usr/local/bin/hmapdev,旧名以软链保留;sdk list/current 正常
This commit is contained in:
295
tools/hmapdev/cmd_init.go
Normal file
295
tools/hmapdev/cmd_init.go
Normal file
@ -0,0 +1,295 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func (p *PlgConfig) ReplacesToSlice() []string {
|
||||
var s []string
|
||||
for from, to := range p.Replaces {
|
||||
s = append(s, from+"="+to)
|
||||
}
|
||||
sort.Strings(s) // deterministic order
|
||||
return s
|
||||
}
|
||||
|
||||
type PlgConfig struct {
|
||||
Name string `json:"name"`
|
||||
NameZh string `json:"name_zh"`
|
||||
NameEn string `json:"name_en"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author"`
|
||||
Entry string `json:"entry"`
|
||||
Tags []string `json:"tags"`
|
||||
Targets string `json:"targets"`
|
||||
OutDir string `json:"outdir,omitempty"`
|
||||
Bundle *bool `json:"bundle,omitempty"`
|
||||
SDKPath string `json:"sdk_path,omitempty"`
|
||||
GoVersion string `json:"go_version,omitempty"`
|
||||
Replaces map[string]string `json:"replaces,omitempty"`
|
||||
SourceDirs []string `json:"source_dirs,omitempty"`
|
||||
}
|
||||
|
||||
// TargetList parses the Targets string into a slice.
|
||||
func (p *PlgConfig) TargetList() []string { return parseTargets(p.Targets) }
|
||||
|
||||
// BundleDefault returns true if bundle mode is not explicitly disabled.
|
||||
func (p *PlgConfig) BundleDefault() bool { return p.Bundle == nil || *p.Bundle }
|
||||
|
||||
// OutDirDefault returns the output directory, defaulting to "dist".
|
||||
func (p *PlgConfig) OutDirDefault() string {
|
||||
if p.OutDir != "" {
|
||||
return p.OutDir
|
||||
}
|
||||
return "dist"
|
||||
}
|
||||
|
||||
type TemplateData struct {
|
||||
Plg PlgConfig
|
||||
IsLua bool
|
||||
|
||||
// Go module info (for go.mod)
|
||||
ModulePath string
|
||||
GoVersion string
|
||||
SDKModule string
|
||||
SDKVersion string
|
||||
|
||||
// SDKLocalPath 是本机 SDK 源码绝对路径,写入生成的 go.mod 作为 replace 目标。
|
||||
//
|
||||
// 为何必须写:gitcode 的模块不在 proxy.golang.org 上,只 require 一个
|
||||
// 版本号的 go.mod 配上缺失的 go.sum,新用户第一次 `hmapdev build`
|
||||
// 必定死在 "missing go.sum entry",而 `go mod tidy` 又会去公共 proxy 拉
|
||||
// 一个不存在的条目。有了本地 replace,go 完全不需要 go.sum 条目。
|
||||
SDKLocalPath string
|
||||
}
|
||||
|
||||
func cmdInit(args []string) {
|
||||
if len(args) < 1 {
|
||||
fmt.Println("Usage: hmapdev init <name> [--lua] [--type remotedevice]")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
isLua := false
|
||||
isRemoteDevice := false
|
||||
for _, a := range args[1:] {
|
||||
switch a {
|
||||
case "--lua":
|
||||
isLua = true
|
||||
case "--type", "-t":
|
||||
// handled in next iteration
|
||||
}
|
||||
}
|
||||
// also check --type remotedevice as a single arg
|
||||
for i, a := range args[1:] {
|
||||
if a == "--type" || a == "-t" {
|
||||
if i+1 < len(args[1:]) {
|
||||
if args[1:][i+1] == "remotedevice" {
|
||||
isRemoteDevice = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if a == "--type=remotedevice" || a == "-t=remotedevice" {
|
||||
isRemoteDevice = true
|
||||
}
|
||||
}
|
||||
|
||||
if isRemoteDevice && isLua {
|
||||
fmt.Println("error: --type remotedevice and --lua are mutually exclusive")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Remote device projects use different scaffold
|
||||
if isRemoteDevice {
|
||||
scaffoldRemoteDevice(name)
|
||||
return
|
||||
}
|
||||
|
||||
dir := name
|
||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
fmt.Printf("error: directory %q already exists\n", dir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Go 插件统一产出 plugin.bin(v1.0.0 子进程模式)。
|
||||
//
|
||||
// 此前这里写 "plugin.so",scaffold 出来的 plg.json 就带着一个已退场的
|
||||
// entry 值,新手跟着模板走会误以为自己在做 C ABI 插件。
|
||||
// build 实际不看这个值(只用它区分 Lua),但模板不应误导。
|
||||
entry := "plugin.bin"
|
||||
var targets string
|
||||
if isLua {
|
||||
entry = "main.lua"
|
||||
targets = "lua"
|
||||
} else {
|
||||
targets = "linux/amd64,windows/amd64"
|
||||
}
|
||||
|
||||
nameEn := strings.ReplaceAll(name, "-", " ")
|
||||
nameEn = strings.Title(nameEn)
|
||||
|
||||
data := TemplateData{
|
||||
Plg: PlgConfig{
|
||||
Name: name,
|
||||
NameZh: "中文名",
|
||||
NameEn: nameEn,
|
||||
Version: "0.1.0",
|
||||
Description: name + " plugin",
|
||||
Author: "HomeAgent",
|
||||
Entry: entry,
|
||||
Tags: []string{name},
|
||||
Targets: targets,
|
||||
},
|
||||
IsLua: isLua,
|
||||
}
|
||||
|
||||
// Detect SDK info for Go plugin go.mod.
|
||||
// 生成的 go.mod 除 require 外还写一条指向本机 SDK 的 replace:
|
||||
// 否则 scaffold 出来的项目第一次 build 必定失败(详见 SDKLocalPath 注释)。
|
||||
if !isLua {
|
||||
sdkMod, goVer, sdkRoot, sdkVer := detectSDKInfo()
|
||||
data.ModulePath = name
|
||||
data.GoVersion = goVer
|
||||
data.SDKModule = sdkMod
|
||||
data.SDKVersion = "v" + sdkVer
|
||||
data.SDKLocalPath = strings.ReplaceAll(sdkRoot, "\\", "/")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
fmt.Printf("error: create dir: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// write plg.json
|
||||
writeTemplate(filepath.Join(dir, "plg.json"), tmplPlgJSON, data)
|
||||
|
||||
// Lua plugins get main.lua + sdk.lua; Go plugins get plugin.go only
|
||||
if isLua {
|
||||
writeTemplate(filepath.Join(dir, "main.lua"), tmplMainLua, data)
|
||||
writeTemplate(filepath.Join(dir, "sdk.lua"), tmplSDKLua, data)
|
||||
} else {
|
||||
writeTemplate(filepath.Join(dir, "plugin.go"), tmplPluginGo, data)
|
||||
}
|
||||
|
||||
// write README.md
|
||||
writeTemplate(filepath.Join(dir, "README.md"), tmplReadme, data)
|
||||
|
||||
// write go.mod for Go plugins
|
||||
if !isLua {
|
||||
writeTemplate(filepath.Join(dir, "go.mod"), tmplGoMod, data)
|
||||
}
|
||||
|
||||
// create thirdpart directory for external library sources
|
||||
os.MkdirAll(filepath.Join(dir, "thirdpart"), 0755)
|
||||
|
||||
fmt.Printf("Created plugin project %q (%s)\n", dir, entry)
|
||||
if isLua {
|
||||
fmt.Printf(" cd %s && lua main.lua (standalone test)\n", dir)
|
||||
}
|
||||
fmt.Printf(" cd %s && hmapdev build\n", dir)
|
||||
}
|
||||
|
||||
// detectSDKInfo reads the HomeAgent SDK's go.mod and meta to get module path, go version, and SDK version.
|
||||
func detectSDKInfo() (modulePath, goVersion, sdkPath, sdkVersion string) {
|
||||
root := activeSDKRoot()
|
||||
gomodPath := filepath.Join(root, "go.mod")
|
||||
data, err := os.ReadFile(gomodPath)
|
||||
if err != nil {
|
||||
fmt.Printf("error: cannot read SDK go.mod at %s: %v\n", gomodPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "module ") {
|
||||
modulePath = strings.TrimSpace(line[7:])
|
||||
}
|
||||
if strings.HasPrefix(line, "go ") {
|
||||
goVersion = strings.TrimSpace(line[3:])
|
||||
}
|
||||
}
|
||||
if modulePath == "" {
|
||||
fmt.Printf("error: no module directive in %s\n", gomodPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
if goVersion == "" {
|
||||
goVersion = "1.21"
|
||||
}
|
||||
sdkVersion = readMetaVersion(root)
|
||||
return modulePath, goVersion, root, sdkVersion
|
||||
}
|
||||
|
||||
// scaffoldRemoteDevice 创建远程设备适配器项目脚手架
|
||||
func scaffoldRemoteDevice(name string) {
|
||||
dir := name
|
||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
fmt.Printf("error: directory %q already exists\n", dir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
nameEn := strings.Title(strings.ReplaceAll(name, "-", " "))
|
||||
|
||||
data := TemplateData{
|
||||
Plg: PlgConfig{
|
||||
Name: name,
|
||||
NameZh: "中文名",
|
||||
NameEn: nameEn,
|
||||
Version: "0.1.0",
|
||||
Description: name + " remote device adapter",
|
||||
Author: "HomeAgent",
|
||||
Entry: name,
|
||||
Tags: []string{name, "remotedevice"},
|
||||
},
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
fmt.Printf("error: create dir: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 写入 main.c
|
||||
writeTemplate(filepath.Join(dir, "main.c"), tmplRemoteDeviceMain, data)
|
||||
|
||||
// 写入 CMakeLists.txt
|
||||
writeTemplate(filepath.Join(dir, "CMakeLists.txt"), tmplRemoteDeviceCMake, data)
|
||||
|
||||
// 创建 SDK 目录(symlink/copy)
|
||||
sdkSrc := filepath.Join("..", "remotedevice")
|
||||
sdkDst := filepath.Join(dir, "ha_remotedevice")
|
||||
if _, err := os.Stat(sdkDst); os.IsNotExist(err) {
|
||||
// 尝试创建符号链接,失败则提示
|
||||
if err := os.Symlink(sdkSrc, sdkDst); err != nil {
|
||||
fmt.Printf(" note: could not create symlink to SDK, copy manually:\n")
|
||||
fmt.Printf(" cp -r %s %s\n", sdkSrc, sdkDst)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Created remote device adapter project %q\n", dir)
|
||||
fmt.Printf(" cd %s && mkdir build && cd build && cmake .. && make\n", dir)
|
||||
fmt.Printf(" Or include as subdirectory in your project:\n")
|
||||
fmt.Printf(" add_subdirectory(%s)\n", dir)
|
||||
}
|
||||
|
||||
func writeTemplate(path, content string, data TemplateData) {
|
||||
tmpl, err := template.New("").Parse(content)
|
||||
if err != nil {
|
||||
fmt.Printf("error: parse template: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
fmt.Printf("error: create %s: %v\n", path, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := tmpl.Execute(f, data); err != nil {
|
||||
fmt.Printf("error: execute template: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user