From c42a2fdc7636554bf5e0ed4de8aab1c547e2b4fb Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Sat, 5 Feb 2022 18:19:12 -0300 Subject: [PATCH] feat: allow a --output flag on goreleaser build (#2701) * feat: allow a --output flag on goreleaser build Signed-off-by: Carlos A Becker * fix: single build always to copies to root Signed-off-by: Carlos A Becker --- cmd/build.go | 32 ++++++++++++++++++- cmd/build_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/cmd/build.go b/cmd/build.go index 184fc08f637..7ce4e4ef462 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -3,12 +3,15 @@ package cmd import ( "fmt" "os" + "path/filepath" "runtime" "time" "github.com/apex/log" "github.com/caarlos0/ctrlc" "github.com/fatih/color" + "github.com/goreleaser/goreleaser/internal/artifact" + "github.com/goreleaser/goreleaser/internal/gio" "github.com/goreleaser/goreleaser/internal/middleware/errhandler" "github.com/goreleaser/goreleaser/internal/middleware/logging" "github.com/goreleaser/goreleaser/internal/middleware/skip" @@ -34,6 +37,7 @@ type buildOpts struct { parallelism int timeout time.Duration singleTarget bool + output string } func newBuildCmd() *buildCmd { @@ -90,6 +94,7 @@ defaulting to the current's machine target if not set. cmd.Flags().BoolVar(&root.opts.singleTarget, "single-target", false, "Builds only for current GOOS and GOARCH") cmd.Flags().StringVar(&root.opts.id, "id", "", "Builds only the specified build id") cmd.Flags().BoolVar(&root.opts.deprecated, "deprecated", false, "Force print the deprecation message - tests only") + cmd.Flags().StringVarP(&root.opts.output, "output", "o", "", "Path to the binary, defaults to the distribution folder according to configs. Only taked into account when using --single-target and a single id (either with --id or if config only has one build)") _ = cmd.Flags().MarkHidden("deprecated") root.cmd = cmd @@ -107,7 +112,7 @@ func buildProject(options buildOpts) (*context.Context, error) { return nil, err } return ctx, ctrlc.Default.Run(ctx, func() error { - for _, pipe := range pipeline.BuildCmdPipeline { + for _, pipe := range setupPipeline(ctx, options) { if err := skip.Maybe( pipe, logging.Log( @@ -123,6 +128,13 @@ func buildProject(options buildOpts) (*context.Context, error) { }) } +func setupPipeline(ctx *context.Context, options buildOpts) []pipeline.Piper { + if options.singleTarget && (options.id != "" || len(ctx.Config.Builds) == 1) { + return append(pipeline.BuildCmdPipeline, withOutputPipe{options.output}) + } + return pipeline.BuildCmdPipeline +} + func setupBuildContext(ctx *context.Context, options buildOpts) error { ctx.Parallelism = runtime.NumCPU() if options.parallelism > 0 { @@ -191,3 +203,21 @@ func setupBuildID(ctx *context.Context, id string) error { 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 { + path := ctx.Artifacts.Filter(artifact.ByType(artifact.Binary)).List()[0].Path + out := w.output + if out == "" { + out = filepath.Base(path) + } + return gio.Copy(path, out) +} diff --git a/cmd/build_test.go b/cmd/build_test.go index 41414fc398a..b6d2c415e37 100644 --- a/cmd/build_test.go +++ b/cmd/build_test.go @@ -5,6 +5,7 @@ import ( "runtime" "testing" + "github.com/goreleaser/goreleaser/internal/pipeline" "github.com/goreleaser/goreleaser/pkg/config" "github.com/goreleaser/goreleaser/pkg/context" "github.com/stretchr/testify/require" @@ -40,6 +41,83 @@ func TestBuildBrokenProject(t *testing.T) { require.EqualError(t, cmd.cmd.Execute(), "failed to parse dir: .: main.go:1:1: expected 'package', found not") } +func TestSetupPipeline(t *testing.T) { + t.Run("regular", func(t *testing.T) { + require.Equal( + t, + pipeline.BuildCmdPipeline, + setupPipeline(context.New(config.Project{}), buildOpts{}), + ) + }) + + t.Run("single-target", func(t *testing.T) { + require.Equal( + t, + pipeline.BuildCmdPipeline, + setupPipeline(context.New(config.Project{}), buildOpts{ + singleTarget: true, + }), + ) + }) + + t.Run("single-target and id", func(t *testing.T) { + require.Equal( + t, + append(pipeline.BuildCmdPipeline, withOutputPipe{""}), + setupPipeline(context.New(config.Project{}), buildOpts{ + singleTarget: true, + id: "foo", + }), + ) + }) + + t.Run("single-target and single build on config", func(t *testing.T) { + require.Equal( + t, + append(pipeline.BuildCmdPipeline, withOutputPipe{""}), + setupPipeline( + context.New(config.Project{ + Builds: []config.Build{{}}, + }), + buildOpts{ + singleTarget: true, + }, + ), + ) + }) + + t.Run("single-target, id and output", func(t *testing.T) { + require.Equal( + t, + append(pipeline.BuildCmdPipeline, withOutputPipe{"foobar"}), + setupPipeline( + context.New(config.Project{}), + buildOpts{ + singleTarget: true, + id: "foo", + output: "foobar", + }, + ), + ) + }) + + t.Run("single-target, single build on config and output", func(t *testing.T) { + require.Equal( + t, + append(pipeline.BuildCmdPipeline, withOutputPipe{"zaz"}), + setupPipeline( + context.New(config.Project{ + Builds: []config.Build{{}}, + }), + buildOpts{ + singleTarget: true, + output: "zaz", + }, + ), + ) + }) +} + func TestBuildFlags(t *testing.T) { setup := func(opts buildOpts) *context.Context { ctx := context.New(config.Project{})