Build from the selected module

This commit is contained in:
2026-10-10 02:53:38 +00:00
parent 8350cbf9c9
commit fdce017aff
15 changed files with 424 additions and 122 deletions

View File

@@ -16,25 +16,20 @@ import (
"gitea.maximumdirect.net/eric/ReleaseKit/internal/artifact"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/releaseidentity"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/tempdir"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/versioncontract"
)
type Target = artifact.Target
type Spec struct {
MainPackage string
BinaryName string
Targets []Target
}
type Spec = artifact.Settings
type Request struct {
RepositoryDir string
ModuleDir string
Version string
Spec Spec
OutputDir string
Output io.Writer
ToolOwner *gotool.Owner
Identity releaseidentity.Identity
Spec Spec
OutputDir string
Output io.Writer
ToolOwner *gotool.Owner
}
type Artifact struct {
@@ -51,6 +46,7 @@ type Result struct {
Tag string
CandidateCommit string
SourceVerified bool
Settings artifact.Settings
Version string
MainPackage string
BinaryName string
@@ -60,25 +56,32 @@ type Result struct {
Compiler string
}
func Build(ctx context.Context, request Request) (Result, error) {
func BuildResolved(ctx context.Context, request Request) (Result, error) {
identity := request.Identity
settings := artifact.NormalizeSettings(request.Spec.MainPackage, request.Spec.BinaryName, request.Spec.Targets)
result := Result{
Version: request.Version,
MainPackage: request.Spec.MainPackage,
BinaryName: request.Spec.BinaryName,
Targets: artifact.NormalizeTargets(request.Spec.Targets),
RepositoryDir: identity.RepositoryDir,
ModuleDir: identity.ModuleDir,
ModulePath: identity.ModulePath,
Tag: identity.Tag,
Settings: settings,
Version: identity.Version,
MainPackage: settings.MainPackage,
BinaryName: settings.BinaryName,
Targets: settings.Targets,
}
if !versioncontract.ValidStableVersion(request.Version) {
return result, fmt.Errorf("%w: %q", candidate.ErrInvalidVersion, request.Version)
if !versioncontract.ValidStableVersion(identity.Version) {
return result, fmt.Errorf("%w: %q", candidate.ErrInvalidVersion, identity.Version)
}
if request.RepositoryDir == "" {
return result, errors.New("build requires a repository directory")
if identity.RepositoryDir == "" || identity.ModuleDir == "" || identity.ModulePath == "" {
return result, errors.New("build requires a resolved module identity")
}
if len(result.Targets) != 0 {
if request.Spec.MainPackage == "" {
if settings.MainPackage == "" {
return result, errors.New("build requires a main package when targets are selected")
}
if !artifact.ValidComponent(request.Spec.BinaryName) {
return result, fmt.Errorf("unsafe binary name %q", request.Spec.BinaryName)
if !artifact.ValidComponent(settings.BinaryName) {
return result, fmt.Errorf("unsafe binary name %q", settings.BinaryName)
}
for _, target := range result.Targets {
if !artifact.ValidComponent(target.OS) || !artifact.ValidComponent(target.Arch) {
@@ -89,11 +92,16 @@ func Build(ctx context.Context, request Request) (Result, error) {
if err := ctx.Err(); err != nil {
return result, fmt.Errorf("build canceled: %w", err)
}
repositoryDir, err := filepath.Abs(request.RepositoryDir)
repositoryDir, err := filepath.Abs(identity.RepositoryDir)
if err != nil {
return result, fmt.Errorf("resolve repository directory: %w", err)
}
result.RepositoryDir = repositoryDir
moduleDir, err := filepath.Abs(identity.ModuleDir)
if err != nil {
return result, fmt.Errorf("resolve module directory: %w", err)
}
result.ModuleDir = moduleDir
commit, err := gitCommit(ctx, repositoryDir)
if err != nil {
return result, err
@@ -105,12 +113,12 @@ func Build(ctx context.Context, request Request) (Result, error) {
}
if len(result.Targets) == 0 {
result.OutputDir = resolvedOutputDir(repositoryDir, request.OutputDir)
result.OutputDir = resolvedOutputDir(moduleDir, request.OutputDir)
return result, nil
}
result.OutputDir = resolvedOutputDir(repositoryDir, request.OutputDir)
declarations := artifact.ExpectedBuild(request.Spec.BinaryName, request.Version, result.Targets)
result.OutputDir = resolvedOutputDir(moduleDir, request.OutputDir)
declarations := artifact.ExpectedBuild(settings.BinaryName, identity.Version, settings.Targets)
filenames := make([]string, len(declarations))
for i, declaration := range declarations {
filenames[i] = declaration.Filename
@@ -121,13 +129,13 @@ func Build(ctx context.Context, request Request) (Result, error) {
owner := request.ToolOwner
if owner == nil {
owner = gotool.NewOwner(repositoryDir)
owner = gotool.NewOwner(moduleDir)
}
goTool, err := owner.Acquire(ctx)
if err != nil {
return result, fmt.Errorf("prepare Go toolchain: %w", err)
}
compiler, err := goTool.Output(ctx, repositoryDir, gotool.Native, gotool.Target{}, nil, "version")
compiler, err := goTool.Output(ctx, moduleDir, gotool.Native, gotool.Target{}, nil, "version")
if err != nil {
return result, fmt.Errorf("identify Go compiler: %w", err)
}
@@ -144,7 +152,7 @@ func Build(ctx context.Context, request Request) (Result, error) {
if request.Output != nil {
fmt.Fprintf(request.Output, "building %s/%s as %s\n", target.OS, target.Arch, filename)
}
if err := buildTarget(ctx, goTool, repositoryDir, request.Spec.MainPackage, path, request.Version, target); err != nil {
if err := buildTarget(ctx, goTool, moduleDir, settings.MainPackage, path, identity.Version, target); err != nil {
return result, fmt.Errorf("build target %s/%s: %w", target.OS, target.Arch, err)
}
artifact := Artifact{Path: path, Filename: filename, Target: target}
@@ -162,26 +170,26 @@ func Build(ctx context.Context, request Request) (Result, error) {
}
}
if smokePath == "" {
temporaryDir, err := os.MkdirTemp("", "releasekit-native-smoke-")
temporaryDir, err := tempdir.Outside(repositoryDir, "releasekit-native-smoke-")
if err != nil {
return result, fmt.Errorf("create native smoke directory: %w", err)
}
defer os.RemoveAll(temporaryDir)
smokePath = filepath.Join(temporaryDir, request.Spec.BinaryName)
smokePath = filepath.Join(temporaryDir, settings.BinaryName)
if host.OS == "windows" {
smokePath += ".exe"
}
if request.Output != nil {
fmt.Fprintf(request.Output, "building temporary version check for %s/%s\n", host.OS, host.Arch)
}
if err := buildTarget(ctx, goTool, repositoryDir, request.Spec.MainPackage, smokePath, request.Version, host); err != nil {
if err := buildTarget(ctx, goTool, moduleDir, settings.MainPackage, smokePath, identity.Version, host); err != nil {
return result, fmt.Errorf("build native version check: %w", err)
}
}
if request.Output != nil {
fmt.Fprintf(request.Output, "checking %s version output\n", request.Spec.BinaryName)
fmt.Fprintf(request.Output, "checking %s version output\n", settings.BinaryName)
}
if err := checkVersion(ctx, smokePath, request.Spec.BinaryName, request.Version); err != nil {
if err := checkVersion(ctx, smokePath, settings.BinaryName, identity.Version); err != nil {
return result, err
}
commitAfter, err := gitCommit(ctx, repositoryDir)
@@ -196,11 +204,11 @@ func Build(ctx context.Context, request Request) (Result, error) {
return result, nil
}
func resolvedOutputDir(repositoryDir, outputDir string) string {
func resolvedOutputDir(moduleDir, outputDir string) string {
if outputDir == "" {
outputDir = filepath.Join(repositoryDir, "dist")
outputDir = filepath.Join(moduleDir, "dist")
} else if !filepath.IsAbs(outputDir) {
outputDir = filepath.Join(repositoryDir, outputDir)
outputDir = filepath.Join(moduleDir, outputDir)
}
resolved, err := filepath.Abs(outputDir)
if err != nil {
@@ -221,9 +229,9 @@ func gitCommit(ctx context.Context, repositoryDir string) (string, error) {
return strings.TrimSpace(string(output)), nil
}
func buildTarget(ctx context.Context, goTool *gotool.Tool, repositoryDir, mainPackage, outputPath, version string, target Target) error {
func buildTarget(ctx context.Context, goTool *gotool.Tool, moduleDir, mainPackage, outputPath, version string, target Target) error {
ldflags := "-s -w -X " + versioncontract.InjectedVersionLinkerSymbol + "=" + version
err := goTool.Run(ctx, repositoryDir, gotool.Static, gotool.Target{OS: target.OS, Arch: target.Arch}, nil,
err := goTool.Run(ctx, moduleDir, gotool.Static, gotool.Target{OS: target.OS, Arch: target.Arch}, nil,
"build", "-o", outputPath, "-mod=readonly", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, mainPackage)
return err
}

View File

@@ -5,9 +5,11 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/releaseidentity"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/tempdir"
)
@@ -18,18 +20,32 @@ func CandidateCallback(spec *Spec, output io.Writer) func(context.Context, candi
selected := *spec
selected.Targets = append([]Target(nil), spec.Targets...)
return func(ctx context.Context, captured candidate.Candidate, owner *gotool.Owner) error {
moduleSubdir, err := filepath.Rel(captured.RepositoryDir, captured.ModuleDir)
if err != nil {
return fmt.Errorf("resolve captured module path: %w", err)
}
identity := releaseidentity.Identity{
Selection: releaseidentity.Selection{
RepositoryDir: captured.RepositoryDir,
ModuleDir: captured.ModuleDir,
ModuleSubdir: filepath.ToSlash(moduleSubdir),
},
ModulePath: captured.ModulePath,
Version: captured.Version,
Tag: captured.Tag,
NotePath: filepath.ToSlash(filepath.Join(moduleSubdir, "docs", "releases", captured.Version+".md")),
}
temporaryDir, err := tempdir.Outside(captured.RepositoryDir, "releasekit-candidate-build-")
if err != nil {
return fmt.Errorf("create temporary candidate build output: %w", err)
}
defer os.RemoveAll(temporaryDir)
_, err = Build(ctx, Request{
RepositoryDir: captured.RepositoryDir,
Version: captured.Version,
Spec: selected,
OutputDir: temporaryDir,
Output: output,
ToolOwner: owner,
_, err = BuildResolved(ctx, Request{
Identity: identity,
Spec: selected,
OutputDir: temporaryDir,
Output: output,
ToolOwner: owner,
})
if err != nil {
return fmt.Errorf("build candidate: %w", err)