Skip to content

Commit

Permalink
feat: improve --single-target (#4442)
Browse files Browse the repository at this point in the history
closes #4437 
closes #4426

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
  • Loading branch information
caarlos0 committed Nov 27, 2023
1 parent 6bce81c commit 25a054c
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 181 deletions.
57 changes: 3 additions & 54 deletions cmd/build.go
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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")
Expand Down
127 changes: 1 addition & 126 deletions cmd/build_test.go
@@ -1,7 +1,6 @@
package cmd

import (
"runtime"
"testing"

"github.com/goreleaser/goreleaser/internal/pipeline"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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])
}
2 changes: 1 addition & 1 deletion internal/pipe/build/build.go
Expand Up @@ -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 {
Expand Down
28 changes: 28 additions & 0 deletions 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
}
43 changes: 43 additions & 0 deletions 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"}))
})
}
25 changes: 25 additions & 0 deletions 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
}

0 comments on commit 25a054c

Please sign in to comment.