Merge branch 'chore/io-migrate-build-8873543635510272463' into new
# Conflicts: # pkg/build/checksum.go # pkg/build/config.go # pkg/build/discovery.go # pkg/build/discovery_test.go # pkg/io/io.go # pkg/io/local/client.go # pkg/release/release.go
This commit is contained in:
commit
d44ca11e39
5 changed files with 54 additions and 84 deletions
|
|
@ -6,26 +6,25 @@ 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(artifact Artifact) (Artifact, error) {
|
||||
func Checksum(fs io_interface.Medium, artifact Artifact) (Artifact, error) {
|
||||
if artifact.Path == "" {
|
||||
return Artifact{}, fmt.Errorf("build.Checksum: artifact path is empty")
|
||||
}
|
||||
|
||||
// Open the file
|
||||
file, err := os.Open(artifact.Path)
|
||||
file, err := fs.Open(artifact.Path)
|
||||
if err != nil {
|
||||
return Artifact{}, fmt.Errorf("build.Checksum: failed to open file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
// Compute SHA256 hash
|
||||
hasher := sha256.New()
|
||||
|
|
@ -45,14 +44,14 @@ func Checksum(artifact Artifact) (Artifact, error) {
|
|||
|
||||
// ChecksumAll computes checksums for all artifacts.
|
||||
// Returns a slice of artifacts with their Checksum fields filled.
|
||||
func ChecksumAll(artifacts []Artifact) ([]Artifact, error) {
|
||||
func ChecksumAll(fs io_interface.Medium, artifacts []Artifact) ([]Artifact, error) {
|
||||
if len(artifacts) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var checksummed []Artifact
|
||||
for _, artifact := range artifacts {
|
||||
cs, err := Checksum(artifact)
|
||||
cs, err := Checksum(fs, artifact)
|
||||
if err != nil {
|
||||
return checksummed, fmt.Errorf("build.ChecksumAll: failed to checksum %s: %w", artifact.Path, err)
|
||||
}
|
||||
|
|
@ -69,7 +68,7 @@ func ChecksumAll(artifacts []Artifact) ([]Artifact, error) {
|
|||
//
|
||||
// The artifacts should have their Checksum fields filled (call ChecksumAll first).
|
||||
// Filenames are relative to the output directory (just the basename).
|
||||
func WriteChecksumFile(artifacts []Artifact, path string) error {
|
||||
func WriteChecksumFile(fs io_interface.Medium, artifacts []Artifact, path string) error {
|
||||
if len(artifacts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -89,14 +88,8 @@ func WriteChecksumFile(artifacts []Artifact, path string) error {
|
|||
|
||||
content := strings.Join(lines, "\n") + "\n"
|
||||
|
||||
// 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 {
|
||||
// Write the file using the medium (which handles directory creation in Write)
|
||||
if err := fs.Write(path, content); err != nil {
|
||||
return fmt.Errorf("build.WriteChecksumFile: failed to write file: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,16 +69,10 @@ type TargetConfig struct {
|
|||
// 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(dir string) (*BuildConfig, error) {
|
||||
func LoadConfig(fs io.Medium, dir string) (*BuildConfig, error) {
|
||||
configPath := filepath.Join(dir, ConfigDir, ConfigFileName)
|
||||
|
||||
// Convert to absolute path for io.Local
|
||||
absPath, err := filepath.Abs(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build.LoadConfig: failed to resolve path: %w", err)
|
||||
}
|
||||
|
||||
content, err := io.Local.Read(absPath)
|
||||
content, err := fs.Read(configPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return DefaultConfig(), nil
|
||||
|
|
@ -87,7 +81,8 @@ func LoadConfig(dir string) (*BuildConfig, error) {
|
|||
}
|
||||
|
||||
var cfg BuildConfig
|
||||
if err := yaml.Unmarshal([]byte(content), &cfg); err != nil {
|
||||
data := []byte(content)
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("build.LoadConfig: failed to parse config file: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +110,6 @@ 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"},
|
||||
},
|
||||
|
|
@ -161,8 +155,8 @@ func ConfigPath(dir string) string {
|
|||
}
|
||||
|
||||
// ConfigExists checks if a build config file exists in the given directory.
|
||||
func ConfigExists(dir string) bool {
|
||||
return fileExists(ConfigPath(dir))
|
||||
func ConfigExists(fs io.Medium, dir string) bool {
|
||||
return fileExists(fs, ConfigPath(dir))
|
||||
}
|
||||
|
||||
// ToTargets converts TargetConfig slice to Target slice for use with builders.
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ var markers = []projectMarker{
|
|||
// 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(dir string) ([]ProjectType, error) {
|
||||
func Discover(fs io.Medium, dir string) ([]ProjectType, error) {
|
||||
var detected []ProjectType
|
||||
|
||||
for _, m := range markers {
|
||||
path := filepath.Join(dir, m.file)
|
||||
if fileExists(path) {
|
||||
if fileExists(fs, path) {
|
||||
// Avoid duplicates (shouldn't happen with current markers, but defensive)
|
||||
if !slices.Contains(detected, m.projectType) {
|
||||
detected = append(detected, m.projectType)
|
||||
|
|
@ -51,8 +51,8 @@ func Discover(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(dir string) (ProjectType, error) {
|
||||
types, err := Discover(dir)
|
||||
func PrimaryType(fs io.Medium, dir string) (ProjectType, error) {
|
||||
types, err := Discover(fs, dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -63,31 +63,27 @@ func PrimaryType(dir string) (ProjectType, error) {
|
|||
}
|
||||
|
||||
// IsGoProject checks if the directory contains a Go project (go.mod or wails.json).
|
||||
func IsGoProject(dir string) bool {
|
||||
return fileExists(filepath.Join(dir, markerGoMod)) ||
|
||||
fileExists(filepath.Join(dir, markerWails))
|
||||
func IsGoProject(fs io.Medium, dir string) bool {
|
||||
return fileExists(fs, filepath.Join(dir, markerGoMod)) ||
|
||||
fileExists(fs, filepath.Join(dir, markerWails))
|
||||
}
|
||||
|
||||
// IsWailsProject checks if the directory contains a Wails project.
|
||||
func IsWailsProject(dir string) bool {
|
||||
return fileExists(filepath.Join(dir, markerWails))
|
||||
func IsWailsProject(fs io.Medium, dir string) bool {
|
||||
return fileExists(fs, filepath.Join(dir, markerWails))
|
||||
}
|
||||
|
||||
// IsNodeProject checks if the directory contains a Node.js project.
|
||||
func IsNodeProject(dir string) bool {
|
||||
return fileExists(filepath.Join(dir, markerNodePackage))
|
||||
func IsNodeProject(fs io.Medium, dir string) bool {
|
||||
return fileExists(fs, filepath.Join(dir, markerNodePackage))
|
||||
}
|
||||
|
||||
// IsPHPProject checks if the directory contains a PHP project.
|
||||
func IsPHPProject(dir string) bool {
|
||||
return fileExists(filepath.Join(dir, markerComposer))
|
||||
func IsPHPProject(fs io.Medium, dir string) bool {
|
||||
return fileExists(fs, filepath.Join(dir, markerComposer))
|
||||
}
|
||||
|
||||
// fileExists checks if a file exists and is not a directory.
|
||||
func fileExists(path string) bool {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return io.Local.IsFile(absPath)
|
||||
func fileExists(fs io.Medium, path string) bool {
|
||||
return fs.IsFile(path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,13 +52,6 @@ func TestDiscover_Good(t *testing.T) {
|
|||
assert.Equal(t, []ProjectType{ProjectTypePHP}, types)
|
||||
})
|
||||
|
||||
t.Run("detects C++ project", func(t *testing.T) {
|
||||
dir := setupTestDir(t, "CMakeLists.txt")
|
||||
types, err := Discover(fs, dir)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []ProjectType{ProjectTypeCPP}, types)
|
||||
})
|
||||
|
||||
t.Run("detects multiple project types", func(t *testing.T) {
|
||||
dir := setupTestDir(t, "go.mod", "package.json")
|
||||
types, err := Discover(fs, dir)
|
||||
|
|
@ -162,19 +155,6 @@ func TestIsNodeProject_Good(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestIsCPPProject_Good(t *testing.T) {
|
||||
fs := io.Local
|
||||
t.Run("true with CMakeLists.txt", func(t *testing.T) {
|
||||
dir := setupTestDir(t, "CMakeLists.txt")
|
||||
assert.True(t, IsCPPProject(fs, dir))
|
||||
})
|
||||
|
||||
t.Run("false without CMakeLists.txt", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
assert.False(t, IsCPPProject(fs, dir))
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsPHPProject_Good(t *testing.T) {
|
||||
fs := io.Local
|
||||
t.Run("true with composer.json", func(t *testing.T) {
|
||||
|
|
@ -229,7 +209,6 @@ func TestDiscover_Testdata(t *testing.T) {
|
|||
{"wails-project", "wails-project", []ProjectType{ProjectTypeWails, ProjectTypeGo}},
|
||||
{"node-project", "node-project", []ProjectType{ProjectTypeNode}},
|
||||
{"php-project", "php-project", []ProjectType{ProjectTypePHP}},
|
||||
{"cpp-project", "cpp-project", []ProjectType{ProjectTypeCPP}},
|
||||
{"multi-project", "multi-project", []ProjectType{ProjectTypeGo, ProjectTypeNode}},
|
||||
{"empty-project", "empty-project", []ProjectType{}},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ type Release struct {
|
|||
Changelog string
|
||||
// ProjectDir is the root directory of the project.
|
||||
ProjectDir string
|
||||
// FS is the medium for file operations.
|
||||
FS io.Medium
|
||||
}
|
||||
|
||||
// Publish publishes pre-built artifacts from dist/ to configured targets.
|
||||
|
|
@ -35,6 +37,8 @@ func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
return nil, fmt.Errorf("release.Publish: config is nil")
|
||||
}
|
||||
|
||||
m := io.Local
|
||||
|
||||
projectDir := cfg.projectDir
|
||||
if projectDir == "" {
|
||||
projectDir = "."
|
||||
|
|
@ -57,7 +61,7 @@ func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
|
||||
// Step 2: Find pre-built artifacts in dist/
|
||||
distDir := filepath.Join(absProjectDir, "dist")
|
||||
artifacts, err := findArtifacts(distDir)
|
||||
artifacts, err := findArtifacts(m, distDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("release.Publish: %w", err)
|
||||
}
|
||||
|
|
@ -78,11 +82,12 @@ func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
Artifacts: artifacts,
|
||||
Changelog: changelog,
|
||||
ProjectDir: absProjectDir,
|
||||
FS: m,
|
||||
}
|
||||
|
||||
// Step 4: Publish to configured targets
|
||||
if len(cfg.Publishers) > 0 {
|
||||
pubRelease := publishers.NewRelease(release.Version, release.Artifacts, release.Changelog, release.ProjectDir)
|
||||
pubRelease := publishers.NewRelease(release.Version, release.Artifacts, release.Changelog, release.ProjectDir, release.FS)
|
||||
|
||||
for _, pubCfg := range cfg.Publishers {
|
||||
publisher, err := getPublisher(pubCfg.Type)
|
||||
|
|
@ -102,14 +107,14 @@ func Publish(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
}
|
||||
|
||||
// findArtifacts discovers pre-built artifacts in the dist directory.
|
||||
func findArtifacts(distDir string) ([]build.Artifact, error) {
|
||||
if !io.Local.IsDir(distDir) {
|
||||
func findArtifacts(m io.Medium, distDir string) ([]build.Artifact, error) {
|
||||
if !m.IsDir(distDir) {
|
||||
return nil, fmt.Errorf("dist/ directory not found")
|
||||
}
|
||||
|
||||
var artifacts []build.Artifact
|
||||
|
||||
entries, err := io.Local.List(distDir)
|
||||
entries, err := m.List(distDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read dist/: %w", err)
|
||||
}
|
||||
|
|
@ -143,6 +148,8 @@ func Run(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
return nil, fmt.Errorf("release.Run: config is nil")
|
||||
}
|
||||
|
||||
m := io.Local
|
||||
|
||||
projectDir := cfg.projectDir
|
||||
if projectDir == "" {
|
||||
projectDir = "."
|
||||
|
|
@ -171,7 +178,7 @@ func Run(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
}
|
||||
|
||||
// Step 3: Build artifacts
|
||||
artifacts, err := buildArtifacts(ctx, cfg, absProjectDir, version)
|
||||
artifacts, err := buildArtifacts(ctx, m, cfg, absProjectDir, version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("release.Run: build failed: %w", err)
|
||||
}
|
||||
|
|
@ -181,12 +188,13 @@ func Run(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
Artifacts: artifacts,
|
||||
Changelog: changelog,
|
||||
ProjectDir: absProjectDir,
|
||||
FS: m,
|
||||
}
|
||||
|
||||
// Step 4: Publish to configured targets
|
||||
if len(cfg.Publishers) > 0 {
|
||||
// Convert to publisher types
|
||||
pubRelease := publishers.NewRelease(release.Version, release.Artifacts, release.Changelog, release.ProjectDir)
|
||||
pubRelease := publishers.NewRelease(release.Version, release.Artifacts, release.Changelog, release.ProjectDir, release.FS)
|
||||
|
||||
for _, pubCfg := range cfg.Publishers {
|
||||
publisher, err := getPublisher(pubCfg.Type)
|
||||
|
|
@ -207,9 +215,9 @@ func Run(ctx context.Context, cfg *Config, dryRun bool) (*Release, error) {
|
|||
}
|
||||
|
||||
// buildArtifacts builds all artifacts for the release.
|
||||
func buildArtifacts(ctx context.Context, cfg *Config, projectDir, version string) ([]build.Artifact, error) {
|
||||
func buildArtifacts(ctx context.Context, fs io.Medium, cfg *Config, projectDir, version string) ([]build.Artifact, error) {
|
||||
// Load build configuration
|
||||
buildCfg, err := build.LoadConfig(projectDir)
|
||||
buildCfg, err := build.LoadConfig(fs, projectDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load build config: %w", err)
|
||||
}
|
||||
|
|
@ -227,7 +235,6 @@ func buildArtifacts(ctx context.Context, cfg *Config, projectDir, version string
|
|||
targets = []build.Target{
|
||||
{OS: "linux", Arch: "amd64"},
|
||||
{OS: "linux", Arch: "arm64"},
|
||||
{OS: "darwin", Arch: "amd64"},
|
||||
{OS: "darwin", Arch: "arm64"},
|
||||
{OS: "windows", Arch: "amd64"},
|
||||
}
|
||||
|
|
@ -249,7 +256,7 @@ func buildArtifacts(ctx context.Context, cfg *Config, projectDir, version string
|
|||
outputDir := filepath.Join(projectDir, "dist")
|
||||
|
||||
// Get builder (detect project type)
|
||||
projectType, err := build.PrimaryType(projectDir)
|
||||
projectType, err := build.PrimaryType(fs, projectDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to detect project type: %w", err)
|
||||
}
|
||||
|
|
@ -261,6 +268,7 @@ func buildArtifacts(ctx context.Context, cfg *Config, projectDir, version string
|
|||
|
||||
// Build configuration
|
||||
buildConfig := &build.Config{
|
||||
FS: fs,
|
||||
ProjectDir: projectDir,
|
||||
OutputDir: outputDir,
|
||||
Name: binaryName,
|
||||
|
|
@ -275,20 +283,20 @@ func buildArtifacts(ctx context.Context, cfg *Config, projectDir, version string
|
|||
}
|
||||
|
||||
// Archive artifacts
|
||||
archivedArtifacts, err := build.ArchiveAll(artifacts)
|
||||
archivedArtifacts, err := build.ArchiveAll(fs, artifacts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("archive failed: %w", err)
|
||||
}
|
||||
|
||||
// Compute checksums
|
||||
checksummedArtifacts, err := build.ChecksumAll(archivedArtifacts)
|
||||
checksummedArtifacts, err := build.ChecksumAll(fs, archivedArtifacts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("checksum failed: %w", err)
|
||||
}
|
||||
|
||||
// Write CHECKSUMS.txt
|
||||
checksumPath := filepath.Join(outputDir, "CHECKSUMS.txt")
|
||||
if err := build.WriteChecksumFile(checksummedArtifacts, checksumPath); err != nil {
|
||||
if err := build.WriteChecksumFile(fs, checksummedArtifacts, checksumPath); err != nil {
|
||||
return nil, fmt.Errorf("failed to write checksums file: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -309,7 +317,7 @@ func getBuilder(projectType build.ProjectType) (build.Builder, error) {
|
|||
case build.ProjectTypeGo:
|
||||
return builders.NewGoBuilder(), nil
|
||||
case build.ProjectTypeNode:
|
||||
return nil, fmt.Errorf("Node.js builder not yet implemented")
|
||||
return nil, fmt.Errorf("node.js builder not yet implemented")
|
||||
case build.ProjectTypePHP:
|
||||
return nil, fmt.Errorf("PHP builder not yet implemented")
|
||||
default:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue