diff --git a/internal/builders/golang/build.go b/internal/builders/golang/build.go index 24eead119..98fb31bc6 100644 --- a/internal/builders/golang/build.go +++ b/internal/builders/golang/build.go @@ -11,22 +11,24 @@ import ( "text/template" "time" - "github.com/goreleaser/goreleaser/build" + api "github.com/goreleaser/goreleaser/build" "github.com/goreleaser/goreleaser/config" "github.com/goreleaser/goreleaser/context" "github.com/goreleaser/goreleaser/internal/artifact" "github.com/pkg/errors" ) +// Default builder instance var Default = &Builder{} func init() { - build.Register("go", Default) + api.Register("go", Default) } -type Builder struct { -} +// Builder is golang builder +type Builder struct{} +// Default set the defaults for a golang build func (*Builder) Default(build config.Build) config.Build { if build.Main == "" { build.Main = "." @@ -43,28 +45,29 @@ func (*Builder) Default(build config.Build) config.Build { if build.Ldflags == "" { build.Ldflags = "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}" } - if build.Lang == "go" && len(build.Targets) == 0 { + if len(build.Targets) == 0 { build.Targets = matrix(build) } return build } -func (*Builder) Build(ctx *context.Context, cfg config.Build, options build.Options) error { - if err := checkMain(ctx, cfg); err != nil { +// Build builds a golang build +func (*Builder) Build(ctx *context.Context, build config.Build, options api.Options) error { + if err := checkMain(ctx, build); err != nil { return err } cmd := []string{"go", "build"} - if cfg.Flags != "" { - cmd = append(cmd, strings.Fields(cfg.Flags)...) + if build.Flags != "" { + cmd = append(cmd, strings.Fields(build.Flags)...) } - flags, err := ldflags(ctx, cfg) + flags, err := ldflags(ctx, build) if err != nil { return err } - cmd = append(cmd, "-ldflags="+flags, "-o", options.Path, cfg.Main) + cmd = append(cmd, "-ldflags="+flags, "-o", options.Path, build.Main) var target = newBuildTarget(options.Target) - var env = append(cfg.Env, target.Env()...) - if err := build.Run(ctx, cmd, env); err != nil { + var env = append(build.Env, target.Env()...) + if err := api.Run(ctx, cmd, env); err != nil { return errors.Wrapf(err, "failed to build for %s", options.Target) } ctx.Artifacts.Add(artifact.Artifact{ @@ -75,7 +78,7 @@ func (*Builder) Build(ctx *context.Context, cfg config.Build, options build.Opti Goarch: target.arch, Goarm: target.arm, Extra: map[string]string{ - "Binary": cfg.Binary, + "Binary": build.Binary, "Ext": options.Ext, }, }) diff --git a/internal/builders/golang/build_test.go b/internal/builders/golang/build_test.go index a46eb35b5..7eeed665c 100644 --- a/internal/builders/golang/build_test.go +++ b/internal/builders/golang/build_test.go @@ -1,13 +1,214 @@ package golang import ( + "io/ioutil" + "path/filepath" + "runtime" "testing" + api "github.com/goreleaser/goreleaser/build" "github.com/goreleaser/goreleaser/config" "github.com/goreleaser/goreleaser/context" + "github.com/goreleaser/goreleaser/internal/testlib" "github.com/stretchr/testify/assert" ) +var runtimeTarget = runtime.GOOS + "_" + runtime.GOARCH + +func TestBuild(t *testing.T) { + folder, back := testlib.Mktmp(t) + defer back() + writeGoodMain(t, folder) + var config = config.Project{ + Builds: []config.Build{ + { + Binary: "foo", + Goos: []string{ + "linux", + "windows", + "darwin", + }, + Goarch: []string{ + "amd64", + "arm", + }, + Goarm: []string{ + "6", + }, + }, + }, + } + var ctx = context.New(config) + var build = Default.Default(ctx.Config.Builds[0]) + assert.ElementsMatch(t, build.Targets, []string{ + "linux_amd64", + "darwin_amd64", + "windows_amd64", + "linux_arm_6", + }) + for _, target := range build.Targets { + var err = Default.Build(ctx, build, api.Options{ + Target: target, + Name: build.Binary, + Path: filepath.Join(folder, "dist", target, build.Binary), + }) + assert.NoError(t, err) + } + + assert.Len(t, ctx.Artifacts.List(), len(build.Targets)) +} + +func TestBuildFailed(t *testing.T) { + folder, back := testlib.Mktmp(t) + defer back() + writeGoodMain(t, folder) + var config = config.Project{ + Builds: []config.Build{ + { + Flags: "-flag-that-dont-exists-to-force-failure", + Targets: []string{ + runtimeTarget, + }, + }, + }, + } + var ctx = context.New(config) + var err = Default.Build(ctx, ctx.Config.Builds[0], api.Options{ + Target: "darwin_amd64", + }) + assertContainsError(t, err, `flag provided but not defined: -flag-that-dont-exists-to-force-failure`) + assert.Empty(t, ctx.Artifacts.List()) +} + +func TestRunInvalidLdflags(t *testing.T) { + folder, back := testlib.Mktmp(t) + defer back() + writeGoodMain(t, folder) + var config = config.Project{ + Builds: []config.Build{ + { + Binary: "nametest", + Flags: "-v", + Ldflags: "-s -w -X main.version={{.Version}", + Targets: []string{ + runtimeTarget, + }, + }, + }, + } + var ctx = context.New(config) + var err = Default.Build(ctx, ctx.Config.Builds[0], api.Options{ + Target: runtimeTarget, + }) + assert.EqualError(t, err, `template: ldflags:1: unexpected "}" in operand`) +} + +func TestRunPipeWithoutMainFunc(t *testing.T) { + folder, back := testlib.Mktmp(t) + defer back() + writeMainWithoutMainFunc(t, folder) + var config = config.Project{ + Builds: []config.Build{ + { + Binary: "no-main", + Hooks: config.Hooks{}, + Targets: []string{ + runtimeTarget, + }, + }, + }, + } + var ctx = context.New(config) + t.Run("empty", func(t *testing.T) { + ctx.Config.Builds[0].Main = "" + assert.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{ + Target: runtimeTarget, + }), `build for no-main does not contain a main function`) + }) + t.Run("not main.go", func(t *testing.T) { + ctx.Config.Builds[0].Main = "foo.go" + assert.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{ + Target: runtimeTarget, + }), `could not open foo.go: stat foo.go: no such file or directory`) + }) + t.Run("glob", func(t *testing.T) { + ctx.Config.Builds[0].Main = "." + assert.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{ + Target: runtimeTarget, + }), `build for no-main does not contain a main function`) + }) + t.Run("fixed main.go", func(t *testing.T) { + ctx.Config.Builds[0].Main = "main.go" + assert.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{ + Target: runtimeTarget, + }), `build for no-main does not contain a main function`) + }) +} + +func TestRunPipeWithMainFuncNotInMainGoFile(t *testing.T) { + folder, back := testlib.Mktmp(t) + defer back() + assert.NoError(t, ioutil.WriteFile( + filepath.Join(folder, "foo.go"), + []byte("package main\nfunc main() {println(0)}"), + 0644, + )) + var config = config.Project{ + Builds: []config.Build{ + { + Binary: "foo", + Hooks: config.Hooks{}, + Targets: []string{ + runtimeTarget, + }, + }, + }, + } + var ctx = context.New(config) + t.Run("empty", func(t *testing.T) { + ctx.Config.Builds[0].Main = "" + assert.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{ + Target: runtimeTarget, + })) + }) + t.Run("foo.go", func(t *testing.T) { + ctx.Config.Builds[0].Main = "foo.go" + assert.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{ + Target: runtimeTarget, + })) + }) + t.Run("glob", func(t *testing.T) { + ctx.Config.Builds[0].Main = "." + assert.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{ + Target: runtimeTarget, + })) + }) +} + +// FIXME: probably should be refactored +func TestRunPipeWithInvalidOS(t *testing.T) { + folder, back := testlib.Mktmp(t) + defer back() + writeGoodMain(t, folder) + var config = config.Project{ + Builds: []config.Build{ + { + Lang: "go", + Flags: "-v", + Goos: []string{ + "windows", + }, + Goarch: []string{ + "arm", + }, + }, + }, + } + assert.NoError(t, Default.Build(context.New(config), config.Builds[0], api.Options{ + Target: "windows_arm", + })) +} + func TestLdFlagsFullTemplate(t *testing.T) { var config = config.Project{ Builds: []config.Build{ @@ -55,3 +256,28 @@ func TestInvalidTemplate(t *testing.T) { }) } } + +// +// Helpers +// + +func writeMainWithoutMainFunc(t *testing.T, folder string) { + assert.NoError(t, ioutil.WriteFile( + filepath.Join(folder, "main.go"), + []byte("package main\nconst a = 2\nfunc notMain() {println(0)}"), + 0644, + )) +} + +func writeGoodMain(t *testing.T, folder string) { + assert.NoError(t, ioutil.WriteFile( + filepath.Join(folder, "main.go"), + []byte("package main\nvar a = 1\nfunc main() {println(0)}"), + 0644, + )) +} + +func assertContainsError(t *testing.T, err error, s string) { + assert.Error(t, err) + assert.Contains(t, err.Error(), s) +} diff --git a/pipeline/build/build.go b/pipeline/build/build.go index 858842812..d49bf12ec 100644 --- a/pipeline/build/build.go +++ b/pipeline/build/build.go @@ -1,4 +1,5 @@ -// Package build needs to be documented +// Package build provides a pipe that can build binaries for several +// languages. package build import ( diff --git a/pipeline/build/build_test.go b/pipeline/build/build_test.go index 67fac54f7..2b227752b 100644 --- a/pipeline/build/build_test.go +++ b/pipeline/build/build_test.go @@ -1,12 +1,12 @@ package build import ( - "io/ioutil" "os" "path/filepath" "runtime" "testing" + api "github.com/goreleaser/goreleaser/build" "github.com/goreleaser/goreleaser/config" "github.com/goreleaser/goreleaser/context" "github.com/goreleaser/goreleaser/internal/testlib" @@ -15,14 +15,31 @@ import ( var emptyEnv []string +type fakeBuilder struct{} + +func (*fakeBuilder) Default(build config.Build) config.Build { + return build +} + +func (*fakeBuilder) Build(ctx *context.Context, build config.Build, options api.Options) error { + return nil +} + +func init() { + api.Register("fake", &fakeBuilder{}) +} + func TestPipeDescription(t *testing.T) { assert.NotEmpty(t, Pipe{}.String()) } func TestBuild(t *testing.T) { + var builder = &fakeBuilder{} + api.Register("fake", builder) var config = config.Project{ Builds: []config.Build{ { + Lang: "fake", Binary: "testing", Flags: "-n", Env: []string{"BLAH=1"}, @@ -30,19 +47,21 @@ func TestBuild(t *testing.T) { }, } var ctx = context.New(config) - assert.NoError(t, doBuild(ctx, ctx.Config.Builds[0], buildtarget.Runtime)) + assert.NoError(t, doBuild(ctx, ctx.Config.Builds[0], "darwin_amd64")) } +// FIXME: test is wrong func TestRunFullPipe(t *testing.T) { folder, back := testlib.Mktmp(t) defer back() writeGoodMain(t, folder) - var binary = filepath.Join(folder, buildtarget.Runtime.String(), "testing") + var binary = filepath.Join(folder, "darwin_amd64", "testing") var pre = filepath.Join(folder, "pre") var post = filepath.Join(folder, "post") var config = config.Project{ Builds: []config.Build{ { + Lang: "fake", Binary: "testing", Flags: "-v", Ldflags: "-X main.test=testing", @@ -103,74 +122,11 @@ func TestRunPipeArmBuilds(t *testing.T) { assert.True(t, exists(binary), binary) } -func TestBuildFailed(t *testing.T) { - folder, back := testlib.Mktmp(t) - defer back() - writeGoodMain(t, folder) - var config = config.Project{ - Builds: []config.Build{ - { - Flags: "-flag-that-dont-exists-to-force-failure", - Goos: []string{ - runtime.GOOS, - }, - Goarch: []string{ - runtime.GOARCH, - }, - }, - }, - } - var ctx = context.New(config) - assertContainsError(t, Pipe{}.Run(ctx), `flag provided but not defined: -flag-that-dont-exists-to-force-failure`) - assert.Empty(t, ctx.Artifacts.List()) -} - -func TestRunPipeWithInvalidOS(t *testing.T) { - folder, back := testlib.Mktmp(t) - defer back() - writeGoodMain(t, folder) - var config = config.Project{ - Builds: []config.Build{ - { - Flags: "-v", - Goos: []string{ - "windows", - }, - Goarch: []string{ - "arm", - }, - }, - }, - } - assert.NoError(t, Pipe{}.Run(context.New(config))) -} - -func TestRunInvalidLdflags(t *testing.T) { - folder, back := testlib.Mktmp(t) - defer back() - writeGoodMain(t, folder) - var config = config.Project{ - Builds: []config.Build{ - { - Binary: "nametest", - Flags: "-v", - Ldflags: "-s -w -X main.version={{.Version}", - Goos: []string{ - runtime.GOOS, - }, - Goarch: []string{ - runtime.GOARCH, - }, - }, - }, - } - assert.EqualError(t, Pipe{}.Run(context.New(config)), `template: ldflags:1: unexpected "}" in operand`) -} - +// FIXME: probably can use fake builder here func TestRunPipeFailingHooks(t *testing.T) { folder, back := testlib.Mktmp(t) defer back() - writeGoodMain(t, folder) + // writeGoodMain(t, folder) var config = config.Project{ Builds: []config.Build{ { @@ -199,80 +155,6 @@ func TestRunPipeFailingHooks(t *testing.T) { }) } -func TestRunPipeWithouMainFunc(t *testing.T) { - folder, back := testlib.Mktmp(t) - defer back() - writeMainWithoutMainFunc(t, folder) - var config = config.Project{ - Builds: []config.Build{ - { - Binary: "no-main", - Hooks: config.Hooks{}, - Goos: []string{ - runtime.GOOS, - }, - Goarch: []string{ - runtime.GOARCH, - }, - }, - }, - } - var ctx = context.New(config) - t.Run("empty", func(t *testing.T) { - ctx.Config.Builds[0].Main = "" - assert.EqualError(t, Pipe{}.Run(ctx), `build for no-main does not contain a main function`) - }) - t.Run("not main.go", func(t *testing.T) { - ctx.Config.Builds[0].Main = "foo.go" - assert.EqualError(t, Pipe{}.Run(ctx), `could not open foo.go: stat foo.go: no such file or directory`) - }) - t.Run("glob", func(t *testing.T) { - ctx.Config.Builds[0].Main = "." - assert.EqualError(t, Pipe{}.Run(ctx), `build for no-main does not contain a main function`) - }) - t.Run("fixed main.go", func(t *testing.T) { - ctx.Config.Builds[0].Main = "main.go" - assert.EqualError(t, Pipe{}.Run(ctx), `build for no-main does not contain a main function`) - }) -} - -func TestRunPipeWithMainFuncNotInMainGoFile(t *testing.T) { - folder, back := testlib.Mktmp(t) - defer back() - assert.NoError(t, ioutil.WriteFile( - filepath.Join(folder, "foo.go"), - []byte("package main\nfunc main() {println(0)}"), - 0644, - )) - var config = config.Project{ - Builds: []config.Build{ - { - Binary: "foo", - Hooks: config.Hooks{}, - Goos: []string{ - runtime.GOOS, - }, - Goarch: []string{ - runtime.GOARCH, - }, - }, - }, - } - var ctx = context.New(config) - t.Run("empty", func(t *testing.T) { - ctx.Config.Builds[0].Main = "" - assert.NoError(t, Pipe{}.Run(ctx)) - }) - t.Run("foo.go", func(t *testing.T) { - ctx.Config.Builds[0].Main = "foo.go" - assert.NoError(t, Pipe{}.Run(ctx)) - }) - t.Run("glob", func(t *testing.T) { - ctx.Config.Builds[0].Main = "." - assert.NoError(t, Pipe{}.Run(ctx)) - }) -} - func TestDefaultNoBuilds(t *testing.T) { var ctx = &context.Context{ Config: config.Project{}, @@ -362,32 +244,6 @@ func TestDefaultFillSingleBuild(t *testing.T) { assert.Equal(t, ctx.Config.Builds[0].Binary, "foo") } -func exists(file string) bool { - _, err := os.Stat(file) - return !os.IsNotExist(err) -} - -func writeMainWithoutMainFunc(t *testing.T, folder string) { - assert.NoError(t, ioutil.WriteFile( - filepath.Join(folder, "main.go"), - []byte("package main\nconst a = 2\nfunc notMain() {println(0)}"), - 0644, - )) -} - -func writeGoodMain(t *testing.T, folder string) { - assert.NoError(t, ioutil.WriteFile( - filepath.Join(folder, "main.go"), - []byte("package main\nvar a = 1\nfunc main() {println(0)}"), - 0644, - )) -} - -func assertContainsError(t *testing.T, err error, s string) { - assert.Error(t, err) - assert.Contains(t, err.Error(), s) -} - func TestExtWindows(t *testing.T) { assert.Equal(t, ".exe", extFor("windows_amd64")) assert.Equal(t, ".exe", extFor("windows_386")) @@ -398,3 +254,17 @@ func TestExtOthers(t *testing.T) { assert.Empty(t, "", extFor("linuxwin_386")) assert.Empty(t, "", extFor("winasdasd_sad")) } + +// +// Helpers +// + +func exists(file string) bool { + _, err := os.Stat(file) + return !os.IsNotExist(err) +} + +func assertContainsError(t *testing.T, err error, s string) { + assert.Error(t, err) + assert.Contains(t, err.Error(), s) +}