package releasekit import ( "context" "fmt" "io" "gitea.maximumdirect.net/eric/ReleaseKit/internal/artifact" internalbuild "gitea.maximumdirect.net/eric/ReleaseKit/internal/build" "gitea.maximumdirect.net/eric/ReleaseKit/internal/git" "gitea.maximumdirect.net/eric/ReleaseKit/internal/releaseidentity" "gitea.maximumdirect.net/eric/ReleaseKit/internal/versioncontract" ) // Target identifies one explicit Go operating-system and architecture pair. type Target struct { OS string // Go operating system name, such as "linux". Arch string // Go architecture name, such as "amd64". } // BuildSpec selects the application package, output name, and target pairs. type BuildSpec struct { MainPackage string // Go package path to build, such as "./cmd/app". BinaryName string // filename component used in output artifacts. Targets []Target // selected target pairs; duplicates are removed in order. } // BuildRequest contains the source and output settings for Build. type BuildRequest struct { RepositoryDir string // directory within the source Git checkout whose HEAD is recorded. // ModuleDir selects a repository-relative module directory; empty selects // the repository root. ModuleDir string Version string // stable vMAJOR.MINOR.PATCH value injected at link time. Spec BuildSpec // application package, artifact name, and targets. OutputDir string // output location; relative paths use ModuleDir and empty selects /dist. Output io.Writer // optional build progress destination. } // BuildArtifact identifies one generated binary and its SHA-256 digest. type BuildArtifact struct { Path string // absolute path to the generated binary. Filename string // generated filename. Target Target // Go target used for this artifact. SHA256 string // lowercase hexadecimal digest of the complete file. } // BuildResult describes the source and artifacts reached by Build. type BuildResult struct { RepositoryDir string // absolute source repository path. ModuleDir string // absolute selected module directory. ModulePath string // selected module's declared Go path. Tag string // complete tag derived for this version. CandidateCommit string // Git HEAD observed when the build started. // SourceVerified reports clean source and the same HEAD observed before and // after a completed build. It is required for binary delivery, not an attestation. SourceVerified bool Version string // requested injected version. MainPackage string // Go package path built. BinaryName string // artifact name prefix. OutputDir string // resolved output directory. Targets []Target // normalized target pairs. Artifacts []BuildArtifact // completed output files, including partial success. Compiler string // output of the selected local Go toolchain's version command. } // Build resolves the selected module's working-tree identity, then compiles the // selected targets with Version injected through buildinfo. It uses the // available compatible local Go toolchain, applies ReleaseKit's readonly // offline build settings, preserves effective cache and tool settings, and // verifies the version command on the Go host before succeeding. Relative // output paths use ModuleDir; an empty path selects its dist directory. An // empty target set records source identity without running the Go toolchain. // Errors return partial build facts in the result. func Build(ctx context.Context, request BuildRequest) (BuildResult, error) { spec := *internalBuildSpec(&request.Spec) result := internalbuild.Result{Version: request.Version} failure := func(err error) (BuildResult, error) { result.Settings = artifact.NormalizeSettings(spec.MainPackage, spec.BinaryName, spec.Targets) return publicBuildResult(result), err } if !versioncontract.ValidStableVersion(request.Version) { return failure(fmt.Errorf("%w: %q", ErrInvalidVersion, request.Version)) } repositoryDir, err := git.RepositoryRoot(ctx, request.RepositoryDir) if err != nil { return failure(err) } result.RepositoryDir = repositoryDir identity, err := releaseidentity.FromWorkingTree(ctx, repositoryDir, request.ModuleDir, request.Version) result.ModuleDir, result.ModulePath, result.Tag = identity.ModuleDir, identity.ModulePath, identity.Tag if err != nil { return failure(err) } result, err = internalbuild.BuildResolved(ctx, internalbuild.Request{ Identity: identity, Spec: spec, OutputDir: request.OutputDir, Output: request.Output, }) return publicBuildResult(result), err } func publicBuildResult(result internalbuild.Result) BuildResult { public := BuildResult{ RepositoryDir: result.RepositoryDir, ModuleDir: result.ModuleDir, ModulePath: result.ModulePath, Tag: result.Tag, CandidateCommit: result.CandidateCommit, SourceVerified: result.SourceVerified, Version: result.Version, MainPackage: result.Settings.MainPackage, BinaryName: result.Settings.BinaryName, OutputDir: result.OutputDir, Compiler: result.Compiler, Targets: make([]Target, 0, len(result.Settings.Targets)), Artifacts: make([]BuildArtifact, 0, len(result.Artifacts)), } for _, target := range result.Settings.Targets { public.Targets = append(public.Targets, Target{OS: target.OS, Arch: target.Arch}) } for _, artifact := range result.Artifacts { public.Artifacts = append(public.Artifacts, BuildArtifact{ Path: artifact.Path, Filename: artifact.Filename, Target: Target{OS: artifact.Target.OS, Arch: artifact.Target.Arch}, SHA256: artifact.SHA256, }) } return public } func internalBuildSpec(spec *BuildSpec) *internalbuild.Spec { if spec == nil { return nil } result := &internalbuild.Spec{MainPackage: spec.MainPackage, BinaryName: spec.BinaryName} for _, target := range spec.Targets { result.Targets = append(result.Targets, internalbuild.Target{OS: target.OS, Arch: target.Arch}) } return result } func internalBuildResult(result BuildResult) *internalbuild.Result { input := internalBuildSpec(&BuildSpec{MainPackage: result.MainPackage, BinaryName: result.BinaryName, Targets: result.Targets}) settings := artifact.NormalizeSettings(input.MainPackage, input.BinaryName, input.Targets) converted := &internalbuild.Result{RepositoryDir: result.RepositoryDir, ModuleDir: result.ModuleDir, ModulePath: result.ModulePath, Tag: result.Tag, CandidateCommit: result.CandidateCommit, SourceVerified: result.SourceVerified, Settings: settings, Version: result.Version, MainPackage: settings.MainPackage, BinaryName: settings.BinaryName, OutputDir: result.OutputDir, Compiler: result.Compiler} for _, target := range settings.Targets { converted.Targets = append(converted.Targets, internalbuild.Target{OS: target.OS, Arch: target.Arch}) } for _, artifact := range result.Artifacts { converted.Artifacts = append(converted.Artifacts, internalbuild.Artifact{Path: artifact.Path, Filename: artifact.Filename, Target: internalbuild.Target{OS: artifact.Target.OS, Arch: artifact.Target.Arch}, SHA256: artifact.SHA256}) } return converted }