mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-15 12:15:11 +00:00
feat: plugindev build --bundle for multi-platform .hmap
- resolveBuild: use plugin.dylib for darwin (was plugin.so) - --bundle flag: build linux/amd64 + darwin/amd64 + windows/amd64, package all binaries into a single .hmap with platforms field - writePluginJSON: accept platforms []string for bundle manifest - add createBundleHmap for zip entries with custom names - add PlgConfig.IsLua() helper
This commit is contained in:
@ -15,6 +15,7 @@ import (
|
|||||||
type BuildConfig struct {
|
type BuildConfig struct {
|
||||||
OutDir string
|
OutDir string
|
||||||
Targets []string // "linux/amd64", "windows/amd64", "lua"
|
Targets []string // "linux/amd64", "windows/amd64", "lua"
|
||||||
|
Bundle bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdBuild(args []string) {
|
func cmdBuild(args []string) {
|
||||||
@ -31,6 +32,8 @@ func cmdBuild(args []string) {
|
|||||||
cfg.Targets = append(cfg.Targets, args[i+1])
|
cfg.Targets = append(cfg.Targets, args[i+1])
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
case "--bundle":
|
||||||
|
cfg.Bundle = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +44,16 @@ func cmdBuild(args []string) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if plg.IsLua() {
|
||||||
|
buildTarget(plg, "lua", cfg.OutDir)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Bundle {
|
||||||
|
buildBundle(plg, cfg.OutDir)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// determine targets
|
// determine targets
|
||||||
targets := cfg.Targets
|
targets := cfg.Targets
|
||||||
if len(targets) == 0 {
|
if len(targets) == 0 {
|
||||||
@ -56,6 +69,82 @@ func cmdBuild(args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allBundleTargets 是 --bundle 模式构建的全部平台。
|
||||||
|
// 每个 OS 只有一个架构(amd64),避免二进制文件名冲突。
|
||||||
|
var allBundleTargets = []struct {
|
||||||
|
target string
|
||||||
|
entry string // 二进制在 zip 中的文件名
|
||||||
|
}{
|
||||||
|
{"linux/amd64", "plugin.so"},
|
||||||
|
{"darwin/amd64", "plugin.dylib"},
|
||||||
|
{"windows/amd64", "plugin.dll"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildBundle(plg *PlgConfig, outDir string) {
|
||||||
|
os.MkdirAll(outDir, 0755)
|
||||||
|
buildDir := "build"
|
||||||
|
os.MkdirAll(buildDir, 0755)
|
||||||
|
|
||||||
|
// Auto-generate C ABI bridge for non-Windows
|
||||||
|
bridgeCleanup := generateBridge("")
|
||||||
|
defer bridgeCleanup()
|
||||||
|
thirdpartCleanup := linkThirdpart("linux/amd64")
|
||||||
|
defer thirdpartCleanup()
|
||||||
|
|
||||||
|
var binaries []binEntry
|
||||||
|
|
||||||
|
for _, bt := range allBundleTargets {
|
||||||
|
cfg, errMsg := resolveBuild(bt.target)
|
||||||
|
if cfg == nil {
|
||||||
|
fmt.Printf(" error: %s\n", errMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outPath := filepath.Join(buildDir, cfg.entryFile)
|
||||||
|
|
||||||
|
cmd := exec.Command("go", "build", "-buildmode=c-shared", "-o", outPath)
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
cmd.Env = append(cmd.Env, "GOOS="+cfg.goos, "GOARCH="+cfg.goarch, "CGO_ENABLED=1")
|
||||||
|
|
||||||
|
if cfg.goos == "windows" {
|
||||||
|
cc := detectWindowsCC()
|
||||||
|
if cc != "" {
|
||||||
|
cmd.Env = append(cmd.Env, "CC="+cc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
fmt.Printf(" compiling %s/%s (-buildmode=c-shared)...\n", cfg.goos, cfg.goarch)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Printf(" error: build %s/%s: %v\n", cfg.goos, cfg.goarch, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binaries = append(binaries, binEntry{src: outPath, zip: bt.entry})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write plugin.json with all platforms declared
|
||||||
|
platforms := map[string]bool{}
|
||||||
|
for _, bt := range allBundleTargets {
|
||||||
|
parts := strings.SplitN(bt.target, "/", 2)
|
||||||
|
platforms[parts[0]] = true
|
||||||
|
}
|
||||||
|
plats := make([]string, 0, len(platforms))
|
||||||
|
for p := range platforms {
|
||||||
|
plats = append(plats, p)
|
||||||
|
}
|
||||||
|
writePluginJSON(plg, plats, "plugin.so")
|
||||||
|
|
||||||
|
// package single .hmap with correctly named entries
|
||||||
|
hmapPath := filepath.Join(outDir, fmt.Sprintf("%s_bundle.hmap", toSnake(plg.NameEn)))
|
||||||
|
createBundleHmap(hmapPath, "plugin.json", binaries)
|
||||||
|
fmt.Printf(" packaged %s\n", filepath.Base(hmapPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PlgConfig) IsLua() bool {
|
||||||
|
return p.Entry == "main.lua"
|
||||||
|
}
|
||||||
|
|
||||||
func readPlgJSON(path string) (*PlgConfig, error) {
|
func readPlgJSON(path string) (*PlgConfig, error) {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -83,7 +172,7 @@ func parseTargets(raw string) []string {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePluginJSON(plg *PlgConfig, entry string) {
|
func writePluginJSON(plg *PlgConfig, platforms []string, entry string) {
|
||||||
m := map[string]interface{}{
|
m := map[string]interface{}{
|
||||||
"name": plg.Name,
|
"name": plg.Name,
|
||||||
"name_zh": plg.NameZh,
|
"name_zh": plg.NameZh,
|
||||||
@ -93,6 +182,9 @@ func writePluginJSON(plg *PlgConfig, entry string) {
|
|||||||
"author": plg.Author,
|
"author": plg.Author,
|
||||||
"entry": entry,
|
"entry": entry,
|
||||||
}
|
}
|
||||||
|
if len(platforms) > 0 {
|
||||||
|
m["platforms"] = platforms
|
||||||
|
}
|
||||||
if len(plg.Tags) > 0 {
|
if len(plg.Tags) > 0 {
|
||||||
m["tags"] = plg.Tags
|
m["tags"] = plg.Tags
|
||||||
}
|
}
|
||||||
@ -120,16 +212,14 @@ func resolveBuild(target string) (*buildConfig, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch goos {
|
switch goos {
|
||||||
case "linux", "darwin", "freebsd", "windows":
|
case "linux":
|
||||||
ext := ".so"
|
return &buildConfig{goos: goos, goarch: goarch, entryFile: "plugin.so"}, ""
|
||||||
if goos == "windows" {
|
case "darwin":
|
||||||
ext = ".dll"
|
return &buildConfig{goos: goos, goarch: goarch, entryFile: "plugin.dylib"}, ""
|
||||||
}
|
case "freebsd":
|
||||||
return &buildConfig{
|
return &buildConfig{goos: goos, goarch: goarch, entryFile: "plugin.so"}, ""
|
||||||
goos: goos,
|
case "windows":
|
||||||
goarch: goarch,
|
return &buildConfig{goos: goos, goarch: goarch, entryFile: "plugin.dll"}, ""
|
||||||
entryFile: "plugin" + ext,
|
|
||||||
}, ""
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Sprintf("unsupported OS %q", goos)
|
return nil, fmt.Sprintf("unsupported OS %q", goos)
|
||||||
}
|
}
|
||||||
@ -140,7 +230,7 @@ func buildTarget(plg *PlgConfig, target, outDir string) {
|
|||||||
|
|
||||||
// Lua: no compilation, package source directly
|
// Lua: no compilation, package source directly
|
||||||
if target == "lua" {
|
if target == "lua" {
|
||||||
writePluginJSON(plg, "main.lua")
|
writePluginJSON(plg, nil, "main.lua")
|
||||||
pkgFiles := []string{"plugin.json", "main.lua"}
|
pkgFiles := []string{"plugin.json", "main.lua"}
|
||||||
for _, f := range []string{"README.md", "LICENSE"} {
|
for _, f := range []string{"README.md", "LICENSE"} {
|
||||||
if _, err := os.Stat(f); err == nil {
|
if _, err := os.Stat(f); err == nil {
|
||||||
@ -184,7 +274,7 @@ func buildTarget(plg *PlgConfig, target, outDir string) {
|
|||||||
defer thirdpartCleanup()
|
defer thirdpartCleanup()
|
||||||
|
|
||||||
// Write plugin.json with the correct entry for this target
|
// Write plugin.json with the correct entry for this target
|
||||||
writePluginJSON(plg, cfg.entryFile)
|
writePluginJSON(plg, nil, cfg.entryFile)
|
||||||
|
|
||||||
cmd := exec.Command("go", "build", "-buildmode=c-shared", "-o", outPath)
|
cmd := exec.Command("go", "build", "-buildmode=c-shared", "-o", outPath)
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
@ -225,6 +315,66 @@ func buildTarget(plg *PlgConfig, target, outDir string) {
|
|||||||
fmt.Printf(" packaged %s\n", hmapName)
|
fmt.Printf(" packaged %s\n", hmapName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type binEntry struct {
|
||||||
|
src string // 磁盘路径,如 build/plugin.so
|
||||||
|
zip string // zip 中条目名,如 plugin.so
|
||||||
|
}
|
||||||
|
|
||||||
|
// createBundleHmap 创建包含多平台二进制的 bundle .hmap 文件。
|
||||||
|
// jsonName 是 plugin.json 在 zip 中的条目名;
|
||||||
|
// binaries 的 src 为磁盘路径,zip 为 zip 中的条目名。
|
||||||
|
func createBundleHmap(hmapPath, jsonName string, binaries []binEntry) {
|
||||||
|
f, err := os.Create(hmapPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error: create hmap %s: %v\n", hmapPath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
w := zip.NewWriter(f)
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
// Add plugin.json
|
||||||
|
writeZipEntry := func(zipName, diskPath string) {
|
||||||
|
info, err := os.Stat(diskPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hdr, err := zip.FileInfoHeader(info)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hdr.Method = zip.Deflate
|
||||||
|
hdr.Name = zipName
|
||||||
|
writer, err := w.CreateHeader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src, err := os.Open(diskPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
io.Copy(writer, src)
|
||||||
|
src.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
writeZipEntry(jsonName, jsonName)
|
||||||
|
|
||||||
|
// Add each platform binary with the correct zip entry name
|
||||||
|
for _, b := range binaries {
|
||||||
|
if _, err := os.Stat(b.src); err == nil {
|
||||||
|
writeZipEntry(b.zip, b.src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add optional metadata files
|
||||||
|
for _, fname := range []string{"README.md", "LICENSE"} {
|
||||||
|
if _, err := os.Stat(fname); err == nil {
|
||||||
|
writeZipEntry(fname, fname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createHmap(hmapPath string, files []string) {
|
func createHmap(hmapPath string, files []string) {
|
||||||
f, err := os.Create(hmapPath)
|
f, err := os.Create(hmapPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user