You've already forked goreleaser
							
							
				mirror of
				https://github.com/goreleaser/goreleaser.git
				synced 2025-10-30 23:58:09 +02:00 
			
		
		
		
	feat: initial proxy build support (#2129)
* feat: allow to use ModulePath on templates Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * feat: initial proxy build support Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: build Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: main check Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: make it more flexible Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: small improvements Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: copy go.sum Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: root mod proxy Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: test Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: snapshots Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: lint Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: proxy main pkg Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: environment variables Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * test: added some tests to go mod proxy feature Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: improve test Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: linte Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: goreleaser.yml Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: simplify tests Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * test: test build Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: revert unwanted changes Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: allow to run when no mod.suym Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * docs: example Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: not a go module on go 1.15 Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * docs: improve docs as per comments Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							90f2ba6925
						
					
				
				
					commit
					8306b946d3
				
			| @@ -4,6 +4,8 @@ before: | ||||
|   hooks: | ||||
|     - go mod tidy | ||||
|     - ./scripts/completions.sh | ||||
| gomod: | ||||
|   proxy: true | ||||
| builds: | ||||
| - env: | ||||
|     - CGO_ENABLED=0 | ||||
|   | ||||
| @@ -71,8 +71,10 @@ func (*Builder) WithDefaults(build config.Build) (config.Build, error) { | ||||
|  | ||||
| // Build builds a golang build. | ||||
| func (*Builder) Build(ctx *context.Context, build config.Build, options api.Options) error { | ||||
| 	if err := checkMain(build); err != nil { | ||||
| 		return err | ||||
| 	if !ctx.Config.GoMod.Proxy { | ||||
| 		if err := checkMain(build); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	target, err := newBuildTarget(options.Target) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| @@ -548,6 +549,47 @@ func TestRunPipeWithoutMainFunc(t *testing.T) { | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestRunPipeWithProxiedRepo(t *testing.T) { | ||||
| 	folder := testlib.Mktmp(t) | ||||
| 	proxied := filepath.Join(folder, "dist/proxy/default") | ||||
| 	require.NoError(t, os.MkdirAll(proxied, 0o750)) | ||||
| 	require.NoError(t, ioutil.WriteFile( | ||||
| 		filepath.Join(proxied, "main.go"), | ||||
| 		[]byte("// +build: main\npackage main\nimport github.com/goreleaser/goreleaser"), | ||||
| 		0o666, | ||||
| 	)) | ||||
| 	require.NoError(t, ioutil.WriteFile( | ||||
| 		filepath.Join(proxied, "go.mod"), | ||||
| 		[]byte("module foo\nrequire github.com/goreleaser/goreleaser v0.161.1"), | ||||
| 		0o666, | ||||
| 	)) | ||||
| 	cmd := exec.Command("go", "mod", "download") | ||||
| 	cmd.Dir = proxied | ||||
| 	require.NoError(t, cmd.Run()) | ||||
| 	config := config.Project{ | ||||
| 		GoMod: config.GoMod{ | ||||
| 			Proxy: true, | ||||
| 		}, | ||||
| 		Builds: []config.Build{ | ||||
| 			{ | ||||
| 				Binary: "foo", | ||||
| 				Hooks:  config.HookConfig{}, | ||||
| 				Main:   "github.com/goreleaser/goreleaser", | ||||
| 				Dir:    proxied, | ||||
| 				Targets: []string{ | ||||
| 					runtimeTarget, | ||||
| 				}, | ||||
| 				GoBinary: "go", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	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, ioutil.WriteFile( | ||||
|   | ||||
| @@ -1,14 +1,29 @@ | ||||
| // Package gomod provides go modules utilities, such as template variables and the ability to proxy the module from | ||||
| // proxy.golang.org. | ||||
| package gomod | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/apex/log" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe" | ||||
| 	"github.com/goreleaser/goreleaser/internal/tmpl" | ||||
| 	"github.com/goreleaser/goreleaser/pkg/config" | ||||
| 	"github.com/goreleaser/goreleaser/pkg/context" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	go115NotAGoModuleError = "go list -m: not using modules" | ||||
| 	go116NotAGoModuleError = "command-line-arguments" | ||||
| ) | ||||
|  | ||||
| // Pipe for env. | ||||
| type Pipe struct{} | ||||
|  | ||||
| @@ -16,14 +31,17 @@ func (Pipe) String() string { | ||||
| 	return "loading go mod information" | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	go115NotAGoModuleError = "go list -m: not using modules" | ||||
| 	go116NotAGoModuleError = "command-line-arguments" | ||||
| ) | ||||
| // Default sets the pipe defaults. | ||||
| func (Pipe) Default(ctx *context.Context) error { | ||||
| 	if ctx.Config.GoMod.GoBinary == "" { | ||||
| 		ctx.Config.GoMod.GoBinary = "go" | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Run the pipe. | ||||
| func (Pipe) Run(ctx *context.Context) error { | ||||
| 	out, err := exec.CommandContext(ctx, "go", "list", "-m").CombinedOutput() | ||||
| 	out, err := exec.CommandContext(ctx, ctx.Config.GoMod.GoBinary, "list", "-m").CombinedOutput() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get module path: %w: %s", err, string(out)) | ||||
| 	} | ||||
| @@ -35,5 +53,139 @@ func (Pipe) Run(ctx *context.Context) error { | ||||
|  | ||||
| 	ctx.ModulePath = result | ||||
|  | ||||
| 	if !ctx.Config.GoMod.Proxy { | ||||
| 		return pipe.Skip("gomod.proxy is disabled") | ||||
| 	} | ||||
|  | ||||
| 	if ctx.Snapshot { | ||||
| 		return pipe.ErrSnapshotEnabled | ||||
| 	} | ||||
|  | ||||
| 	return setupProxy(ctx) | ||||
| } | ||||
|  | ||||
| func setupProxy(ctx *context.Context) error { | ||||
| 	for i := range ctx.Config.Builds { | ||||
| 		build := &ctx.Config.Builds[i] | ||||
| 		if err := proxyBuild(ctx, build); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| const goModTpl = ` | ||||
| module {{ .BuildID }} | ||||
|  | ||||
| require {{ .ModulePath }} {{ .Tag }} | ||||
| ` | ||||
|  | ||||
| const mainGoTpl = ` | ||||
| // +build main | ||||
| package main | ||||
|  | ||||
| import _ "{{ .Main }}" | ||||
| ` | ||||
|  | ||||
| // ErrProxy happens when something goes wrong while proxying the current go module. | ||||
| type ErrProxy struct { | ||||
| 	err     error | ||||
| 	details string | ||||
| } | ||||
|  | ||||
| func newErrProxy(err error) error { | ||||
| 	return ErrProxy{ | ||||
| 		err: err, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func newDetailedErrProxy(err error, details string) error { | ||||
| 	return ErrProxy{ | ||||
| 		err:     err, | ||||
| 		details: details, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e ErrProxy) Error() string { | ||||
| 	out := fmt.Sprintf("failed to proxy module: %v", e.err) | ||||
| 	if e.details != "" { | ||||
| 		return fmt.Sprintf("%s: %s", out, e.details) | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func (e ErrProxy) Unwrap() error { | ||||
| 	return e.err | ||||
| } | ||||
|  | ||||
| func proxyBuild(ctx *context.Context, build *config.Build) error { | ||||
| 	mainPackage := path.Join(ctx.ModulePath, build.Main) | ||||
| 	template := tmpl.New(ctx).WithExtraFields(tmpl.Fields{ | ||||
| 		"Main":    mainPackage, | ||||
| 		"BuildID": build.ID, | ||||
| 	}) | ||||
|  | ||||
| 	log.Infof("proxying %s@%s to build %s", ctx.ModulePath, ctx.Git.CurrentTag, mainPackage) | ||||
|  | ||||
| 	mod, err := template.Apply(goModTpl) | ||||
| 	if err != nil { | ||||
| 		return newErrProxy(err) | ||||
| 	} | ||||
|  | ||||
| 	main, err := template.Apply(mainGoTpl) | ||||
| 	if err != nil { | ||||
| 		return newErrProxy(err) | ||||
| 	} | ||||
|  | ||||
| 	dir := filepath.Join(ctx.Config.Dist, "proxy", build.ID) | ||||
|  | ||||
| 	log.Debugf("creating needed files") | ||||
|  | ||||
| 	if err := os.MkdirAll(dir, 0o755); err != nil { | ||||
| 		return newErrProxy(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(main), 0o666); err != nil { | ||||
| 		return newErrProxy(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(mod), 0o666); err != nil { | ||||
| 		return newErrProxy(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := copyGoSum("go.sum", filepath.Join(dir, "go.sum")); err != nil { | ||||
| 		return newErrProxy(err) | ||||
| 	} | ||||
|  | ||||
| 	log.Debugf("tidying") | ||||
| 	cmd := exec.CommandContext(ctx, ctx.Config.GoMod.GoBinary, "mod", "tidy") | ||||
| 	cmd.Dir = dir | ||||
| 	cmd.Env = append(ctx.Config.GoMod.Env, os.Environ()...) | ||||
| 	if out, err := cmd.CombinedOutput(); err != nil { | ||||
| 		return newDetailedErrProxy(err, string(out)) | ||||
| 	} | ||||
|  | ||||
| 	build.Main = mainPackage | ||||
| 	build.Dir = dir | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func copyGoSum(src, dst string) error { | ||||
| 	r, err := os.OpenFile(src, os.O_RDONLY, 0o666) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, os.ErrNotExist) { | ||||
| 			return nil | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	w, err := os.Create(dst) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer w.Close() | ||||
|  | ||||
| 	_, err = io.Copy(w, r) | ||||
| 	return err | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| package gomod | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/goreleaser/goreleaser/internal/testlib" | ||||
| @@ -12,25 +15,211 @@ import ( | ||||
|  | ||||
| func TestRun(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 	require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 	testlib.AssertSkipped(t, Pipe{}.Run(ctx)) | ||||
| 	require.Equal(t, "github.com/goreleaser/goreleaser", ctx.ModulePath) | ||||
| } | ||||
|  | ||||
| func TestRunSnapshot(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{ | ||||
| 		GoMod: config.GoMod{ | ||||
| 			Proxy: true, | ||||
| 		}, | ||||
| 	}) | ||||
| 	ctx.Snapshot = true | ||||
| 	require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 	testlib.AssertSkipped(t, Pipe{}.Run(ctx)) | ||||
| 	require.Equal(t, "github.com/goreleaser/goreleaser", ctx.ModulePath) | ||||
| } | ||||
|  | ||||
| func TestRunOutsideGoModule(t *testing.T) { | ||||
| 	require.NoError(t, os.Chdir(t.TempDir())) | ||||
| 	dir := testlib.Mktmp(t) | ||||
| 	require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\nfunc main() {println(0)}"), 0o666)) | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 	testlib.AssertSkipped(t, Pipe{}.Run(ctx)) | ||||
| 	require.Empty(t, ctx.ModulePath) | ||||
| } | ||||
|  | ||||
| func TestRunCommandError(t *testing.T) { | ||||
| 	os.Unsetenv("PATH") | ||||
| 	require.NoError(t, os.Chdir(t.TempDir())) | ||||
| 	ctx := context.New(config.Project{}) | ||||
| 	require.EqualError(t, Pipe{}.Run(ctx), "failed to get module path: exec: \"go\": executable file not found in $PATH: ") | ||||
| 	ctx := context.New(config.Project{ | ||||
| 		GoMod: config.GoMod{ | ||||
| 			GoBinary: "not-a-valid-binary", | ||||
| 		}, | ||||
| 	}) | ||||
| 	require.EqualError(t, Pipe{}.Run(ctx), "failed to get module path: exec: \"not-a-valid-binary\": executable file not found in $PATH: ") | ||||
| 	require.Empty(t, ctx.ModulePath) | ||||
| } | ||||
|  | ||||
| func TestDescription(t *testing.T) { | ||||
| 	require.NotEmpty(t, Pipe{}.String()) | ||||
| } | ||||
|  | ||||
| func TestGoModProxy(t *testing.T) { | ||||
| 	t.Run("goreleaser", func(t *testing.T) { | ||||
| 		dir := testlib.Mktmp(t) | ||||
| 		dist := filepath.Join(dir, "dist") | ||||
| 		ctx := context.New(config.Project{ | ||||
| 			Dist: dist, | ||||
| 			GoMod: config.GoMod{ | ||||
| 				Proxy: true, | ||||
| 			}, | ||||
| 			Builds: []config.Build{ | ||||
| 				{ | ||||
| 					ID:     "foo", | ||||
| 					Goos:   []string{runtime.GOOS}, | ||||
| 					Goarch: []string{runtime.GOARCH}, | ||||
| 					Main:   ".", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}) | ||||
| 		ctx.Git.CurrentTag = "v0.161.1" | ||||
|  | ||||
| 		mod := "github.com/goreleaser/goreleaser" | ||||
|  | ||||
| 		fakeGoModAndSum(t, mod) | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 		requireGoMod(t, mod, ctx.Git.CurrentTag) | ||||
| 		requireMainGo(t, mod) | ||||
| 		require.Equal(t, mod, ctx.Config.Builds[0].Main) | ||||
| 		require.Equal(t, filepath.Join(dist, "proxy", "foo"), ctx.Config.Builds[0].Dir) | ||||
| 		require.Equal(t, mod, ctx.ModulePath) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("nfpm", func(t *testing.T) { | ||||
| 		dir := testlib.Mktmp(t) | ||||
| 		dist := filepath.Join(dir, "dist") | ||||
| 		ctx := context.New(config.Project{ | ||||
| 			Dist: dist, | ||||
| 			GoMod: config.GoMod{ | ||||
| 				Proxy: true, | ||||
| 			}, | ||||
| 			Builds: []config.Build{ | ||||
| 				{ | ||||
| 					ID:     "foo", | ||||
| 					Goos:   []string{runtime.GOOS}, | ||||
| 					Goarch: []string{runtime.GOARCH}, | ||||
| 					Main:   "./cmd/nfpm", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}) | ||||
| 		ctx.Git.CurrentTag = "v2.3.1" | ||||
|  | ||||
| 		mod := "github.com/goreleaser/nfpm/v2" | ||||
| 		fakeGoModAndSum(t, mod) | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 		requireGoMod(t, mod, ctx.Git.CurrentTag) | ||||
| 		requireMainGo(t, mod+"/cmd/nfpm") | ||||
| 		require.Equal(t, mod+"/cmd/nfpm", ctx.Config.Builds[0].Main) | ||||
| 		require.Equal(t, filepath.Join(dist, "proxy", "foo"), ctx.Config.Builds[0].Dir) | ||||
| 		require.Equal(t, mod, ctx.ModulePath) | ||||
| 	}) | ||||
|  | ||||
| 	// this repo does not have a go.sum file, which is ok, a project might not have any dependencies | ||||
| 	t.Run("no go.sum", func(t *testing.T) { | ||||
| 		dir := testlib.Mktmp(t) | ||||
| 		dist := filepath.Join(dir, "dist") | ||||
| 		ctx := context.New(config.Project{ | ||||
| 			Dist: dist, | ||||
| 			GoMod: config.GoMod{ | ||||
| 				Proxy: true, | ||||
| 			}, | ||||
| 			Builds: []config.Build{ | ||||
| 				{ | ||||
| 					ID:     "foo", | ||||
| 					Goos:   []string{runtime.GOOS}, | ||||
| 					Goarch: []string{runtime.GOARCH}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}) | ||||
| 		ctx.Git.CurrentTag = "v0.0.1" | ||||
|  | ||||
| 		mod := "github.com/goreleaser/example-mod-proxy" | ||||
| 		fakeGoMod(t, mod) | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		require.NoError(t, Pipe{}.Run(ctx)) | ||||
| 		requireGoMod(t, mod, ctx.Git.CurrentTag) | ||||
| 		requireMainGo(t, mod) | ||||
| 		require.Equal(t, mod, ctx.Config.Builds[0].Main) | ||||
| 		require.Equal(t, filepath.Join(dist, "proxy", "foo"), ctx.Config.Builds[0].Dir) | ||||
| 		require.Equal(t, mod, ctx.ModulePath) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("no perms", func(t *testing.T) { | ||||
| 		for file, mode := range map[string]os.FileMode{ | ||||
| 			"go.mod":          0o500, | ||||
| 			"go.sum":          0o500, | ||||
| 			"main.go":         0o500, | ||||
| 			"../../../go.sum": 0o300, | ||||
| 		} { | ||||
| 			t.Run(file, func(t *testing.T) { | ||||
| 				dir := testlib.Mktmp(t) | ||||
| 				dist := filepath.Join(dir, "dist") | ||||
| 				ctx := context.New(config.Project{ | ||||
| 					Dist: dist, | ||||
| 					GoMod: config.GoMod{ | ||||
| 						Proxy: true, | ||||
| 					}, | ||||
| 					Builds: []config.Build{ | ||||
| 						{ | ||||
| 							ID:     "foo", | ||||
| 							Goos:   []string{runtime.GOOS}, | ||||
| 							Goarch: []string{runtime.GOARCH}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}) | ||||
| 				ctx.Git.CurrentTag = "v0.161.1" | ||||
|  | ||||
| 				mod := "github.com/goreleaser/goreleaser" | ||||
|  | ||||
| 				fakeGoModAndSum(t, mod) | ||||
| 				require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 				require.NoError(t, Pipe{}.Run(ctx)) // should succeed at first | ||||
|  | ||||
| 				// change perms of a file and run again, which should now fail on that file. | ||||
| 				require.NoError(t, os.Chmod(filepath.Join(dist, "proxy", "foo", file), mode)) | ||||
| 				require.ErrorAs(t, Pipe{}.Run(ctx), &ErrProxy{}) | ||||
| 			}) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func requireGoMod(tb testing.TB, module, version string) { | ||||
| 	tb.Helper() | ||||
|  | ||||
| 	mod, err := os.ReadFile("dist/proxy/foo/go.mod") | ||||
| 	require.NoError(tb, err) | ||||
| 	require.Equal(tb, fmt.Sprintf(`module foo | ||||
|  | ||||
| go 1.16 | ||||
|  | ||||
| require %s %s | ||||
| `, module, version), string(mod)) | ||||
| } | ||||
|  | ||||
| func requireMainGo(tb testing.TB, module string) { | ||||
| 	tb.Helper() | ||||
|  | ||||
| 	main, err := os.ReadFile("dist/proxy/foo/main.go") | ||||
| 	require.NoError(tb, err) | ||||
| 	require.Equal(tb, fmt.Sprintf(` | ||||
| // +build main | ||||
| package main | ||||
|  | ||||
| import _ "%s" | ||||
| `, module), string(main)) | ||||
| } | ||||
|  | ||||
| func fakeGoModAndSum(tb testing.TB, module string) { | ||||
| 	tb.Helper() | ||||
|  | ||||
| 	fakeGoMod(tb, module) | ||||
| 	require.NoError(tb, os.WriteFile("go.sum", []byte("\n"), 0o666)) | ||||
| } | ||||
|  | ||||
| func fakeGoMod(tb testing.TB, module string) { | ||||
| 	tb.Helper() | ||||
| 	require.NoError(tb, os.WriteFile("go.mod", []byte(fmt.Sprintf("module %s\n", module)), 0o666)) | ||||
| } | ||||
|   | ||||
| @@ -38,7 +38,6 @@ type Piper interface { | ||||
| // BuildPipeline contains all build-related pipe implementations in order. | ||||
| // nolint:gochecknoglobals | ||||
| var BuildPipeline = []Piper{ | ||||
| 	gomod.Pipe{},           // setup gomod-related stuff | ||||
| 	env.Pipe{},             // load and validate environment variables | ||||
| 	git.Pipe{},             // get and validate git repo state | ||||
| 	semver.Pipe{},          // parse current tag to a semver | ||||
| @@ -46,6 +45,7 @@ var BuildPipeline = []Piper{ | ||||
| 	defaults.Pipe{},        // load default configs | ||||
| 	snapshot.Pipe{},        // snapshot version handling | ||||
| 	dist.Pipe{},            // ensure ./dist is clean | ||||
| 	gomod.Pipe{},           // setup gomod-related stuff | ||||
| 	effectiveconfig.Pipe{}, // writes the actual config (with defaults et al set) to dist | ||||
| 	changelog.Pipe{},       // builds the release changelog | ||||
| 	build.Pipe{},           // build | ||||
|   | ||||
							
								
								
									
										4
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								main.go
									
									
									
									
									
								
							| @@ -3,6 +3,7 @@ package main | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"runtime/debug" | ||||
|  | ||||
| 	"github.com/goreleaser/goreleaser/cmd" | ||||
| ) | ||||
| @@ -34,5 +35,8 @@ func buildVersion(version, commit, date, builtBy string) string { | ||||
| 	if builtBy != "" { | ||||
| 		result = fmt.Sprintf("%s\nbuilt by: %s", result, builtBy) | ||||
| 	} | ||||
| 	if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" { | ||||
| 		result = fmt.Sprintf("%s\nmodule version: %s, checksum: %s", result, info.Main.Version, info.Main.Sum) | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|   | ||||
| @@ -610,6 +610,7 @@ type Project struct { | ||||
| 	EnvFiles        EnvFiles         `yaml:"env_files,omitempty"` | ||||
| 	Before          Before           `yaml:",omitempty"` | ||||
| 	Source          Source           `yaml:",omitempty"` | ||||
| 	GoMod           GoMod            `yaml:"gomod,omitempty"` | ||||
|  | ||||
| 	// this is a hack ¯\_(ツ)_/¯ | ||||
| 	SingleBuild Build `yaml:"build,omitempty"` | ||||
| @@ -624,6 +625,12 @@ type Project struct { | ||||
| 	GiteaURLs GiteaURLs `yaml:"gitea_urls,omitempty"` | ||||
| } | ||||
|  | ||||
| type GoMod struct { | ||||
| 	Proxy    bool     `yaml:",omitempty"` | ||||
| 	Env      []string `yaml:",omitempty"` | ||||
| 	GoBinary string   `yaml:",omitempty"` | ||||
| } | ||||
|  | ||||
| // Load config file. | ||||
| func Load(file string) (config Project, err error) { | ||||
| 	f, err := os.Open(file) // #nosec | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import ( | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/build" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/checksums" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/docker" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/gomod" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/milestone" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/nfpm" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/project" | ||||
| @@ -39,6 +40,7 @@ var Defaulters = []Defaulter{ | ||||
| 	snapshot.Pipe{}, | ||||
| 	release.Pipe{}, | ||||
| 	project.Pipe{}, | ||||
| 	gomod.Pipe{}, | ||||
| 	build.Pipe{}, | ||||
| 	sourcearchive.Pipe{}, | ||||
| 	archive.Pipe{}, | ||||
|   | ||||
							
								
								
									
										85
									
								
								www/docs/cookbooks/build-go-modules.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								www/docs/cookbooks/build-go-modules.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| # Building Go modules | ||||
|  | ||||
| With the default configs, you can already build a Go module without issues. | ||||
|  | ||||
| But, if you want to access module information in runtime (e.g. `debug.BuildInfo` or `go version -m $binary`), you'll | ||||
| need to setup GoReleaser to "proxy" that module before building it. | ||||
|  | ||||
| To do that, you can simply add this to your config: | ||||
|  | ||||
| ```yaml | ||||
| # goreleaser.yml | ||||
| gomod: | ||||
|   proxy: true | ||||
| ``` | ||||
|  | ||||
| In practice, what this does is: | ||||
|  | ||||
| - for each of your builds, create a `dist/proxy/{{ build.id }}`; | ||||
| - creates a `go.mod` that requires your __main module__ at the __current tag__; | ||||
| - creates a `main.go` that imports your __main package__; | ||||
| - copy the project's `go.sum` to that folder. | ||||
|  | ||||
| In which: | ||||
|  | ||||
| - __build.id__: the `id` property in your `build` definition; | ||||
| - __main module__: is the output of `go list -m`; | ||||
| - __main package__: is the __main module__ + your build's `main`; | ||||
| - __current tag__: is the tag that is being built. | ||||
|  | ||||
| So, let's say: | ||||
|  | ||||
| - __main module__: `github.com/goreleaser/nfpm/v2`; | ||||
| - build's `main`: `./cmd/nfpm/`; | ||||
| - __current tag__: `v2.5.0`. | ||||
|  | ||||
| GoReleaser will create a `main.go` like: | ||||
|  | ||||
| ```go | ||||
| // +build: main | ||||
| package main | ||||
|  | ||||
| import _ "github.com/goreleaser/nfpm/v2/cmd/nfpm" | ||||
| ``` | ||||
|  | ||||
| a `go.mod` like: | ||||
|  | ||||
| ``` | ||||
| module nfpm | ||||
|  | ||||
| require github.com/goreleaser/nfpm/v2 v2.5.0 | ||||
| ``` | ||||
|  | ||||
| Then, it'll run: | ||||
|  | ||||
| ```sh | ||||
| go mod tidy | ||||
| ``` | ||||
|  | ||||
| And, to build, it will use something like: | ||||
|  | ||||
| ```shell | ||||
| go build -o nfpm github.com/goreleaser/nfpm/v2/cmd/nfpm | ||||
| ``` | ||||
|  | ||||
| This will resolve the source code from the defined module proxy using `proxy.golang.org`. | ||||
| Your project's `go.sum` will be used to verify any modules that are downloaded, with `sum.golang.org` "filling in" any gaps. | ||||
|  | ||||
| ## Limitations | ||||
|  | ||||
| 1. Extra files will still be copied from the current project's root folder and not from the proxy cache; | ||||
| 1. You can't build packages that are not contained in the main module. | ||||
|  | ||||
| ## More information | ||||
|  | ||||
| You can find more information about it on the [issue][issue] that originated it and its subsequent [pull request][pr]. | ||||
|  | ||||
| Make sure to also read the [relevant documentation][docs] for more options. | ||||
|  | ||||
| [issue]: https://github.com/goreleaser/goreleaser/issues/1354 | ||||
| [pr]: https://github.com/goreleaser/goreleaser/pull/2129 | ||||
| [docs]: /customization/gomod/ | ||||
|  | ||||
| ## Real example | ||||
|  | ||||
| Source code of a working example can be found at [goreleaser/example-mod-proxy](https://github.com/goreleaser/example-mod-proxy). | ||||
							
								
								
									
										37
									
								
								www/docs/customization/gomod.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								www/docs/customization/gomod.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| --- | ||||
| title: Go Modules | ||||
| --- | ||||
|  | ||||
| GoReleaser has support for creating verifiable builds. | ||||
| A [verifiable build][vgo] is one that records enough information to be precise about exactly how to repeat it. | ||||
| All dependencies are loaded via `proxy.golang.org`, and verified against the checksum database `sum.golang.org`. | ||||
| A GoReleaser-created verifiable build will include module information in the resulting binary, which can be printed using `go version -m mybinary`. | ||||
|  | ||||
| Configuration options available are described bellow. | ||||
|  | ||||
| ```yaml | ||||
| # goreleaser.yml | ||||
|  | ||||
| gomod: | ||||
|   # Proxy a module from proxy.golang.org, making the builds verifiable. | ||||
|   # This will only be effective if running against a tag. Snapshots will ignore this setting. | ||||
|   # | ||||
|   # Default is false. | ||||
|   proxy: true | ||||
|  | ||||
|   # If proxy is true, use these environment variables when running `go mod` commands (namely, `go mod tidy`). | ||||
|   # Defaults to `os.Environ()`. | ||||
|   env: | ||||
|     - GOPROXY=https://proxy.golang.org,direct | ||||
|     - GOSUMDB=sum.golang.org | ||||
|     - GOPRIVATE=example.com/blah | ||||
|  | ||||
|   # Which Go binary to use. | ||||
|   # Defaults to `go`. | ||||
|   gobinary: go1.15 | ||||
| ``` | ||||
|  | ||||
| !!! tip | ||||
|     You can use `debug.ReadBuildInfo()` to get the version/checksum/dependencies of the module. | ||||
|  | ||||
| [vgo]: https://research.swtch.com/vgo-repro | ||||
| @@ -62,6 +62,7 @@ nav: | ||||
|   - customization/artifactory.md | ||||
|   - customization/bintray.md | ||||
|   - customization/blob.md | ||||
|   - customization/gomod.md | ||||
|   - customization/build.md | ||||
|   - customization/checksum.md | ||||
|   - customization/publishers.md | ||||
| @@ -87,6 +88,7 @@ nav: | ||||
| - Cookbooks: | ||||
|   - About: cookbooks/index.md | ||||
|   - Blog Posts: cookbooks/blog-posts.md | ||||
|   - cookbooks//build-go-modules.md | ||||
|   - cookbooks/semantic-release.md | ||||
|   - cookbooks/release-a-library.md | ||||
|   - cookbooks/publish-to-nexus.md | ||||
|   | ||||
		Reference in New Issue
	
	Block a user