Repository: goreleaser/goreleaser
Pull request #3213 (2022-06-29 02:31:01)

feat: allow parallelism by build target
Instead of running parallel builds within a build "section", do it for every build.

closes #3210 #3209


Patch:
diff --git a/cmd/build.go b/cmd/build.go
index 83d14d0d..e2afc054 100644
--- a/cmd/build.go
+++ b/cmd/build.go
@@ -5,6 +5,7 @@ import (
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 	"time"
 
 	"github.com/caarlos0/ctrlc"
@@ -27,7 +28,7 @@ type buildCmd struct {
 
 type buildOpts struct {
 	config        string
-	id            string
+	ids           []string
 	snapshot      bool
 	skipValidate  bool
 	skipBefore    bool
@@ -77,7 +78,7 @@ When using ` + "`--single-target`" + `, the ` + "`GOOS`" + ` and ` + "`GOARCH`"
 	cmd.Flags().IntVarP(&root.opts.parallelism, "parallelism", "p", 0, "Amount tasks to run concurrently (default: number of CPUs)")
 	cmd.Flags().DurationVar(&root.opts.timeout, "timeout", 30*time.Minute, "Timeout to the entire build process")
 	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().StringArrayVar(&root.opts.ids, "id", nil, "Builds only the specified build ids")
 	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 config only has one build)")
 	_ = cmd.Flags().MarkHidden("deprecated")
@@ -113,7 +114,7 @@ func buildProject(options buildOpts) (*context.Context, error) {
 }
 
 func setupPipeline(ctx *context.Context, options buildOpts) []pipeline.Piper {
-	if options.output != "" && options.singleTarget && (options.id != "" || len(ctx.Config.Builds) == 1) {
+	if options.output != "" && options.singleTarget && (len(options.ids) > 0 || len(ctx.Config.Builds) == 1) {
 		return append(pipeline.BuildCmdPipeline, withOutputPipe{options.output})
 	}
 	return pipeline.BuildCmdPipeline
@@ -136,8 +137,8 @@ func setupBuildContext(ctx *context.Context, options buildOpts) error {
 		setupBuildSingleTarget(ctx)
 	}
 
-	if options.id != "" {
-		if err := setupBuildID(ctx, options.id); err != nil {
+	if len(options.ids) > 0 {
+		if err := setupBuildID(ctx, options.ids); err != nil {
 			return err
 		}
 	}
@@ -167,7 +168,7 @@ func setupBuildSingleTarget(ctx *context.Context) {
 	}
 }
 
-func setupBuildID(ctx *context.Context, id string) error {
+func setupBuildID(ctx *context.Context, ids []string) error {
 	if len(ctx.Config.Builds) < 2 {
 		log.Warn("single build in config, '--id' ignored")
 		return nil
@@ -175,14 +176,16 @@ func setupBuildID(ctx *context.Context, id string) error {
 
 	var keep []config.Build
 	for _, build := range ctx.Config.Builds {
-		if build.ID == id {
-			keep = append(keep, build)
-			break
+		for _, id := range ids {
+			if build.ID == id {
+				keep = append(keep, build)
+				break
+			}
 		}
 	}
 
 	if len(keep) == 0 {
-		return fmt.Errorf("no builds with id '%s'", id)
+		return fmt.Errorf("no builds with ids %s", strings.Join(ids, ", "))
 	}
 
 	ctx.Config.Builds = keep
diff --git a/internal/pipe/build/build.go b/internal/pipe/build/build.go
index e9198dbb..a05f42d2 100644
--- a/internal/pipe/build/build.go
+++ b/internal/pipe/build/build.go
@@ -31,17 +31,16 @@ func (Pipe) String() string {
 
 // Run the pipe.
 func (Pipe) Run(ctx *context.Context) error {
+	g := semerrgroup.New(ctx.Parallelism)
 	for _, build := range ctx.Config.Builds {
 		if build.Skip {
 			log.WithField("id", build.ID).Info("skip is set")
 			continue
 		}
 		log.WithField("build", build).Debug("building")
-		if err := runPipeOnBuild(ctx, build); err != nil {
-			return err
-		}
+		runPipeOnBuild(ctx, g, build)
 	}
-	return nil
+	return g.Wait()
 }
 
 // Default sets the pipe defaults.
@@ -81,8 +80,7 @@ func buildWithDefaults(ctx *context.Context, build config.Build) (config.Build,
 	return builders.For(build.Builder).WithDefaults(build)
 }
 
-func runPipeOnBuild(ctx *context.Context, build config.Build) error {
-	g := semerrgroup.New(ctx.Parallelism)
+func runPipeOnBuild(ctx *context.Context, g semerrgroup.Group, build config.Build) {
 	for _, target := range build.Targets {
 		target := target
 		build := build
@@ -106,8 +104,6 @@ func runPipeOnBuild(ctx *context.Context, build config.Build) error {
 			return nil
 		})
 	}
-
-	return g.Wait()
 }
 
 func runHook(ctx *context.Context, opts builders.Options, buildEnv []string, hooks config.Hooks) error {


Test patch:
diff --git a/cmd/build_test.go b/cmd/build_test.go
index 5bb879df..396a2ed3 100644
--- a/cmd/build_test.go
+++ b/cmd/build_test.go
@@ -66,7 +66,7 @@ func TestSetupPipeline(t *testing.T) {
 			pipeline.BuildCmdPipeline,
 			setupPipeline(context.New(config.Project{}), buildOpts{
 				singleTarget: true,
-				id:           "foo",
+				ids:          []string{"foo"},
 			}),
 		)
 	})
@@ -77,7 +77,7 @@ func TestSetupPipeline(t *testing.T) {
 			append(pipeline.BuildCmdPipeline, withOutputPipe{"foobar"}),
 			setupPipeline(context.New(config.Project{}), buildOpts{
 				singleTarget: true,
-				id:           "foo",
+				ids:          []string{"foo"},
 				output:       ".",
 			}),
 		)
@@ -106,7 +106,7 @@ func TestSetupPipeline(t *testing.T) {
 				context.New(config.Project{}),
 				buildOpts{
 					singleTarget: true,
-					id:           "foo",
+					ids:          []string{"foo"},
 					output:       "foobar",
 				},
 			),
@@ -205,7 +205,39 @@ func TestBuildFlags(t *testing.T) {
 				},
 			})
 			require.NoError(t, setupBuildContext(ctx, buildOpts{
-				id: "foo",
+				ids: []string{"foo"},
+			}))
+		})
+
+		t.Run("match-multiple", func(t *testing.T) {
+			ctx := context.New(config.Project{
+				Builds: []config.Build{
+					{
+						ID: "default",
+					},
+					{
+						ID: "foo",
+					},
+				},
+			})
+			require.NoError(t, setupBuildContext(ctx, buildOpts{
+				ids: []string{"foo", "default"},
+			}))
+		})
+
+		t.Run("match-partial", func(t *testing.T) {
+			ctx := context.New(config.Project{
+				Builds: []config.Build{
+					{
+						ID: "default",
+					},
+					{
+						ID: "foo",
+					},
+				},
+			})
+			require.NoError(t, setupBuildContext(ctx, buildOpts{
+				ids: []string{"foo", "notdefault"},
 			}))
 		})
 
@@ -221,14 +253,14 @@ func TestBuildFlags(t *testing.T) {
 				},
 			})
 			require.EqualError(t, setupBuildContext(ctx, buildOpts{
-				id: "bar",
-			}), "no builds with id 'bar'")
+				ids: []string{"bar", "fooz"},
+			}), "no builds with ids bar, fooz")
 		})
 
 		t.Run("default config", func(t *testing.T) {
 			ctx := context.New(config.Project{})
 			require.NoError(t, setupBuildContext(ctx, buildOpts{
-				id: "aaa",
+				ids: []string{"aaa"},
 			}))
 		})
 
@@ -241,7 +273,7 @@ func TestBuildFlags(t *testing.T) {
 				},
 			})
 			require.NoError(t, setupBuildContext(ctx, buildOpts{
-				id: "not foo but doesnt matter",
+				ids: []string{"not foo but doesnt matter"},
 			}))
 		})
 	})
diff --git a/internal/pipe/build/build_test.go b/internal/pipe/build/build_test.go
index 83f98dc6..7155f8fd 100644
--- a/internal/pipe/build/build_test.go
+++ b/internal/pipe/build/build_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 
 	"github.com/goreleaser/goreleaser/internal/artifact"
+	"github.com/goreleaser/goreleaser/internal/semerrgroup"
 	"github.com/goreleaser/goreleaser/internal/testlib"
 	"github.com/goreleaser/goreleaser/internal/tmpl"
 	api "github.com/goreleaser/goreleaser/pkg/build"
@@ -489,8 +490,9 @@ func TestBuild_hooksKnowGoosGoarch(t *testing.T) {
 			build,
 		},
 	})
-	err := runPipeOnBuild(ctx, build)
-	require.NoError(t, err)
+	g := semerrgroup.New(ctx.Parallelism)
+	runPipeOnBuild(ctx, g, build)
+	require.NoError(t, g.Wait())
 	require.FileExists(t, filepath.Join(tmpDir, "pre-hook-amd64-linux"))
 	require.FileExists(t, filepath.Join(tmpDir, "post-hook-amd64-linux"))
 }
@@ -520,8 +522,9 @@ func TestPipeOnBuild_hooksRunPerTarget(t *testing.T) {
 			build,
 		},
 	})
-	err := runPipeOnBuild(ctx, build)
-	require.NoError(t, err)
+	g := semerrgroup.New(ctx.Parallelism)
+	runPipeOnBuild(ctx, g, build)
+	require.NoError(t, g.Wait())
 	require.FileExists(t, filepath.Join(tmpDir, "pre-hook-linux_amd64"))
 	require.FileExists(t, filepath.Join(tmpDir, "pre-hook-darwin_amd64"))
 	require.FileExists(t, filepath.Join(tmpDir, "pre-hook-windows_amd64"))
@@ -543,8 +546,9 @@ func TestPipeOnBuild_invalidBinaryTpl(t *testing.T) {
 			build,
 		},
 	})
-	err := runPipeOnBuild(ctx, build)
-	require.EqualError(t, err, `template: tmpl:1:11: executing "tmpl" at <.XYZ>: map has no entry for key "XYZ"`)
+	g := semerrgroup.New(ctx.Parallelism)
+	runPipeOnBuild(ctx, g, build)
+	require.EqualError(t, g.Wait(), `template: tmpl:1:11: executing "tmpl" at <.XYZ>: map has no entry for key "XYZ"`)
 }
 
 func TestBuildOptionsForTarget(t *testing.T) {