1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-21 21:07:19 +02:00
Carlos Alexandro Becker 30630bfc7f
feat: add builds.no_main_check and builds.command (#3064)
* feat: add builds.no_main_check and builds.command

Added two more options to the builds section:
- command: allow to override the command being run. E.g. instead of
  `go build`, one could run `go test -c` by setting gobinary, command
  and flags, respectively.
- no_main_check: by default, goreleaser will fail if there's no main
  function. This option allows to disable that check.

This PR effectively make GoReleaser able to release Go test binaries
instead of just "regular" binaries.

closes #3037

Signed-off-by: Carlos A Becker <caarlos0@gmail.com>

* test: fix broken tests

Signed-off-by: Carlos A Becker <caarlos0@gmail.com>
2022-04-25 22:07:02 -03:00

1340 lines
32 KiB
Go

package golang
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/testlib"
"github.com/goreleaser/goreleaser/internal/tmpl"
api "github.com/goreleaser/goreleaser/pkg/build"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
"github.com/stretchr/testify/require"
)
var runtimeTarget = runtime.GOOS + "_" + runtime.GOARCH
func TestWithDefaults(t *testing.T) {
for name, testcase := range map[string]struct {
build config.Build
targets []string
goBinary string
}{
"full": {
build: config.Build{
ID: "foo",
Binary: "foo",
Goos: []string{
"linux",
"windows",
"darwin",
},
Goarch: []string{
"amd64",
"arm",
"mips",
},
Goarm: []string{
"6",
},
Gomips: []string{
"softfloat",
},
Goamd64: []string{
"v2",
"v3",
},
GoBinary: "go1.2.3",
},
targets: []string{
"linux_amd64_v2",
"linux_amd64_v3",
"linux_mips_softfloat",
"darwin_amd64_v2",
"darwin_amd64_v3",
"windows_amd64_v3",
"windows_amd64_v2",
"windows_arm_6",
"linux_arm_6",
},
goBinary: "go1.2.3",
},
"empty": {
build: config.Build{
ID: "foo2",
Binary: "foo",
},
targets: []string{
"linux_amd64_v1",
"linux_386",
"linux_arm64",
"darwin_amd64_v1",
"darwin_arm64",
},
goBinary: "go",
},
"custom targets": {
build: config.Build{
ID: "foo3",
Binary: "foo",
Targets: []string{
"linux_386",
"darwin_amd64_v2",
},
},
targets: []string{
"linux_386",
"darwin_amd64_v2",
},
goBinary: "go",
},
"custom targets no amd64": {
build: config.Build{
ID: "foo3",
Binary: "foo",
Targets: []string{
"linux_386",
"darwin_amd64",
},
},
targets: []string{
"linux_386",
"darwin_amd64_v1",
},
goBinary: "go",
},
"custom targets no arm": {
build: config.Build{
ID: "foo3",
Binary: "foo",
Targets: []string{"linux_arm"},
},
targets: []string{"linux_arm_6"},
goBinary: "go",
},
"custom targets no mips": {
build: config.Build{
ID: "foo3",
Binary: "foo",
Targets: []string{"linux_mips"},
},
targets: []string{"linux_mips_hardfloat"},
goBinary: "go",
},
"custom targets no mipsle": {
build: config.Build{
ID: "foo3",
Binary: "foo",
Targets: []string{"linux_mipsle"},
},
targets: []string{"linux_mipsle_hardfloat"},
goBinary: "go",
},
"custom targets no mips64": {
build: config.Build{
ID: "foo3",
Binary: "foo",
Targets: []string{"linux_mips64"},
},
targets: []string{"linux_mips64_hardfloat"},
goBinary: "go",
},
"custom targets no mips64le": {
build: config.Build{
ID: "foo3",
Binary: "foo",
Targets: []string{"linux_mips64le"},
},
targets: []string{"linux_mips64le_hardfloat"},
goBinary: "go",
},
"empty with custom dir": {
build: config.Build{
ID: "foo2",
Binary: "foo",
Dir: "./testdata",
},
targets: []string{
"linux_amd64_v1",
"linux_386",
"linux_arm64",
"darwin_amd64_v1",
"darwin_arm64",
},
goBinary: "go",
},
"empty with custom dir that doest exist": {
build: config.Build{
ID: "foo2",
Binary: "foo",
Dir: "./nope",
},
targets: []string{
"linux_amd64_v1",
"linux_386",
"linux_arm64",
"darwin_amd64_v1",
"darwin_arm64",
},
goBinary: "go",
},
} {
t.Run(name, func(t *testing.T) {
if testcase.build.GoBinary != "" && testcase.build.GoBinary != "go" {
createFakeGoBinaryWithVersion(t, testcase.build.GoBinary, "go1.18")
}
config := config.Project{
Builds: []config.Build{
testcase.build,
},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
build, err := Default.WithDefaults(ctx.Config.Builds[0])
require.NoError(t, err)
require.ElementsMatch(t, build.Targets, testcase.targets)
require.EqualValues(t, testcase.goBinary, build.GoBinary)
})
}
}
func TestDefaults(t *testing.T) {
t.Run("command not set", func(t *testing.T) {
build, err := Default.WithDefaults(config.Build{})
require.NoError(t, err)
require.Equal(t, "build", build.Command)
})
t.Run("command set", func(t *testing.T) {
build, err := Default.WithDefaults(config.Build{
Command: "test",
})
require.NoError(t, err)
require.Equal(t, "test", build.Command)
})
}
// createFakeGoBinaryWithVersion creates a temporary executable with the
// given name, which will output a go version string with the given version.
// The temporary directory created by this function will be placed in the PATH
// variable for the duration of (and cleaned up at the end of) the
// current test run.
func createFakeGoBinaryWithVersion(tb testing.TB, name, version string) {
tb.Helper()
d := tb.TempDir()
require.NoError(tb, os.WriteFile(
filepath.Join(d, name),
[]byte(fmt.Sprintf("#!/bin/sh\necho %s", version)),
0o755,
))
currentPath := os.Getenv("PATH")
tb.Cleanup(func() {
require.NoError(tb, os.Setenv("PATH", currentPath))
})
path := fmt.Sprintf("%s%c%s", d, os.PathListSeparator, currentPath)
require.NoError(tb, os.Setenv("PATH", path))
}
func TestInvalidTargets(t *testing.T) {
type testcase struct {
build config.Build
expectedErr string
}
for s, tc := range map[string]testcase{
"goos": {
build: config.Build{
Goos: []string{"darwin", "darwim"},
},
expectedErr: "invalid goos: darwim",
},
"goarch": {
build: config.Build{
Goarch: []string{"amd64", "i386", "386"},
},
expectedErr: "invalid goarch: i386",
},
"goarm": {
build: config.Build{
Goarch: []string{"arm"},
Goarm: []string{"6", "9", "8", "7"},
},
expectedErr: "invalid goarm: 9",
},
"gomips": {
build: config.Build{
Goarch: []string{"mips"},
Gomips: []string{"softfloat", "mehfloat", "hardfloat"},
},
expectedErr: "invalid gomips: mehfloat",
},
"goamd64": {
build: config.Build{
Goarch: []string{"amd64"},
Goamd64: []string{"v1", "v431"},
},
expectedErr: "invalid goamd64: v431",
},
} {
t.Run(s, func(t *testing.T) {
config := config.Project{
Builds: []config.Build{
tc.build,
},
}
ctx := context.New(config)
_, err := Default.WithDefaults(ctx.Config.Builds[0])
require.EqualError(t, err, tc.expectedErr)
})
}
}
func TestBuild(t *testing.T) {
folder := testlib.Mktmp(t)
writeGoodMain(t, folder)
config := config.Project{
Builds: []config.Build{
{
ID: "foo",
Env: []string{"GO111MODULE=off"},
Binary: "bin/foo-{{ .Version }}",
Targets: []string{
"linux_amd64",
"darwin_amd64",
"windows_amd64",
"linux_arm_6",
"js_wasm",
"linux_mips_softfloat",
"linux_mips64le_softfloat",
},
GoBinary: "go",
Command: "build",
BuildDetails: config.BuildDetails{
Asmflags: []string{".=", "all="},
Gcflags: []string{"all="},
Flags: []string{"{{.Env.GO_FLAGS}}"},
Tags: []string{"osusergo", "netgo", "static_build"},
},
},
},
}
ctx := context.New(config)
ctx.Env["GO_FLAGS"] = "-v"
ctx.Git.CurrentTag = "v5.6.7"
ctx.Version = ctx.Git.CurrentTag
build := ctx.Config.Builds[0]
for _, target := range build.Targets {
var ext string
if strings.HasPrefix(target, "windows") {
ext = ".exe"
} else if target == "js_wasm" {
ext = ".wasm"
}
bin, terr := tmpl.New(ctx).Apply(build.Binary)
require.NoError(t, terr)
// injecting some delay here to force inconsistent mod times on bins
time.Sleep(2 * time.Second)
parts := strings.Split(target, "_")
goos := parts[0]
goarch := parts[1]
goarm := ""
gomips := ""
if len(parts) > 2 {
if strings.Contains(goarch, "arm") {
goarm = parts[2]
}
if strings.Contains(goarch, "mips") {
gomips = parts[2]
}
}
err := Default.Build(ctx, build, api.Options{
Target: target,
Name: bin + ext,
Path: filepath.Join(folder, "dist", target, bin+ext),
Goos: goos,
Goarch: goarch,
Goarm: goarm,
Gomips: gomips,
Ext: ext,
})
require.NoError(t, err)
}
require.ElementsMatch(t, ctx.Artifacts.List(), []*artifact.Artifact{
{
Name: "bin/foo-v5.6.7",
Path: filepath.Join(folder, "dist", "linux_amd64", "bin", "foo-v5.6.7"),
Goos: "linux",
Goarch: "amd64",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: "",
artifact.ExtraBinary: "foo-v5.6.7",
artifact.ExtraID: "foo",
},
},
{
Name: "bin/foo-v5.6.7",
Path: filepath.Join(folder, "dist", "linux_mips_softfloat", "bin", "foo-v5.6.7"),
Goos: "linux",
Goarch: "mips",
Gomips: "softfloat",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: "",
artifact.ExtraBinary: "foo-v5.6.7",
artifact.ExtraID: "foo",
},
},
{
Name: "bin/foo-v5.6.7",
Path: filepath.Join(folder, "dist", "linux_mips64le_softfloat", "bin", "foo-v5.6.7"),
Goos: "linux",
Goarch: "mips64le",
Gomips: "softfloat",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: "",
artifact.ExtraBinary: "foo-v5.6.7",
artifact.ExtraID: "foo",
},
},
{
Name: "bin/foo-v5.6.7",
Path: filepath.Join(folder, "dist", "darwin_amd64", "bin", "foo-v5.6.7"),
Goos: "darwin",
Goarch: "amd64",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: "",
artifact.ExtraBinary: "foo-v5.6.7",
artifact.ExtraID: "foo",
},
},
{
Name: "bin/foo-v5.6.7",
Path: filepath.Join(folder, "dist", "linux_arm_6", "bin", "foo-v5.6.7"),
Goos: "linux",
Goarch: "arm",
Goarm: "6",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: "",
artifact.ExtraBinary: "foo-v5.6.7",
artifact.ExtraID: "foo",
},
},
{
Name: "bin/foo-v5.6.7.exe",
Path: filepath.Join(folder, "dist", "windows_amd64", "bin", "foo-v5.6.7.exe"),
Goos: "windows",
Goarch: "amd64",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: ".exe",
artifact.ExtraBinary: "foo-v5.6.7",
artifact.ExtraID: "foo",
},
},
{
Name: "bin/foo-v5.6.7.wasm",
Path: filepath.Join(folder, "dist", "js_wasm", "bin", "foo-v5.6.7.wasm"),
Goos: "js",
Goarch: "wasm",
Type: artifact.Binary,
Extra: map[string]interface{}{
artifact.ExtraExt: ".wasm",
artifact.ExtraBinary: "foo-v5.6.7",
artifact.ExtraID: "foo",
},
},
})
modTimes := map[time.Time]bool{}
for _, bin := range ctx.Artifacts.List() {
if bin.Type != artifact.Binary {
continue
}
fi, err := os.Stat(bin.Path)
require.NoError(t, err)
// make this a suitable map key, per docs: https://golang.org/pkg/time/#Time
modTime := fi.ModTime().UTC().Round(0)
if modTimes[modTime] {
t.Fatal("duplicate modified time found, times should be different by default")
}
modTimes[modTime] = true
}
}
func TestBuildCodeInSubdir(t *testing.T) {
folder := testlib.Mktmp(t)
subdir := filepath.Join(folder, "bar")
err := os.Mkdir(subdir, 0o755)
require.NoError(t, err)
writeGoodMain(t, subdir)
config := config.Project{
Builds: []config.Build{
{
ID: "foo",
Env: []string{"GO111MODULE=off"},
Dir: "bar",
Binary: "foo",
Targets: []string{
runtimeTarget,
},
GoBinary: "go",
Command: "build",
},
},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
build := ctx.Config.Builds[0]
err = Default.Build(ctx, build, api.Options{
Target: runtimeTarget,
Name: build.Binary,
Path: filepath.Join(folder, "dist", runtimeTarget, build.Binary),
Ext: "",
})
require.NoError(t, err)
}
func TestBuildWithDotGoDir(t *testing.T) {
folder := testlib.Mktmp(t)
require.NoError(t, os.Mkdir(filepath.Join(folder, ".go"), 0o755))
writeGoodMain(t, folder)
config := config.Project{
Builds: []config.Build{
{
ID: "foo",
Env: []string{"GO111MODULE=off"},
Binary: "foo",
Targets: []string{runtimeTarget},
GoBinary: "go",
Command: "build",
},
},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
build := ctx.Config.Builds[0]
require.NoError(t, Default.Build(ctx, build, api.Options{
Target: runtimeTarget,
Name: build.Binary,
Path: filepath.Join(folder, "dist", runtimeTarget, build.Binary),
Ext: "",
}))
}
func TestBuildFailed(t *testing.T) {
folder := testlib.Mktmp(t)
writeGoodMain(t, folder)
config := config.Project{
Builds: []config.Build{
{
ID: "buildid",
BuildDetails: config.BuildDetails{
Flags: []string{"-flag-that-dont-exists-to-force-failure"},
},
Targets: []string{
runtimeTarget,
},
GoBinary: "go",
Command: "build",
},
},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
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`)
require.Empty(t, ctx.Artifacts.List())
}
func TestRunInvalidAsmflags(t *testing.T) {
folder := testlib.Mktmp(t)
writeGoodMain(t, folder)
config := config.Project{
Builds: []config.Build{
{
Binary: "nametest",
BuildDetails: config.BuildDetails{
Asmflags: []string{"{{.Version}"},
},
Targets: []string{
runtimeTarget,
},
},
},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
})
require.EqualError(t, err, `template: tmpl:1: unexpected "}" in operand`)
}
func TestRunInvalidGcflags(t *testing.T) {
folder := testlib.Mktmp(t)
writeGoodMain(t, folder)
config := config.Project{
Builds: []config.Build{
{
Binary: "nametest",
BuildDetails: config.BuildDetails{
Gcflags: []string{"{{.Version}"},
},
Targets: []string{
runtimeTarget,
},
},
},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
})
require.EqualError(t, err, `template: tmpl:1: unexpected "}" in operand`)
}
func TestRunInvalidLdflags(t *testing.T) {
folder := testlib.Mktmp(t)
writeGoodMain(t, folder)
config := config.Project{
Builds: []config.Build{
{
Binary: "nametest",
BuildDetails: config.BuildDetails{
Flags: []string{"-v"},
Ldflags: []string{"-s -w -X main.version={{.Version}"},
},
Targets: []string{
runtimeTarget,
},
},
},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
})
require.EqualError(t, err, `template: tmpl:1: unexpected "}" in operand`)
}
func TestRunInvalidFlags(t *testing.T) {
folder := testlib.Mktmp(t)
writeGoodMain(t, folder)
config := config.Project{
Builds: []config.Build{
{
Binary: "nametest",
BuildDetails: config.BuildDetails{
Flags: []string{"{{.Env.GOOS}"},
},
Targets: []string{
runtimeTarget,
},
},
},
}
ctx := context.New(config)
err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
})
require.EqualError(t, err, `template: tmpl:1: unexpected "}" in operand`)
}
func TestRunPipeWithoutMainFunc(t *testing.T) {
newCtx := func(t *testing.T) *context.Context {
t.Helper()
folder := testlib.Mktmp(t)
writeMainWithoutMainFunc(t, folder)
config := config.Project{
Builds: []config.Build{{Binary: "no-main"}},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
return ctx
}
t.Run("empty", func(t *testing.T) {
ctx := newCtx(t)
ctx.Config.Builds[0].Main = ""
require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
}), errNoMain{"no-main"}.Error())
})
t.Run("not main.go", func(t *testing.T) {
ctx := newCtx(t)
ctx.Config.Builds[0].Main = "foo.go"
require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
}), `couldn't find main file: stat foo.go: no such file or directory`)
})
t.Run("glob", func(t *testing.T) {
ctx := newCtx(t)
ctx.Config.Builds[0].Main = "."
require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
}), errNoMain{"no-main"}.Error())
})
t.Run("fixed main.go", func(t *testing.T) {
ctx := newCtx(t)
ctx.Config.Builds[0].Main = "main.go"
require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
}), errNoMain{"no-main"}.Error())
})
t.Run("using gomod.proxy", func(t *testing.T) {
ctx := newCtx(t)
ctx.Config.GoMod.Proxy = true
ctx.Config.Builds[0].Dir = "dist/proxy/test"
ctx.Config.Builds[0].Main = "github.com/caarlos0/test"
ctx.Config.Builds[0].UnproxiedDir = "."
ctx.Config.Builds[0].UnproxiedMain = "."
require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
}), errNoMain{"no-main"}.Error())
})
}
func TestBuildTests(t *testing.T) {
folder := testlib.Mktmp(t)
writeTest(t, folder)
config := config.Project{
Builds: []config.Build{{
Binary: "foo.test",
Command: "test",
BuildDetails: config.BuildDetails{
Flags: []string{"-c"},
},
}},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
ctx.Config.Builds[0].NoMainCheck = true
build, err := Default.WithDefaults(config.Builds[0])
require.NoError(t, err)
require.NoError(t, Default.Build(ctx, build, api.Options{
Target: runtimeTarget,
}))
}
func TestRunPipeWithProxiedRepo(t *testing.T) {
folder := testlib.Mktmp(t)
out, err := exec.Command("git", "clone", "https://github.com/goreleaser/goreleaser", "-b", "v0.161.1", "--depth=1", ".").CombinedOutput()
require.NoError(t, err, string(out))
proxied := filepath.Join(folder, "dist/proxy/default")
require.NoError(t, os.MkdirAll(proxied, 0o750))
require.NoError(t, os.WriteFile(
filepath.Join(proxied, "main.go"),
[]byte(`// +build main
package main
import _ "github.com/goreleaser/goreleaser"
`),
0o666,
))
require.NoError(t, os.WriteFile(
filepath.Join(proxied, "go.mod"),
[]byte("module foo\nrequire github.com/goreleaser/goreleaser v0.161.1"),
0o666,
))
cmd := exec.Command("go", "mod", "tidy")
cmd.Dir = proxied
require.NoError(t, cmd.Run())
config := config.Project{
GoMod: config.GoMod{
Proxy: true,
},
Builds: []config.Build{
{
Binary: "foo",
Main: "github.com/goreleaser/goreleaser",
Dir: proxied,
UnproxiedMain: ".",
UnproxiedDir: ".",
Targets: []string{
runtimeTarget,
},
GoBinary: "go",
Command: "build",
},
},
}
ctx := context.New(config)
require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
}))
}
func TestRunPipeWithMainFuncNotInMainGoFile(t *testing.T) {
folder := testlib.Mktmp(t)
require.NoError(t, os.WriteFile(
filepath.Join(folder, "foo.go"),
[]byte("package main\nfunc main() {println(0)}"),
0o644,
))
config := config.Project{
Builds: []config.Build{
{
Env: []string{"GO111MODULE=off"},
Binary: "foo",
Hooks: config.BuildHookConfig{},
Targets: []string{
runtimeTarget,
},
GoBinary: "go",
Command: "build",
},
},
}
ctx := context.New(config)
ctx.Git.CurrentTag = "5.6.7"
t.Run("empty", func(t *testing.T) {
ctx.Config.Builds[0].Main = ""
require.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"
require.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 = "."
require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
Target: runtimeTarget,
}))
})
}
func TestLdFlagsFullTemplate(t *testing.T) {
run := time.Now().UTC()
commit := time.Now().AddDate(-1, 0, 0)
ctx := &context.Context{
Git: context.GitInfo{
CurrentTag: "v1.2.3",
Commit: "123",
CommitDate: commit,
},
Date: run,
Version: "1.2.3",
Env: map[string]string{"FOO": "123"},
}
artifact := &artifact.Artifact{Goarch: "amd64"}
flags, err := tmpl.New(ctx).WithArtifact(artifact, map[string]string{}).
Apply(`-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" }} -X main.arch={{.Arch}} -X main.commitDate={{.CommitDate}}`)
require.NoError(t, err)
require.Contains(t, flags, "-s -w")
require.Contains(t, flags, "-X main.version=1.2.3")
require.Contains(t, flags, "-X main.tag=v1.2.3")
require.Contains(t, flags, "-X main.commit=123")
require.Contains(t, flags, fmt.Sprintf("-X main.date=%d", run.Year()))
require.Contains(t, flags, fmt.Sprintf("-X main.time=%d", run.Year()))
require.Contains(t, flags, `-X "main.foo=123"`)
require.Contains(t, flags, `-X main.arch=amd64`)
require.Contains(t, flags, fmt.Sprintf("-X main.commitDate=%d", commit.Year()))
}
func TestInvalidTemplate(t *testing.T) {
for template, eerr := range map[string]string{
"{{ .Nope }": `template: tmpl:1: unexpected "}" in operand`,
"{{.Env.NOPE}}": `template: tmpl:1:6: executing "tmpl" at <.Env.NOPE>: map has no entry for key "NOPE"`,
} {
t.Run(template, func(t *testing.T) {
ctx := context.New(config.Project{})
ctx.Git.CurrentTag = "3.4.1"
flags, err := tmpl.New(ctx).Apply(template)
require.EqualError(t, err, eerr)
require.Empty(t, flags)
})
}
}
func TestProcessFlags(t *testing.T) {
ctx := &context.Context{
Version: "1.2.3",
}
ctx.Git.CurrentTag = "5.6.7"
artifact := &artifact.Artifact{
Name: "name",
Goos: "darwin",
Goarch: "amd64",
Goarm: "7",
Extra: map[string]interface{}{
artifact.ExtraBinary: "binary",
},
}
source := []string{
"flag",
"{{.Version}}",
"{{.Os}}",
"{{.Arch}}",
"{{.Arm}}",
"{{.Binary}}",
"{{.ArtifactName}}",
}
expected := []string{
"-testflag=flag",
"-testflag=1.2.3",
"-testflag=darwin",
"-testflag=amd64",
"-testflag=7",
"-testflag=binary",
"-testflag=name",
}
flags, err := processFlags(ctx, artifact, []string{}, source, "-testflag=")
require.NoError(t, err)
require.Len(t, flags, 7)
require.Equal(t, expected, flags)
}
func TestProcessFlagsInvalid(t *testing.T) {
ctx := &context.Context{}
source := []string{
"{{.Version}",
}
expected := `template: tmpl:1: unexpected "}" in operand`
flags, err := processFlags(ctx, &artifact.Artifact{}, []string{}, source, "-testflag=")
require.EqualError(t, err, expected)
require.Nil(t, flags)
}
func TestBuildModTimestamp(t *testing.T) {
// round to seconds since this will be a unix timestamp
modTime := time.Now().AddDate(-1, 0, 0).Round(1 * time.Second).UTC()
folder := testlib.Mktmp(t)
writeGoodMain(t, folder)
config := config.Project{
Builds: []config.Build{
{
ID: "foo",
Env: []string{"GO111MODULE=off"},
Binary: "bin/foo-{{ .Version }}",
Targets: []string{
"linux_amd64",
"darwin_amd64",
"windows_amd64",
"linux_arm_6",
"js_wasm",
"linux_mips_softfloat",
"linux_mips64le_softfloat",
},
BuildDetails: config.BuildDetails{
Asmflags: []string{".=", "all="},
Gcflags: []string{"all="},
Flags: []string{"{{.Env.GO_FLAGS}}"},
},
ModTimestamp: fmt.Sprintf("%d", modTime.Unix()),
GoBinary: "go",
Command: "build",
},
},
}
ctx := context.New(config)
ctx.Env["GO_FLAGS"] = "-v"
ctx.Git.CurrentTag = "v5.6.7"
ctx.Version = ctx.Git.CurrentTag
build := ctx.Config.Builds[0]
for _, target := range build.Targets {
var ext string
if strings.HasPrefix(target, "windows") {
ext = ".exe"
} else if target == "js_wasm" {
ext = ".wasm"
}
bin, terr := tmpl.New(ctx).Apply(build.Binary)
require.NoError(t, terr)
// injecting some delay here to force inconsistent mod times on bins
time.Sleep(2 * time.Second)
err := Default.Build(ctx, build, api.Options{
Target: target,
Name: bin + ext,
Path: filepath.Join(folder, "dist", target, bin+ext),
Ext: ext,
})
require.NoError(t, err)
}
for _, bin := range ctx.Artifacts.List() {
if bin.Type != artifact.Binary {
continue
}
fi, err := os.Stat(bin.Path)
require.NoError(t, err)
require.True(t, modTime.Equal(fi.ModTime()), "inconsistent mod times found when specifying ModTimestamp")
}
}
func TestBuildGoBuildLine(t *testing.T) {
requireEqualCmd := func(tb testing.TB, build config.Build, expected []string) {
tb.Helper()
config := config.Project{
Builds: []config.Build{build},
}
ctx := context.New(config)
ctx.Version = "1.2.3"
ctx.Git.Commit = "aaa"
line, err := buildGoBuildLine(ctx, config.Builds[0], api.Options{
Path: config.Builds[0].Binary,
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: ".",
BuildDetails: config.BuildDetails{
Asmflags: []string{"asmflag1", "asmflag2"},
Gcflags: []string{"gcflag1", "gcflag2"},
Flags: []string{"-flag1", "-flag2"},
Tags: []string{"tag1", "tag2"},
Ldflags: []string{"ldflag1", "ldflag2"},
},
Binary: "foo",
GoBinary: "go",
Command: "build",
}, []string{
"go", "build",
"-flag1", "-flag2",
"-asmflags=asmflag1", "-asmflags=asmflag2",
"-gcflags=gcflag1", "-gcflags=gcflag2",
"-tags=tag1,tag2",
"-ldflags=ldflag1 ldflag2",
"-o", "foo", ".",
})
})
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",
Binary: "foo",
Command: "build",
}, []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: ".",
GoBinary: "go",
Command: "build",
Binary: "foo",
}, strings.Fields("go build -o foo ."))
})
t.Run("test", func(t *testing.T) {
requireEqualCmd(t, config.Build{
Main: ".",
GoBinary: "go",
Command: "test",
Binary: "foo.test",
BuildDetails: config.BuildDetails{
Flags: []string{"-c"},
},
}, strings.Fields("go test -c -o foo.test ."))
})
t.Run("ldflags1", func(t *testing.T) {
requireEqualCmd(t, config.Build{
Main: ".",
BuildDetails: config.BuildDetails{
Ldflags: []string{"-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.builtBy=goreleaser"},
},
GoBinary: "go",
Command: "build",
Binary: "foo",
}, []string{
"go", "build",
"-ldflags=-s -w -X main.version=1.2.3 -X main.commit=aaa -X main.builtBy=goreleaser",
"-o", "foo", ".",
})
})
t.Run("ldflags2", func(t *testing.T) {
requireEqualCmd(t, config.Build{
Main: ".",
BuildDetails: config.BuildDetails{
Ldflags: []string{"-s -w", "-X main.version={{.Version}}"},
},
GoBinary: "go",
Binary: "foo",
Command: "build",
}, []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
//
func writeMainWithoutMainFunc(t *testing.T, folder string) {
t.Helper()
require.NoError(t, os.WriteFile(
filepath.Join(folder, "main.go"),
[]byte("package main\nconst a = 2\nfunc notMain() {println(0)}"),
0o644,
))
}
func writeGoodMain(t *testing.T, folder string) {
t.Helper()
require.NoError(t, os.WriteFile(
filepath.Join(folder, "main.go"),
[]byte("package main\nvar a = 1\nfunc main() {println(0)}"),
0o644,
))
}
func writeTest(t *testing.T, folder string) {
t.Helper()
require.NoError(t, os.WriteFile(
filepath.Join(folder, "main_test.go"),
[]byte("package main\nimport\"testing\"\nfunc TestFoo(t *testing.T) {t.Log(\"OK\")}"),
0o644,
))
require.NoError(t, os.WriteFile(
filepath.Join(folder, "go.mod"),
[]byte("module foo\n"),
0o666,
))
}
func assertContainsError(t *testing.T, err error, s string) {
t.Helper()
require.Error(t, err)
require.Contains(t, err.Error(), s)
}