cli/pkg/build/config.go
Snider 7c25034261 chore(io): Migrate pkg/build to Medium abstraction
- Updated io.Medium interface with Open() and Create() methods to support streaming.
- Migrated pkg/build, pkg/build/builders, and pkg/build/signing to use io.Medium.
- Added FS field to build.Config and updated build.Builder interface.
- Refactored checksum and archive logic to use io.Medium streaming.
- Updated pkg/release and pkg/build/buildcmd to use io.Local.
- Updated unit tests to match new signatures.
2026-02-04 14:23:46 +00:00

169 lines
4.7 KiB
Go

// Package build provides project type detection and cross-compilation for the Core build system.
// This file handles configuration loading from .core/build.yaml files.
package build
import (
"fmt"
"os"
"path/filepath"
"github.com/host-uk/core/pkg/build/signing"
"github.com/host-uk/core/pkg/io"
"gopkg.in/yaml.v3"
)
// ConfigFileName is the name of the build configuration file.
const ConfigFileName = "build.yaml"
// ConfigDir is the directory where build configuration is stored.
const ConfigDir = ".core"
// BuildConfig holds the complete build configuration loaded from .core/build.yaml.
// This is distinct from Config which holds runtime build parameters.
type BuildConfig struct {
// Version is the config file format version.
Version int `yaml:"version"`
// Project contains project metadata.
Project Project `yaml:"project"`
// Build contains build settings.
Build Build `yaml:"build"`
// Targets defines the build targets.
Targets []TargetConfig `yaml:"targets"`
// Sign contains code signing configuration.
Sign signing.SignConfig `yaml:"sign,omitempty"`
}
// Project holds project metadata.
type Project struct {
// Name is the project name.
Name string `yaml:"name"`
// Description is a brief description of the project.
Description string `yaml:"description"`
// Main is the path to the main package (e.g., ./cmd/core).
Main string `yaml:"main"`
// Binary is the output binary name.
Binary string `yaml:"binary"`
}
// Build holds build-time settings.
type Build struct {
// CGO enables CGO for the build.
CGO bool `yaml:"cgo"`
// Flags are additional build flags (e.g., ["-trimpath"]).
Flags []string `yaml:"flags"`
// LDFlags are linker flags (e.g., ["-s", "-w"]).
LDFlags []string `yaml:"ldflags"`
// Env are additional environment variables.
Env []string `yaml:"env"`
}
// TargetConfig defines a build target in the config file.
// This is separate from Target to allow for additional config-specific fields.
type TargetConfig struct {
// OS is the target operating system (e.g., "linux", "darwin", "windows").
OS string `yaml:"os"`
// Arch is the target architecture (e.g., "amd64", "arm64").
Arch string `yaml:"arch"`
}
// LoadConfig loads build configuration from the .core/build.yaml file in the given directory.
// If the config file does not exist, it returns DefaultConfig().
// Returns an error if the file exists but cannot be parsed.
func LoadConfig(fs io.Medium, dir string) (*BuildConfig, error) {
configPath := filepath.Join(dir, ConfigDir, ConfigFileName)
content, err := fs.Read(configPath)
if err != nil {
if os.IsNotExist(err) {
return DefaultConfig(), nil
}
return nil, fmt.Errorf("build.LoadConfig: failed to read config file: %w", err)
}
var cfg BuildConfig
data := []byte(content)
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("build.LoadConfig: failed to parse config file: %w", err)
}
// Apply defaults for any missing fields
applyDefaults(&cfg)
return &cfg, nil
}
// DefaultConfig returns sensible defaults for Go projects.
func DefaultConfig() *BuildConfig {
return &BuildConfig{
Version: 1,
Project: Project{
Name: "",
Main: ".",
Binary: "",
},
Build: Build{
CGO: false,
Flags: []string{"-trimpath"},
LDFlags: []string{"-s", "-w"},
Env: []string{},
},
Targets: []TargetConfig{
{OS: "linux", Arch: "amd64"},
{OS: "linux", Arch: "arm64"},
{OS: "darwin", Arch: "arm64"},
{OS: "windows", Arch: "amd64"},
},
Sign: signing.DefaultSignConfig(),
}
}
// applyDefaults fills in default values for any empty fields in the config.
func applyDefaults(cfg *BuildConfig) {
defaults := DefaultConfig()
if cfg.Version == 0 {
cfg.Version = defaults.Version
}
if cfg.Project.Main == "" {
cfg.Project.Main = defaults.Project.Main
}
if cfg.Build.Flags == nil {
cfg.Build.Flags = defaults.Build.Flags
}
if cfg.Build.LDFlags == nil {
cfg.Build.LDFlags = defaults.Build.LDFlags
}
if cfg.Build.Env == nil {
cfg.Build.Env = defaults.Build.Env
}
if len(cfg.Targets) == 0 {
cfg.Targets = defaults.Targets
}
// Expand environment variables in sign config
cfg.Sign.ExpandEnv()
}
// ConfigPath returns the path to the build config file for a given directory.
func ConfigPath(dir string) string {
return filepath.Join(dir, ConfigDir, ConfigFileName)
}
// ConfigExists checks if a build config file exists in the given directory.
func ConfigExists(fs io.Medium, dir string) bool {
return fileExists(fs, ConfigPath(dir))
}
// ToTargets converts TargetConfig slice to Target slice for use with builders.
func (cfg *BuildConfig) ToTargets() []Target {
targets := make([]Target, len(cfg.Targets))
for i, t := range cfg.Targets {
targets[i] = Target(t)
}
return targets
}