diff --git a/internal/builders/golang/build.go b/internal/builders/golang/build.go index e12eab27c1b..ebb4b23fcb4 100644 --- a/internal/builders/golang/build.go +++ b/internal/builders/golang/build.go @@ -19,6 +19,7 @@ import ( api "github.com/goreleaser/goreleaser/pkg/build" "github.com/goreleaser/goreleaser/pkg/config" "github.com/goreleaser/goreleaser/pkg/context" + "github.com/imdario/mergo" ) // Default builder instance. @@ -129,29 +130,60 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti return nil } +func withOverrides(ctx *context.Context, build config.Build, options api.Options) (config.BuildDetails, error) { + optsTarget := options.Goos + options.Goarch + options.Goarm + options.Gomips + for _, o := range build.BuildDetailsOverrides { + overrideTarget, err := tmpl.New(ctx).Apply(o.Goos + o.Goarch + o.Gomips + o.Goarm) + if err != nil { + return build.BuildDetails, err + } + + if optsTarget == overrideTarget { + dets := config.BuildDetails{ + Ldflags: build.BuildDetails.Ldflags, + Tags: build.BuildDetails.Tags, + Flags: build.BuildDetails.Flags, + Asmflags: build.BuildDetails.Asmflags, + Gcflags: build.BuildDetails.Gcflags, + } + if err := mergo.Merge(&dets, o.BuildDetails, mergo.WithOverride); err != nil { + return build.BuildDetails, err + } + log.WithField("dets", dets).Info("will use") + return dets, nil + } + } + return build.BuildDetails, nil +} + func buildGoBuildLine(ctx *context.Context, build config.Build, options api.Options, artifact *artifact.Artifact, env []string) ([]string, error) { cmd := []string{build.GoBinary, "build"} - flags, err := processFlags(ctx, artifact, env, build.Flags, "") + + details, err := withOverrides(ctx, build, options) + if err != nil { + return cmd, err + } + flags, err := processFlags(ctx, artifact, env, details.Flags, "") if err != nil { return cmd, err } cmd = append(cmd, flags...) - asmflags, err := processFlags(ctx, artifact, env, build.Asmflags, "-asmflags=") + asmflags, err := processFlags(ctx, artifact, env, details.Asmflags, "-asmflags=") if err != nil { return cmd, err } cmd = append(cmd, asmflags...) - gcflags, err := processFlags(ctx, artifact, env, build.Gcflags, "-gcflags=") + gcflags, err := processFlags(ctx, artifact, env, details.Gcflags, "-gcflags=") if err != nil { return cmd, err } cmd = append(cmd, gcflags...) // tags is not a repeatable flag - if len(build.Tags) > 0 { - tags, err := processFlags(ctx, artifact, env, build.Tags, "") + if len(details.Tags) > 0 { + tags, err := processFlags(ctx, artifact, env, details.Tags, "") if err != nil { return cmd, err } @@ -159,9 +191,9 @@ func buildGoBuildLine(ctx *context.Context, build config.Build, options api.Opti } // ldflags is not a repeatable flag - if len(build.Ldflags) > 0 { + if len(details.Ldflags) > 0 { // flag prefix is skipped because ldflags need to output a single string - ldflags, err := processFlags(ctx, artifact, env, build.Ldflags, "") + ldflags, err := processFlags(ctx, artifact, env, details.Ldflags, "") if err != nil { return cmd, err } diff --git a/internal/builders/golang/build_test.go b/internal/builders/golang/build_test.go index 358e9e0b3e7..3b6c0337616 100644 --- a/internal/builders/golang/build_test.go +++ b/internal/builders/golang/build_test.go @@ -226,11 +226,13 @@ func TestBuild(t *testing.T) { "linux_mips_softfloat", "linux_mips64le_softfloat", }, - Asmflags: []string{".=", "all="}, - Gcflags: []string{"all="}, - Flags: []string{"{{.Env.GO_FLAGS}}"}, - Tags: []string{"osusergo", "netgo", "static_build"}, GoBinary: "go", + BuildDetails: config.BuildDetails{ + Asmflags: []string{".=", "all="}, + Gcflags: []string{"all="}, + Flags: []string{"{{.Env.GO_FLAGS}}"}, + Tags: []string{"osusergo", "netgo", "static_build"}, + }, }, }, } @@ -450,8 +452,10 @@ func TestBuildFailed(t *testing.T) { config := config.Project{ Builds: []config.Build{ { - ID: "buildid", - Flags: []string{"-flag-that-dont-exists-to-force-failure"}, + ID: "buildid", + BuildDetails: config.BuildDetails{ + Flags: []string{"-flag-that-dont-exists-to-force-failure"}, + }, Targets: []string{ runtimeTarget, }, @@ -474,8 +478,10 @@ func TestRunInvalidAsmflags(t *testing.T) { config := config.Project{ Builds: []config.Build{ { - Binary: "nametest", - Asmflags: []string{"{{.Version}"}, + Binary: "nametest", + BuildDetails: config.BuildDetails{ + Asmflags: []string{"{{.Version}"}, + }, Targets: []string{ runtimeTarget, }, @@ -496,8 +502,10 @@ func TestRunInvalidGcflags(t *testing.T) { config := config.Project{ Builds: []config.Build{ { - Binary: "nametest", - Gcflags: []string{"{{.Version}"}, + Binary: "nametest", + BuildDetails: config.BuildDetails{ + Gcflags: []string{"{{.Version}"}, + }, Targets: []string{ runtimeTarget, }, @@ -518,9 +526,11 @@ func TestRunInvalidLdflags(t *testing.T) { config := config.Project{ Builds: []config.Build{ { - Binary: "nametest", - Flags: []string{"-v"}, - Ldflags: []string{"-s -w -X main.version={{.Version}"}, + Binary: "nametest", + BuildDetails: config.BuildDetails{ + Flags: []string{"-v"}, + Ldflags: []string{"-s -w -X main.version={{.Version}"}, + }, Targets: []string{ runtimeTarget, }, @@ -542,7 +552,9 @@ func TestRunInvalidFlags(t *testing.T) { Builds: []config.Build{ { Binary: "nametest", - Flags: []string{"{{.Env.GOOS}"}, + BuildDetails: config.BuildDetails{ + Flags: []string{"{{.Env.GOOS}"}, + }, Targets: []string{ runtimeTarget, }, @@ -832,9 +844,11 @@ func TestBuildModTimestamp(t *testing.T) { "linux_mips_softfloat", "linux_mips64le_softfloat", }, - Asmflags: []string{".=", "all="}, - Gcflags: []string{"all="}, - Flags: []string{"{{.Env.GO_FLAGS}}"}, + BuildDetails: config.BuildDetails{ + Asmflags: []string{".=", "all="}, + Gcflags: []string{"all="}, + Flags: []string{"{{.Env.GO_FLAGS}}"}, + }, ModTimestamp: fmt.Sprintf("%d", modTime.Unix()), GoBinary: "go", }, @@ -888,19 +902,25 @@ func TestBuildGoBuildLine(t *testing.T) { ctx.Version = "1.2.3" ctx.Git.Commit = "aaa" - line, err := buildGoBuildLine(ctx, config.Builds[0], api.Options{Path: "foo"}, &artifact.Artifact{}, []string{}) + line, err := buildGoBuildLine(ctx, config.Builds[0], api.Options{ + Path: "foo", + Goos: "linux", + Goarch: "amd64", + }, &artifact.Artifact{}, []string{}) require.NoError(t, err) require.Equal(t, expected, line) } t.Run("full", func(t *testing.T) { requireEqualCmd(t, config.Build{ - Main: ".", - Asmflags: []string{"asmflag1", "asmflag2"}, - Gcflags: []string{"gcflag1", "gcflag2"}, - Flags: []string{"-flag1", "-flag2"}, - Tags: []string{"tag1", "tag2"}, - Ldflags: []string{"ldflag1", "ldflag2"}, + Main: ".", + BuildDetails: config.BuildDetails{ + Asmflags: []string{"asmflag1", "asmflag2"}, + Gcflags: []string{"gcflag1", "gcflag2"}, + Flags: []string{"-flag1", "-flag2"}, + Tags: []string{"tag1", "tag2"}, + Ldflags: []string{"ldflag1", "ldflag2"}, + }, GoBinary: "go", }, []string{ "go", "build", @@ -913,6 +933,41 @@ func TestBuildGoBuildLine(t *testing.T) { }) }) + t.Run("with overrides", func(t *testing.T) { + requireEqualCmd(t, config.Build{ + Main: ".", + BuildDetails: config.BuildDetails{ + Asmflags: []string{"asmflag1", "asmflag2"}, + Gcflags: []string{"gcflag1", "gcflag2"}, + Flags: []string{"-flag1", "-flag2"}, + Tags: []string{"tag1", "tag2"}, + Ldflags: []string{"ldflag1", "ldflag2"}, + }, + BuildDetailsOverrides: []config.BuildDetailsOverride{ + { + Goos: "linux", + Goarch: "amd64", + BuildDetails: config.BuildDetails{ + Asmflags: []string{"asmflag3"}, + Gcflags: []string{"gcflag3"}, + Flags: []string{"-flag3"}, + Tags: []string{"tag3"}, + Ldflags: []string{"ldflag3"}, + }, + }, + }, + GoBinary: "go", + }, []string{ + "go", "build", + "-flag3", + "-asmflags=asmflag3", + "-gcflags=gcflag3", + "-tags=tag3", + "-ldflags=ldflag3", + "-o", "foo", ".", + }) + }) + t.Run("simple", func(t *testing.T) { requireEqualCmd(t, config.Build{ Main: ".", @@ -922,8 +977,10 @@ func TestBuildGoBuildLine(t *testing.T) { t.Run("ldflags1", func(t *testing.T) { requireEqualCmd(t, config.Build{ - Main: ".", - Ldflags: []string{"-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.builtBy=goreleaser"}, + Main: ".", + BuildDetails: config.BuildDetails{ + Ldflags: []string{"-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.builtBy=goreleaser"}, + }, GoBinary: "go", }, []string{ "go", "build", @@ -934,13 +991,179 @@ func TestBuildGoBuildLine(t *testing.T) { t.Run("ldflags2", func(t *testing.T) { requireEqualCmd(t, config.Build{ - Main: ".", - Ldflags: []string{"-s -w", "-X main.version={{.Version}}"}, + Main: ".", + BuildDetails: config.BuildDetails{ + Ldflags: []string{"-s -w", "-X main.version={{.Version}}"}, + }, GoBinary: "go", }, []string{"go", "build", "-ldflags=-s -w -X main.version=1.2.3", "-o", "foo", "."}) }) } +func TestOverrides(t *testing.T) { + t.Run("linux amd64", func(t *testing.T) { + dets, err := withOverrides( + context.New(config.Project{}), + config.Build{ + BuildDetails: config.BuildDetails{ + Ldflags: []string{"original"}, + }, + BuildDetailsOverrides: []config.BuildDetailsOverride{ + { + Goos: "linux", + Goarch: "amd64", + BuildDetails: config.BuildDetails{ + Ldflags: []string{"overridden"}, + }, + }, + }, + }, api.Options{ + Goos: "linux", + Goarch: "amd64", + }, + ) + require.NoError(t, err) + require.Equal(t, dets, config.BuildDetails{ + Ldflags: []string{"overridden"}, + }) + }) + + t.Run("single sided", func(t *testing.T) { + dets, err := withOverrides( + context.New(config.Project{}), + config.Build{ + BuildDetails: config.BuildDetails{}, + BuildDetailsOverrides: []config.BuildDetailsOverride{ + { + Goos: "linux", + Goarch: "amd64", + BuildDetails: config.BuildDetails{ + Ldflags: []string{"overridden"}, + Tags: []string{"tag1"}, + Asmflags: []string{"asm1"}, + Gcflags: []string{"gcflag1"}, + }, + }, + }, + }, api.Options{ + Goos: "linux", + Goarch: "amd64", + }, + ) + require.NoError(t, err) + require.Equal(t, dets, config.BuildDetails{ + Ldflags: []string{"overridden"}, + Gcflags: []string{"gcflag1"}, + Asmflags: []string{"asm1"}, + Tags: []string{"tag1"}, + }) + }) + + t.Run("with template", func(t *testing.T) { + dets, err := withOverrides( + context.New(config.Project{}), + config.Build{ + BuildDetails: config.BuildDetails{ + Ldflags: []string{"original"}, + Asmflags: []string{"asm1"}, + }, + BuildDetailsOverrides: []config.BuildDetailsOverride{ + { + Goos: "{{ .Runtime.Goos }}", + Goarch: "{{ .Runtime.Goarch }}", + BuildDetails: config.BuildDetails{ + Ldflags: []string{"overridden"}, + }, + }, + }, + }, api.Options{ + Goos: runtime.GOOS, + Goarch: runtime.GOARCH, + }, + ) + require.NoError(t, err) + require.Equal(t, dets, config.BuildDetails{ + Ldflags: []string{"overridden"}, + Asmflags: []string{"asm1"}, + }) + }) + + t.Run("with invalid template", func(t *testing.T) { + _, err := withOverrides( + context.New(config.Project{}), + config.Build{ + BuildDetailsOverrides: []config.BuildDetailsOverride{ + { + Goos: "{{ .Runtime.Goos }", + }, + }, + }, api.Options{ + Goos: runtime.GOOS, + Goarch: runtime.GOARCH, + }, + ) + require.EqualError(t, err, `template: tmpl:1: unexpected "}" in operand`) + }) + + t.Run("with goarm", func(t *testing.T) { + dets, err := withOverrides( + context.New(config.Project{}), + config.Build{ + BuildDetails: config.BuildDetails{ + Ldflags: []string{"original"}, + }, + BuildDetailsOverrides: []config.BuildDetailsOverride{ + { + Goos: "linux", + Goarch: "arm", + Goarm: "6", + BuildDetails: config.BuildDetails{ + Ldflags: []string{"overridden"}, + }, + }, + }, + }, api.Options{ + Goos: "linux", + Goarch: "arm", + Goarm: "6", + }, + ) + require.NoError(t, err) + require.Equal(t, dets, config.BuildDetails{ + Ldflags: []string{"overridden"}, + }) + }) + + t.Run("with gomips", func(t *testing.T) { + dets, err := withOverrides( + context.New(config.Project{}), + config.Build{ + BuildDetails: config.BuildDetails{ + Ldflags: []string{"original"}, + }, + BuildDetailsOverrides: []config.BuildDetailsOverride{ + { + Goos: "linux", + Goarch: "mips", + Gomips: "softfloat", + BuildDetails: config.BuildDetails{ + Ldflags: []string{"overridden"}, + }, + }, + }, + }, api.Options{ + Goos: "linux", + Goarch: "mips", + Gomips: "softfloat", + }, + ) + require.NoError(t, err) + require.Equal(t, dets, config.BuildDetails{ + Ldflags: []string{"overridden"}, + }) + }) +} + // // Helpers // diff --git a/internal/pipe/build/build_test.go b/internal/pipe/build/build_test.go index 7163c4cbaa4..bffe80bc763 100644 --- a/internal/pipe/build/build_test.go +++ b/internal/pipe/build/build_test.go @@ -70,8 +70,10 @@ func TestBuild(t *testing.T) { { Builder: "fake", Binary: "testing.v{{.Version}}", - Flags: []string{"-n"}, - Env: []string{"BLAH=1"}, + BuildDetails: config.BuildDetails{ + Flags: []string{"-n"}, + }, + Env: []string{"BLAH=1"}, }, }, } @@ -98,8 +100,10 @@ func TestRunPipe(t *testing.T) { { Builder: "fake", Binary: "testing", - Flags: []string{"-v"}, - Ldflags: []string{"-X main.test=testing"}, + BuildDetails: config.BuildDetails{ + Flags: []string{"-v"}, + Ldflags: []string{"-X main.test=testing"}, + }, Targets: []string{"linux_amd64"}, }, }, @@ -122,8 +126,10 @@ func TestRunFullPipe(t *testing.T) { ID: "build1", Builder: "fake", Binary: "testing", - Flags: []string{"-v"}, - Ldflags: []string{"-X main.test=testing"}, + BuildDetails: config.BuildDetails{ + Flags: []string{"-v"}, + Ldflags: []string{"-X main.test=testing"}, + }, Hooks: config.BuildHookConfig{ Pre: []config.Hook{ {Cmd: "touch " + pre}, @@ -159,8 +165,10 @@ func TestRunFullPipeFail(t *testing.T) { { Builder: "fakeFail", Binary: "testing", - Flags: []string{"-v"}, - Ldflags: []string{"-X main.test=testing"}, + BuildDetails: config.BuildDetails{ + Flags: []string{"-v"}, + Ldflags: []string{"-X main.test=testing"}, + }, Hooks: config.BuildHookConfig{ Pre: []config.Hook{ {Cmd: "touch " + pre}, @@ -320,11 +328,13 @@ func TestDefaultPartialBuilds(t *testing.T) { Main: "./cmd/main.go", }, { - ID: "build2", - Binary: "foo", - Dir: "baz", - Ldflags: []string{"-s -w"}, - Goarch: []string{"386"}, + ID: "build2", + Binary: "foo", + Dir: "baz", + BuildDetails: config.BuildDetails{ + Ldflags: []string{"-s -w"}, + }, + Goarch: []string{"386"}, }, }, }, @@ -657,7 +667,9 @@ func TestRunHookFailWithLogs(t *testing.T) { { Builder: "fakeFail", Binary: "testing", - Flags: []string{"-v"}, + BuildDetails: config.BuildDetails{ + Flags: []string{"-v"}, + }, Hooks: config.BuildHookConfig{ Pre: []config.Hook{ {Cmd: "sh -c 'echo foo; exit 1'"}, diff --git a/pkg/config/config.go b/pkg/config/config.go index 6ce409c5124..3ae3b80b5f6 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -297,21 +297,35 @@ type Build struct { Ignore []IgnoredBuild `yaml:"ignore,omitempty"` Dir string `yaml:"dir,omitempty"` Main string `yaml:"main,omitempty"` - Ldflags StringArray `yaml:"ldflags,omitempty"` - Tags FlagArray `yaml:"tags,omitempty"` - Flags FlagArray `yaml:"flags,omitempty"` Binary string `yaml:"binary,omitempty"` Hooks BuildHookConfig `yaml:"hooks,omitempty"` Env []string `yaml:"env,omitempty"` Builder string `yaml:"builder,omitempty"` - Asmflags StringArray `yaml:"asmflags,omitempty"` - Gcflags StringArray `yaml:"gcflags,omitempty"` ModTimestamp string `yaml:"mod_timestamp,omitempty"` Skip bool `yaml:"skip,omitempty"` GoBinary string `yaml:"gobinary,omitempty"` NoUniqueDistDir bool `yaml:"no_unique_dist_dir,omitempty"` UnproxiedMain string `yaml:"-"` // used by gomod.proxy UnproxiedDir string `yaml:"-"` // used by gomod.proxy + + BuildDetails `yaml:",inline"` // nolint: tagliatelle + BuildDetailsOverrides []BuildDetailsOverride `yaml:"overrides,omitempty"` +} + +type BuildDetailsOverride struct { + Goos string `yaml:"goos,omitempty"` + Goarch string `yaml:"goarch,omitempty"` + Goarm string `yaml:"goarm,omitempty"` + Gomips string `yaml:"gomips,omitempty"` + BuildDetails `yaml:",inline"` // nolint: tagliatelle +} + +type BuildDetails struct { + Ldflags StringArray `yaml:"ldflags,omitempty"` + Tags FlagArray `yaml:"tags,omitempty"` + Flags FlagArray `yaml:"flags,omitempty"` + Asmflags StringArray `yaml:"asmflags,omitempty"` + Gcflags StringArray `yaml:"gcflags,omitempty"` } type BuildHookConfig struct { diff --git a/www/docs/customization/build.md b/www/docs/customization/build.md index a8f778bc170..4344182e519 100644 --- a/www/docs/customization/build.md +++ b/www/docs/customization/build.md @@ -155,6 +155,25 @@ builds: # Valid options are: `go` and `prebuilt`. # Defaults to `go`. builder: prebuilt + + # Overrides allows to override some fields for specific targets. + # This can be specially useful when using CGO. + # Note: it'll only match if the full target matches. + # + # Defaults to empty. + overrides: + - goos: darwin + goarch: arm64 + goarm: '' + gomips: '' + ldflags: + - foo + tags: + - bar + asmflags: + - foobar + gcflags: + - foobaz ``` !!! tip