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