diff --git a/pkg/build/checksum.go b/pkg/build/checksum.go index 6610edff..d9aa240f 100644 --- a/pkg/build/checksum.go +++ b/pkg/build/checksum.go @@ -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) } diff --git a/pkg/build/config.go b/pkg/build/config.go index ea01b3b2..365dad49 100644 --- a/pkg/build/config.go +++ b/pkg/build/config.go @@ -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. diff --git a/pkg/build/discovery.go b/pkg/build/discovery.go index 209c2cf8..8e9748c8 100644 --- a/pkg/build/discovery.go +++ b/pkg/build/discovery.go @@ -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) }