mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-03 10:37:46 +00:00
169 lines
4.2 KiB
Go
169 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// targetSpec is the minimal subset of TinyGo's target JSON we need.
|
|
type targetSpec struct {
|
|
Inherits []string `json:"inherits,omitempty"`
|
|
BuildTags []string `json:"build-tags,omitempty"`
|
|
GOOS string `json:"goos,omitempty"`
|
|
GOARCH string `json:"goarch,omitempty"`
|
|
GC string `json:"gc,omitempty"`
|
|
Scheduler string `json:"scheduler,omitempty"`
|
|
Serial string `json:"serial,omitempty"`
|
|
FlashMethod string `json:"flash-method,omitempty"`
|
|
FlashCommand string `json:"flash-command,omitempty"`
|
|
Emulator string `json:"emulator,omitempty"`
|
|
}
|
|
|
|
// Target is a resolved target with inheritance applied.
|
|
type Target struct {
|
|
Name string
|
|
BuildTags []string
|
|
GOOS string
|
|
GOARCH string
|
|
GC string
|
|
Scheduler string
|
|
Serial string
|
|
}
|
|
|
|
// override merges src properties into dst following TinyGo's semantics:
|
|
// strings override if non-empty, slices append with dedup.
|
|
func (dst *targetSpec) override(src *targetSpec) {
|
|
if src.GOOS != "" {
|
|
dst.GOOS = src.GOOS
|
|
}
|
|
if src.GOARCH != "" {
|
|
dst.GOARCH = src.GOARCH
|
|
}
|
|
if src.GC != "" {
|
|
dst.GC = src.GC
|
|
}
|
|
if src.Scheduler != "" {
|
|
dst.Scheduler = src.Scheduler
|
|
}
|
|
if src.Serial != "" {
|
|
dst.Serial = src.Serial
|
|
}
|
|
if src.FlashMethod != "" {
|
|
dst.FlashMethod = src.FlashMethod
|
|
}
|
|
if src.FlashCommand != "" {
|
|
dst.FlashCommand = src.FlashCommand
|
|
}
|
|
if src.Emulator != "" {
|
|
dst.Emulator = src.Emulator
|
|
}
|
|
dst.BuildTags = appendUnique(dst.BuildTags, src.BuildTags...)
|
|
}
|
|
|
|
func appendUnique(dst []string, src ...string) []string {
|
|
seen := make(map[string]bool, len(dst))
|
|
for _, s := range dst {
|
|
seen[s] = true
|
|
}
|
|
for _, s := range src {
|
|
if !seen[s] {
|
|
dst = append(dst, s)
|
|
seen[s] = true
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
|
|
func loadRawTargets(dir string) (map[string]*targetSpec, error) {
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
specs := make(map[string]*targetSpec)
|
|
for _, e := range entries {
|
|
if !e.Type().IsRegular() || !strings.HasSuffix(e.Name(), ".json") {
|
|
continue
|
|
}
|
|
name := strings.TrimSuffix(e.Name(), ".json")
|
|
data, err := os.ReadFile(filepath.Join(dir, e.Name()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var spec targetSpec
|
|
if err := json.Unmarshal(data, &spec); err != nil {
|
|
return nil, fmt.Errorf("parsing %s: %w", e.Name(), err)
|
|
}
|
|
specs[name] = &spec
|
|
}
|
|
return specs, nil
|
|
}
|
|
|
|
func resolveSpec(name string, raw map[string]*targetSpec, cache map[string]*targetSpec, resolving map[string]bool) (*targetSpec, error) {
|
|
if cached, ok := cache[name]; ok {
|
|
return cached, nil
|
|
}
|
|
if resolving[name] {
|
|
return nil, fmt.Errorf("circular inheritance: %s", name)
|
|
}
|
|
spec, ok := raw[name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown target: %s", name)
|
|
}
|
|
|
|
resolving[name] = true
|
|
defer delete(resolving, name)
|
|
|
|
result := &targetSpec{}
|
|
for _, parent := range spec.Inherits {
|
|
resolved, err := resolveSpec(parent, raw, cache, resolving)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("resolving %s parent %s: %w", name, parent, err)
|
|
}
|
|
result.override(resolved)
|
|
}
|
|
result.override(spec)
|
|
|
|
cache[name] = result
|
|
return result, nil
|
|
}
|
|
|
|
// LoadTargets reads all target JSONs from dir, resolves inheritance,
|
|
// and returns the resolved targets. If includeBase is false, targets
|
|
// without flash/emulator configuration are excluded (base/parent targets).
|
|
func LoadTargets(dir string, includeBase bool) ([]Target, error) {
|
|
raw, err := loadRawTargets(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cache := make(map[string]*targetSpec)
|
|
resolving := make(map[string]bool)
|
|
|
|
var targets []Target
|
|
for name := range raw {
|
|
resolved, err := resolveSpec(name, raw, cache, resolving)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !includeBase && resolved.FlashMethod == "" && resolved.FlashCommand == "" && resolved.Emulator == "" {
|
|
continue
|
|
}
|
|
targets = append(targets, Target{
|
|
Name: name,
|
|
BuildTags: resolved.BuildTags,
|
|
GOOS: resolved.GOOS,
|
|
GOARCH: resolved.GOARCH,
|
|
GC: resolved.GC,
|
|
Scheduler: resolved.Scheduler,
|
|
Serial: resolved.Serial,
|
|
})
|
|
}
|
|
|
|
sort.Slice(targets, func(i, j int) bool { return targets[i].Name < targets[j].Name })
|
|
return targets, nil
|
|
}
|