From 25a054c5e113c6b121aaff3841bdffa7f316bd8c Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Mon, 27 Nov 2023 18:29:50 -0300 Subject: [PATCH] feat: improve --single-target (#4442) closes #4437 closes #4426 --------- Signed-off-by: Carlos Alexandro Becker --- cmd/build.go | 57 +----------- cmd/build_test.go | 127 +------------------------- internal/pipe/build/build.go | 2 +- internal/pipe/build/filter.go | 28 ++++++ internal/pipe/build/filter_test.go | 43 +++++++++ internal/pipe/partial/partial.go | 25 +++++ internal/pipe/partial/partial_test.go | 57 ++++++++++++ internal/pipeline/pipeline.go | 3 + internal/testctx/testctx.go | 4 + pkg/context/context.go | 2 + www/docs/contributing.md | 1 + 11 files changed, 168 insertions(+), 181 deletions(-) create mode 100644 internal/pipe/build/filter.go create mode 100644 internal/pipe/build/filter_test.go create mode 100644 internal/pipe/partial/partial.go create mode 100644 internal/pipe/partial/partial_test.go diff --git a/cmd/build.go b/cmd/build.go index c1402971257..529a70d0391 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - "os" "path/filepath" "runtime" "strings" @@ -22,7 +21,6 @@ import ( "github.com/goreleaser/goreleaser/pkg/config" "github.com/goreleaser/goreleaser/pkg/context" "github.com/spf13/cobra" - "golang.org/x/exp/slices" ) type buildCmd struct { @@ -125,7 +123,7 @@ When using ` + "`--single-target`" + `, the ` + "`GOOS`" + ` and ` + "`GOARCH`" &root.opts.skips, "skip", nil, - fmt.Sprintf("Skip the given options (valid options are %s)", skips.Build.String()), + fmt.Sprintf("Skip the given options (valid options are: %s)", skips.Build.String()), ) _ = cmd.RegisterFlagCompletionFunc("skip", func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) { return skips.Build.Complete(toComplete), cobra.ShellCompDirectiveDefault @@ -176,6 +174,7 @@ func setupBuildContext(ctx *context.Context, options buildOpts) error { } log.Debugf("parallelism: %v", ctx.Parallelism) ctx.Snapshot = options.snapshot + if err := skips.SetBuild(ctx, options.skips...); err != nil { return err } @@ -205,9 +204,7 @@ func setupBuildContext(ctx *context.Context, options buildOpts) error { ctx.Clean = options.clean || options.rmDist if options.singleTarget { - if err := setupBuildSingleTarget(ctx); err != nil { - return err - } + ctx.Partial = true } if len(options.ids) > 0 { @@ -226,54 +223,6 @@ func setupBuildContext(ctx *context.Context, options buildOpts) error { return nil } -func setupBuildSingleTarget(ctx *context.Context) error { - 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{}) - } - var keep []config.Build - for _, build := range ctx.Config.Builds { - if !shouldBuild(build, goos, goarch) { - continue - } - build.Goos = []string{goos} - build.Goarch = []string{goarch} - build.Goarm = nil - build.Gomips = nil - build.Goamd64 = nil - build.Targets = nil - keep = append(keep, build) - } - - ctx.Config.Builds = keep - ctx.Config.UniversalBinaries = nil - - if len(keep) == 0 { - return fmt.Errorf("no builds matching --single-target %s/%s", goos, goarch) - } - - return nil -} - -func shouldBuild(build config.Build, goos, goarch string) bool { - if len(build.Targets) > 0 { - return slices.ContainsFunc(build.Targets, func(e string) bool { - return strings.HasPrefix(e, fmt.Sprintf("%s_%s", goos, goarch)) - }) - } - return (len(build.Goos) == 0 && len(build.Goarch) == 0) || - (slices.Contains(build.Goos, goos) && - slices.Contains(build.Goarch, goarch)) -} - func setupBuildID(ctx *context.Context, ids []string) error { if len(ctx.Config.Builds) < 2 { log.Warn("single build in config, '--id' ignored") diff --git a/cmd/build_test.go b/cmd/build_test.go index d97de541aba..210a8b858f8 100644 --- a/cmd/build_test.go +++ b/cmd/build_test.go @@ -1,7 +1,6 @@ package cmd import ( - "runtime" "testing" "github.com/goreleaser/goreleaser/internal/pipeline" @@ -133,17 +132,7 @@ func TestSetupPipeline(t *testing.T) { func TestBuildFlags(t *testing.T) { setup := func(opts buildOpts) *context.Context { - ctx := testctx.NewWithCfg(config.Project{ - Builds: []config.Build{ - { - Goos: []string{runtime.GOOS}, - Goarch: []string{runtime.GOARCH}, - }, - { - Targets: []string{"linux_arm64"}, - }, - }, - }) + ctx := testctx.New() require.NoError(t, setupBuildContext(ctx, opts)) return ctx } @@ -189,49 +178,6 @@ func TestBuildFlags(t *testing.T) { }).Clean) }) - t.Run("single-target", func(t *testing.T) { - opts := buildOpts{ - singleTarget: true, - } - - t.Run("runtime", func(t *testing.T) { - result := setup(opts) - require.Len(t, result.Config.Builds, 1) - require.Equal(t, []string{runtime.GOOS}, result.Config.Builds[0].Goos) - require.Equal(t, []string{runtime.GOARCH}, result.Config.Builds[0].Goarch) - }) - - t.Run("no matches", func(t *testing.T) { - t.Setenv("GOOS", "windows") - t.Setenv("GOARCH", "arm64") - ctx := testctx.NewWithCfg(config.Project{ - Builds: []config.Build{{ - Goos: []string{"linux"}, - }}, - }) - require.EqualError(t, setupBuildContext(ctx, opts), "no builds matching --single-target windows/arm64") - }) - - t.Run("default config", func(t *testing.T) { - ctx := testctx.NewWithCfg(config.Project{ - Builds: []config.Build{{}}, - }) - require.NoError(t, setupBuildContext(ctx, opts)) - require.Len(t, ctx.Config.Builds, 1) - require.Equal(t, []string{runtime.GOOS}, ctx.Config.Builds[0].Goos) - require.Equal(t, []string{runtime.GOARCH}, ctx.Config.Builds[0].Goarch) - }) - - t.Run("from env", func(t *testing.T) { - t.Setenv("GOOS", "linux") - t.Setenv("GOARCH", "arm64") - result := setup(opts) - require.Len(t, result.Config.Builds, 1) - require.Equal(t, []string{"linux"}, result.Config.Builds[0].Goos) - require.Equal(t, []string{"arm64"}, result.Config.Builds[0].Goarch) - }) - }) - t.Run("id", func(t *testing.T) { t.Run("match", func(t *testing.T) { ctx := testctx.NewWithCfg(config.Project{ @@ -318,74 +264,3 @@ func TestBuildFlags(t *testing.T) { }) }) } - -func TestBuildSingleTargetWithSpecificTargets(t *testing.T) { - ctx := testctx.NewWithCfg(config.Project{ - ProjectName: "test", - Builds: []config.Build{ - { - Targets: []string{ - "linux_amd64_v1", - "darwin_arm64", - "darwin_amd64_v1", - }, - }, - }, - UniversalBinaries: []config.UniversalBinary{ - {Replace: true}, - }, - }) - - t.Setenv("GOOS", "darwin") - t.Setenv("GOARCH", "amd64") - setupBuildSingleTarget(ctx) - require.Len(t, ctx.Config.Builds, 1) - require.Equal(t, config.Build{ - Goos: []string{"darwin"}, - Goarch: []string{"amd64"}, - }, ctx.Config.Builds[0]) - require.Nil(t, ctx.Config.UniversalBinaries) -} - -func TestBuildSingleTargetNoMatch(t *testing.T) { - ctx := testctx.NewWithCfg(config.Project{ - ProjectName: "test", - Builds: []config.Build{ - { - Goos: []string{"linux", "darwin"}, - Goarch: []string{"amd64", "arm64"}, - Goamd64: []string{"v1", "v2"}, - Goarm: []string{"6"}, - Gomips: []string{"anything"}, - }, - }, - }) - - t.Setenv("GOOS", "windows") - t.Setenv("GOARCH", "amd64") - require.Error(t, setupBuildSingleTarget(ctx)) - require.Empty(t, ctx.Config.Builds) -} - -func TestBuildSingleTargetRemoveOtherOptions(t *testing.T) { - ctx := testctx.NewWithCfg(config.Project{ - ProjectName: "test", - Builds: []config.Build{ - { - Goos: []string{"linux", "darwin"}, - Goarch: []string{"amd64", "arm64"}, - Goamd64: []string{"v1", "v2"}, - Goarm: []string{"6"}, - Gomips: []string{"anything"}, - }, - }, - }) - - t.Setenv("GOOS", "linux") - t.Setenv("GOARCH", "amd64") - setupBuildSingleTarget(ctx) - require.Equal(t, config.Build{ - Goos: []string{"linux"}, - Goarch: []string{"amd64"}, - }, ctx.Config.Builds[0]) -} diff --git a/internal/pipe/build/build.go b/internal/pipe/build/build.go index c29527f9534..9d755452a1a 100644 --- a/internal/pipe/build/build.go +++ b/internal/pipe/build/build.go @@ -88,7 +88,7 @@ func buildWithDefaults(ctx *context.Context, build config.Build) (config.Build, } func runPipeOnBuild(ctx *context.Context, g semerrgroup.Group, build config.Build) { - for _, target := range build.Targets { + for _, target := range filter(ctx, build.Targets) { target := target build := build g.Go(func() error { diff --git a/internal/pipe/build/filter.go b/internal/pipe/build/filter.go new file mode 100644 index 00000000000..7fe15f32aaf --- /dev/null +++ b/internal/pipe/build/filter.go @@ -0,0 +1,28 @@ +package build + +import ( + "fmt" + "strings" + + "github.com/caarlos0/log" + "github.com/goreleaser/goreleaser/pkg/context" +) + +func filter(ctx *context.Context, targets []string) []string { + if !ctx.Partial { + return targets + } + + target := ctx.PartialTarget + log.WithField("match", fmt.Sprintf("target=%s", target)).Infof("partial build") + + var result []string + for _, t := range targets { + if !strings.HasPrefix(t, target) { + continue + } + result = append(result, t) + break + } + return result +} diff --git a/internal/pipe/build/filter_test.go b/internal/pipe/build/filter_test.go new file mode 100644 index 00000000000..cfca4787d6f --- /dev/null +++ b/internal/pipe/build/filter_test.go @@ -0,0 +1,43 @@ +package build + +import ( + "testing" + + "github.com/goreleaser/goreleaser/internal/testctx" + "github.com/goreleaser/goreleaser/pkg/context" + "github.com/stretchr/testify/require" +) + +var filterTestTargets = []string{ + "linux_amd64_v1", + "linux_arm64", + "linux_riscv64", + "darwin_amd64_v1", + "darwin_amd64_v2", + "darwin_arm64", +} + +func TestFilter(t *testing.T) { + t.Run("none", func(t *testing.T) { + ctx := testctx.New() + require.Equal(t, filterTestTargets, filter(ctx, filterTestTargets)) + }) + + t.Run("target", func(t *testing.T) { + ctx := testctx.New(func(ctx *context.Context) { + ctx.Partial = true + ctx.PartialTarget = "darwin_amd64" + }) + require.Equal(t, []string{ + "darwin_amd64_v1", + }, filter(ctx, filterTestTargets)) + }) + + t.Run("target no match", func(t *testing.T) { + ctx := testctx.New(func(ctx *context.Context) { + ctx.Partial = true + ctx.PartialTarget = "linux_amd64_v1" + }) + require.Empty(t, filter(ctx, []string{"darwin_amd64_v1"})) + }) +} diff --git a/internal/pipe/partial/partial.go b/internal/pipe/partial/partial.go new file mode 100644 index 00000000000..0f4f2f93168 --- /dev/null +++ b/internal/pipe/partial/partial.go @@ -0,0 +1,25 @@ +package partial + +import ( + "os" + "runtime" + + "github.com/charmbracelet/x/exp/ordered" + "github.com/goreleaser/goreleaser/pkg/context" +) + +type Pipe struct{} + +func (Pipe) String() string { return "partial" } +func (Pipe) Skip(ctx *context.Context) bool { return !ctx.Partial } + +func (Pipe) Run(ctx *context.Context) error { + ctx.PartialTarget = getFilter() + return nil +} + +func getFilter() string { + goos := ordered.First(os.Getenv("GGOOS"), os.Getenv("GOOS"), runtime.GOOS) + goarch := ordered.First(os.Getenv("GGOARCH"), os.Getenv("GOARCH"), runtime.GOARCH) + return goos + "_" + goarch +} diff --git a/internal/pipe/partial/partial_test.go b/internal/pipe/partial/partial_test.go new file mode 100644 index 00000000000..c93cacddeca --- /dev/null +++ b/internal/pipe/partial/partial_test.go @@ -0,0 +1,57 @@ +package partial + +import ( + "fmt" + "runtime" + "testing" + + "github.com/goreleaser/goreleaser/internal/testctx" + "github.com/goreleaser/goreleaser/pkg/config" + "github.com/stretchr/testify/require" +) + +var pipe = Pipe{} + +func TestString(t *testing.T) { + require.NotEmpty(t, pipe.String()) +} + +func TestSkip(t *testing.T) { + t.Run("partial", func(t *testing.T) { + ctx := testctx.New(testctx.Partial) + require.False(t, pipe.Skip(ctx)) + }) + + t.Run("full", func(t *testing.T) { + require.True(t, pipe.Skip(testctx.New())) + }) +} + +func TestRun(t *testing.T) { + t.Run("target", func(t *testing.T) { + ctx := testctx.NewWithCfg(config.Project{ + Dist: "dist", + }, testctx.Partial) + t.Setenv("GOOS", "windows") + t.Setenv("GOARCH", "arm64") + require.NoError(t, pipe.Run(ctx)) + require.Equal(t, "windows_arm64", ctx.PartialTarget) + }) + t.Run("using GGOOS and GGOARCH", func(t *testing.T) { + ctx := testctx.NewWithCfg(config.Project{ + Dist: "dist", + }, testctx.Partial) + t.Setenv("GGOOS", "windows") + t.Setenv("GGOARCH", "arm64") + require.NoError(t, pipe.Run(ctx)) + require.Equal(t, "windows_arm64", ctx.PartialTarget) + }) + t.Run("using runtime", func(t *testing.T) { + ctx := testctx.NewWithCfg(config.Project{ + Dist: "dist", + }, testctx.Partial) + require.NoError(t, pipe.Run(ctx)) + target := fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH) + require.Equal(t, target, ctx.PartialTarget) + }) +} diff --git a/internal/pipeline/pipeline.go b/internal/pipeline/pipeline.go index d8c9d7991b8..c3dd32d2d52 100644 --- a/internal/pipeline/pipeline.go +++ b/internal/pipeline/pipeline.go @@ -24,6 +24,7 @@ import ( "github.com/goreleaser/goreleaser/internal/pipe/metadata" "github.com/goreleaser/goreleaser/internal/pipe/nfpm" "github.com/goreleaser/goreleaser/internal/pipe/nix" + "github.com/goreleaser/goreleaser/internal/pipe/partial" "github.com/goreleaser/goreleaser/internal/pipe/prebuild" "github.com/goreleaser/goreleaser/internal/pipe/publish" "github.com/goreleaser/goreleaser/internal/pipe/reportsizes" @@ -59,6 +60,8 @@ var BuildPipeline = []Piper{ semver.Pipe{}, // load default configs defaults.Pipe{}, + // setup things for partial builds/releases + partial.Pipe{}, // snapshot version handling snapshot.Pipe{}, // run global hooks before build diff --git a/internal/testctx/testctx.go b/internal/testctx/testctx.go index 0fcf59841bd..5769e1a1444 100644 --- a/internal/testctx/testctx.go +++ b/internal/testctx/testctx.go @@ -116,6 +116,10 @@ func Snapshot(ctx *context.Context) { ctx.Snapshot = true } +func Partial(ctx *context.Context) { + ctx.Partial = true +} + func NewWithCfg(c config.Project, opts ...Opt) *context.Context { ctx := context.New(c) for _, opt := range opts { diff --git a/pkg/context/context.go b/pkg/context/context.go index 37033d4077d..f193b8bf3d3 100644 --- a/pkg/context/context.go +++ b/pkg/context/context.go @@ -89,8 +89,10 @@ type Context struct { ReleaseFooterTmpl string Version string ModulePath string + PartialTarget string Snapshot bool FailFast bool + Partial bool SkipTokenCheck bool Clean bool PreRelease bool diff --git a/www/docs/contributing.md b/www/docs/contributing.md index 4861ef8f61b..e78c857866d 100644 --- a/www/docs/contributing.md +++ b/www/docs/contributing.md @@ -20,6 +20,7 @@ Other things you might need to run the tests: - [Podman](https://podman.io/) - [Snapcraft](https://snapcraft.io/) - [Syft](https://github.com/anchore/syft) +- [upx](https://upx.github.io/) Clone `goreleaser` anywhere: