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:
commit
650fd4d8c8
3 changed files with 74 additions and 64 deletions
|
|
@ -6,25 +6,26 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
io_interface "github.com/host-uk/core/pkg/io"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
coreio "github.com/host-uk/core/pkg/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Checksum computes SHA256 for an artifact and returns the artifact with the Checksum field filled.
|
// 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 == "" {
|
if artifact.Path == "" {
|
||||||
return Artifact{}, fmt.Errorf("build.Checksum: artifact path is empty")
|
return Artifact{}, fmt.Errorf("build.Checksum: artifact path is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the file
|
// Open the file
|
||||||
file, err := fs.Open(artifact.Path)
|
file, err := os.Open(artifact.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Artifact{}, fmt.Errorf("build.Checksum: failed to open file: %w", err)
|
return Artifact{}, fmt.Errorf("build.Checksum: failed to open file: %w", err)
|
||||||
}
|
}
|
||||||
defer func() { _ = file.Close() }()
|
defer file.Close()
|
||||||
|
|
||||||
// Compute SHA256 hash
|
// Compute SHA256 hash
|
||||||
hasher := sha256.New()
|
hasher := sha256.New()
|
||||||
|
|
@ -44,14 +45,14 @@ func Checksum(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
|
||||||
|
|
||||||
// ChecksumAll computes checksums for all artifacts.
|
// ChecksumAll computes checksums for all artifacts.
|
||||||
// Returns a slice of artifacts with their Checksum fields filled.
|
// 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 {
|
if len(artifacts) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var checksummed []Artifact
|
var checksummed []Artifact
|
||||||
for _, artifact := range artifacts {
|
for _, artifact := range artifacts {
|
||||||
cs, err := Checksum(fs, artifact)
|
cs, err := Checksum(artifact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return checksummed, fmt.Errorf("build.ChecksumAll: failed to checksum %s: %w", artifact.Path, err)
|
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).
|
// The artifacts should have their Checksum fields filled (call ChecksumAll first).
|
||||||
// Filenames are relative to the output directory (just the basename).
|
// 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 {
|
if len(artifacts) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -88,8 +89,14 @@ func WriteChecksumFile(fs io_interface.Medium, artifacts []Artifact, path string
|
||||||
|
|
||||||
content := strings.Join(lines, "\n") + "\n"
|
content := strings.Join(lines, "\n") + "\n"
|
||||||
|
|
||||||
// Write the file using the medium (which handles directory creation in Write)
|
// Ensure directory exists
|
||||||
if err := fs.Write(path, content); err != nil {
|
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)
|
return fmt.Errorf("build.WriteChecksumFile: failed to write file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/build/signing"
|
"github.com/host-uk/core/pkg/build/signing"
|
||||||
"github.com/host-uk/core/pkg/config"
|
|
||||||
"github.com/host-uk/core/pkg/io"
|
"github.com/host-uk/core/pkg/io"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigFileName is the name of the build configuration file.
|
// 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.
|
// This is distinct from Config which holds runtime build parameters.
|
||||||
type BuildConfig struct {
|
type BuildConfig struct {
|
||||||
// Version is the config file format version.
|
// Version is the config file format version.
|
||||||
Version int `yaml:"version" mapstructure:"version"`
|
Version int `yaml:"version"`
|
||||||
// Project contains project metadata.
|
// Project contains project metadata.
|
||||||
Project Project `yaml:"project" mapstructure:"project"`
|
Project Project `yaml:"project"`
|
||||||
// Build contains build settings.
|
// Build contains build settings.
|
||||||
Build Build `yaml:"build" mapstructure:"build"`
|
Build Build `yaml:"build"`
|
||||||
// Targets defines the build targets.
|
// Targets defines the build targets.
|
||||||
Targets []TargetConfig `yaml:"targets" mapstructure:"targets"`
|
Targets []TargetConfig `yaml:"targets"`
|
||||||
// Sign contains code signing configuration.
|
// Sign contains code signing configuration.
|
||||||
Sign signing.SignConfig `yaml:"sign,omitempty" mapstructure:"sign,omitempty"`
|
Sign signing.SignConfig `yaml:"sign,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Project holds project metadata.
|
// Project holds project metadata.
|
||||||
type Project struct {
|
type Project struct {
|
||||||
// Name is the project name.
|
// Name is the project name.
|
||||||
Name string `yaml:"name" mapstructure:"name"`
|
Name string `yaml:"name"`
|
||||||
// Description is a brief description of the project.
|
// 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 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 is the output binary name.
|
||||||
Binary string `yaml:"binary" mapstructure:"binary"`
|
Binary string `yaml:"binary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build holds build-time settings.
|
// Build holds build-time settings.
|
||||||
type Build struct {
|
type Build struct {
|
||||||
// CGO enables CGO for the build.
|
// 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 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 are linker flags (e.g., ["-s", "-w"]).
|
||||||
LDFlags []string `yaml:"ldflags" mapstructure:"ldflags"`
|
LDFlags []string `yaml:"ldflags"`
|
||||||
// Env are additional environment variables.
|
// Env are additional environment variables.
|
||||||
Env []string `yaml:"env" mapstructure:"env"`
|
Env []string `yaml:"env"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TargetConfig defines a build target in the config file.
|
// TargetConfig defines a build target in the config file.
|
||||||
// This is separate from Target to allow for additional config-specific fields.
|
// This is separate from Target to allow for additional config-specific fields.
|
||||||
type TargetConfig struct {
|
type TargetConfig struct {
|
||||||
// OS is the target operating system (e.g., "linux", "darwin", "windows").
|
// 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 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.
|
// LoadConfig loads build configuration from the .core/build.yaml file in the given directory.
|
||||||
// If the config file does not exist, it returns DefaultConfig().
|
// If the config file does not exist, it returns DefaultConfig().
|
||||||
// Returns an error if the file exists but cannot be parsed.
|
// 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)
|
configPath := filepath.Join(dir, ConfigDir, ConfigFileName)
|
||||||
|
|
||||||
if !fs.Exists(configPath) {
|
// Convert to absolute path for io.Local
|
||||||
return DefaultConfig(), nil
|
absPath, err := filepath.Abs(configPath)
|
||||||
}
|
|
||||||
|
|
||||||
// Use centralized config service
|
|
||||||
c, err := config.New(config.WithMedium(fs), config.WithPath(configPath))
|
|
||||||
if err != nil {
|
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()
|
content, err := io.Local.Read(absPath)
|
||||||
if err := c.Get("", cfg); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("build.LoadConfig: %w", err)
|
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)
|
var cfg BuildConfig
|
||||||
applyDefaults(cfg)
|
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.
|
// DefaultConfig returns sensible defaults for Go projects.
|
||||||
|
|
@ -110,6 +115,7 @@ func DefaultConfig() *BuildConfig {
|
||||||
Targets: []TargetConfig{
|
Targets: []TargetConfig{
|
||||||
{OS: "linux", Arch: "amd64"},
|
{OS: "linux", Arch: "amd64"},
|
||||||
{OS: "linux", Arch: "arm64"},
|
{OS: "linux", Arch: "arm64"},
|
||||||
|
{OS: "darwin", Arch: "amd64"},
|
||||||
{OS: "darwin", Arch: "arm64"},
|
{OS: "darwin", Arch: "arm64"},
|
||||||
{OS: "windows", Arch: "amd64"},
|
{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.
|
// ConfigExists checks if a build config file exists in the given directory.
|
||||||
func ConfigExists(fs io.Medium, dir string) bool {
|
func ConfigExists(dir string) bool {
|
||||||
return fs.IsFile(ConfigPath(dir))
|
return fileExists(ConfigPath(dir))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToTargets converts TargetConfig slice to Target slice for use with builders.
|
// ToTargets converts TargetConfig slice to Target slice for use with builders.
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ const (
|
||||||
markerWails = "wails.json"
|
markerWails = "wails.json"
|
||||||
markerNodePackage = "package.json"
|
markerNodePackage = "package.json"
|
||||||
markerComposer = "composer.json"
|
markerComposer = "composer.json"
|
||||||
markerCMake = "CMakeLists.txt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// projectMarker maps a marker file to its project type.
|
// projectMarker maps a marker file to its project type.
|
||||||
|
|
@ -29,18 +28,17 @@ var markers = []projectMarker{
|
||||||
{markerGoMod, ProjectTypeGo},
|
{markerGoMod, ProjectTypeGo},
|
||||||
{markerNodePackage, ProjectTypeNode},
|
{markerNodePackage, ProjectTypeNode},
|
||||||
{markerComposer, ProjectTypePHP},
|
{markerComposer, ProjectTypePHP},
|
||||||
{markerCMake, ProjectTypeCPP},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discover detects project types in the given directory by checking for marker files.
|
// 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).
|
// 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.
|
// 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
|
var detected []ProjectType
|
||||||
|
|
||||||
for _, m := range markers {
|
for _, m := range markers {
|
||||||
path := filepath.Join(dir, m.file)
|
path := filepath.Join(dir, m.file)
|
||||||
if fileExists(fs, path) {
|
if fileExists(path) {
|
||||||
// Avoid duplicates (shouldn't happen with current markers, but defensive)
|
// Avoid duplicates (shouldn't happen with current markers, but defensive)
|
||||||
if !slices.Contains(detected, m.projectType) {
|
if !slices.Contains(detected, m.projectType) {
|
||||||
detected = append(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.
|
// PrimaryType returns the most specific project type detected in the directory.
|
||||||
// Returns empty string if no project type is detected.
|
// Returns empty string if no project type is detected.
|
||||||
func PrimaryType(fs io.Medium, dir string) (ProjectType, error) {
|
func PrimaryType(dir string) (ProjectType, error) {
|
||||||
types, err := Discover(fs, dir)
|
types, err := Discover(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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).
|
// IsGoProject checks if the directory contains a Go project (go.mod or wails.json).
|
||||||
func IsGoProject(fs io.Medium, dir string) bool {
|
func IsGoProject(dir string) bool {
|
||||||
return fileExists(fs, filepath.Join(dir, markerGoMod)) ||
|
return fileExists(filepath.Join(dir, markerGoMod)) ||
|
||||||
fileExists(fs, filepath.Join(dir, markerWails))
|
fileExists(filepath.Join(dir, markerWails))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWailsProject checks if the directory contains a Wails project.
|
// IsWailsProject checks if the directory contains a Wails project.
|
||||||
func IsWailsProject(fs io.Medium, dir string) bool {
|
func IsWailsProject(dir string) bool {
|
||||||
return fileExists(fs, filepath.Join(dir, markerWails))
|
return fileExists(filepath.Join(dir, markerWails))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNodeProject checks if the directory contains a Node.js project.
|
// IsNodeProject checks if the directory contains a Node.js project.
|
||||||
func IsNodeProject(fs io.Medium, dir string) bool {
|
func IsNodeProject(dir string) bool {
|
||||||
return fileExists(fs, filepath.Join(dir, markerNodePackage))
|
return fileExists(filepath.Join(dir, markerNodePackage))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPHPProject checks if the directory contains a PHP project.
|
// IsPHPProject checks if the directory contains a PHP project.
|
||||||
func IsPHPProject(fs io.Medium, dir string) bool {
|
func IsPHPProject(dir string) bool {
|
||||||
return fileExists(fs, filepath.Join(dir, markerComposer))
|
return fileExists(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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileExists checks if a file exists and is not a directory.
|
// fileExists checks if a file exists and is not a directory.
|
||||||
func fileExists(fs io.Medium, path string) bool {
|
func fileExists(path string) bool {
|
||||||
return fs.IsFile(path)
|
absPath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return io.Local.IsFile(absPath)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue