diff --git a/config/config.go b/config/config.go index f2d8bbc3b..90977a53a 100644 --- a/config/config.go +++ b/config/config.go @@ -6,6 +6,7 @@ import ( "io" "io/ioutil" "os" + "strings" "github.com/apex/log" "gopkg.in/yaml.v2" @@ -78,6 +79,42 @@ type IgnoredBuild struct { Goos, Goarch, Goarm string } +// StringArray is a wrapper for an array of strings +type StringArray []string + +// UnmarshalYAML is a custom unmarshaler that wraps strings in arrays +func (a *StringArray) UnmarshalYAML(unmarshal func(interface{}) error) error { + var strings []string + if err := unmarshal(&strings); err != nil { + var str string + if err := unmarshal(&str); err != nil { + return err + } + *a = []string{str} + } else { + *a = strings + } + return nil +} + +// FlagArray is a wrapper for an array of strings +type FlagArray []string + +// UnmarshalYAML is a custom unmarshaler that wraps strings in arrays +func (a *FlagArray) UnmarshalYAML(unmarshal func(interface{}) error) error { + var flags []string + if err := unmarshal(&flags); err != nil { + var flagstr string + if err := unmarshal(&flagstr); err != nil { + return err + } + *a = strings.Fields(flagstr) + } else { + *a = flags + } + return nil +} + // Build contains the build configuration section type Build struct { Goos []string `yaml:",omitempty"` @@ -86,14 +123,14 @@ type Build struct { Targets []string `yaml:",omitempty"` Ignore []IgnoredBuild `yaml:",omitempty"` Main string `yaml:",omitempty"` - Ldflags string `yaml:",omitempty"` - Flags string `yaml:",omitempty"` + Ldflags StringArray `yaml:",omitempty"` + Flags FlagArray `yaml:",omitempty"` Binary string `yaml:",omitempty"` Hooks Hooks `yaml:",omitempty"` Env []string `yaml:",omitempty"` Lang string `yaml:",omitempty"` - Asmflags string `yaml:",omitempty"` - Gcflags string `yaml:",omitempty"` + Asmflags StringArray `yaml:",omitempty"` + Gcflags StringArray `yaml:",omitempty"` } // FormatOverride is used to specify a custom format for a specific GOOS. diff --git a/config/config_array_test.go b/config/config_array_test.go new file mode 100644 index 000000000..19417dcda --- /dev/null +++ b/config/config_array_test.go @@ -0,0 +1,126 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" +) + +type Unmarshaled struct { + Strings StringArray `yaml:",omitempty"` + Flags FlagArray `yaml:",omitempty"` +} + +type yamlUnmarshalTestCase struct { + yaml string + expected Unmarshaled + err string +} + +var stringArrayTests = []yamlUnmarshalTestCase{ + { + "", + Unmarshaled{}, + "", + }, + { + "strings: []", + Unmarshaled{ + Strings: StringArray{}, + }, + "", + }, + { + "strings: [one two, three]", + Unmarshaled{ + Strings: StringArray{"one two", "three"}, + }, + "", + }, + { + "strings: one two", + Unmarshaled{ + Strings: StringArray{"one two"}, + }, + "", + }, + { + "strings: {key: val}", + Unmarshaled{}, + "yaml: unmarshal errors:\n line 1: cannot unmarshal !!map into string", + }, +} + +var flagArrayTests = []yamlUnmarshalTestCase{ + { + "", + Unmarshaled{}, + "", + }, + { + "flags: []", + Unmarshaled{ + Flags: FlagArray{}, + }, + "", + }, + { + "flags: [one two, three]", + Unmarshaled{ + Flags: FlagArray{"one two", "three"}, + }, + "", + }, + { + "flags: one two", + Unmarshaled{ + Flags: FlagArray{"one", "two"}, + }, + "", + }, + { + "flags: {key: val}", + Unmarshaled{}, + "yaml: unmarshal errors:\n line 1: cannot unmarshal !!map into string", + }, +} + +func TestStringArray(t *testing.T) { + for _, testCase := range stringArrayTests { + var actual Unmarshaled + + err := yaml.UnmarshalStrict([]byte(testCase.yaml), &actual) + if testCase.err == "" { + assert.NoError(t, err) + assert.Equal(t, testCase.expected, actual) + } else { + assert.EqualError(t, err, testCase.err) + } + } +} + +// func TestStringArrayFailure(t *testing.T) { + // var source = ` +// strings: + // key: val +// ` + + // var actual Unmarshaled + // err := yaml.UnmarshalStrict([]byte(source), &actual) + // // assert.EqualError(t, err, ) +// } + +func TestFlagArray(t *testing.T) { + for _, testCase := range flagArrayTests { + var actual Unmarshaled + + err := yaml.UnmarshalStrict([]byte(testCase.yaml), &actual) + if testCase.err == "" { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, testCase.err) + } + assert.Equal(t, testCase.expected, actual) + } +} diff --git a/internal/builders/golang/build.go b/internal/builders/golang/build.go index 19eb0d6ce..13b5311f3 100644 --- a/internal/builders/golang/build.go +++ b/internal/builders/golang/build.go @@ -44,8 +44,8 @@ func (*Builder) WithDefaults(build config.Build) config.Build { if len(build.Goarm) == 0 { build.Goarm = []string{"6"} } - if build.Ldflags == "" { - build.Ldflags = "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}" + if len(build.Ldflags) == 0 { + build.Ldflags = []string{"-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"} } if len(build.Targets) == 0 { build.Targets = matrix(build) @@ -59,31 +59,29 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti return err } cmd := []string{"go", "build"} - if build.Flags != "" { - cmd = append(cmd, strings.Fields(build.Flags)...) - } - if build.Asmflags != "" { - flags, err := processField(ctx, build.Asmflags, "asmflags") - if err != nil { - return err - } - cmd = append(cmd, "-asmflags="+flags) - } + cmd = append(cmd, build.Flags...) - if build.Gcflags != "" { - flags, err := processField(ctx, build.Gcflags, "gcflags") - if err != nil { - return err - } - cmd = append(cmd, "-gcflags="+flags) - } - - flags, err := processField(ctx, build.Ldflags, "ldflags") + asmflags, err := processFlags(ctx, build.Asmflags, "asmflags", "-asmflags=") if err != nil { return err } - cmd = append(cmd, "-ldflags="+flags, "-o", options.Path, build.Main) + cmd = append(cmd, asmflags...) + + gcflags, err := processFlags(ctx, build.Gcflags, "gcflags", "-gcflags=") + if err != nil { + return err + } + cmd = append(cmd, gcflags...) + + ldflags, err := processFlags(ctx, build.Ldflags, "ldflags", "-ldflags=") + if err != nil { + return err + } + cmd = append(cmd, ldflags...) + + cmd = append(cmd, "-o", options.Path, build.Main) + target, err := newBuildTarget(options.Target) if err != nil { return err @@ -107,6 +105,18 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti return nil } +func processFlags(ctx *context.Context, flags []string, flagName string, flagPrefix string) ([]string, error) { + processed := make([]string, 0, len(flags)) + for _, rawFlag := range flags { + flag, err := processField(ctx, rawFlag, flagName) + if err != nil { + return nil, err + } + processed = append(processed, flagPrefix+flag) + } + return processed, nil +} + func processField(ctx *context.Context, field string, fieldName string) (string, error) { var data = struct { Commit string diff --git a/internal/builders/golang/build_test.go b/internal/builders/golang/build_test.go index e7735c91c..dd963aa74 100644 --- a/internal/builders/golang/build_test.go +++ b/internal/builders/golang/build_test.go @@ -84,8 +84,8 @@ func TestBuild(t *testing.T) { "windows_amd64", "linux_arm_6", }, - Asmflags: "all=", - Gcflags: "all=", + Asmflags: []string{".=", "all="}, + Gcflags: []string{"all="}, }, }, } @@ -160,7 +160,7 @@ func TestBuildFailed(t *testing.T) { var config = config.Project{ Builds: []config.Build{ { - Flags: "-flag-that-dont-exists-to-force-failure", + Flags: []string{"-flag-that-dont-exists-to-force-failure"}, Targets: []string{ runtimeTarget, }, @@ -207,7 +207,7 @@ func TestRunInvalidAsmflags(t *testing.T) { Builds: []config.Build{ { Binary: "nametest", - Asmflags: "{{.Version}", + Asmflags: []string{"{{.Version}"}, Targets: []string{ runtimeTarget, }, @@ -229,7 +229,7 @@ func TestRunInvalidGcflags(t *testing.T) { Builds: []config.Build{ { Binary: "nametest", - Gcflags: "{{.Version}", + Gcflags: []string{"{{.Version}"}, Targets: []string{ runtimeTarget, }, @@ -251,8 +251,8 @@ func TestRunInvalidLdflags(t *testing.T) { Builds: []config.Build{ { Binary: "nametest", - Flags: "-v", - Ldflags: "-s -w -X main.version={{.Version}", + Flags: []string{"-v"}, + Ldflags: []string{"-s -w -X main.version={{.Version}"}, Targets: []string{ runtimeTarget, }, @@ -352,7 +352,9 @@ func TestLdFlagsFullTemplate(t *testing.T) { var config = config.Project{ Builds: []config.Build{ { - Ldflags: `-s -w -X main.version={{.Version}} -X main.tag={{.Tag}} -X main.date={{.Date}} -X main.commit={{.Commit}} -X "main.foo={{.Env.FOO}}" -X main.time={{ time "20060102" }}`, + Ldflags: []string{ + `-s -w -X main.version={{.Version}} -X main.tag={{.Tag}} -X main.date={{.Date}} -X main.commit={{.Commit}} -X "main.foo={{.Env.FOO}}" -X main.time={{ time "20060102" }}`, + }, }, }, } @@ -365,7 +367,7 @@ func TestLdFlagsFullTemplate(t *testing.T) { Config: config, Env: map[string]string{"FOO": "123"}, } - flags, err := processField(ctx, ctx.Config.Builds[0].Ldflags, "ldflags") + flags, err := processField(ctx, ctx.Config.Builds[0].Ldflags[0], "ldflags") assert.NoError(t, err) assert.Contains(t, flags, "-s -w") assert.Contains(t, flags, "-X main.version=1.2.3") @@ -385,7 +387,7 @@ func TestInvalidTemplate(t *testing.T) { t.Run(template, func(tt *testing.T) { var config = config.Project{ Builds: []config.Build{ - {Ldflags: template}, + {Ldflags: []string{template}}, }, } var ctx = &context.Context{ @@ -398,6 +400,41 @@ func TestInvalidTemplate(t *testing.T) { } } +func TestProcessFlags(t *testing.T) { + var ctx = &context.Context{ + Version: "1.2.3", + } + + var source = []string{ + "{{.Version}}", + "flag", + } + + var expected = []string{ + "-testflag=1.2.3", + "-testflag=flag", + } + + flags, err := processFlags(ctx, source, "testflag", "-testflag=") + assert.NoError(t, err) + assert.Len(t, flags, 2) + assert.Equal(t, expected, flags) +} + +func TestProcessFlagsInvalid(t *testing.T) { + var ctx = &context.Context{} + + var source = []string{ + "{{.Version}", + } + + var expected = `template: testflag:1: unexpected "}" in operand` + + flags, err := processFlags(ctx, source, "testflag", "-testflag=") + assert.EqualError(t, err, expected) + assert.Nil(t, flags) +} + // // Helpers // diff --git a/pipeline/build/build_test.go b/pipeline/build/build_test.go index cb1e33c91..7bdca1e87 100644 --- a/pipeline/build/build_test.go +++ b/pipeline/build/build_test.go @@ -53,7 +53,7 @@ func TestBuild(t *testing.T) { { Lang: "fake", Binary: "testing.v{{.Version}}", - Flags: "-n", + Flags: []string{"-n"}, Env: []string{"BLAH=1"}, }, }, @@ -77,8 +77,8 @@ func TestRunPipe(t *testing.T) { { Lang: "fake", Binary: "testing", - Flags: "-v", - Ldflags: "-X main.test=testing", + Flags: []string{"-v"}, + Ldflags: []string{"-X main.test=testing"}, Targets: []string{"whatever"}, }, }, @@ -98,8 +98,8 @@ func TestRunFullPipe(t *testing.T) { { Lang: "fake", Binary: "testing", - Flags: "-v", - Ldflags: "-X main.test=testing", + Flags: []string{"-v"}, + Ldflags: []string{"-X main.test=testing"}, Hooks: config.Hooks{ Pre: "touch " + pre, Post: "touch " + post, @@ -125,8 +125,8 @@ func TestRunFullPipeFail(t *testing.T) { { Lang: "fakeFail", Binary: "testing", - Flags: "-v", - Ldflags: "-X main.test=testing", + Flags: []string{"-v"}, + Ldflags: []string{"-X main.test=testing"}, Hooks: config.Hooks{ Pre: "touch " + pre, Post: "touch " + post, @@ -212,7 +212,8 @@ func TestDefaultEmptyBuild(t *testing.T) { assert.Equal(t, []string{"linux", "darwin"}, build.Goos) assert.Equal(t, []string{"amd64", "386"}, build.Goarch) assert.Equal(t, []string{"6"}, build.Goarm) - assert.Equal(t, "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}", build.Ldflags) + assert.Len(t, build.Ldflags, 1) + assert.Equal(t, "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}", build.Ldflags[0]) } func TestDefaultPartialBuilds(t *testing.T) { @@ -226,7 +227,7 @@ func TestDefaultPartialBuilds(t *testing.T) { }, { Binary: "foo", - Ldflags: "-s -w", + Ldflags: []string{"-s -w"}, Goarch: []string{"386"}, }, }, @@ -240,7 +241,8 @@ func TestDefaultPartialBuilds(t *testing.T) { assert.Equal(t, []string{"linux"}, build.Goos) assert.Equal(t, []string{"amd64", "386"}, build.Goarch) assert.Equal(t, []string{"6"}, build.Goarm) - assert.Equal(t, "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}", build.Ldflags) + assert.Len(t, build.Ldflags, 1) + assert.Equal(t, "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}", build.Ldflags[0]) }) t.Run("build1", func(t *testing.T) { var build = ctx.Config.Builds[1] @@ -249,7 +251,8 @@ func TestDefaultPartialBuilds(t *testing.T) { assert.Equal(t, []string{"linux", "darwin"}, build.Goos) assert.Equal(t, []string{"386"}, build.Goarch) assert.Equal(t, []string{"6"}, build.Goarm) - assert.Equal(t, "-s -w", build.Ldflags) + assert.Len(t, build.Ldflags, 1) + assert.Equal(t, "-s -w", build.Ldflags[0]) }) } diff --git a/www/content/build.md b/www/content/build.md index d4fb9fa3b..01436879d 100644 --- a/www/content/build.md +++ b/www/content/build.md @@ -32,10 +32,12 @@ builds: # Set flags for custom build tags. # Default is empty. - flags: -tags dev + flags: + - -tags + - dev - # Custom asmflags template. - # This is parsed with the Go template engine and the following variables + # Custom asmflags templates. + # These are parsed with the Go template engine and the following variables # are available: # - Date # - Commit @@ -47,10 +49,12 @@ builds: # `time "2006-01-02"` too if you need custom formats # # Default is empty. - asmflags: all=-trimpath={{.Env.GOPATH}} + asmflags: + - -D mysymbol + - all=-trimpath={{.Env.GOPATH}} - # Custom gcflags template. - # This is parsed with the Go template engine and the following variables + # Custom gcflags templates. + # These are parsed with the Go template engine and the following variables # are available: # - Date # - Commit @@ -62,10 +66,12 @@ builds: # `time "2006-01-02"` too if you need custom formats # # Default is empty. - gcflags: all=-trimpath={{.Env.GOPATH}} + gcflags: + - all=-trimpath={{.Env.GOPATH}} + - ./dontoptimizeme=-N - # Custom ldflags template. - # This is parsed with the Go template engine and the following variables + # Custom ldflags templates. + # These are parsed with the Go template engine and the following variables # are available: # - Date # - Commit @@ -77,7 +83,9 @@ builds: # `time "2006-01-02"` too if you need custom formats # # Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`. - ldflags: -s -w -X main.build={{.Version}} + ldflags: + - -s -w -X main.build={{.Version}} + - ./usemsan=-msan # Custom environment variables to be set during the builds. # Default is empty. @@ -130,7 +138,8 @@ example: ```yaml builds: - - ldflags: -s -w -X "main.goversion={{.Env.GOVERSION}}" + - ldflags: + - -s -w -X "main.goversion={{.Env.GOVERSION}}" ``` Then you can run: