mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-24 04:16:27 +02:00
Merge pull request #522 from goreleaser/build
feat: support multiple build systems
This commit is contained in:
commit
81ab872b3d
37
build/build.go
Normal file
37
build/build.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Package build provides the API for external builders
|
||||
package build
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
)
|
||||
|
||||
var (
|
||||
builders = map[string]Builder{}
|
||||
lock sync.Mutex
|
||||
)
|
||||
|
||||
// Register registers a builder to a given lang
|
||||
func Register(lang string, builder Builder) {
|
||||
lock.Lock()
|
||||
builders[lang] = builder
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
// For gets the previously registered builder for the given lang
|
||||
func For(lang string) Builder {
|
||||
return builders[lang]
|
||||
}
|
||||
|
||||
// Options to be passed down to a builder
|
||||
type Options struct {
|
||||
Name, Path, Ext, Target string
|
||||
}
|
||||
|
||||
// Builder defines a builder
|
||||
type Builder interface {
|
||||
WithDefaults(build config.Build) config.Build
|
||||
Build(ctx *context.Context, build config.Build, options Options) error
|
||||
}
|
24
build/build_test.go
Normal file
24
build/build_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type dummy struct{}
|
||||
|
||||
func (*dummy) WithDefaults(build config.Build) config.Build {
|
||||
return build
|
||||
}
|
||||
func (*dummy) Build(ctx *context.Context, build config.Build, options Options) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRegisterAndGet(t *testing.T) {
|
||||
var builder = &dummy{}
|
||||
Register("dummy", builder)
|
||||
assert.Equal(t, builder, For("dummy"))
|
||||
}
|
@ -71,6 +71,7 @@ type Build struct {
|
||||
Goos []string `yaml:",omitempty"`
|
||||
Goarch []string `yaml:",omitempty"`
|
||||
Goarm []string `yaml:",omitempty"`
|
||||
Targets []string `yaml:",omitempty"`
|
||||
Ignore []IgnoredBuild `yaml:",omitempty"`
|
||||
Main string `yaml:",omitempty"`
|
||||
Ldflags string `yaml:",omitempty"`
|
||||
@ -78,6 +79,7 @@ type Build struct {
|
||||
Binary string `yaml:",omitempty"`
|
||||
Hooks Hooks `yaml:",omitempty"`
|
||||
Env []string `yaml:",omitempty"`
|
||||
Lang string `yaml:",omitempty"`
|
||||
}
|
||||
|
||||
// FormatOverride is used to specify a custom format for a specific GOOS.
|
||||
|
202
internal/builders/golang/build.go
Normal file
202
internal/builders/golang/build.go
Normal file
@ -0,0 +1,202 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
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() {
|
||||
api.Register("go", Default)
|
||||
}
|
||||
|
||||
// Builder is golang builder
|
||||
type Builder struct{}
|
||||
|
||||
// WithDefaults sets the defaults for a golang build and returns it
|
||||
func (*Builder) WithDefaults(build config.Build) config.Build {
|
||||
if build.Main == "" {
|
||||
build.Main = "."
|
||||
}
|
||||
if len(build.Goos) == 0 {
|
||||
build.Goos = []string{"linux", "darwin"}
|
||||
}
|
||||
if len(build.Goarch) == 0 {
|
||||
build.Goarch = []string{"amd64", "386"}
|
||||
}
|
||||
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.Targets) == 0 {
|
||||
build.Targets = matrix(build)
|
||||
}
|
||||
return build
|
||||
}
|
||||
|
||||
// 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 build.Flags != "" {
|
||||
cmd = append(cmd, strings.Fields(build.Flags)...)
|
||||
}
|
||||
flags, err := ldflags(ctx, build)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = append(cmd, "-ldflags="+flags, "-o", options.Path, build.Main)
|
||||
target, err := newBuildTarget(options.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var env = append(build.Env, target.Env()...)
|
||||
if err := run(ctx, cmd, env); err != nil {
|
||||
return errors.Wrapf(err, "failed to build for %s", options.Target)
|
||||
}
|
||||
ctx.Artifacts.Add(artifact.Artifact{
|
||||
Type: artifact.Binary,
|
||||
Path: options.Path,
|
||||
Name: options.Name,
|
||||
Goos: target.os,
|
||||
Goarch: target.arch,
|
||||
Goarm: target.arm,
|
||||
Extra: map[string]string{
|
||||
"Binary": build.Binary,
|
||||
"Ext": options.Ext,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func ldflags(ctx *context.Context, build config.Build) (string, error) {
|
||||
var data = struct {
|
||||
Commit string
|
||||
Tag string
|
||||
Version string
|
||||
Date string
|
||||
Env map[string]string
|
||||
}{
|
||||
Commit: ctx.Git.Commit,
|
||||
Tag: ctx.Git.CurrentTag,
|
||||
Version: ctx.Version,
|
||||
Date: time.Now().UTC().Format(time.RFC3339),
|
||||
Env: ctx.Env,
|
||||
}
|
||||
var out bytes.Buffer
|
||||
t, err := template.New("ldflags").
|
||||
Option("missingkey=error").
|
||||
Parse(build.Ldflags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = t.Execute(&out, data)
|
||||
return out.String(), err
|
||||
}
|
||||
|
||||
func run(ctx *context.Context, command, env []string) error {
|
||||
/* #nosec */
|
||||
var cmd = exec.CommandContext(ctx, command[0], command[1:]...)
|
||||
var log = log.WithField("env", env).WithField("cmd", command)
|
||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||
cmd.Env = append(cmd.Env, env...)
|
||||
log.WithField("cmd", command).WithField("env", env).Debug("running")
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
log.WithError(err).Debug("failed")
|
||||
return errors.New(string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type buildTarget struct {
|
||||
os, arch, arm string
|
||||
}
|
||||
|
||||
func newBuildTarget(s string) (buildTarget, error) {
|
||||
var t = buildTarget{}
|
||||
parts := strings.Split(s, "_")
|
||||
if len(parts) < 2 {
|
||||
return t, fmt.Errorf("%s is not a valid build target", s)
|
||||
}
|
||||
t.os = parts[0]
|
||||
t.arch = parts[1]
|
||||
if len(parts) == 3 {
|
||||
t.arm = parts[2]
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (b buildTarget) Env() []string {
|
||||
return []string{
|
||||
"GOOS=" + b.os,
|
||||
"GOARCH=" + b.arch,
|
||||
"GOARM=" + b.arm,
|
||||
}
|
||||
}
|
||||
|
||||
func checkMain(ctx *context.Context, build config.Build) error {
|
||||
var main = build.Main
|
||||
if main == "" {
|
||||
main = "."
|
||||
}
|
||||
stat, ferr := os.Stat(main)
|
||||
if os.IsNotExist(ferr) {
|
||||
return errors.Wrapf(ferr, "could not open %s", main)
|
||||
}
|
||||
if stat.IsDir() {
|
||||
packs, err := parser.ParseDir(token.NewFileSet(), main, nil, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse dir: %s", main)
|
||||
}
|
||||
for _, pack := range packs {
|
||||
for _, file := range pack.Files {
|
||||
if hasMain(file) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("build for %s does not contain a main function", build.Binary)
|
||||
}
|
||||
file, err := parser.ParseFile(token.NewFileSet(), main, nil, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse file: %s", main)
|
||||
}
|
||||
if hasMain(file) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("build for %s does not contain a main function", build.Binary)
|
||||
}
|
||||
|
||||
func hasMain(file *ast.File) bool {
|
||||
for _, decl := range file.Decls {
|
||||
fn, isFn := decl.(*ast.FuncDecl)
|
||||
if !isFn {
|
||||
continue
|
||||
}
|
||||
if fn.Name.Name == "main" && fn.Recv == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
376
internal/builders/golang/build_test.go
Normal file
376
internal/builders/golang/build_test.go
Normal file
@ -0,0 +1,376 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
api "github.com/goreleaser/goreleaser/build"
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
"github.com/goreleaser/goreleaser/internal/testlib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var runtimeTarget = runtime.GOOS + "_" + runtime.GOARCH
|
||||
|
||||
func TestWithDefaults(t *testing.T) {
|
||||
for name, testcase := range map[string]struct {
|
||||
build config.Build
|
||||
targets []string
|
||||
}{
|
||||
"full": {
|
||||
build: config.Build{
|
||||
Binary: "foo",
|
||||
Goos: []string{
|
||||
"linux",
|
||||
"windows",
|
||||
"darwin",
|
||||
},
|
||||
Goarch: []string{
|
||||
"amd64",
|
||||
"arm",
|
||||
},
|
||||
Goarm: []string{
|
||||
"6",
|
||||
},
|
||||
},
|
||||
targets: []string{
|
||||
"linux_amd64",
|
||||
"darwin_amd64",
|
||||
"windows_amd64",
|
||||
"linux_arm_6",
|
||||
},
|
||||
},
|
||||
"empty": {
|
||||
build: config.Build{
|
||||
Binary: "foo",
|
||||
},
|
||||
targets: []string{
|
||||
"linux_amd64",
|
||||
"linux_386",
|
||||
"darwin_amd64",
|
||||
"darwin_386",
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(tt *testing.T) {
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
testcase.build,
|
||||
},
|
||||
}
|
||||
var ctx = context.New(config)
|
||||
var build = Default.WithDefaults(ctx.Config.Builds[0])
|
||||
assert.ElementsMatch(t, build.Targets, testcase.targets)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
folder, back := testlib.Mktmp(t)
|
||||
defer back()
|
||||
writeGoodMain(t, folder)
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Binary: "foo",
|
||||
Targets: []string{
|
||||
"linux_amd64",
|
||||
"darwin_amd64",
|
||||
"windows_amd64",
|
||||
"linux_arm_6",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
var ctx = context.New(config)
|
||||
var build = ctx.Config.Builds[0]
|
||||
for _, target := range build.Targets {
|
||||
var ext string
|
||||
if strings.HasPrefix(target, "windows") {
|
||||
ext = ".exe"
|
||||
}
|
||||
var err = Default.Build(ctx, build, api.Options{
|
||||
Target: target,
|
||||
Name: build.Binary,
|
||||
Path: filepath.Join(folder, "dist", target, build.Binary),
|
||||
Ext: ext,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.ElementsMatch(t, ctx.Artifacts.List(), []artifact.Artifact{
|
||||
{
|
||||
Name: "foo",
|
||||
Path: filepath.Join(folder, "dist", "linux_amd64", "foo"),
|
||||
Goos: "linux",
|
||||
Goarch: "amd64",
|
||||
Type: artifact.Binary,
|
||||
Extra: map[string]string{
|
||||
"Ext": "",
|
||||
"Binary": "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Path: filepath.Join(folder, "dist", "darwin_amd64", "foo"),
|
||||
Goos: "darwin",
|
||||
Goarch: "amd64",
|
||||
Type: artifact.Binary,
|
||||
Extra: map[string]string{
|
||||
"Ext": "",
|
||||
"Binary": "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Path: filepath.Join(folder, "dist", "linux_arm_6", "foo"),
|
||||
Goos: "linux",
|
||||
Goarch: "arm",
|
||||
Goarm: "6",
|
||||
Type: artifact.Binary,
|
||||
Extra: map[string]string{
|
||||
"Ext": "",
|
||||
"Binary": "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Path: filepath.Join(folder, "dist", "windows_amd64", "foo"),
|
||||
Goos: "windows",
|
||||
Goarch: "amd64",
|
||||
Type: artifact.Binary,
|
||||
Extra: map[string]string{
|
||||
"Ext": ".exe",
|
||||
"Binary": "foo",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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 TestBuildInvalidTarget(t *testing.T) {
|
||||
folder, back := testlib.Mktmp(t)
|
||||
defer back()
|
||||
writeGoodMain(t, folder)
|
||||
var target = "linux"
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Binary: "foo",
|
||||
Targets: []string{target},
|
||||
},
|
||||
},
|
||||
}
|
||||
var ctx = context.New(config)
|
||||
var build = ctx.Config.Builds[0]
|
||||
var err = Default.Build(ctx, build, api.Options{
|
||||
Target: target,
|
||||
Name: build.Binary,
|
||||
Path: filepath.Join(folder, "dist", target, build.Binary),
|
||||
})
|
||||
assert.EqualError(t, err, "linux is not a valid build target")
|
||||
assert.Len(t, ctx.Artifacts.List(), 0)
|
||||
}
|
||||
|
||||
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,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
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}}"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
var ctx = &context.Context{
|
||||
Git: context.GitInfo{
|
||||
CurrentTag: "v1.2.3",
|
||||
Commit: "123",
|
||||
},
|
||||
Version: "1.2.3",
|
||||
Config: config,
|
||||
Env: map[string]string{"FOO": "123"},
|
||||
}
|
||||
flags, err := ldflags(ctx, ctx.Config.Builds[0])
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, flags, "-s -w")
|
||||
assert.Contains(t, flags, "-X main.version=1.2.3")
|
||||
assert.Contains(t, flags, "-X main.tag=v1.2.3")
|
||||
assert.Contains(t, flags, "-X main.commit=123")
|
||||
assert.Contains(t, flags, "-X main.date=")
|
||||
assert.Contains(t, flags, `-X "main.foo=123"`)
|
||||
}
|
||||
|
||||
func TestInvalidTemplate(t *testing.T) {
|
||||
for template, eerr := range map[string]string{
|
||||
"{{ .Nope }": `template: ldflags:1: unexpected "}" in operand`,
|
||||
"{{.Env.NOPE}}": `template: ldflags:1:6: executing "ldflags" at <.Env.NOPE>: map has no entry for key "NOPE"`,
|
||||
} {
|
||||
t.Run(template, func(tt *testing.T) {
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
{Ldflags: template},
|
||||
},
|
||||
}
|
||||
var ctx = &context.Context{
|
||||
Config: config,
|
||||
}
|
||||
flags, err := ldflags(ctx, ctx.Config.Builds[0])
|
||||
assert.EqualError(tt, err, eerr)
|
||||
assert.Empty(tt, flags)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 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)
|
||||
}
|
2
internal/builders/golang/doc.go
Normal file
2
internal/builders/golang/doc.go
Normal file
@ -0,0 +1,2 @@
|
||||
// Package golang provides a Builder implementation for golang.
|
||||
package golang
|
@ -1,52 +1,70 @@
|
||||
package buildtarget
|
||||
package golang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
)
|
||||
|
||||
// All returns all valid build targets for a given build
|
||||
func All(build config.Build) (targets []Target) {
|
||||
type target struct {
|
||||
os, arch, arm string
|
||||
}
|
||||
|
||||
func (t target) String() string {
|
||||
if t.arm != "" {
|
||||
return fmt.Sprintf("%s_%s_%s", t.os, t.arch, t.arm)
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", t.os, t.arch)
|
||||
}
|
||||
|
||||
func matrix(build config.Build) (result []string) {
|
||||
var targets []target
|
||||
for _, target := range allBuildTargets(build) {
|
||||
if !valid(target) {
|
||||
log.WithField("target", target.PrettyString()).
|
||||
log.WithField("target", target).
|
||||
Debug("skipped invalid build")
|
||||
continue
|
||||
}
|
||||
if ignored(build, target) {
|
||||
log.WithField("target", target.PrettyString()).
|
||||
log.WithField("target", target).
|
||||
Debug("skipped ignored build")
|
||||
continue
|
||||
}
|
||||
targets = append(targets, target)
|
||||
}
|
||||
for _, target := range targets {
|
||||
result = append(result, target.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func allBuildTargets(build config.Build) (targets []Target) {
|
||||
func allBuildTargets(build config.Build) (targets []target) {
|
||||
for _, goos := range build.Goos {
|
||||
for _, goarch := range build.Goarch {
|
||||
if goarch == "arm" {
|
||||
for _, goarm := range build.Goarm {
|
||||
targets = append(targets, New(goos, goarch, goarm))
|
||||
targets = append(targets, target{goos, goarch, goarm})
|
||||
}
|
||||
continue
|
||||
}
|
||||
targets = append(targets, New(goos, goarch, ""))
|
||||
targets = append(targets, target{goos, goarch, ""})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ignored(build config.Build, target Target) bool {
|
||||
// TODO: this could be improved by using a map
|
||||
// https://github.com/goreleaser/goreleaser/pull/522#discussion_r164245014
|
||||
func ignored(build config.Build, target target) bool {
|
||||
for _, ig := range build.Ignore {
|
||||
if ig.Goos != "" && ig.Goos != target.OS {
|
||||
if ig.Goos != "" && ig.Goos != target.os {
|
||||
continue
|
||||
}
|
||||
if ig.Goarch != "" && ig.Goarch != target.Arch {
|
||||
if ig.Goarch != "" && ig.Goarch != target.arch {
|
||||
continue
|
||||
}
|
||||
if ig.Goarm != "" && ig.Goarm != target.Arm {
|
||||
if ig.Goarm != "" && ig.Goarm != target.arm {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
@ -54,8 +72,8 @@ func ignored(build config.Build, target Target) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func valid(target Target) bool {
|
||||
var s = target.OS + target.Arch
|
||||
func valid(target target) bool {
|
||||
var s = target.os + target.arch
|
||||
for _, a := range validTargets {
|
||||
if a == s {
|
||||
return true
|
@ -1,4 +1,4 @@
|
||||
package buildtarget
|
||||
package golang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -40,19 +40,19 @@ func TestAllBuildTargets(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, []Target{
|
||||
New("linux", "386", ""),
|
||||
New("linux", "amd64", ""),
|
||||
New("linux", "arm", "6"),
|
||||
New("linux", "arm64", ""),
|
||||
New("darwin", "amd64", ""),
|
||||
New("freebsd", "386", ""),
|
||||
New("freebsd", "amd64", ""),
|
||||
New("freebsd", "arm", "6"),
|
||||
New("freebsd", "arm", "7"),
|
||||
New("openbsd", "386", ""),
|
||||
New("openbsd", "amd64", ""),
|
||||
}, All(build))
|
||||
assert.Equal(t, []string{
|
||||
"linux_386",
|
||||
"linux_amd64",
|
||||
"linux_arm_6",
|
||||
"linux_arm64",
|
||||
"darwin_amd64",
|
||||
"freebsd_386",
|
||||
"freebsd_amd64",
|
||||
"freebsd_arm_6",
|
||||
"freebsd_arm_7",
|
||||
"openbsd_386",
|
||||
"openbsd_amd64",
|
||||
}, matrix(build))
|
||||
}
|
||||
|
||||
func TestGoosGoarchCombos(t *testing.T) {
|
||||
@ -99,7 +99,7 @@ func TestGoosGoarchCombos(t *testing.T) {
|
||||
}
|
||||
for _, p := range platforms {
|
||||
t.Run(fmt.Sprintf("%v %v valid=%v", p.os, p.arch, p.valid), func(t *testing.T) {
|
||||
assert.Equal(t, p.valid, valid(New(p.os, p.arch, "")))
|
||||
assert.Equal(t, p.valid, valid(target{p.os, p.arch, ""}))
|
||||
})
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package buildtarget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Runtime is the current runtime build target
|
||||
var Runtime = Target{runtime.GOOS, runtime.GOARCH, ""}
|
||||
|
||||
// New build Target
|
||||
func New(goos, goarch, goarm string) Target {
|
||||
return Target{goos, goarch, goarm}
|
||||
}
|
||||
|
||||
// Target is a build target
|
||||
type Target struct {
|
||||
OS, Arch, Arm string
|
||||
}
|
||||
|
||||
// Env returns the current Target as environment variables
|
||||
func (t Target) Env() []string {
|
||||
return []string{
|
||||
"GOOS=" + t.OS,
|
||||
"GOARCH=" + t.Arch,
|
||||
"GOARM=" + t.Arm,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Target) String() string {
|
||||
// TODO: maybe replace this as suggested to OS_ArchArm?
|
||||
return fmt.Sprintf("%v%v%v", t.OS, t.Arch, t.Arm)
|
||||
}
|
||||
|
||||
// PrettyString is a prettier version of the String method.
|
||||
func (t Target) PrettyString() string {
|
||||
return fmt.Sprintf("%v/%v%v", t.OS, t.Arch, t.Arm)
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package buildtarget
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
assert.Equal(
|
||||
t,
|
||||
[]string{"GOOS=linux", "GOARCH=arm64", "GOARM=6"},
|
||||
New("linux", "arm64", "6").Env(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
assert.Equal(
|
||||
t,
|
||||
"linuxarm7",
|
||||
New("linux", "arm", "7").String(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestPrettyString(t *testing.T) {
|
||||
assert.Equal(
|
||||
t,
|
||||
"linux/arm646",
|
||||
New("linux", "arm64", "6").PrettyString(),
|
||||
)
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
// Package buildtarget provides the utilities targeting build matrixes.
|
||||
// TODO: probably this package should be removed and used only inside the build package
|
||||
package buildtarget
|
@ -1,11 +0,0 @@
|
||||
package ext
|
||||
|
||||
import "github.com/goreleaser/goreleaser/internal/buildtarget"
|
||||
|
||||
// For returns the binary extension for the given platform
|
||||
func For(target buildtarget.Target) string {
|
||||
if target.OS == "windows" {
|
||||
return ".exe"
|
||||
}
|
||||
return ""
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package ext
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/internal/buildtarget"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExtWindows(t *testing.T) {
|
||||
assert.Equal(t, ".exe", For(buildtarget.New("windows", "", "")))
|
||||
assert.Equal(t, ".exe", For(buildtarget.New("windows", "adm64", "")))
|
||||
}
|
||||
|
||||
func TestExtOthers(t *testing.T) {
|
||||
assert.Empty(t, "", For(buildtarget.New("linux", "", "")))
|
||||
assert.Empty(t, "", For(buildtarget.New("linuxwin", "", "")))
|
||||
assert.Empty(t, "", For(buildtarget.New("winasdasd", "sad", "6")))
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
// Package build provides a pipe that can build binaries for several
|
||||
// languages.
|
||||
package build
|
||||
|
||||
import (
|
||||
@ -10,11 +12,12 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
builders "github.com/goreleaser/goreleaser/build"
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
"github.com/goreleaser/goreleaser/internal/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/internal/ext"
|
||||
|
||||
// langs to init
|
||||
_ "github.com/goreleaser/goreleaser/internal/builders/golang"
|
||||
)
|
||||
|
||||
// Pipe for build
|
||||
@ -28,9 +31,6 @@ func (Pipe) String() string {
|
||||
func (Pipe) Run(ctx *context.Context) error {
|
||||
for _, build := range ctx.Config.Builds {
|
||||
log.WithField("build", build).Debug("building")
|
||||
if err := checkMain(ctx, build); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runPipeOnBuild(ctx, build); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -52,25 +52,13 @@ func (Pipe) Default(ctx *context.Context) error {
|
||||
}
|
||||
|
||||
func buildWithDefaults(ctx *context.Context, build config.Build) config.Build {
|
||||
if build.Lang == "" {
|
||||
build.Lang = "go"
|
||||
}
|
||||
if build.Binary == "" {
|
||||
build.Binary = ctx.Config.Release.GitHub.Name
|
||||
}
|
||||
if build.Main == "" {
|
||||
build.Main = "."
|
||||
}
|
||||
if len(build.Goos) == 0 {
|
||||
build.Goos = []string{"linux", "darwin"}
|
||||
}
|
||||
if len(build.Goarch) == 0 {
|
||||
build.Goarch = []string{"amd64", "386"}
|
||||
}
|
||||
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}}"
|
||||
}
|
||||
return build
|
||||
return builders.For(build.Lang).WithDefaults(build)
|
||||
}
|
||||
|
||||
func runPipeOnBuild(ctx *context.Context, build config.Build) error {
|
||||
@ -79,7 +67,7 @@ func runPipeOnBuild(ctx *context.Context, build config.Build) error {
|
||||
}
|
||||
sem := make(chan bool, ctx.Parallelism)
|
||||
var g errgroup.Group
|
||||
for _, target := range buildtarget.All(build) {
|
||||
for _, target := range build.Targets {
|
||||
sem <- true
|
||||
target := target
|
||||
build := build
|
||||
@ -102,51 +90,36 @@ func runHook(ctx *context.Context, env []string, hook string) error {
|
||||
}
|
||||
log.WithField("hook", hook).Info("running hook")
|
||||
cmd := strings.Fields(hook)
|
||||
return run(ctx, buildtarget.Runtime, cmd, env)
|
||||
return run(ctx, cmd, env)
|
||||
}
|
||||
|
||||
func doBuild(ctx *context.Context, build config.Build, target buildtarget.Target) error {
|
||||
var ext = ext.For(target)
|
||||
var binaryName = build.Binary + ext
|
||||
var binary = filepath.Join(ctx.Config.Dist, target.String(), binaryName)
|
||||
log.WithField("binary", binary).Info("building")
|
||||
cmd := []string{"go", "build"}
|
||||
if build.Flags != "" {
|
||||
cmd = append(cmd, strings.Fields(build.Flags)...)
|
||||
}
|
||||
flags, err := ldflags(ctx, build)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = append(cmd, "-ldflags="+flags, "-o", binary, build.Main)
|
||||
if err := run(ctx, target, cmd, build.Env); err != nil {
|
||||
return errors.Wrapf(err, "failed to build for %s", target)
|
||||
}
|
||||
ctx.Artifacts.Add(artifact.Artifact{
|
||||
Type: artifact.Binary,
|
||||
Path: binary,
|
||||
Name: binaryName,
|
||||
Goos: target.OS,
|
||||
Goarch: target.Arch,
|
||||
Goarm: target.Arm,
|
||||
Extra: map[string]string{
|
||||
"Binary": build.Binary,
|
||||
"Ext": ext,
|
||||
},
|
||||
func doBuild(ctx *context.Context, build config.Build, target string) error {
|
||||
var ext = extFor(target)
|
||||
var name = build.Binary + ext
|
||||
var path = filepath.Join(ctx.Config.Dist, target, name)
|
||||
log.WithField("binary", path).Info("building")
|
||||
return builders.For(build.Lang).Build(ctx, build, builders.Options{
|
||||
Target: target,
|
||||
Name: name,
|
||||
Path: path,
|
||||
Ext: ext,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(ctx *context.Context, target buildtarget.Target, command, env []string) error {
|
||||
func extFor(target string) string {
|
||||
if strings.Contains(target, "windows") {
|
||||
return ".exe"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func run(ctx *context.Context, command, env []string) error {
|
||||
/* #nosec */
|
||||
var cmd = exec.CommandContext(ctx, command[0], command[1:]...)
|
||||
env = append(env, target.Env()...)
|
||||
var log = log.WithField("target", target.PrettyString()).
|
||||
WithField("env", env).
|
||||
WithField("cmd", command)
|
||||
var log = log.WithField("env", env).WithField("cmd", command)
|
||||
cmd.Env = append(cmd.Env, os.Environ()...)
|
||||
cmd.Env = append(cmd.Env, env...)
|
||||
log.Debug("running")
|
||||
log.WithField("cmd", command).WithField("env", env).Debug("running")
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
log.WithError(err).Debug("failed")
|
||||
return errors.New(string(out))
|
||||
|
@ -1,47 +1,57 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"errors"
|
||||
"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/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
"github.com/goreleaser/goreleaser/internal/testlib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var emptyEnv []string
|
||||
var fakeArtifact = artifact.Artifact{
|
||||
Name: "fake",
|
||||
}
|
||||
|
||||
type fakeBuilder struct {
|
||||
fail bool
|
||||
}
|
||||
|
||||
func (*fakeBuilder) WithDefaults(build config.Build) config.Build {
|
||||
return build
|
||||
}
|
||||
|
||||
var errFailedBuild = errors.New("fake builder failed")
|
||||
|
||||
func (f *fakeBuilder) Build(ctx *context.Context, build config.Build, options api.Options) error {
|
||||
if f.fail {
|
||||
return errFailedBuild
|
||||
}
|
||||
ctx.Artifacts.Add(fakeArtifact)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
api.Register("fake", &fakeBuilder{})
|
||||
api.Register("fakeFail", &fakeBuilder{
|
||||
fail: true,
|
||||
})
|
||||
}
|
||||
|
||||
func TestPipeDescription(t *testing.T) {
|
||||
assert.NotEmpty(t, Pipe{}.String())
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
assert.NoError(t, run(
|
||||
context.New(config.Project{}),
|
||||
buildtarget.Runtime,
|
||||
[]string{"go", "list", "./..."},
|
||||
emptyEnv,
|
||||
))
|
||||
}
|
||||
|
||||
func TestRunInvalidCommand(t *testing.T) {
|
||||
assert.Error(t, run(
|
||||
context.New(config.Project{}),
|
||||
buildtarget.Runtime,
|
||||
[]string{"gggggo", "nope"},
|
||||
emptyEnv,
|
||||
))
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Lang: "fake",
|
||||
Binary: "testing",
|
||||
Flags: "-n",
|
||||
Env: []string{"BLAH=1"},
|
||||
@ -49,19 +59,35 @@ 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"))
|
||||
}
|
||||
|
||||
func TestRunPipe(t *testing.T) {
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Lang: "fake",
|
||||
Binary: "testing",
|
||||
Flags: "-v",
|
||||
Ldflags: "-X main.test=testing",
|
||||
Targets: []string{"whatever"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var ctx = context.New(config)
|
||||
assert.NoError(t, Pipe{}.Run(ctx))
|
||||
assert.Equal(t, ctx.Artifacts.List(), []artifact.Artifact{fakeArtifact})
|
||||
}
|
||||
|
||||
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 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",
|
||||
@ -69,138 +95,52 @@ func TestRunFullPipe(t *testing.T) {
|
||||
Pre: "touch " + pre,
|
||||
Post: "touch " + post,
|
||||
},
|
||||
Goos: []string{
|
||||
runtime.GOOS,
|
||||
},
|
||||
Goarch: []string{
|
||||
runtime.GOARCH,
|
||||
},
|
||||
},
|
||||
},
|
||||
Archive: config.Archive{
|
||||
Replacements: map[string]string{
|
||||
"linux": "linuxx",
|
||||
"darwin": "darwinn",
|
||||
Targets: []string{"whatever"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var ctx = context.New(config)
|
||||
assert.NoError(t, Pipe{}.Run(ctx))
|
||||
assert.Len(t, ctx.Artifacts.List(), 1)
|
||||
assert.True(t, exists(binary), binary)
|
||||
assert.Equal(t, ctx.Artifacts.List(), []artifact.Artifact{fakeArtifact})
|
||||
assert.True(t, exists(pre), pre)
|
||||
assert.True(t, exists(post), post)
|
||||
}
|
||||
|
||||
func TestRunPipeArmBuilds(t *testing.T) {
|
||||
func TestRunFullPipeFail(t *testing.T) {
|
||||
folder, back := testlib.Mktmp(t)
|
||||
defer back()
|
||||
writeGoodMain(t, folder)
|
||||
var binary = filepath.Join(folder, "linuxarm6", "armtesting")
|
||||
var pre = filepath.Join(folder, "pre")
|
||||
var post = filepath.Join(folder, "post")
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Binary: "armtesting",
|
||||
Lang: "fakeFail",
|
||||
Binary: "testing",
|
||||
Flags: "-v",
|
||||
Ldflags: "-X main.test=armtesting",
|
||||
Goos: []string{
|
||||
"linux",
|
||||
},
|
||||
Goarch: []string{
|
||||
"arm",
|
||||
"arm64",
|
||||
},
|
||||
Goarm: []string{
|
||||
"6",
|
||||
Ldflags: "-X main.test=testing",
|
||||
Hooks: config.Hooks{
|
||||
Pre: "touch " + pre,
|
||||
Post: "touch " + post,
|
||||
},
|
||||
Targets: []string{"whatever"},
|
||||
},
|
||||
},
|
||||
}
|
||||
var ctx = context.New(config)
|
||||
assert.NoError(t, Pipe{}.Run(ctx))
|
||||
assert.Len(t, ctx.Artifacts.List(), 2)
|
||||
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.EqualError(t, Pipe{}.Run(ctx), errFailedBuild.Error())
|
||||
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`)
|
||||
assert.True(t, exists(pre), pre)
|
||||
assert.False(t, exists(post), post)
|
||||
}
|
||||
|
||||
func TestRunPipeFailingHooks(t *testing.T) {
|
||||
folder, back := testlib.Mktmp(t)
|
||||
defer back()
|
||||
writeGoodMain(t, folder)
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Binary: "hooks",
|
||||
Hooks: config.Hooks{},
|
||||
Goos: []string{
|
||||
runtime.GOOS,
|
||||
},
|
||||
Goarch: []string{
|
||||
runtime.GOARCH,
|
||||
},
|
||||
Lang: "fake",
|
||||
Binary: "hooks",
|
||||
Hooks: config.Hooks{},
|
||||
Targets: []string{"whatever"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -218,80 +158,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{},
|
||||
@ -381,28 +247,22 @@ func TestDefaultFillSingleBuild(t *testing.T) {
|
||||
assert.Equal(t, ctx.Config.Builds[0].Binary, "foo")
|
||||
}
|
||||
|
||||
func TestExtWindows(t *testing.T) {
|
||||
assert.Equal(t, ".exe", extFor("windows_amd64"))
|
||||
assert.Equal(t, ".exe", extFor("windows_386"))
|
||||
}
|
||||
|
||||
func TestExtOthers(t *testing.T) {
|
||||
assert.Empty(t, "", extFor("linux_amd64"))
|
||||
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 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)
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func checkMain(ctx *context.Context, build config.Build) error {
|
||||
var main = build.Main
|
||||
if main == "" {
|
||||
main = "."
|
||||
}
|
||||
stat, ferr := os.Stat(main)
|
||||
if os.IsNotExist(ferr) {
|
||||
return errors.Wrapf(ferr, "could not open %s", main)
|
||||
}
|
||||
if stat.IsDir() {
|
||||
packs, err := parser.ParseDir(token.NewFileSet(), main, nil, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse dir: %s", main)
|
||||
}
|
||||
for _, pack := range packs {
|
||||
for _, file := range pack.Files {
|
||||
if hasMain(file) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("build for %s does not contain a main function", build.Binary)
|
||||
}
|
||||
file, err := parser.ParseFile(token.NewFileSet(), build.Main, nil, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse file: %s", build.Main)
|
||||
}
|
||||
if hasMain(file) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("build for %s does not contain a main function", build.Binary)
|
||||
}
|
||||
|
||||
func hasMain(file *ast.File) bool {
|
||||
for _, decl := range file.Decls {
|
||||
fn, isFn := decl.(*ast.FuncDecl)
|
||||
if !isFn {
|
||||
continue
|
||||
}
|
||||
if fn.Name.Name == "main" && fn.Recv == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
// Package build implements Piper and Defaulter and can build Go projects for
|
||||
// several platforms, with pre and post hook support.
|
||||
// Build also checks wether the current project has a main function, parses
|
||||
// ldflags and other goodies.
|
||||
package build
|
@ -1,37 +0,0 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
)
|
||||
|
||||
type ldflagsData struct {
|
||||
Date string
|
||||
Tag string
|
||||
Commit string
|
||||
Version string
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
func ldflags(ctx *context.Context, build config.Build) (string, error) {
|
||||
var data = ldflagsData{
|
||||
Commit: ctx.Git.Commit,
|
||||
Tag: ctx.Git.CurrentTag,
|
||||
Version: ctx.Version,
|
||||
Date: time.Now().UTC().Format(time.RFC3339),
|
||||
Env: ctx.Env,
|
||||
}
|
||||
var out bytes.Buffer
|
||||
t, err := template.New("ldflags").
|
||||
Option("missingkey=error").
|
||||
Parse(build.Ldflags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = t.Execute(&out, data)
|
||||
return out.String(), err
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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}}"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
var ctx = &context.Context{
|
||||
Git: context.GitInfo{
|
||||
CurrentTag: "v1.2.3",
|
||||
Commit: "123",
|
||||
},
|
||||
Version: "1.2.3",
|
||||
Config: config,
|
||||
Env: map[string]string{"FOO": "123"},
|
||||
}
|
||||
flags, err := ldflags(ctx, ctx.Config.Builds[0])
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, flags, "-s -w")
|
||||
assert.Contains(t, flags, "-X main.version=1.2.3")
|
||||
assert.Contains(t, flags, "-X main.tag=v1.2.3")
|
||||
assert.Contains(t, flags, "-X main.commit=123")
|
||||
assert.Contains(t, flags, "-X main.date=")
|
||||
assert.Contains(t, flags, `-X "main.foo=123"`)
|
||||
}
|
||||
|
||||
func TestInvalidTemplate(t *testing.T) {
|
||||
for template, eerr := range map[string]string{
|
||||
"{{ .Nope }": `template: ldflags:1: unexpected "}" in operand`,
|
||||
"{{.Env.NOPE}}": `template: ldflags:1:6: executing "ldflags" at <.Env.NOPE>: map has no entry for key "NOPE"`,
|
||||
} {
|
||||
t.Run(template, func(tt *testing.T) {
|
||||
var config = config.Project{
|
||||
Builds: []config.Build{
|
||||
{Ldflags: template},
|
||||
},
|
||||
}
|
||||
var ctx = &context.Context{
|
||||
Config: config,
|
||||
}
|
||||
flags, err := ldflags(ctx, ctx.Config.Builds[0])
|
||||
assert.EqualError(tt, err, eerr)
|
||||
assert.Empty(tt, flags)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user