Files
llgo/internal/targets/loader.go
Li Jie b80a54eb0f feat: implement target configuration system for issue #1176
Add comprehensive target configuration parsing and inheritance system:

- Create internal/targets package with config structures
- Support JSON configuration loading with inheritance resolution
- Implement multi-level inheritance (e.g., rp2040 → cortex-m0plus → cortex-m)
- Add 206 target configurations from TinyGo for embedded platforms
- Support core fields: name, llvm-target, cpu, features, build-tags, goos, goarch, cflags, ldflags
- Provide high-level resolver interface for target lookup
- Include comprehensive unit tests with 100% target parsing coverage

This foundation enables future -target parameter support for cross-compilation
to diverse embedded platforms beyond current GOOS/GOARCH limitations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-29 15:12:58 +08:00

177 lines
4.0 KiB
Go

package targets
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
// Loader handles loading and parsing target configurations
type Loader struct {
targetsDir string
cache map[string]*RawConfig
}
// NewLoader creates a new target configuration loader
func NewLoader(targetsDir string) *Loader {
return &Loader{
targetsDir: targetsDir,
cache: make(map[string]*RawConfig),
}
}
// LoadRaw loads a raw configuration without resolving inheritance
func (l *Loader) LoadRaw(name string) (*RawConfig, error) {
// Check cache first
if config, exists := l.cache[name]; exists {
return config, nil
}
// Construct file path
configPath := filepath.Join(l.targetsDir, name+".json")
// Read file
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read target config %s: %w", name, err)
}
// Parse JSON
var config RawConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("failed to parse target config %s: %w", name, err)
}
// Set the name
config.Name = name
// Cache the result
l.cache[name] = &config
return &config, nil
}
// Load loads a target configuration with inheritance resolved
func (l *Loader) Load(name string) (*Config, error) {
raw, err := l.LoadRaw(name)
if err != nil {
return nil, err
}
return l.resolveInheritance(raw)
}
// LoadAll loads all target configurations in the targets directory
func (l *Loader) LoadAll() (map[string]*Config, error) {
entries, err := os.ReadDir(l.targetsDir)
if err != nil {
return nil, fmt.Errorf("failed to read targets directory: %w", err)
}
configs := make(map[string]*Config)
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
continue
}
name := strings.TrimSuffix(entry.Name(), ".json")
config, err := l.Load(name)
if err != nil {
return nil, fmt.Errorf("failed to load target %s: %w", name, err)
}
configs[name] = config
}
return configs, nil
}
// resolveInheritance resolves inheritance chain for a configuration
func (l *Loader) resolveInheritance(raw *RawConfig) (*Config, error) {
if !raw.HasInheritance() {
// No inheritance, return as-is
return &raw.Config, nil
}
// Start with base config
result := &Config{Name: raw.Name}
// Apply inheritance in order
for _, parentName := range raw.GetInherits() {
parent, err := l.Load(parentName)
if err != nil {
return nil, fmt.Errorf("failed to load parent config %s: %w", parentName, err)
}
// Merge parent into result
l.mergeConfig(result, parent)
}
// Finally, apply current config on top
l.mergeConfig(result, &raw.Config)
return result, nil
}
// mergeConfig merges source config into destination config
// Non-empty values in source override those in destination
func (l *Loader) mergeConfig(dst, src *Config) {
if src.LLVMTarget != "" {
dst.LLVMTarget = src.LLVMTarget
}
if src.CPU != "" {
dst.CPU = src.CPU
}
if src.Features != "" {
dst.Features = src.Features
}
if src.GOOS != "" {
dst.GOOS = src.GOOS
}
if src.GOARCH != "" {
dst.GOARCH = src.GOARCH
}
if src.Linker != "" {
dst.Linker = src.Linker
}
// Merge slices (append, don't replace)
if len(src.BuildTags) > 0 {
dst.BuildTags = append(dst.BuildTags, src.BuildTags...)
}
if len(src.CFlags) > 0 {
dst.CFlags = append(dst.CFlags, src.CFlags...)
}
if len(src.LDFlags) > 0 {
dst.LDFlags = append(dst.LDFlags, src.LDFlags...)
}
}
// GetTargetsDir returns the targets directory path
func (l *Loader) GetTargetsDir() string {
return l.targetsDir
}
// ListTargets returns a list of all available target names
func (l *Loader) ListTargets() ([]string, error) {
entries, err := os.ReadDir(l.targetsDir)
if err != nil {
return nil, fmt.Errorf("failed to read targets directory: %w", err)
}
var targets []string
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
continue
}
name := strings.TrimSuffix(entry.Name(), ".json")
targets = append(targets, name)
}
return targets, nil
}