package main import ( "context" "errors" "fmt" "io" "os" "os/signal" "path/filepath" "strings" "syscall" "time" "go.maximumdirect.net/releasekit" "go.maximumdirect.net/releasekit/buildinfo" "go.maximumdirect.net/releasekit/cmd/releasekit/internal/cli" "go.maximumdirect.net/releasekit/internal/artifact" "go.maximumdirect.net/releasekit/internal/git" "go.maximumdirect.net/releasekit/internal/gotool" "go.maximumdirect.net/releasekit/internal/releaseidentity" ) func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) code := run(ctx, os.Args[1:], os.Stdout, os.Stderr) stop() os.Exit(code) } func run(ctx context.Context, args []string, stdout, stderr io.Writer) int { invocation, err := cli.Parse(args) if err != nil { fmt.Fprintf(stderr, "%v\n%s\n", err, cli.Usage) return 2 } if invocation.Command == cli.ReconcileAction || invocation.Command == cli.PublicationAttemptsAction { return runRecovery(ctx, invocation, stdout, stderr) } if invocation.Command != cli.VersionCommand { invocation.Repository, err = git.RepositoryRoot(ctx, invocation.Repository) if err != nil { fmt.Fprintf(stderr, "%v\n", err) return 1 } } switch invocation.Command { case cli.VersionCommand: fmt.Fprintf(stdout, "releasekit %s\n", buildinfo.Version()) return 0 case cli.CheckAction, cli.PublishAction: return runCheckOrPublish(ctx, invocation, stdout, stderr) case cli.BuildAction: return runBuild(ctx, invocation, stdout, stderr) case cli.DeliveryAction: return runDelivery(ctx, invocation, stdout, stderr) case cli.CIDeliveryAction: return runCIDelivery(ctx, invocation, stdout, stderr) } return 2 } func runCheckOrPublish(ctx context.Context, invocation cli.Invocation, stdout, stderr io.Writer) int { settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath) if err != nil { fmt.Fprintf(stderr, "load profile: %v\n", err) return 2 } request := candidateRequest(invocation, settings, stderr) if invocation.Command == cli.PublishAction { if err := checkExpectedModule(ctx, invocation.Repository, invocation.ExpectedModule, settings.ModuleDir); err != nil { fmt.Fprintf(stderr, "profile module selection: %v\n", err) return 2 } request.ExpectedPushURL = settings.ExpectedPushURL request.PublicationRecordDir = invocation.RecordDir fmt.Fprintf(stderr, "validating and publishing module %s version %s\n", selectedModuleLabel(settings.ModuleDir), invocation.Version) result, err := releasekit.Publish(ctx, request) reportPublicationRecord(ctx, result, err, stderr) if err != nil { fmt.Fprintf(stderr, "publication state=%s phase=%s module=%s module-dir=%s version=%s tag=%s commit=%s tag-object=%s endpoint=%q: %s\n", result.State, result.Phase, result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, result.TagObject, git.DisplayURL(result.Endpoint), candidateFailureText(err)) if errors.Is(err, releasekit.ErrInvalidVersion) { return 2 } if result.State == releasekit.PublicationUnknown { return 3 } return 1 } fmt.Fprintf(stdout, "published %s as %s at %s (tag object %s)\n", result.Version, result.Tag, result.CandidateCommit, result.TagObject) return 0 } candidate, err := releasekit.Check(ctx, request) if err != nil { fmt.Fprintf(stderr, "check failed module=%s module-dir=%s version=%s tag=%s commit=%s: %s\n", candidate.ModulePath, candidate.ModuleDir, candidate.Version, candidate.Tag, candidate.Commit, candidateFailureText(err)) if errors.Is(err, releasekit.ErrInvalidVersion) { return 2 } return 1 } fmt.Fprintf(stdout, "local candidate module %s version %s tag %s at %s is valid\n", candidate.ModulePath, candidate.Version, candidate.Tag, candidate.Commit) return 0 } func reportPublicationRecord(ctx context.Context, result releasekit.PublicationResult, publicationErr error, stderr io.Writer) { if result.RecordError != nil { fmt.Fprintf(stderr, "publication record warning: %s\n", git.DisplayError(result.RecordError)) } if result.RecordPath == "" { return } fmt.Fprintf(stderr, "publication record saved: %s\n", git.DisplayDiagnostic(result.RecordPath)) if publicationErr == nil || !publicationOffersRecovery(result.Phase) { return } // Handoff preparation follows the failed publication and has its own bounded // lifetime, including when publication's context was canceled. handoffContext, cancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second) defer cancel() command, err := selectedPublicationRecoveryCommand(handoffContext, result) if err != nil { fmt.Fprintf(stderr, "recovery tool preparation warning: %s\n", git.DisplayError(err)) command, err = publicationRecoveryCommand(result.RecordPath) if err != nil { fmt.Fprintf(stderr, "recovery command unavailable: %s\n", git.DisplayError(err)) fmt.Fprintln(stderr, "Use your selected supported ReleaseKit CLI with this record if the original executable is unavailable.") return } } fmt.Fprintf(stderr, "recovery command: %s\n", git.DisplayDiagnostic(command)) fmt.Fprintln(stderr, "If this entry point is unavailable, use your selected supported ReleaseKit CLI with the same record.") } func selectedPublicationRecoveryCommand(ctx context.Context, result releasekit.PublicationResult) (string, error) { if result.Evidence.ModuleDir != "" { common, err := git.CommonDirectory(ctx, result.RepositoryDir) if err != nil { return "", err } args, err := gotool.RecoveryTool(ctx, result.Evidence.ModuleDir, filepath.Join(common, "releasekit", "publication-recovery")) if err != nil { return "", err } if args != nil { prefix := "env go -C " + shellQuote(git.DisplayDiagnostic(args.Directory)) command := "(export GOWORK=off GOTOOLCHAIN=local && releasekit_host_os=$(" + prefix + " env GOHOSTOS) && releasekit_host_arch=$(" + prefix + " env GOHOSTARCH) && GOOS=\"$releasekit_host_os\" GOARCH=\"$releasekit_host_arch\" " + prefix for _, arg := range args.Args { command += " " + shellQuote(git.DisplayDiagnostic(arg)) } path, err := filepath.Abs(git.DisplayDiagnostic(result.RecordPath)) if err != nil { return "", err } return command + " reconcile --record " + shellQuote(path) + ")", nil } } return publicationRecoveryCommand(result.RecordPath) } func publicationOffersRecovery(phase releasekit.PublicationPhase) bool { switch phase { case releasekit.PublicationTagCreation, releasekit.PublicationPush, releasekit.PublicationReconciliation: return true default: return false } } func publicationRecoveryCommand(recordPath string) (string, error) { executable, err := os.Executable() if err != nil { return "", fmt.Errorf("find current executable: %w", err) } executable, err = filepath.Abs(executable) if err != nil { return "", fmt.Errorf("resolve current executable path: %w", err) } if gotool.TemporaryExecutable(executable) { return "", fmt.Errorf("current executable belongs to a temporary Go build; select a supported ReleaseKit CLI for the saved record") } // Redact before path cleaning can change a pasted URL's recognizable syntax. recordPath, err = filepath.Abs(git.DisplayDiagnostic(recordPath)) if err != nil { return "", fmt.Errorf("resolve publication record path: %w", err) } return shellQuote(executable) + " reconcile --record " + shellQuote(recordPath), nil } func shellQuote(argument string) string { return "'" + strings.ReplaceAll(argument, "'", "'\"'\"'") + "'" } func runBuild(ctx context.Context, invocation cli.Invocation, stdout, stderr io.Writer) int { settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath) if err != nil { fmt.Fprintf(stderr, "load profile: %v\n", err) return 2 } if settings.Build == nil { fmt.Fprintln(stderr, "build requires build settings in the profile") return 2 } result, err := releasekit.Build(ctx, releasekit.BuildRequest{ RepositoryDir: invocation.Repository, ModuleDir: settings.ModuleDir, Version: invocation.Version, Spec: *configuredBuildSpec(settings.Build), OutputDir: invocation.OutputDir, Output: stderr, }) if result.OutputDir != "" && len(result.Targets) != 0 { if receiptErr := cli.RemoveBuildReceipt(result.OutputDir); receiptErr != nil { fmt.Fprintf(stderr, "%v\n", receiptErr) return 1 } } if err != nil { fmt.Fprintf(stderr, "build failed module=%s module-dir=%s version=%s tag=%s commit=%s: %v\n", result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, err) for _, artifact := range result.Artifacts { fmt.Fprintf(stderr, "built %s %s\n", artifact.Filename, artifact.SHA256) } if errors.Is(err, releasekit.ErrInvalidVersion) { return 2 } return 1 } if len(result.Targets) == 0 { fmt.Fprintf(stdout, "no build targets selected for module %s version %s tag %s at %s\n", result.ModulePath, result.Version, result.Tag, result.CandidateCommit) return 0 } receipt := receiptFromBuild(result) receiptPath, err := cli.WriteBuildReceipt(result.OutputDir, receipt) if err != nil { fmt.Fprintf(stderr, "write build receipt: %v\n", err) return 1 } stored, err := cli.LoadBuildReceipt(receiptPath) if err != nil { fmt.Fprintf(stderr, "verify build receipt: %v\n", err) return 1 } if !stored.MatchesBuild(result.RepositoryDir, result.CandidateCommit, result.Version, *settings.Build) { fmt.Fprintln(stderr, "build receipt does not match the selected source and build settings") return 1 } fmt.Fprintf(stdout, "built %d artifacts for module %s version %s tag %s at %s with %s\n", len(result.Artifacts), result.ModulePath, result.Version, result.Tag, result.CandidateCommit, result.Compiler) for _, artifact := range result.Artifacts { fmt.Fprintf(stdout, "%s %s\n", artifact.Filename, artifact.SHA256) } fmt.Fprintf(stdout, "build receipt: %s\n", receiptPath) return 0 } func candidateRequest(invocation cli.Invocation, settings cli.Settings, output io.Writer) releasekit.ReleaseRequest { request := releasekit.ReleaseRequest{ RepositoryDir: invocation.Repository, ModuleDir: settings.ModuleDir, Product: settings.Product, Version: invocation.Version, Remote: settings.Remote, Validation: validationSpec(settings.Validation), Build: configuredBuildSpec(settings.Build), Output: output, } if len(settings.Checks) != 0 { checks := settings.Checks request.AdditionalChecks = func(ctx context.Context, candidate releasekit.Candidate) error { return cli.RunChecks(ctx, candidate.ModuleDir, checks, map[string]string{ "RELEASEKIT_VERSION": candidate.Version, "RELEASEKIT_CANDIDATE": candidate.Commit, "RELEASEKIT_REPOSITORY_DIR": candidate.RepositoryDir, "RELEASEKIT_MODULE_DIR": candidate.ModuleDir, "RELEASEKIT_MODULE_PATH": candidate.ModulePath, "RELEASEKIT_TAG": candidate.Tag, }, output, output) } } return request } func validationSpec(settings *cli.ValidationSettings) releasekit.ValidationSpec { if settings == nil { return releasekit.ValidationSpec{} } result := releasekit.ValidationSpec{ SharedFiles: copyStringsPreservingEmpty(settings.SharedFiles), ForbiddenFiles: copyStringsPreservingEmpty(settings.ForbiddenFiles), GoFormat: settings.GoFormat, ModuleDistribution: releasekit.ModuleDistributionValidationSpec{ WorkspaceFiles: settings.ModuleDistribution.WorkspaceFiles, LocalReplacements: settings.ModuleDistribution.LocalReplacements, }, Go: releasekit.GoValidationSpec{ Race: settings.Go.Race, FullVet: settings.Go.FullVet, CompileTargets: make([]releasekit.Target, len(settings.Go.CompileTargets)), }, } if settings.Markdown != nil { result.Markdown = &releasekit.MarkdownValidationSpec{ EnforceCheckoutBoundary: settings.Markdown.EnforceCheckoutBoundary, } } if settings.FileSize != nil { var limitBytes *int64 if settings.FileSize.LimitBytes != nil { limit := *settings.FileSize.LimitBytes limitBytes = &limit } result.FileSize = &releasekit.FileSizeValidationSpec{ LimitBytes: limitBytes, Exclusions: append([]string(nil), settings.FileSize.Exclusions...), } } for index, target := range settings.Go.CompileTargets { goos, goarch, _ := strings.Cut(target, "/") result.Go.CompileTargets[index] = releasekit.Target{OS: goos, Arch: goarch} } return result } func candidateFailureText(err error) string { return err.Error() } func checkExpectedModule(ctx context.Context, repositoryDir, expectedModule, profileModule string) error { if expectedModule == "" { return nil } commit, err := git.CaptureHead(ctx, repositoryDir) if err != nil { return err } expected, err := releaseidentity.ResolveCommittedSelection(ctx, repositoryDir, expectedModule, commit) if err != nil { return fmt.Errorf("normalize expected module %q: %w", expectedModule, err) } selected, err := releaseidentity.ResolveCommittedSelection(ctx, repositoryDir, profileModule, commit) if err != nil { return fmt.Errorf("normalize profile module %q: %w", profileModule, err) } if expected.ModuleSubdir != selected.ModuleSubdir { return fmt.Errorf("expected module %q resolves to %q, but the profile selects %q", expectedModule, expected.ModuleSubdir, selected.ModuleSubdir) } return nil } func selectedModuleLabel(moduleDir string) string { if moduleDir == "" { return "." } return moduleDir } func copyStringsPreservingEmpty(values []string) []string { if values == nil { return nil } return append([]string{}, values...) } func runDelivery(ctx context.Context, invocation cli.Invocation, stdout, stderr io.Writer) int { settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath) if err != nil { fmt.Fprintf(stderr, "load profile: %v\n", err) return 2 } return deliverWithSettings(ctx, invocation, settings, stdout, stderr) } func runCIDelivery(ctx context.Context, invocation cli.Invocation, stdout, stderr io.Writer) int { err := releasekit.PrepareTaggedCheckout(ctx, releasekit.TaggedCheckoutRequest{ RepositoryDir: invocation.Repository, Tag: invocation.ExpectedTag, Remote: invocation.Remote, ExpectedCommit: invocation.Commit, VerifyOnly: invocation.Prepared, }) if err != nil { fmt.Fprintf(stderr, "prepare tagged checkout: %v\n", err) if errors.Is(err, releasekit.ErrInvalidVersion) { return 2 } return 1 } settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath) if err != nil { fmt.Fprintf(stderr, "load profile: %v\n", err) return 2 } if err := checkExpectedModule(ctx, invocation.Repository, invocation.ExpectedModule, settings.ModuleDir); err != nil { fmt.Fprintf(stderr, "profile module selection: %v\n", err) return 2 } identity, err := releaseidentity.FromCommitted(ctx, invocation.Repository, settings.ModuleDir, invocation.Version, invocation.Commit) if err != nil { fmt.Fprintf(stderr, "resolve CI release identity: %v\n", err) return 1 } if invocation.ExpectedTag != identity.Tag { fmt.Fprintf(stderr, "expected delivery tag %q does not match selected module tag %q\n", invocation.ExpectedTag, identity.Tag) return 1 } destination, status := deliveryDestination(invocation.Repository, settings, stderr) if status != 0 { return status } if settings.Build != nil && len(settings.Build.Targets) != 0 { if err := releasekit.PreflightDelivery(ctx, destination); err != nil { fmt.Fprintf(stderr, "delivery credential preflight: %v\n", err) return deliveryPreflightExitStatus(err) } if err := releasekit.PrepareBuildDependencies(ctx, releasekit.DependencyPreparationRequest{ RepositoryDir: invocation.Repository, ModuleDir: settings.ModuleDir, }); err != nil { fmt.Fprintf(stderr, "prepare build dependencies: %v\n", err) return 1 } } return deliverWithDestination(ctx, invocation, settings, destination, stdout, stderr) } // deliverWithSettings adapts prepared CLI state to the public delivery facade. // CI entry points can reuse it after preparing the checkout and loading its profile. func deliverWithSettings(ctx context.Context, invocation cli.Invocation, settings cli.Settings, stdout, stderr io.Writer) int { destination, status := deliveryDestination(invocation.Repository, settings, stderr) if status != 0 { return status } return deliverWithDestination(ctx, invocation, settings, destination, stdout, stderr) } func deliveryDestination(repositoryDir string, settings cli.Settings, stderr io.Writer) (releasekit.DeliveryDestination, int) { delivery := settings.Delivery if delivery == nil { fmt.Fprintln(stderr, "deliver requires an explicit delivery.forge selection in the profile") return releasekit.DeliveryDestination{}, 2 } switch strings.TrimSpace(delivery.Forge) { case "": fmt.Fprintln(stderr, "deliver requires delivery.forge in the profile") return releasekit.DeliveryDestination{}, 2 case string(releasekit.ForgeGitea): default: fmt.Fprintf(stderr, "unsupported delivery forge %q\n", delivery.Forge) return releasekit.DeliveryDestination{}, 2 } if strings.TrimSpace(delivery.BaseURL) == "" || strings.TrimSpace(delivery.Owner) == "" || strings.TrimSpace(delivery.Repository) == "" { fmt.Fprintln(stderr, "deliver requires base_url, owner, and repository in the profile delivery settings") return releasekit.DeliveryDestination{}, 2 } tokenEnv := delivery.TokenEnv if strings.TrimSpace(tokenEnv) == "" { tokenEnv = "RELEASEKIT_GITEA_TOKEN" } token, ok := os.LookupEnv(tokenEnv) if (!ok || strings.TrimSpace(token) == "") && delivery.TokenFile != "" { name := delivery.TokenFile if !filepath.IsAbs(name) { name = filepath.Join(repositoryDir, name) } contents, err := os.ReadFile(name) if err != nil { fmt.Fprintf(stderr, "read delivery token file: %v\n", err) return releasekit.DeliveryDestination{}, 2 } token = strings.TrimSpace(string(contents)) if token == "" { fmt.Fprintln(stderr, "delivery token file is empty or blank") return releasekit.DeliveryDestination{}, 2 } } if strings.TrimSpace(token) == "" { fmt.Fprintf(stderr, "deliver requires a non-empty token in environment variable %s\n", tokenEnv) return releasekit.DeliveryDestination{}, 2 } return releasekit.DeliveryDestination{Forge: releasekit.ForgeGitea, BaseURL: delivery.BaseURL, Owner: delivery.Owner, Repository: delivery.Repository, Token: token}, 0 } func deliverWithDestination(ctx context.Context, invocation cli.Invocation, settings cli.Settings, destination releasekit.DeliveryDestination, stdout, stderr io.Writer) int { hasTargets := settings.Build != nil && len(settings.Build.Targets) != 0 if invocation.AssetsDir != "" && !hasTargets { fmt.Fprintln(stderr, "--assets requires build targets in the profile") return 2 } var buildSpec *releasekit.BuildSpec var prebuilt *releasekit.BuildResult if invocation.AssetsDir == "" && settings.Build != nil { buildSpec = configuredBuildSpec(settings.Build) } if hasTargets { if invocation.AssetsDir != "" { commit, err := git.CaptureHead(ctx, invocation.Repository) if err != nil { fmt.Fprintf(stderr, "capture asset source commit: %v\n", err) return 1 } selection, err := releaseidentity.ResolveCommittedSelection(ctx, invocation.Repository, settings.ModuleDir, commit) if err != nil { fmt.Fprintf(stderr, "resolve asset module directory: %v\n", err) return 1 } assetsDir := artifact.OutputDirectory(selection.ModuleDir, invocation.AssetsDir) receipt, err := cli.LoadBuildReceipt(filepath.Join(assetsDir, cli.BuildReceiptName)) if err != nil { fmt.Fprintf(stderr, "load verified build receipt: %v\n", err) return 1 } if !receipt.MatchesBuild(invocation.Repository, receipt.CandidateCommit, invocation.Version, *settings.Build) { fmt.Fprintln(stderr, "build receipt does not match the selected repository, version, or build settings") return 1 } prebuilt = buildResultFromReceipt(receipt) } } result, err := releasekit.Deliver(ctx, releasekit.DeliveryRequest{ RepositoryDir: invocation.Repository, ModuleDir: settings.ModuleDir, Product: settings.Product, Version: invocation.Version, ExpectedTag: invocation.ExpectedTag, Destination: destination, Build: buildSpec, Prebuilt: prebuilt, Output: stderr, }) if err != nil { return reportDeliveryFailure(stderr, result, err) } fmt.Fprintf(stdout, "delivered module %s version %s tag %s at %s (state %s)\n", result.ModulePath, result.Version, result.Tag, result.CandidateCommit, result.State) if result.ReleaseURL != "" { fmt.Fprintf(stdout, "release: %s\n", result.ReleaseURL) } writeDeliveryAssets(stdout, result) return 0 } func reportDeliveryFailure(output io.Writer, result releasekit.DeliveryResult, err error) int { if result.State == releasekit.DeliveryComplete { fmt.Fprintln(output, "hosted delivery completed; local cleanup failed") } fmt.Fprintf(output, "delivery state=%s module=%s module-dir=%s version=%s tag=%s commit=%s url=%s: %v\n", result.State, result.ModulePath, result.ModuleDir, result.Version, result.Tag, result.CandidateCommit, result.ReleaseURL, err) writeDeliveryAssets(output, result) writeDeliveryRecovery(output, result) if result.State == releasekit.DeliveryComplete { return 1 } if errors.Is(err, releasekit.ErrInvalidVersion) { return 2 } var preflightErr *releasekit.DeliveryPreflightError if errors.As(err, &preflightErr) { return deliveryPreflightExitStatus(preflightErr) } if result.State == releasekit.DeliveryUnknown { return 3 } return 1 } func deliveryPreflightExitStatus(err error) int { var preflightErr *releasekit.DeliveryPreflightError if !errors.As(err, &preflightErr) { return 1 } switch preflightErr.Kind { case releasekit.DeliveryPreflightInvalidDestination, releasekit.DeliveryPreflightMissingCredential, releasekit.DeliveryPreflightInvalidCredential, releasekit.DeliveryPreflightInsufficientPermissions, releasekit.DeliveryPreflightInaccessibleDestination: return 2 default: return 1 } } func writeDeliveryAssets(output io.Writer, result releasekit.DeliveryResult) { for _, group := range []struct { label string assets []releasekit.DeliveryAsset }{{"matching", result.Matching}, {"uploaded", result.Uploaded}, {"pending", result.Pending}, {"conflicting", result.Conflicting}, {"uncertain", result.Uncertain}} { for _, asset := range group.assets { fmt.Fprintf(output, "%s %s %s\n", group.label, asset.Filename, asset.SHA256) } } } func writeDeliveryRecovery(output io.Writer, result releasekit.DeliveryResult) { switch result.State { case releasekit.DeliveryNotAttempted: fmt.Fprintf(output, "recovery: fix the local or preflight failure for module %s version %s tag %s before retrying; no remote write was attempted\n", result.ModulePath, result.Version, result.Tag) case releasekit.DeliveryPartial: fmt.Fprintf(output, "recovery: fix the reported cause, then retry module %s version %s tag %s\n", result.ModulePath, result.Version, result.Tag) case releasekit.DeliveryConflict: fmt.Fprintf(output, "recovery: investigate conflicting hosted content for module %s version %s tag %s; use a new version if the source or published contents need correction\n", result.ModulePath, result.Version, result.Tag) case releasekit.DeliveryUnknown: fmt.Fprintf(output, "recovery: resolve the remote state for module %s version %s tag %s before retrying\n", result.ModulePath, result.Version, result.Tag) default: return } if len(result.Matching)+len(result.Uploaded)+len(result.Conflicting)+len(result.Uncertain) != 0 { fmt.Fprintln(output, "if rebuilding could change asset bytes already on the server, retain the original build artifacts and retry with deliver --assets DIR") } } func buildResultFromReceipt(receipt cli.BuildReceipt) *releasekit.BuildResult { result := &releasekit.BuildResult{ RepositoryDir: receipt.RepositoryDir, ModuleDir: receipt.ModuleDir, ModulePath: receipt.ModulePath, Tag: receipt.Tag, CandidateCommit: receipt.CandidateCommit, SourceVerified: receipt.SourceVerified, Version: receipt.Version, MainPackage: receipt.MainPackage, BinaryName: receipt.BinaryName, OutputDir: receipt.OutputDir, Compiler: receipt.Compiler, } for _, target := range receipt.Targets { result.Targets = append(result.Targets, releasekit.Target{OS: target.OS, Arch: target.Arch}) } for _, artifact := range receipt.Artifacts { result.Artifacts = append(result.Artifacts, releasekit.BuildArtifact{ Path: artifact.Path, Filename: artifact.Filename, Target: releasekit.Target{OS: artifact.Target.OS, Arch: artifact.Target.Arch}, SHA256: artifact.SHA256, }) } return result } func configuredBuildSpec(settings *cli.BuildSettings) *releasekit.BuildSpec { if settings == nil { return nil } spec := &releasekit.BuildSpec{ MainPackage: settings.MainPackage, BinaryName: settings.BinaryName, Targets: make([]releasekit.Target, 0, len(settings.Targets)), } for _, value := range settings.Targets { goos, goarch, _ := strings.Cut(value, "/") spec.Targets = append(spec.Targets, releasekit.Target{OS: goos, Arch: goarch}) } return spec } func receiptFromBuild(result releasekit.BuildResult) cli.BuildReceipt { receipt := cli.BuildReceipt{ SchemaVersion: cli.BuildReceiptSchemaVersion, RepositoryDir: result.RepositoryDir, ModuleDir: result.ModuleDir, ModulePath: result.ModulePath, Tag: result.Tag, CandidateCommit: result.CandidateCommit, SourceVerified: result.SourceVerified, Version: result.Version, MainPackage: result.MainPackage, BinaryName: result.BinaryName, OutputDir: result.OutputDir, Compiler: result.Compiler, Targets: make([]cli.ReceiptTarget, 0, len(result.Targets)), Artifacts: make([]cli.ReceiptArtifact, 0, len(result.Artifacts)), } for _, target := range result.Targets { receipt.Targets = append(receipt.Targets, cli.ReceiptTarget{OS: target.OS, Arch: target.Arch}) } for _, artifact := range result.Artifacts { receipt.Artifacts = append(receipt.Artifacts, cli.ReceiptArtifact{ Path: artifact.Path, Filename: artifact.Filename, Target: cli.ReceiptTarget{OS: artifact.Target.OS, Arch: artifact.Target.Arch}, SHA256: artifact.SHA256, }) } return receipt }