1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2024-12-31 01:53:50 +02:00

feat: improve --single-target (#4442)

closes #4437 
closes #4426

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
Carlos Alexandro Becker 2023-11-27 18:29:50 -03:00 committed by GitHub
parent 6bce81c0be
commit 25a054c5e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 168 additions and 181 deletions

View File

@ -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")

View File

@ -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])
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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"}))
})
}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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: