Merge branch 'fix/io-migration-build' into new

# Conflicts:
#	pkg/build/checksum.go
#	pkg/build/config.go
#	pkg/build/discovery.go
This commit is contained in:
Snider 2026-02-08 21:28:17 +00:00
commit eeca300765
3 changed files with 74 additions and 64 deletions

View file

@ -6,25 +6,26 @@ import (
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
io_interface "github.com/host-uk/core/pkg/io"
"sort"
"strings"
coreio "github.com/host-uk/core/pkg/io"
)
// Checksum computes SHA256 for an artifact and returns the artifact with the Checksum field filled.
func Checksum(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
func Checksum(artifact Artifact) (Artifact, error) {
if artifact.Path == "" {
return Artifact{}, fmt.Errorf("build.Checksum: artifact path is empty")
}
// Open the file
file, err := fs.Open(artifact.Path)
file, err := os.Open(artifact.Path)
if err != nil {
return Artifact{}, fmt.Errorf("build.Checksum: failed to open file: %w", err)
}
defer func() { _ = file.Close() }()
defer file.Close()
// Compute SHA256 hash
hasher := sha256.New()
@ -44,14 +45,14 @@ func Checksum(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
// ChecksumAll computes checksums for all artifacts.
// Returns a slice of artifacts with their Checksum fields filled.
func ChecksumAll(fs io_interface.Medium, artifacts []Artifact) ([]Artifact, error) {
func ChecksumAll(artifacts []Artifact) ([]Artifact, error) {
if len(artifacts) == 0 {
return nil, nil
}
var checksummed []Artifact
for _, artifact := range artifacts {
cs, err := Checksum(fs, artifact)
cs, err := Checksum(artifact)
if err != nil {
return checksummed, fmt.Errorf("build.ChecksumAll: failed to checksum %s: %w", artifact.Path, err)
}
@ -68,7 +69,7 @@ func ChecksumAll(fs io_interface.Medium, artifacts []Artifact) ([]Artifact, erro
//
// The artifacts should have their Checksum fields filled (call ChecksumAll first).
// Filenames are relative to the output directory (just the basename).
func WriteChecksumFile(fs io_interface.Medium, artifacts []Artifact, path string) error {
func WriteChecksumFile(artifacts []Artifact, path string) error {
if len(artifacts) == 0 {
return nil
}
@ -88,8 +89,14 @@ func WriteChecksumFile(fs io_interface.Medium, artifacts []Artifact, path string
content := strings.Join(lines, "\n") + "\n"
// Write the file using the medium (which handles directory creation in Write)
if err := fs.Write(path, content); err != nil {
// Ensure directory exists
dir := filepath.Dir(path)
if err := coreio.Local.EnsureDir(dir); err != nil {
return fmt.Errorf("build.WriteChecksumFile: failed to create directory: %w", err)
}
// Write the file
if err := coreio.Local.Write(path, content); err != nil {
return fmt.Errorf("build.WriteChecksumFile: failed to write file: %w", err)
}

View file

@ -4,11 +4,12 @@ package build
import (
"fmt"
"os"
"path/filepath"
"github.com/host-uk/core/pkg/build/signing"
"github.com/host-uk/core/pkg/config"
"github.com/host-uk/core/pkg/io"
"gopkg.in/yaml.v3"
)
// ConfigFileName is the name of the build configuration file.
@ -21,75 +22,79 @@ const ConfigDir = ".core"
// This is distinct from Config which holds runtime build parameters.
type BuildConfig struct {
// Version is the config file format version.
Version int `yaml:"version" mapstructure:"version"`
Version int `yaml:"version"`
// Project contains project metadata.
Project Project `yaml:"project" mapstructure:"project"`
Project Project `yaml:"project"`
// Build contains build settings.
Build Build `yaml:"build" mapstructure:"build"`
Build Build `yaml:"build"`
// Targets defines the build targets.
Targets []TargetConfig `yaml:"targets" mapstructure:"targets"`
Targets []TargetConfig `yaml:"targets"`
// Sign contains code signing configuration.
Sign signing.SignConfig `yaml:"sign,omitempty" mapstructure:"sign,omitempty"`
Sign signing.SignConfig `yaml:"sign,omitempty"`
}
// Project holds project metadata.
type Project struct {
// Name is the project name.
Name string `yaml:"name" mapstructure:"name"`
Name string `yaml:"name"`
// Description is a brief description of the project.
Description string `yaml:"description" mapstructure:"description"`
Description string `yaml:"description"`
// Main is the path to the main package (e.g., ./cmd/core).
Main string `yaml:"main" mapstructure:"main"`
Main string `yaml:"main"`
// Binary is the output binary name.
Binary string `yaml:"binary" mapstructure:"binary"`
Binary string `yaml:"binary"`
}
// Build holds build-time settings.
type Build struct {
// CGO enables CGO for the build.
CGO bool `yaml:"cgo" mapstructure:"cgo"`
CGO bool `yaml:"cgo"`
// Flags are additional build flags (e.g., ["-trimpath"]).
Flags []string `yaml:"flags" mapstructure:"flags"`
Flags []string `yaml:"flags"`
// LDFlags are linker flags (e.g., ["-s", "-w"]).
LDFlags []string `yaml:"ldflags" mapstructure:"ldflags"`
LDFlags []string `yaml:"ldflags"`
// Env are additional environment variables.
Env []string `yaml:"env" mapstructure:"env"`
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" mapstructure:"os"`
OS string `yaml:"os"`
// Arch is the target architecture (e.g., "amd64", "arm64").
Arch string `yaml:"arch" mapstructure:"arch"`
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) {
func LoadConfig(dir string) (*BuildConfig, error) {
configPath := filepath.Join(dir, ConfigDir, ConfigFileName)
if !fs.Exists(configPath) {
return DefaultConfig(), nil
}
// Use centralized config service
c, err := config.New(config.WithMedium(fs), config.WithPath(configPath))
// Convert to absolute path for io.Local
absPath, err := filepath.Abs(configPath)
if err != nil {
return nil, fmt.Errorf("build.LoadConfig: %w", err)
return nil, fmt.Errorf("build.LoadConfig: failed to resolve path: %w", err)
}
cfg := DefaultConfig()
if err := c.Get("", cfg); err != nil {
return nil, fmt.Errorf("build.LoadConfig: %w", err)
content, err := io.Local.Read(absPath)
if err != nil {
if os.IsNotExist(err) {
return DefaultConfig(), nil
}
return nil, fmt.Errorf("build.LoadConfig: failed to read config file: %w", err)
}
// Apply defaults for any missing fields (centralized Get might not fill everything)
applyDefaults(cfg)
var cfg BuildConfig
if err := yaml.Unmarshal([]byte(content), &cfg); err != nil {
return nil, fmt.Errorf("build.LoadConfig: failed to parse config file: %w", err)
}
return cfg, nil
// Apply defaults for any missing fields
applyDefaults(&cfg)
return &cfg, nil
}
// DefaultConfig returns sensible defaults for Go projects.
@ -110,6 +115,7 @@ func DefaultConfig() *BuildConfig {
Targets: []TargetConfig{
{OS: "linux", Arch: "amd64"},
{OS: "linux", Arch: "arm64"},
{OS: "darwin", Arch: "amd64"},
{OS: "darwin", Arch: "arm64"},
{OS: "windows", Arch: "amd64"},
},
@ -155,8 +161,8 @@ func ConfigPath(dir string) string {
}
// ConfigExists checks if a build config file exists in the given directory.
func ConfigExists(fs io.Medium, dir string) bool {
return fs.IsFile(ConfigPath(dir))
func ConfigExists(dir string) bool {
return fileExists(ConfigPath(dir))
}
// ToTargets converts TargetConfig slice to Target slice for use with builders.

View file

@ -13,7 +13,6 @@ const (
markerWails = "wails.json"
markerNodePackage = "package.json"
markerComposer = "composer.json"
markerCMake = "CMakeLists.txt"
)
// projectMarker maps a marker file to its project type.
@ -29,18 +28,17 @@ var markers = []projectMarker{
{markerGoMod, ProjectTypeGo},
{markerNodePackage, ProjectTypeNode},
{markerComposer, ProjectTypePHP},
{markerCMake, ProjectTypeCPP},
}
// Discover detects project types in the given directory by checking for marker files.
// Returns a slice of detected project types, ordered by priority (most specific first).
// For example, a Wails project returns [wails, go] since it has both wails.json and go.mod.
func Discover(fs io.Medium, dir string) ([]ProjectType, error) {
func Discover(dir string) ([]ProjectType, error) {
var detected []ProjectType
for _, m := range markers {
path := filepath.Join(dir, m.file)
if fileExists(fs, path) {
if fileExists(path) {
// Avoid duplicates (shouldn't happen with current markers, but defensive)
if !slices.Contains(detected, m.projectType) {
detected = append(detected, m.projectType)
@ -53,8 +51,8 @@ func Discover(fs io.Medium, dir string) ([]ProjectType, error) {
// PrimaryType returns the most specific project type detected in the directory.
// Returns empty string if no project type is detected.
func PrimaryType(fs io.Medium, dir string) (ProjectType, error) {
types, err := Discover(fs, dir)
func PrimaryType(dir string) (ProjectType, error) {
types, err := Discover(dir)
if err != nil {
return "", err
}
@ -65,32 +63,31 @@ func PrimaryType(fs io.Medium, dir string) (ProjectType, error) {
}
// IsGoProject checks if the directory contains a Go project (go.mod or wails.json).
func IsGoProject(fs io.Medium, dir string) bool {
return fileExists(fs, filepath.Join(dir, markerGoMod)) ||
fileExists(fs, filepath.Join(dir, markerWails))
func IsGoProject(dir string) bool {
return fileExists(filepath.Join(dir, markerGoMod)) ||
fileExists(filepath.Join(dir, markerWails))
}
// IsWailsProject checks if the directory contains a Wails project.
func IsWailsProject(fs io.Medium, dir string) bool {
return fileExists(fs, filepath.Join(dir, markerWails))
func IsWailsProject(dir string) bool {
return fileExists(filepath.Join(dir, markerWails))
}
// IsNodeProject checks if the directory contains a Node.js project.
func IsNodeProject(fs io.Medium, dir string) bool {
return fileExists(fs, filepath.Join(dir, markerNodePackage))
func IsNodeProject(dir string) bool {
return fileExists(filepath.Join(dir, markerNodePackage))
}
// IsPHPProject checks if the directory contains a PHP project.
func IsPHPProject(fs io.Medium, dir string) bool {
return fileExists(fs, filepath.Join(dir, markerComposer))
}
// IsCPPProject checks if the directory contains a C++ project.
func IsCPPProject(fs io.Medium, dir string) bool {
return fileExists(fs, filepath.Join(dir, markerCMake))
func IsPHPProject(dir string) bool {
return fileExists(filepath.Join(dir, markerComposer))
}
// fileExists checks if a file exists and is not a directory.
func fileExists(fs io.Medium, path string) bool {
return fs.IsFile(path)
func fileExists(path string) bool {
absPath, err := filepath.Abs(path)
if err != nil {
return false
}
return io.Local.IsFile(absPath)
}