github.com/goreleaser/goreleaser@v1.25.1/internal/builders/golang/build_test.go (about)

     1  package golang
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/goreleaser/goreleaser/internal/artifact"
    14  	"github.com/goreleaser/goreleaser/internal/testctx"
    15  	"github.com/goreleaser/goreleaser/internal/testlib"
    16  	"github.com/goreleaser/goreleaser/internal/tmpl"
    17  	api "github.com/goreleaser/goreleaser/pkg/build"
    18  	"github.com/goreleaser/goreleaser/pkg/config"
    19  	"github.com/goreleaser/goreleaser/pkg/context"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  var runtimeTarget = runtime.GOOS + "_" + runtime.GOARCH
    24  
    25  func TestWithDefaults(t *testing.T) {
    26  	for name, testcase := range map[string]struct {
    27  		build    config.Build
    28  		targets  []string
    29  		goBinary string
    30  	}{
    31  		"full": {
    32  			build: config.Build{
    33  				ID:     "foo",
    34  				Binary: "foo",
    35  				Goos: []string{
    36  					"linux",
    37  					"windows",
    38  					"darwin",
    39  				},
    40  				Goarch: []string{
    41  					"amd64",
    42  					"arm",
    43  					"mips",
    44  				},
    45  				Goarm: []string{
    46  					"6",
    47  				},
    48  				Gomips: []string{
    49  					"softfloat",
    50  				},
    51  				Goamd64: []string{
    52  					"v2",
    53  					"v3",
    54  				},
    55  				GoBinary: "go1.2.3",
    56  			},
    57  			targets: []string{
    58  				"linux_amd64_v2",
    59  				"linux_amd64_v3",
    60  				"linux_mips_softfloat",
    61  				"darwin_amd64_v2",
    62  				"darwin_amd64_v3",
    63  				"windows_amd64_v3",
    64  				"windows_amd64_v2",
    65  				"windows_arm_6",
    66  				"linux_arm_6",
    67  			},
    68  			goBinary: "go1.2.3",
    69  		},
    70  		"empty": {
    71  			build: config.Build{
    72  				ID:     "foo2",
    73  				Binary: "foo",
    74  			},
    75  			targets: []string{
    76  				"linux_amd64_v1",
    77  				"linux_386",
    78  				"linux_arm64",
    79  				"darwin_amd64_v1",
    80  				"darwin_arm64",
    81  				"windows_amd64_v1",
    82  				"windows_arm64",
    83  				"windows_386",
    84  			},
    85  			goBinary: "go",
    86  		},
    87  		"custom targets": {
    88  			build: config.Build{
    89  				ID:     "foo3",
    90  				Binary: "foo",
    91  				Targets: []string{
    92  					"linux_386",
    93  					"darwin_amd64_v2",
    94  				},
    95  			},
    96  			targets: []string{
    97  				"linux_386",
    98  				"darwin_amd64_v2",
    99  			},
   100  			goBinary: "go",
   101  		},
   102  		"custom targets no amd64": {
   103  			build: config.Build{
   104  				ID:     "foo3",
   105  				Binary: "foo",
   106  				Targets: []string{
   107  					"linux_386",
   108  					"darwin_amd64",
   109  				},
   110  			},
   111  			targets: []string{
   112  				"linux_386",
   113  				"darwin_amd64_v1",
   114  			},
   115  			goBinary: "go",
   116  		},
   117  		"custom targets no arm": {
   118  			build: config.Build{
   119  				ID:      "foo3",
   120  				Binary:  "foo",
   121  				Targets: []string{"linux_arm"},
   122  			},
   123  			targets:  []string{"linux_arm_6"},
   124  			goBinary: "go",
   125  		},
   126  		"custom targets no mips": {
   127  			build: config.Build{
   128  				ID:      "foo3",
   129  				Binary:  "foo",
   130  				Targets: []string{"linux_mips"},
   131  			},
   132  			targets:  []string{"linux_mips_hardfloat"},
   133  			goBinary: "go",
   134  		},
   135  		"custom targets no mipsle": {
   136  			build: config.Build{
   137  				ID:      "foo3",
   138  				Binary:  "foo",
   139  				Targets: []string{"linux_mipsle"},
   140  			},
   141  			targets:  []string{"linux_mipsle_hardfloat"},
   142  			goBinary: "go",
   143  		},
   144  		"custom targets no mips64": {
   145  			build: config.Build{
   146  				ID:      "foo3",
   147  				Binary:  "foo",
   148  				Targets: []string{"linux_mips64"},
   149  			},
   150  			targets:  []string{"linux_mips64_hardfloat"},
   151  			goBinary: "go",
   152  		},
   153  		"custom targets no mips64le": {
   154  			build: config.Build{
   155  				ID:      "foo3",
   156  				Binary:  "foo",
   157  				Targets: []string{"linux_mips64le"},
   158  			},
   159  			targets:  []string{"linux_mips64le_hardfloat"},
   160  			goBinary: "go",
   161  		},
   162  		"empty with custom dir": {
   163  			build: config.Build{
   164  				ID:     "foo2",
   165  				Binary: "foo",
   166  				Dir:    "./testdata",
   167  			},
   168  			targets: []string{
   169  				"linux_amd64_v1",
   170  				"linux_386",
   171  				"linux_arm64",
   172  				"darwin_amd64_v1",
   173  				"darwin_arm64",
   174  				"windows_amd64_v1",
   175  				"windows_arm64",
   176  				"windows_386",
   177  			},
   178  			goBinary: "go",
   179  		},
   180  		"empty with custom dir that doest exist": {
   181  			build: config.Build{
   182  				ID:     "foo2",
   183  				Binary: "foo",
   184  				Dir:    "./nope",
   185  			},
   186  			targets: []string{
   187  				"linux_amd64_v1",
   188  				"linux_386",
   189  				"linux_arm64",
   190  				"darwin_amd64_v1",
   191  				"darwin_arm64",
   192  				"windows_amd64_v1",
   193  				"windows_arm64",
   194  				"windows_386",
   195  			},
   196  			goBinary: "go",
   197  		},
   198  		"go first class targets": {
   199  			build: config.Build{
   200  				ID:      "foo3",
   201  				Binary:  "foo",
   202  				Targets: []string{goStableFirstClassTargetsName},
   203  			},
   204  			targets:  go118FirstClassTargets,
   205  			goBinary: "go",
   206  		},
   207  		"go 1.18 first class targets": {
   208  			build: config.Build{
   209  				ID:      "foo3",
   210  				Binary:  "foo",
   211  				Targets: []string{go118FirstClassTargetsName},
   212  			},
   213  			targets:  go118FirstClassTargets,
   214  			goBinary: "go",
   215  		},
   216  		"go 1.18 first class targets plus custom": {
   217  			build: config.Build{
   218  				ID:      "foo3",
   219  				Binary:  "foo",
   220  				Targets: []string{"linux_amd64_v1", go118FirstClassTargetsName, "darwin_amd64_v2"},
   221  			},
   222  			targets:  append(go118FirstClassTargets, "darwin_amd64_v2"),
   223  			goBinary: "go",
   224  		},
   225  		"repeatin targets": {
   226  			build: config.Build{
   227  				ID:      "foo3",
   228  				Binary:  "foo",
   229  				Targets: []string{go118FirstClassTargetsName, go118FirstClassTargetsName, goStableFirstClassTargetsName},
   230  			},
   231  			targets:  go118FirstClassTargets,
   232  			goBinary: "go",
   233  		},
   234  	} {
   235  		t.Run(name, func(t *testing.T) {
   236  			if testcase.build.GoBinary != "" && testcase.build.GoBinary != "go" {
   237  				createFakeGoBinaryWithVersion(t, testcase.build.GoBinary, "go1.18")
   238  			}
   239  			ctx := testctx.NewWithCfg(config.Project{
   240  				Builds: []config.Build{
   241  					testcase.build,
   242  				},
   243  			}, testctx.WithCurrentTag("5.6.7"))
   244  			build, err := Default.WithDefaults(ctx.Config.Builds[0])
   245  			require.NoError(t, err)
   246  			require.ElementsMatch(t, build.Targets, testcase.targets)
   247  			require.EqualValues(t, testcase.goBinary, build.GoBinary)
   248  		})
   249  	}
   250  }
   251  
   252  func TestDefaults(t *testing.T) {
   253  	t.Run("command not set", func(t *testing.T) {
   254  		build, err := Default.WithDefaults(config.Build{})
   255  		require.NoError(t, err)
   256  		require.Equal(t, "build", build.Command)
   257  	})
   258  	t.Run("command set", func(t *testing.T) {
   259  		build, err := Default.WithDefaults(config.Build{
   260  			Command: "test",
   261  		})
   262  		require.NoError(t, err)
   263  		require.Equal(t, "test", build.Command)
   264  	})
   265  }
   266  
   267  // createFakeGoBinaryWithVersion creates a temporary executable with the
   268  // given name, which will output a go version string with the given version.
   269  //
   270  // The temporary directory created by this function will be placed in the
   271  // PATH variable for the duration of (and cleaned up at the end of) the
   272  // current test run.
   273  func createFakeGoBinaryWithVersion(tb testing.TB, name, version string) {
   274  	tb.Helper()
   275  	d := tb.TempDir()
   276  
   277  	require.NoError(tb, os.WriteFile(
   278  		filepath.Join(d, name),
   279  		[]byte(fmt.Sprintf("#!/bin/sh\necho %s", version)),
   280  		0o755,
   281  	))
   282  
   283  	currentPath := os.Getenv("PATH")
   284  
   285  	path := fmt.Sprintf("%s%c%s", d, os.PathListSeparator, currentPath)
   286  	tb.Setenv("PATH", path)
   287  }
   288  
   289  func TestInvalidTargets(t *testing.T) {
   290  	type testcase struct {
   291  		build       config.Build
   292  		expectedErr string
   293  	}
   294  	for s, tc := range map[string]testcase{
   295  		"goos": {
   296  			build: config.Build{
   297  				Goos: []string{"darwin", "darwim"},
   298  			},
   299  			expectedErr: "invalid goos: darwim",
   300  		},
   301  		"goarch": {
   302  			build: config.Build{
   303  				Goarch: []string{"amd64", "i386", "386"},
   304  			},
   305  			expectedErr: "invalid goarch: i386",
   306  		},
   307  		"goarm": {
   308  			build: config.Build{
   309  				Goarch: []string{"arm"},
   310  				Goarm:  []string{"6", "9", "8", "7"},
   311  			},
   312  			expectedErr: "invalid goarm: 9",
   313  		},
   314  		"gomips": {
   315  			build: config.Build{
   316  				Goarch: []string{"mips"},
   317  				Gomips: []string{"softfloat", "mehfloat", "hardfloat"},
   318  			},
   319  			expectedErr: "invalid gomips: mehfloat",
   320  		},
   321  		"goamd64": {
   322  			build: config.Build{
   323  				Goarch:  []string{"amd64"},
   324  				Goamd64: []string{"v1", "v431"},
   325  			},
   326  			expectedErr: "invalid goamd64: v431",
   327  		},
   328  	} {
   329  		t.Run(s, func(t *testing.T) {
   330  			ctx := testctx.NewWithCfg(config.Project{
   331  				Builds: []config.Build{
   332  					tc.build,
   333  				},
   334  			})
   335  			_, err := Default.WithDefaults(ctx.Config.Builds[0])
   336  			require.EqualError(t, err, tc.expectedErr)
   337  		})
   338  	}
   339  }
   340  
   341  func TestBuild(t *testing.T) {
   342  	folder := testlib.Mktmp(t)
   343  	writeGoodMain(t, folder)
   344  	ctx := testctx.NewWithCfg(config.Project{
   345  		Env: []string{"GO_FLAGS=-v", "GOBIN=go"},
   346  		Builds: []config.Build{
   347  			{
   348  				ID:     "foo",
   349  				Binary: "bin/foo-{{ .Version }}",
   350  				Targets: []string{
   351  					"linux_amd64",
   352  					"darwin_amd64",
   353  					"windows_amd64",
   354  					"linux_arm_6",
   355  					"js_wasm",
   356  					"linux_mips_softfloat",
   357  					"linux_mips64le_softfloat",
   358  				},
   359  				GoBinary: "{{ .Env.GOBIN }}",
   360  				Command:  "build",
   361  				BuildDetails: config.BuildDetails{
   362  					Env: []string{
   363  						"GO111MODULE=off",
   364  						`TEST_T={{- if eq .Os "windows" -}}
   365  						w
   366  						{{- else if eq .Os "darwin" -}}
   367  						d
   368  						{{- else if eq .Os "linux" -}}
   369  						l
   370  						{{- end -}}`,
   371  					},
   372  					Asmflags: []string{".=", "all="},
   373  					Gcflags:  []string{"all="},
   374  					Flags:    []string{"{{.Env.GO_FLAGS}}"},
   375  					Tags:     []string{"osusergo", "netgo", "static_build"},
   376  				},
   377  			},
   378  		},
   379  	}, testctx.WithCurrentTag("v5.6.7"), testctx.WithVersion("v5.6.7"))
   380  	build := ctx.Config.Builds[0]
   381  	for _, target := range build.Targets {
   382  		var ext string
   383  		if strings.HasPrefix(target, "windows") {
   384  			ext = ".exe"
   385  		} else if target == "js_wasm" {
   386  			ext = ".wasm"
   387  		}
   388  		bin, terr := tmpl.New(ctx).Apply(build.Binary)
   389  		require.NoError(t, terr)
   390  
   391  		// injecting some delay here to force inconsistent mod times on bins
   392  		time.Sleep(2 * time.Second)
   393  
   394  		parts := strings.Split(target, "_")
   395  		goos := parts[0]
   396  		goarch := parts[1]
   397  		goarm := ""
   398  		gomips := ""
   399  		if len(parts) > 2 {
   400  			if strings.Contains(goarch, "arm") {
   401  				goarm = parts[2]
   402  			}
   403  			if strings.Contains(goarch, "mips") {
   404  				gomips = parts[2]
   405  			}
   406  		}
   407  		err := Default.Build(ctx, build, api.Options{
   408  			Target: target,
   409  			Name:   bin + ext,
   410  			Path:   filepath.Join(folder, "dist", target, bin+ext),
   411  			Goos:   goos,
   412  			Goarch: goarch,
   413  			Goarm:  goarm,
   414  			Gomips: gomips,
   415  			Ext:    ext,
   416  		})
   417  		require.NoError(t, err)
   418  	}
   419  	require.ElementsMatch(t, ctx.Artifacts.List(), []*artifact.Artifact{
   420  		{
   421  			Name:   "bin/foo-v5.6.7",
   422  			Path:   filepath.Join("dist", "linux_amd64", "bin", "foo-v5.6.7"),
   423  			Goos:   "linux",
   424  			Goarch: "amd64",
   425  			Type:   artifact.Binary,
   426  			Extra: map[string]interface{}{
   427  				artifact.ExtraExt:    "",
   428  				artifact.ExtraBinary: "foo-v5.6.7",
   429  				artifact.ExtraID:     "foo",
   430  				"testEnvs":           []string{"TEST_T=l"},
   431  			},
   432  		},
   433  		{
   434  			Name:   "bin/foo-v5.6.7",
   435  			Path:   filepath.Join("dist", "linux_mips_softfloat", "bin", "foo-v5.6.7"),
   436  			Goos:   "linux",
   437  			Goarch: "mips",
   438  			Gomips: "softfloat",
   439  			Type:   artifact.Binary,
   440  			Extra: map[string]interface{}{
   441  				artifact.ExtraExt:    "",
   442  				artifact.ExtraBinary: "foo-v5.6.7",
   443  				artifact.ExtraID:     "foo",
   444  				"testEnvs":           []string{"TEST_T=l"},
   445  			},
   446  		},
   447  		{
   448  			Name:   "bin/foo-v5.6.7",
   449  			Path:   filepath.Join("dist", "linux_mips64le_softfloat", "bin", "foo-v5.6.7"),
   450  			Goos:   "linux",
   451  			Goarch: "mips64le",
   452  			Gomips: "softfloat",
   453  			Type:   artifact.Binary,
   454  			Extra: map[string]interface{}{
   455  				artifact.ExtraExt:    "",
   456  				artifact.ExtraBinary: "foo-v5.6.7",
   457  				artifact.ExtraID:     "foo",
   458  				"testEnvs":           []string{"TEST_T=l"},
   459  			},
   460  		},
   461  		{
   462  			Name:   "bin/foo-v5.6.7",
   463  			Path:   filepath.Join("dist", "darwin_amd64", "bin", "foo-v5.6.7"),
   464  			Goos:   "darwin",
   465  			Goarch: "amd64",
   466  			Type:   artifact.Binary,
   467  			Extra: map[string]interface{}{
   468  				artifact.ExtraExt:    "",
   469  				artifact.ExtraBinary: "foo-v5.6.7",
   470  				artifact.ExtraID:     "foo",
   471  				"testEnvs":           []string{"TEST_T=d"},
   472  			},
   473  		},
   474  		{
   475  			Name:   "bin/foo-v5.6.7",
   476  			Path:   filepath.Join("dist", "linux_arm_6", "bin", "foo-v5.6.7"),
   477  			Goos:   "linux",
   478  			Goarch: "arm",
   479  			Goarm:  "6",
   480  			Type:   artifact.Binary,
   481  			Extra: map[string]interface{}{
   482  				artifact.ExtraExt:    "",
   483  				artifact.ExtraBinary: "foo-v5.6.7",
   484  				artifact.ExtraID:     "foo",
   485  				"testEnvs":           []string{"TEST_T=l"},
   486  			},
   487  		},
   488  		{
   489  			Name:   "bin/foo-v5.6.7.exe",
   490  			Path:   filepath.Join("dist", "windows_amd64", "bin", "foo-v5.6.7.exe"),
   491  			Goos:   "windows",
   492  			Goarch: "amd64",
   493  			Type:   artifact.Binary,
   494  			Extra: map[string]interface{}{
   495  				artifact.ExtraExt:    ".exe",
   496  				artifact.ExtraBinary: "foo-v5.6.7",
   497  				artifact.ExtraID:     "foo",
   498  				"testEnvs":           []string{"TEST_T=w"},
   499  			},
   500  		},
   501  		{
   502  			Name:   "bin/foo-v5.6.7.wasm",
   503  			Path:   filepath.Join("dist", "js_wasm", "bin", "foo-v5.6.7.wasm"),
   504  			Goos:   "js",
   505  			Goarch: "wasm",
   506  			Type:   artifact.Binary,
   507  			Extra: map[string]interface{}{
   508  				artifact.ExtraExt:    ".wasm",
   509  				artifact.ExtraBinary: "foo-v5.6.7",
   510  				artifact.ExtraID:     "foo",
   511  				"testEnvs":           []string{"TEST_T="},
   512  			},
   513  		},
   514  	})
   515  
   516  	modTimes := map[int64]bool{}
   517  	for _, bin := range ctx.Artifacts.List() {
   518  		if bin.Type != artifact.Binary {
   519  			continue
   520  		}
   521  
   522  		fi, err := os.Stat(bin.Path)
   523  		require.NoError(t, err)
   524  
   525  		// make this a suitable map key, per docs: https://golang.org/pkg/time/#Time
   526  		modTime := fi.ModTime().UTC().Round(0).Unix()
   527  
   528  		if modTimes[modTime] {
   529  			t.Fatal("duplicate modified time found, times should be different by default")
   530  		}
   531  		modTimes[modTime] = true
   532  	}
   533  }
   534  
   535  func TestBuildInvalidEnv(t *testing.T) {
   536  	folder := testlib.Mktmp(t)
   537  	writeGoodMain(t, folder)
   538  	ctx := testctx.NewWithCfg(config.Project{
   539  		Builds: []config.Build{
   540  			{
   541  				ID:     "foo",
   542  				Dir:    ".",
   543  				Binary: "foo",
   544  				Targets: []string{
   545  					runtimeTarget,
   546  				},
   547  				GoBinary: "go",
   548  				BuildDetails: config.BuildDetails{
   549  					Env: []string{"GO111MODULE={{ .Nope }}"},
   550  				},
   551  			},
   552  		},
   553  	}, testctx.WithCurrentTag("5.6.7"))
   554  	build := ctx.Config.Builds[0]
   555  	err := Default.Build(ctx, build, api.Options{
   556  		Target: runtimeTarget,
   557  		Name:   build.Binary,
   558  		Path:   filepath.Join("dist", runtimeTarget, build.Binary),
   559  		Ext:    "",
   560  	})
   561  	testlib.RequireTemplateError(t, err)
   562  }
   563  
   564  func TestBuildCodeInSubdir(t *testing.T) {
   565  	folder := testlib.Mktmp(t)
   566  	subdir := filepath.Join(folder, "bar")
   567  	err := os.Mkdir(subdir, 0o755)
   568  	require.NoError(t, err)
   569  	writeGoodMain(t, subdir)
   570  	ctx := testctx.NewWithCfg(config.Project{
   571  		Builds: []config.Build{
   572  			{
   573  				ID:     "foo",
   574  				Dir:    "bar",
   575  				Binary: "foo",
   576  				Targets: []string{
   577  					runtimeTarget,
   578  				},
   579  				GoBinary: "go",
   580  				Command:  "build",
   581  				BuildDetails: config.BuildDetails{
   582  					Env: []string{"GO111MODULE=off"},
   583  				},
   584  			},
   585  		},
   586  	}, testctx.WithCurrentTag("5.6.7"))
   587  	build := ctx.Config.Builds[0]
   588  	err = Default.Build(ctx, build, api.Options{
   589  		Target: runtimeTarget,
   590  		Name:   build.Binary,
   591  		Path:   filepath.Join("dist", runtimeTarget, build.Binary),
   592  		Ext:    "",
   593  	})
   594  	require.NoError(t, err)
   595  }
   596  
   597  func TestBuildWithDotGoDir(t *testing.T) {
   598  	folder := testlib.Mktmp(t)
   599  	require.NoError(t, os.Mkdir(filepath.Join(folder, ".go"), 0o755))
   600  	writeGoodMain(t, folder)
   601  	ctx := testctx.NewWithCfg(config.Project{
   602  		Builds: []config.Build{
   603  			{
   604  				ID:       "foo",
   605  				Binary:   "foo",
   606  				Targets:  []string{runtimeTarget},
   607  				GoBinary: "go",
   608  				Command:  "build",
   609  				BuildDetails: config.BuildDetails{
   610  					Env: []string{"GO111MODULE=off"},
   611  				},
   612  			},
   613  		},
   614  	}, testctx.WithCurrentTag("5.6.7"))
   615  	build := ctx.Config.Builds[0]
   616  	require.NoError(t, Default.Build(ctx, build, api.Options{
   617  		Target: runtimeTarget,
   618  		Name:   build.Binary,
   619  		Path:   filepath.Join("dist", runtimeTarget, build.Binary),
   620  		Ext:    "",
   621  	}))
   622  }
   623  
   624  func TestBuildFailed(t *testing.T) {
   625  	folder := testlib.Mktmp(t)
   626  	writeGoodMain(t, folder)
   627  	ctx := testctx.NewWithCfg(config.Project{
   628  		Builds: []config.Build{
   629  			{
   630  				ID: "buildid",
   631  				BuildDetails: config.BuildDetails{
   632  					Flags: []string{"-flag-that-dont-exists-to-force-failure"},
   633  				},
   634  				Targets: []string{
   635  					runtimeTarget,
   636  				},
   637  				GoBinary: "go",
   638  				Command:  "build",
   639  			},
   640  		},
   641  	}, testctx.WithCurrentTag("5.6.7"))
   642  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   643  		Target: "darwin_amd64",
   644  	})
   645  	require.ErrorContains(t, err, `flag provided but not defined: -flag-that-dont-exists-to-force-failure`)
   646  	require.Empty(t, ctx.Artifacts.List())
   647  }
   648  
   649  func TestRunInvalidAsmflags(t *testing.T) {
   650  	folder := testlib.Mktmp(t)
   651  	writeGoodMain(t, folder)
   652  	ctx := testctx.NewWithCfg(config.Project{
   653  		Builds: []config.Build{
   654  			{
   655  				Binary: "nametest",
   656  				BuildDetails: config.BuildDetails{
   657  					Asmflags: []string{"{{.Version}"},
   658  				},
   659  				Targets: []string{
   660  					runtimeTarget,
   661  				},
   662  			},
   663  		},
   664  	}, testctx.WithCurrentTag("5.6.7"))
   665  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   666  		Target: runtimeTarget,
   667  	})
   668  	testlib.RequireTemplateError(t, err)
   669  }
   670  
   671  func TestRunInvalidGcflags(t *testing.T) {
   672  	folder := testlib.Mktmp(t)
   673  	writeGoodMain(t, folder)
   674  	ctx := testctx.NewWithCfg(config.Project{
   675  		Builds: []config.Build{
   676  			{
   677  				Binary: "nametest",
   678  				BuildDetails: config.BuildDetails{
   679  					Gcflags: []string{"{{.Version}"},
   680  				},
   681  				Targets: []string{
   682  					runtimeTarget,
   683  				},
   684  			},
   685  		},
   686  	}, testctx.WithCurrentTag("5.6.7"))
   687  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   688  		Target: runtimeTarget,
   689  	})
   690  	testlib.RequireTemplateError(t, err)
   691  }
   692  
   693  func TestRunInvalidLdflags(t *testing.T) {
   694  	folder := testlib.Mktmp(t)
   695  	writeGoodMain(t, folder)
   696  	ctx := testctx.NewWithCfg(config.Project{
   697  		Builds: []config.Build{
   698  			{
   699  				Binary: "nametest",
   700  				BuildDetails: config.BuildDetails{
   701  					Flags:   []string{"-v"},
   702  					Ldflags: []string{"-s -w -X main.version={{.Version}"},
   703  				},
   704  				Targets: []string{
   705  					runtimeTarget,
   706  				},
   707  			},
   708  		},
   709  	}, testctx.WithCurrentTag("5.6.7"))
   710  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   711  		Target: runtimeTarget,
   712  	})
   713  	testlib.RequireTemplateError(t, err)
   714  }
   715  
   716  func TestRunInvalidFlags(t *testing.T) {
   717  	folder := testlib.Mktmp(t)
   718  	writeGoodMain(t, folder)
   719  	ctx := testctx.NewWithCfg(config.Project{
   720  		Builds: []config.Build{
   721  			{
   722  				Binary: "nametest",
   723  				BuildDetails: config.BuildDetails{
   724  					Flags: []string{"{{.Env.GOOS}"},
   725  				},
   726  				Targets: []string{
   727  					runtimeTarget,
   728  				},
   729  			},
   730  		},
   731  	})
   732  	err := Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   733  		Target: runtimeTarget,
   734  	})
   735  	testlib.RequireTemplateError(t, err)
   736  }
   737  
   738  func TestRunPipeWithoutMainFunc(t *testing.T) {
   739  	newCtx := func(t *testing.T) *context.Context {
   740  		t.Helper()
   741  		folder := testlib.Mktmp(t)
   742  		writeMainWithoutMainFunc(t, folder)
   743  		ctx := testctx.NewWithCfg(config.Project{
   744  			Builds: []config.Build{{Binary: "no-main"}},
   745  		}, testctx.WithCurrentTag("5.6.7"))
   746  		return ctx
   747  	}
   748  	t.Run("empty", func(t *testing.T) {
   749  		ctx := newCtx(t)
   750  		ctx.Config.Builds[0].Main = ""
   751  		require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   752  			Target: runtimeTarget,
   753  		}), errNoMain{"no-main"}.Error())
   754  	})
   755  	t.Run("not main.go", func(t *testing.T) {
   756  		ctx := newCtx(t)
   757  		ctx.Config.Builds[0].Main = "foo.go"
   758  		require.ErrorIs(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   759  			Target: runtimeTarget,
   760  		}), os.ErrNotExist)
   761  	})
   762  	t.Run("glob", func(t *testing.T) {
   763  		ctx := newCtx(t)
   764  		ctx.Config.Builds[0].Main = "."
   765  		require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   766  			Target: runtimeTarget,
   767  		}), errNoMain{"no-main"}.Error())
   768  	})
   769  	t.Run("fixed main.go", func(t *testing.T) {
   770  		ctx := newCtx(t)
   771  		ctx.Config.Builds[0].Main = "main.go"
   772  		require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   773  			Target: runtimeTarget,
   774  		}), errNoMain{"no-main"}.Error())
   775  	})
   776  	t.Run("using gomod.proxy", func(t *testing.T) {
   777  		ctx := newCtx(t)
   778  		ctx.Config.GoMod.Proxy = true
   779  		ctx.Config.Builds[0].Dir = "dist/proxy/test"
   780  		ctx.Config.Builds[0].Main = "github.com/caarlos0/test"
   781  		ctx.Config.Builds[0].UnproxiedDir = "."
   782  		ctx.Config.Builds[0].UnproxiedMain = "."
   783  		require.EqualError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   784  			Target: runtimeTarget,
   785  		}), errNoMain{"no-main"}.Error())
   786  	})
   787  }
   788  
   789  func TestBuildTests(t *testing.T) {
   790  	folder := testlib.Mktmp(t)
   791  	writeTest(t, folder)
   792  	ctx := testctx.NewWithCfg(config.Project{
   793  		Builds: []config.Build{{
   794  			Binary:  "foo.test",
   795  			Command: "test",
   796  			BuildDetails: config.BuildDetails{
   797  				Flags: []string{"-c"},
   798  			},
   799  			NoMainCheck: true,
   800  		}},
   801  	}, testctx.WithCurrentTag("5.6.7"))
   802  	build, err := Default.WithDefaults(ctx.Config.Builds[0])
   803  	require.NoError(t, err)
   804  	require.NoError(t, Default.Build(ctx, build, api.Options{
   805  		Target: runtimeTarget,
   806  	}))
   807  }
   808  
   809  func TestRunPipeWithProxiedRepo(t *testing.T) {
   810  	folder := testlib.Mktmp(t)
   811  	out, err := exec.Command("git", "clone", "https://github.com/goreleaser/goreleaser", "-b", "v0.161.1", "--depth=1", ".").CombinedOutput()
   812  	require.NoError(t, err, string(out))
   813  
   814  	proxied := filepath.Join(folder, "dist/proxy/default")
   815  	require.NoError(t, os.MkdirAll(proxied, 0o750))
   816  	require.NoError(t, os.WriteFile(
   817  		filepath.Join(proxied, "main.go"),
   818  		[]byte(`// +build main
   819  package main
   820  
   821  import _ "github.com/goreleaser/goreleaser"
   822  `),
   823  		0o666,
   824  	))
   825  	require.NoError(t, os.WriteFile(
   826  		filepath.Join(proxied, "go.mod"),
   827  		[]byte("module foo\nrequire github.com/goreleaser/goreleaser v0.161.1"),
   828  		0o666,
   829  	))
   830  
   831  	cmd := exec.Command("go", "mod", "tidy")
   832  	cmd.Dir = proxied
   833  	require.NoError(t, cmd.Run())
   834  
   835  	ctx := testctx.NewWithCfg(config.Project{
   836  		GoMod: config.GoMod{
   837  			Proxy: true,
   838  		},
   839  		Builds: []config.Build{
   840  			{
   841  				Binary:        "foo",
   842  				Main:          "github.com/goreleaser/goreleaser",
   843  				Dir:           proxied,
   844  				UnproxiedMain: ".",
   845  				UnproxiedDir:  ".",
   846  				Targets: []string{
   847  					runtimeTarget,
   848  				},
   849  				GoBinary: "go",
   850  				Command:  "build",
   851  			},
   852  		},
   853  	})
   854  
   855  	require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   856  		Target: runtimeTarget,
   857  	}))
   858  }
   859  
   860  func TestRunPipeWithMainFuncNotInMainGoFile(t *testing.T) {
   861  	folder := testlib.Mktmp(t)
   862  	require.NoError(t, os.WriteFile(
   863  		filepath.Join(folder, "foo.go"),
   864  		[]byte("package main\nfunc main() {println(0)}"),
   865  		0o644,
   866  	))
   867  	ctx := testctx.NewWithCfg(config.Project{
   868  		Builds: []config.Build{
   869  			{
   870  				Binary: "foo",
   871  				Hooks:  config.BuildHookConfig{},
   872  				Targets: []string{
   873  					runtimeTarget,
   874  				},
   875  				BuildDetails: config.BuildDetails{
   876  					Env: []string{"GO111MODULE=off"},
   877  				},
   878  				GoBinary: "go",
   879  				Command:  "build",
   880  			},
   881  		},
   882  	}, testctx.WithCurrentTag("5.6.7"))
   883  	t.Run("empty", func(t *testing.T) {
   884  		ctx.Config.Builds[0].Main = ""
   885  		require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   886  			Target: runtimeTarget,
   887  		}))
   888  	})
   889  	t.Run("foo.go", func(t *testing.T) {
   890  		ctx.Config.Builds[0].Main = "foo.go"
   891  		require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   892  			Target: runtimeTarget,
   893  		}))
   894  	})
   895  	t.Run("glob", func(t *testing.T) {
   896  		ctx.Config.Builds[0].Main = "."
   897  		require.NoError(t, Default.Build(ctx, ctx.Config.Builds[0], api.Options{
   898  			Target: runtimeTarget,
   899  		}))
   900  	})
   901  }
   902  
   903  func TestLdFlagsFullTemplate(t *testing.T) {
   904  	run := time.Now().UTC()
   905  	commit := time.Now().AddDate(-1, 0, 0)
   906  	ctx := testctx.New(
   907  		testctx.WithCurrentTag("v1.2.3"),
   908  		testctx.WithCommit("123"),
   909  		testctx.WithCommitDate(commit),
   910  		testctx.WithVersion("1.2.3"),
   911  		testctx.WithEnv(map[string]string{"FOO": "123"}),
   912  		testctx.WithDate(run),
   913  	)
   914  	artifact := &artifact.Artifact{Goarch: "amd64"}
   915  	flags, err := tmpl.New(ctx).WithArtifact(artifact).
   916  		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}}`)
   917  	require.NoError(t, err)
   918  	require.Contains(t, flags, "-s -w")
   919  	require.Contains(t, flags, "-X main.version=1.2.3")
   920  	require.Contains(t, flags, "-X main.tag=v1.2.3")
   921  	require.Contains(t, flags, "-X main.commit=123")
   922  	require.Contains(t, flags, fmt.Sprintf("-X main.date=%d", run.Year()))
   923  	require.Contains(t, flags, fmt.Sprintf("-X main.time=%d", run.Year()))
   924  	require.Contains(t, flags, `-X "main.foo=123"`)
   925  	require.Contains(t, flags, `-X main.arch=amd64`)
   926  	require.Contains(t, flags, fmt.Sprintf("-X main.commitDate=%d", commit.Year()))
   927  }
   928  
   929  func TestInvalidTemplate(t *testing.T) {
   930  	for _, template := range []string{
   931  		"{{ .Nope }",
   932  		"{{.Env.NOPE}}",
   933  	} {
   934  		t.Run(template, func(t *testing.T) {
   935  			ctx := testctx.New(testctx.WithCurrentTag("3.4.1"))
   936  			flags, err := tmpl.New(ctx).Apply(template)
   937  			testlib.RequireTemplateError(t, err)
   938  			require.Empty(t, flags)
   939  		})
   940  	}
   941  }
   942  
   943  func TestProcessFlags(t *testing.T) {
   944  	ctx := testctx.New(
   945  		testctx.WithVersion("1.2.3"),
   946  		testctx.WithCurrentTag("5.6.7"),
   947  	)
   948  
   949  	artifact := &artifact.Artifact{
   950  		Name:   "name",
   951  		Goos:   "darwin",
   952  		Goarch: "amd64",
   953  		Goarm:  "7",
   954  		Extra: map[string]interface{}{
   955  			artifact.ExtraBinary: "binary",
   956  		},
   957  	}
   958  
   959  	source := []string{
   960  		"flag",
   961  		"{{.Version}}",
   962  		"{{.Os}}",
   963  		"{{.Arch}}",
   964  		"{{.Arm}}",
   965  		"{{.Binary}}",
   966  		"{{.ArtifactName}}",
   967  	}
   968  
   969  	expected := []string{
   970  		"-testflag=flag",
   971  		"-testflag=1.2.3",
   972  		"-testflag=darwin",
   973  		"-testflag=amd64",
   974  		"-testflag=7",
   975  		"-testflag=binary",
   976  		"-testflag=name",
   977  	}
   978  
   979  	flags, err := processFlags(ctx, artifact, []string{}, source, "-testflag=")
   980  	require.NoError(t, err)
   981  	require.Len(t, flags, 7)
   982  	require.Equal(t, expected, flags)
   983  }
   984  
   985  func TestProcessFlagsInvalid(t *testing.T) {
   986  	ctx := testctx.New()
   987  	source := []string{
   988  		"{{.Version}",
   989  	}
   990  	flags, err := processFlags(ctx, &artifact.Artifact{}, []string{}, source, "-testflag=")
   991  	testlib.RequireTemplateError(t, err)
   992  	require.Nil(t, flags)
   993  }
   994  
   995  func TestBuildModTimestamp(t *testing.T) {
   996  	// round to seconds since this will be a unix timestamp
   997  	modTime := time.Now().AddDate(-1, 0, 0).Round(1 * time.Second).UTC()
   998  
   999  	folder := testlib.Mktmp(t)
  1000  	writeGoodMain(t, folder)
  1001  
  1002  	ctx := testctx.NewWithCfg(
  1003  		config.Project{
  1004  			Env: []string{"GO_FLAGS=-v"},
  1005  			Builds: []config.Build{{
  1006  				ID:     "foo",
  1007  				Binary: "bin/foo-{{ .Version }}",
  1008  				Targets: []string{
  1009  					"linux_amd64",
  1010  					"darwin_amd64",
  1011  					"windows_amd64",
  1012  					"linux_arm_6",
  1013  					"js_wasm",
  1014  					"linux_mips_softfloat",
  1015  					"linux_mips64le_softfloat",
  1016  				},
  1017  				BuildDetails: config.BuildDetails{
  1018  					Env:      []string{"GO111MODULE=off"},
  1019  					Asmflags: []string{".=", "all="},
  1020  					Gcflags:  []string{"all="},
  1021  					Flags:    []string{"{{.Env.GO_FLAGS}}"},
  1022  				},
  1023  				ModTimestamp: fmt.Sprintf("%d", modTime.Unix()),
  1024  				GoBinary:     "go",
  1025  				Command:      "build",
  1026  			}},
  1027  		},
  1028  		testctx.WithCurrentTag("v5.6.7"),
  1029  		testctx.WithVersion("5.6.7"),
  1030  	)
  1031  	build := ctx.Config.Builds[0]
  1032  	for _, target := range build.Targets {
  1033  		var ext string
  1034  		if strings.HasPrefix(target, "windows") {
  1035  			ext = ".exe"
  1036  		} else if target == "js_wasm" {
  1037  			ext = ".wasm"
  1038  		}
  1039  		bin, terr := tmpl.New(ctx).Apply(build.Binary)
  1040  		require.NoError(t, terr)
  1041  
  1042  		// injecting some delay here to force inconsistent mod times on bins
  1043  		time.Sleep(2 * time.Second)
  1044  
  1045  		err := Default.Build(ctx, build, api.Options{
  1046  			Target: target,
  1047  			Name:   bin + ext,
  1048  			Path:   filepath.Join(folder, "dist", target, bin+ext),
  1049  			Ext:    ext,
  1050  		})
  1051  		require.NoError(t, err)
  1052  	}
  1053  
  1054  	for _, bin := range ctx.Artifacts.List() {
  1055  		if bin.Type != artifact.Binary {
  1056  			continue
  1057  		}
  1058  
  1059  		fi, err := os.Stat(bin.Path)
  1060  		require.NoError(t, err)
  1061  		require.True(t, modTime.Equal(fi.ModTime()), "inconsistent mod times found when specifying ModTimestamp")
  1062  	}
  1063  }
  1064  
  1065  func TestBuildGoBuildLine(t *testing.T) {
  1066  	requireEqualCmd := func(tb testing.TB, build config.Build, expected []string) {
  1067  		tb.Helper()
  1068  		ctx := testctx.NewWithCfg(
  1069  			config.Project{
  1070  				Builds: []config.Build{build},
  1071  			},
  1072  			testctx.WithVersion("1.2.3"),
  1073  			testctx.WithGitInfo(context.GitInfo{Commit: "aaa"}),
  1074  			testctx.WithEnv(map[string]string{"GOBIN": "go"}),
  1075  		)
  1076  		options := api.Options{
  1077  			Path:   ctx.Config.Builds[0].Binary,
  1078  			Goos:   "linux",
  1079  			Goarch: "amd64",
  1080  		}
  1081  
  1082  		dets, err := withOverrides(ctx, build, options)
  1083  		require.NoError(t, err)
  1084  
  1085  		line, err := buildGoBuildLine(
  1086  			ctx,
  1087  			build,
  1088  			dets,
  1089  			options,
  1090  			&artifact.Artifact{},
  1091  			[]string{},
  1092  		)
  1093  		require.NoError(t, err)
  1094  		require.Equal(t, expected, line)
  1095  	}
  1096  
  1097  	t.Run("full", func(t *testing.T) {
  1098  		requireEqualCmd(t, config.Build{
  1099  			Main: ".",
  1100  			BuildDetails: config.BuildDetails{
  1101  				Asmflags: []string{"asmflag1", "asmflag2"},
  1102  				Gcflags:  []string{"gcflag1", "gcflag2"},
  1103  				Flags:    []string{"-flag1", "-flag2"},
  1104  				Tags:     []string{"tag1", "tag2"},
  1105  				Ldflags:  []string{"ldflag1", "ldflag2"},
  1106  			},
  1107  			Binary:   "foo",
  1108  			GoBinary: "{{ .Env.GOBIN }}",
  1109  			Command:  "build",
  1110  		}, []string{
  1111  			"go", "build",
  1112  			"-flag1", "-flag2",
  1113  			"-asmflags=asmflag1", "-asmflags=asmflag2",
  1114  			"-gcflags=gcflag1", "-gcflags=gcflag2",
  1115  			"-tags=tag1,tag2",
  1116  			"-ldflags=ldflag1 ldflag2",
  1117  			"-o", "foo", ".",
  1118  		})
  1119  	})
  1120  
  1121  	t.Run("with overrides", func(t *testing.T) {
  1122  		requireEqualCmd(t, config.Build{
  1123  			Main: ".",
  1124  			BuildDetails: config.BuildDetails{
  1125  				Asmflags: []string{"asmflag1", "asmflag2"},
  1126  				Gcflags:  []string{"gcflag1", "gcflag2"},
  1127  				Flags:    []string{"-flag1", "-flag2"},
  1128  				Tags:     []string{"tag1", "tag2"},
  1129  				Ldflags:  []string{"ldflag1", "ldflag2"},
  1130  			},
  1131  			BuildDetailsOverrides: []config.BuildDetailsOverride{
  1132  				{
  1133  					Goos:   "linux",
  1134  					Goarch: "amd64",
  1135  					BuildDetails: config.BuildDetails{
  1136  						Asmflags: []string{"asmflag3"},
  1137  						Gcflags:  []string{"gcflag3"},
  1138  						Flags:    []string{"-flag3"},
  1139  						Tags:     []string{"tag3"},
  1140  						Ldflags:  []string{"ldflag3"},
  1141  					},
  1142  				},
  1143  			},
  1144  			GoBinary: "go",
  1145  			Binary:   "foo",
  1146  			Command:  "build",
  1147  		}, []string{
  1148  			"go", "build",
  1149  			"-flag3",
  1150  			"-asmflags=asmflag3",
  1151  			"-gcflags=gcflag3",
  1152  			"-tags=tag3",
  1153  			"-ldflags=ldflag3",
  1154  			"-o", "foo", ".",
  1155  		})
  1156  	})
  1157  
  1158  	t.Run("simple", func(t *testing.T) {
  1159  		requireEqualCmd(t, config.Build{
  1160  			Main:     ".",
  1161  			GoBinary: "go",
  1162  			Command:  "build",
  1163  			Binary:   "foo",
  1164  		}, strings.Fields("go build -o foo ."))
  1165  	})
  1166  
  1167  	t.Run("test", func(t *testing.T) {
  1168  		requireEqualCmd(t, config.Build{
  1169  			Main:     ".",
  1170  			GoBinary: "go",
  1171  			Command:  "test",
  1172  			Binary:   "foo.test",
  1173  			BuildDetails: config.BuildDetails{
  1174  				Flags: []string{"-c"},
  1175  			},
  1176  		}, strings.Fields("go test -c -o foo.test ."))
  1177  	})
  1178  
  1179  	t.Run("build test always as c flags", func(t *testing.T) {
  1180  		requireEqualCmd(t, config.Build{
  1181  			Main:     ".",
  1182  			GoBinary: "go",
  1183  			Command:  "test",
  1184  			Binary:   "foo.test",
  1185  		}, strings.Fields("go test -c -o foo.test ."))
  1186  	})
  1187  
  1188  	t.Run("ldflags1", func(t *testing.T) {
  1189  		requireEqualCmd(t, config.Build{
  1190  			Main: ".",
  1191  			BuildDetails: config.BuildDetails{
  1192  				Ldflags: []string{"-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.builtBy=goreleaser"},
  1193  			},
  1194  			GoBinary: "go",
  1195  			Command:  "build",
  1196  			Binary:   "foo",
  1197  		}, []string{
  1198  			"go", "build",
  1199  			"-ldflags=-s -w -X main.version=1.2.3 -X main.commit=aaa -X main.builtBy=goreleaser",
  1200  			"-o", "foo", ".",
  1201  		})
  1202  	})
  1203  
  1204  	t.Run("ldflags2", func(t *testing.T) {
  1205  		requireEqualCmd(t, config.Build{
  1206  			Main: ".",
  1207  			BuildDetails: config.BuildDetails{
  1208  				Ldflags: []string{"-s -w", "-X main.version={{.Version}}"},
  1209  			},
  1210  			GoBinary: "go",
  1211  			Binary:   "foo",
  1212  			Command:  "build",
  1213  		}, []string{"go", "build", "-ldflags=-s -w -X main.version=1.2.3", "-o", "foo", "."})
  1214  	})
  1215  }
  1216  
  1217  func TestOverrides(t *testing.T) {
  1218  	t.Run("linux amd64", func(t *testing.T) {
  1219  		dets, err := withOverrides(
  1220  			testctx.New(),
  1221  			config.Build{
  1222  				BuildDetails: config.BuildDetails{
  1223  					Ldflags: []string{"original"},
  1224  					Env:     []string{"BAR=foo", "FOO=bar"},
  1225  				},
  1226  				BuildDetailsOverrides: []config.BuildDetailsOverride{
  1227  					{
  1228  						Goos:   "linux",
  1229  						Goarch: "amd64",
  1230  						BuildDetails: config.BuildDetails{
  1231  							Ldflags: []string{"overridden"},
  1232  							Env:     []string{"FOO=overridden"},
  1233  						},
  1234  					},
  1235  				},
  1236  			}, api.Options{
  1237  				Goos:   "linux",
  1238  				Goarch: "amd64",
  1239  			},
  1240  		)
  1241  		require.NoError(t, err)
  1242  		require.ElementsMatch(t, dets.Ldflags, []string{"overridden"})
  1243  		require.ElementsMatch(t, dets.Env, []string{"BAR=foo", "FOO=overridden"})
  1244  	})
  1245  
  1246  	t.Run("single sided", func(t *testing.T) {
  1247  		dets, err := withOverrides(
  1248  			testctx.New(),
  1249  			config.Build{
  1250  				BuildDetails: config.BuildDetails{},
  1251  				BuildDetailsOverrides: []config.BuildDetailsOverride{
  1252  					{
  1253  						Goos:   "linux",
  1254  						Goarch: "amd64",
  1255  						BuildDetails: config.BuildDetails{
  1256  							Ldflags:  []string{"overridden"},
  1257  							Tags:     []string{"tag1"},
  1258  							Asmflags: []string{"asm1"},
  1259  							Gcflags:  []string{"gcflag1"},
  1260  						},
  1261  					},
  1262  				},
  1263  			}, api.Options{
  1264  				Goos:   "linux",
  1265  				Goarch: "amd64",
  1266  			},
  1267  		)
  1268  		require.NoError(t, err)
  1269  		require.Equal(t, config.BuildDetails{
  1270  			Ldflags:  []string{"overridden"},
  1271  			Gcflags:  []string{"gcflag1"},
  1272  			Asmflags: []string{"asm1"},
  1273  			Tags:     []string{"tag1"},
  1274  			Env:      []string{},
  1275  		}, dets)
  1276  	})
  1277  
  1278  	t.Run("with template", func(t *testing.T) {
  1279  		dets, err := withOverrides(
  1280  			testctx.New(),
  1281  			config.Build{
  1282  				BuildDetails: config.BuildDetails{
  1283  					Ldflags:  []string{"original"},
  1284  					Asmflags: []string{"asm1"},
  1285  				},
  1286  				BuildDetailsOverrides: []config.BuildDetailsOverride{
  1287  					{
  1288  						Goos:   "{{ .Runtime.Goos }}",
  1289  						Goarch: "{{ .Runtime.Goarch }}",
  1290  						BuildDetails: config.BuildDetails{
  1291  							Ldflags: []string{"overridden"},
  1292  						},
  1293  					},
  1294  				},
  1295  			}, api.Options{
  1296  				Goos:   runtime.GOOS,
  1297  				Goarch: runtime.GOARCH,
  1298  			},
  1299  		)
  1300  		require.NoError(t, err)
  1301  		require.Equal(t, config.BuildDetails{
  1302  			Ldflags:  []string{"overridden"},
  1303  			Asmflags: []string{"asm1"},
  1304  			Env:      []string{},
  1305  		}, dets)
  1306  	})
  1307  
  1308  	t.Run("with invalid template", func(t *testing.T) {
  1309  		_, err := withOverrides(
  1310  			testctx.New(),
  1311  			config.Build{
  1312  				BuildDetailsOverrides: []config.BuildDetailsOverride{
  1313  					{
  1314  						Goos: "{{ .Runtime.Goos }",
  1315  					},
  1316  				},
  1317  			}, api.Options{
  1318  				Goos:   runtime.GOOS,
  1319  				Goarch: runtime.GOARCH,
  1320  			},
  1321  		)
  1322  		testlib.RequireTemplateError(t, err)
  1323  	})
  1324  
  1325  	t.Run("with goarm", func(t *testing.T) {
  1326  		dets, err := withOverrides(
  1327  			testctx.New(),
  1328  			config.Build{
  1329  				BuildDetails: config.BuildDetails{
  1330  					Ldflags: []string{"original"},
  1331  				},
  1332  				BuildDetailsOverrides: []config.BuildDetailsOverride{
  1333  					{
  1334  						Goos:   "linux",
  1335  						Goarch: "arm",
  1336  						Goarm:  "6",
  1337  						BuildDetails: config.BuildDetails{
  1338  							Ldflags: []string{"overridden"},
  1339  						},
  1340  					},
  1341  				},
  1342  			}, api.Options{
  1343  				Goos:   "linux",
  1344  				Goarch: "arm",
  1345  				Goarm:  "6",
  1346  			},
  1347  		)
  1348  		require.NoError(t, err)
  1349  		require.Equal(t, config.BuildDetails{
  1350  			Ldflags: []string{"overridden"},
  1351  			Env:     []string{},
  1352  		}, dets)
  1353  	})
  1354  
  1355  	t.Run("with gomips", func(t *testing.T) {
  1356  		dets, err := withOverrides(
  1357  			testctx.New(),
  1358  			config.Build{
  1359  				BuildDetails: config.BuildDetails{
  1360  					Ldflags: []string{"original"},
  1361  				},
  1362  				BuildDetailsOverrides: []config.BuildDetailsOverride{
  1363  					{
  1364  						Goos:   "linux",
  1365  						Goarch: "mips",
  1366  						Gomips: "softfloat",
  1367  						BuildDetails: config.BuildDetails{
  1368  							Ldflags: []string{"overridden"},
  1369  						},
  1370  					},
  1371  				},
  1372  			}, api.Options{
  1373  				Goos:   "linux",
  1374  				Goarch: "mips",
  1375  				Gomips: "softfloat",
  1376  			},
  1377  		)
  1378  		require.NoError(t, err)
  1379  		require.Equal(t, config.BuildDetails{
  1380  			Ldflags: []string{"overridden"},
  1381  			Env:     []string{},
  1382  		}, dets)
  1383  	})
  1384  }
  1385  
  1386  func TestWarnIfTargetsAndOtherOptionsTogether(t *testing.T) {
  1387  	nonEmpty := []string{"foo", "bar"}
  1388  	for name, fn := range map[string]func(*config.Build){
  1389  		"goos":    func(b *config.Build) { b.Goos = nonEmpty },
  1390  		"goarch":  func(b *config.Build) { b.Goarch = nonEmpty },
  1391  		"goarm":   func(b *config.Build) { b.Goarm = nonEmpty },
  1392  		"gomips":  func(b *config.Build) { b.Gomips = nonEmpty },
  1393  		"goamd64": func(b *config.Build) { b.Goamd64 = nonEmpty },
  1394  		"ignores": func(b *config.Build) { b.Ignore = []config.IgnoredBuild{{Goos: "linux"}} },
  1395  		"multiple": func(b *config.Build) {
  1396  			b.Goos = nonEmpty
  1397  			b.Goarch = nonEmpty
  1398  			b.Goarm = nonEmpty
  1399  			b.Gomips = nonEmpty
  1400  			b.Goamd64 = nonEmpty
  1401  			b.Ignore = []config.IgnoredBuild{{Goos: "linux"}}
  1402  		},
  1403  	} {
  1404  		t.Run(name, func(t *testing.T) {
  1405  			b := config.Build{
  1406  				Targets: nonEmpty,
  1407  			}
  1408  			fn(&b)
  1409  			require.True(t, warnIfTargetsAndOtherOptionTogether(b))
  1410  		})
  1411  	}
  1412  }
  1413  
  1414  func TestInvalidGoBinaryTpl(t *testing.T) {
  1415  	folder := testlib.Mktmp(t)
  1416  	require.NoError(t, os.Mkdir(filepath.Join(folder, ".go"), 0o755))
  1417  	writeGoodMain(t, folder)
  1418  	ctx := testctx.NewWithCfg(config.Project{
  1419  		Builds: []config.Build{
  1420  			{
  1421  				Targets:  []string{runtimeTarget},
  1422  				GoBinary: "{{.Foo}}",
  1423  				Command:  "build",
  1424  			},
  1425  		},
  1426  	})
  1427  	build := ctx.Config.Builds[0]
  1428  	testlib.RequireTemplateError(t, Default.Build(ctx, build, api.Options{
  1429  		Target: runtimeTarget,
  1430  		Name:   build.Binary,
  1431  		Path:   filepath.Join("dist", runtimeTarget, build.Binary),
  1432  		Ext:    "",
  1433  	}))
  1434  }
  1435  
  1436  //
  1437  // Helpers
  1438  //
  1439  
  1440  func writeMainWithoutMainFunc(t *testing.T, folder string) {
  1441  	t.Helper()
  1442  	require.NoError(t, os.WriteFile(
  1443  		filepath.Join(folder, "main.go"),
  1444  		[]byte("package main\nconst a = 2\nfunc notMain() {println(0)}"),
  1445  		0o644,
  1446  	))
  1447  }
  1448  
  1449  func writeGoodMain(t *testing.T, folder string) {
  1450  	t.Helper()
  1451  	require.NoError(t, os.WriteFile(
  1452  		filepath.Join(folder, "main.go"),
  1453  		[]byte("package main\nvar a = 1\nfunc main() {println(0)}"),
  1454  		0o644,
  1455  	))
  1456  }
  1457  
  1458  func writeTest(t *testing.T, folder string) {
  1459  	t.Helper()
  1460  	require.NoError(t, os.WriteFile(
  1461  		filepath.Join(folder, "main_test.go"),
  1462  		[]byte("package main\nimport\"testing\"\nfunc TestFoo(t *testing.T) {t.Log(\"OK\")}"),
  1463  		0o644,
  1464  	))
  1465  	require.NoError(t, os.WriteFile(
  1466  		filepath.Join(folder, "go.mod"),
  1467  		[]byte("module foo\n"),
  1468  		0o666,
  1469  	))
  1470  }