1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-08 03:31:59 +02:00
goreleaser/cmd/build.go

244 lines
7.9 KiB
Go
Raw Normal View History

package cmd
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/caarlos0/ctrlc"
"github.com/caarlos0/log"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/deprecate"
"github.com/goreleaser/goreleaser/internal/gio"
feat: improve output and pipe skipping (#2480) * refactor: improve middleware Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: upload tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: twitter tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: source tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: snapshot tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * test: improved some tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: snapcraft skip Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip slack Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip sign Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip scoop Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip reddit Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip discord Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip publish Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip nfpm Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip milestone Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip custompublishers Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip checksums Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip changelog Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip brew Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip blob Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip before Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip artifactory Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip announce Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip defaults Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: cmds Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip docker Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * chore: todo Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: go.mod Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip release Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: remove old skip pipe errors Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip teams Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip brew Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix/test: skip smtp Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: lint issues Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip docker Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip brew and scoop Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip docker Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip http/artifactory Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * test: increase coverage Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * test: fix Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>
2021-09-18 15:21:29 +02:00
"github.com/goreleaser/goreleaser/internal/middleware/errhandler"
"github.com/goreleaser/goreleaser/internal/middleware/logging"
"github.com/goreleaser/goreleaser/internal/middleware/skip"
"github.com/goreleaser/goreleaser/internal/pipeline"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
"github.com/spf13/cobra"
)
type buildCmd struct {
cmd *cobra.Command
opts buildOpts
}
type buildOpts struct {
config string
ids []string
snapshot bool
skipValidate bool
skipBefore bool
skipPostHooks bool
clean bool
rmDist bool // deprecated
deprecated bool
parallelism int
timeout time.Duration
singleTarget bool
output string
}
func newBuildCmd() *buildCmd {
root := &buildCmd{}
// nolint: dupl
cmd := &cobra.Command{
Use: "build",
Aliases: []string{"b"},
Short: "Builds the current project",
Long: `The ` + "`goreleaser build`" + ` command is analogous to the ` + "`go build`" + ` command, in the sense it only builds binaries.
2022-04-20 00:34:24 +02:00
Its intended usage is, for example, within Makefiles to avoid setting up ldflags and etc in several places. That way, the GoReleaser config becomes the source of truth for how the binaries should be built.
It also allows you to generate a local build for your current machine only using the ` + "`--single-target`" + ` option, and specific build IDs using the ` + "`--id`" + ` option in case you have more than one.
2022-04-20 00:34:24 +02:00
When using ` + "`--single-target`" + `, the ` + "`GOOS`" + ` and ` + "`GOARCH`" + ` environment variables are used to determine the target, defaulting to the current machine target if not set.
`,
SilenceUsage: true,
SilenceErrors: true,
Args: cobra.NoArgs,
ValidArgsFunction: cobra.NoFileCompletions,
RunE: timedRunE("build", func(cmd *cobra.Command, args []string) error {
ctx, err := buildProject(root.opts)
if err != nil {
return err
}
deprecateWarn(ctx)
return nil
}),
}
cmd.Flags().StringVarP(&root.opts.config, "config", "f", "", "Load configuration from file")
_ = cmd.MarkFlagFilename("config", "yaml", "yml")
cmd.Flags().BoolVar(&root.opts.snapshot, "snapshot", false, "Generate an unversioned snapshot build, skipping all validations")
cmd.Flags().BoolVar(&root.opts.skipValidate, "skip-validate", false, "Skips several sanity checks")
cmd.Flags().BoolVar(&root.opts.skipBefore, "skip-before", false, "Skips global before hooks")
cmd.Flags().BoolVar(&root.opts.skipPostHooks, "skip-post-hooks", false, "Skips all post-build hooks")
cmd.Flags().BoolVar(&root.opts.clean, "clean", false, "Remove the dist folder before building")
cmd.Flags().BoolVar(&root.opts.rmDist, "rm-dist", false, "Remove the dist folder before building")
cmd.Flags().IntVarP(&root.opts.parallelism, "parallelism", "p", 0, "Amount tasks to run concurrently (default: number of CPUs)")
_ = cmd.RegisterFlagCompletionFunc("parallelism", cobra.NoFileCompletions)
cmd.Flags().DurationVar(&root.opts.timeout, "timeout", 30*time.Minute, "Timeout to the entire build process")
_ = cmd.RegisterFlagCompletionFunc("timeout", cobra.NoFileCompletions)
cmd.Flags().BoolVar(&root.opts.singleTarget, "single-target", false, "Builds only for current GOOS and GOARCH, regardless of what's set in the configuration file")
cmd.Flags().StringArrayVar(&root.opts.ids, "id", nil, "Builds only the specified build ids")
_ = cmd.RegisterFlagCompletionFunc("id", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
cfg, err := loadConfig(root.opts.config)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
ids := make([]string, 0, len(cfg.Builds))
for _, build := range cfg.Builds {
ids = append(ids, build.ID)
}
return ids, cobra.ShellCompDirectiveNoFileComp
})
cmd.Flags().BoolVar(&root.opts.deprecated, "deprecated", false, "Force print the deprecation message - tests only")
cmd.Flags().StringVarP(&root.opts.output, "output", "o", "", "Copy the binary to the path after the build. Only taken into account when using --single-target and a single id (either with --id or if configuration only has one build)")
_ = cmd.MarkFlagFilename("output", "")
_ = cmd.Flags().MarkHidden("rm-dist")
_ = cmd.Flags().MarkHidden("deprecated")
root.cmd = cmd
return root
}
func buildProject(options buildOpts) (*context.Context, error) {
cfg, err := loadConfig(options.config)
if err != nil {
return nil, err
}
ctx, cancel := context.NewWithTimeout(cfg, options.timeout)
defer cancel()
if err := setupBuildContext(ctx, options); err != nil {
return nil, err
}
return ctx, ctrlc.Default.Run(ctx, func() error {
for _, pipe := range setupPipeline(ctx, options) {
feat: improve output and pipe skipping (#2480) * refactor: improve middleware Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: upload tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: twitter tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: source tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: snapshot tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * test: improved some tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: snapcraft skip Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip slack Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip sign Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip scoop Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip reddit Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip discord Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip publish Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip nfpm Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip milestone Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip custompublishers Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip checksums Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip changelog Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip brew Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip blob Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip before Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip artifactory Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip announce Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip defaults Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: cmds Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip docker Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * chore: todo Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: go.mod Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip release Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: remove old skip pipe errors Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip teams Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip brew Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix/test: skip smtp Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: lint issues Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip docker Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip brew and scoop Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip docker Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: skip http/artifactory Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * test: increase coverage Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * test: fix Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>
2021-09-18 15:21:29 +02:00
if err := skip.Maybe(
pipe,
logging.Log(
pipe.String(),
errhandler.Handle(pipe.Run),
),
)(ctx); err != nil {
return err
}
}
return nil
})
}
func setupPipeline(ctx *context.Context, options buildOpts) []pipeline.Piper {
if options.output != "" && options.singleTarget && (len(options.ids) > 0 || len(ctx.Config.Builds) == 1) {
return append(pipeline.BuildCmdPipeline, withOutputPipe{options.output})
}
return pipeline.BuildCmdPipeline
}
func setupBuildContext(ctx *context.Context, options buildOpts) error {
ctx.Deprecated = options.deprecated // test only
fix: set parallelism to match Linux container CPU (#3901) <!-- Hi, thanks for contributing! Please make sure you read our CONTRIBUTING guide. Also, add tests and the respective documentation changes as well. --> Currently Goreleaser uses `runtime.NumCPU()` as the default value if `--parallelism` is not set. However, this will get the number of CPUs on the host even when Goreleaser is run in a container with a limit on the maximum number of CPUs that can be used (typically in a Kubernetes pod). Actually, `docker run --cpus=1 goreleaser/goreleaser --debug` shows `parallelism: 4` on my machine. This behavior causes CPU throttling, which increases execution time and, in the worst case, terminates with an error. I ran into this problem with Jenkins where the agent runs on pod ([Kubernetes plugin for Jenkins](https://plugins.jenkins.io/kubernetes/)). This commit introduces [automaxprocs](https://github.com/uber-go/automaxprocs) to fix this issue. This library sets `GOMAXPROCS` to match Linux container CPU quota. I have also looked for a library that can get CPU quota more directly, but this seems to be the best I could find. The reason it is set in a different notation from the automaxprocs README is to prevent logs from being displayed ([comment](https://github.com/uber-go/automaxprocs/issues/18#issuecomment-511330567)). I would have liked to write a test, but this change is dependent on the number of CPUs in the execution environment, so I could not. Instead, I wrote a Dockerfile for testing ```Dockerfile FROM golang:1.20.2 WORKDIR /go/app RUN sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin COPY . . RUN task build ``` and confirmed built binary shows expected parallelism by following commands: ```sh docker build --file Dockerfile.test . -t test-goreleaser docker run --cpus=1 test-goreleaser ./goreleaser build --snapshot --debug # parallelism: 1 docker run test-goreleaser ./goreleaser build --snapshot --debug # parallelism: 4 ``` I also ran the built binary on my Macbook and it was fine.
2023-04-02 22:16:41 +02:00
ctx.Parallelism = runtime.GOMAXPROCS(0)
if options.parallelism > 0 {
ctx.Parallelism = options.parallelism
}
log.Debugf("parallelism: %v", ctx.Parallelism)
ctx.Snapshot = options.snapshot
ctx.SkipValidate = ctx.Snapshot || options.skipValidate
ctx.SkipBefore = options.skipBefore
ctx.SkipPostBuildHooks = options.skipPostHooks
ctx.SkipTokenCheck = true
ctx.Clean = options.clean || options.rmDist
if options.rmDist {
deprecate.NoticeCustom(ctx, "-rm-dist", "--rm-dist was deprecated in favor of --clean, check {{ .URL }} for more details")
}
if options.singleTarget {
setupBuildSingleTarget(ctx)
}
if len(options.ids) > 0 {
if err := setupBuildID(ctx, options.ids); err != nil {
return err
}
}
return nil
}
func setupBuildSingleTarget(ctx *context.Context) {
goos := os.Getenv("GOOS")
if goos == "" {
goos = runtime.GOOS
}
goarch := os.Getenv("GOARCH")
if goarch == "" {
goarch = runtime.GOARCH
}
log.WithField("reason", "single target is enabled").Warnf("building only for %s/%s", goos, goarch)
if len(ctx.Config.Builds) == 0 {
ctx.Config.Builds = append(ctx.Config.Builds, config.Build{})
}
for i := range ctx.Config.Builds {
build := &ctx.Config.Builds[i]
build.Goos = []string{goos}
build.Goarch = []string{goarch}
build.Goarm = nil
build.Gomips = nil
build.Goamd64 = nil
build.Targets = nil
}
ctx.Config.UniversalBinaries = nil
}
func setupBuildID(ctx *context.Context, ids []string) error {
if len(ctx.Config.Builds) < 2 {
log.Warn("single build in config, '--id' ignored")
return nil
}
var keep []config.Build
for _, build := range ctx.Config.Builds {
for _, id := range ids {
if build.ID == id {
keep = append(keep, build)
break
}
}
}
if len(keep) == 0 {
return fmt.Errorf("no builds with ids %s", strings.Join(ids, ", "))
}
ctx.Config.Builds = keep
return nil
}
// withOutputPipe copies the binary from dist to the specified output path.
type withOutputPipe struct {
output string
}
func (w withOutputPipe) String() string {
return fmt.Sprintf("copying binary to %q", w.output)
}
func (w withOutputPipe) Run(ctx *context.Context) error {
bins := ctx.Artifacts.Filter(artifact.ByType(artifact.Binary)).List()
if len(bins) == 0 {
return fmt.Errorf("no binary found")
}
path := bins[0].Path
out := w.output
if out == "." {
out = filepath.Base(path)
}
return gio.Copy(path, out)
}