github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/gomod/gomod_proxy.go (about) 1 // Package gomod provides go modules utilities, such as template variables and the ability to proxy the module from 2 // proxy.golang.org. 3 package gomod 4 5 import ( 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "os/exec" 11 "path" 12 "path/filepath" 13 "regexp" 14 "strings" 15 16 "github.com/caarlos0/log" 17 "github.com/goreleaser/goreleaser/internal/logext" 18 "github.com/goreleaser/goreleaser/internal/tmpl" 19 "github.com/goreleaser/goreleaser/pkg/config" 20 "github.com/goreleaser/goreleaser/pkg/context" 21 ) 22 23 // ErrReplaceWithProxy happens when the configuration has gomod.proxy enabled, 24 // and the go.mod file contains replace directives. 25 // 26 // Replaces does not work with proxying, nor with go installs, 27 // and are made for development only. 28 var ErrReplaceWithProxy = errors.New("cannot use the go.mod replace directive with go mod proxy enabled") 29 30 type CheckGoModPipe struct{} 31 32 func (CheckGoModPipe) String() string { return "checking go.mod" } 33 func (CheckGoModPipe) Skip(ctx *context.Context) bool { 34 return ctx.ModulePath == "" || !ctx.Config.GoMod.Proxy 35 } 36 37 var replaceRe = regexp.MustCompile("^replace .* => .*$") 38 39 // Run the ReplaceCheckPipe. 40 func (CheckGoModPipe) Run(ctx *context.Context) error { 41 for i := range ctx.Config.Builds { 42 build := &ctx.Config.Builds[i] 43 path := filepath.Join(build.UnproxiedDir, "go.mod") 44 mod, err := os.ReadFile(path) 45 if err != nil { 46 log.Errorf("could not check %q", path) 47 return nil 48 } 49 for _, line := range strings.Split(string(mod), "\n") { 50 if !replaceRe.MatchString(line) { 51 continue 52 } 53 log.Warnf( 54 "your %[2]s file has %[1]s directive in it, and go mod proxying is enabled - "+ 55 "this does not work, and you need to either disable it or remove the %[1]s directive", 56 logext.Keyword("replace"), 57 logext.Keyword("go.mod"), 58 ) 59 log.Warnf("the offending line is %s", logext.Keyword(strings.TrimSpace(line))) 60 if ctx.Snapshot { 61 // only warn on snapshots 62 break 63 } 64 return ErrReplaceWithProxy 65 } 66 } 67 68 return nil 69 } 70 71 // ProxyPipe for gomod proxy. 72 type ProxyPipe struct{} 73 74 func (ProxyPipe) String() string { return "proxying go module" } 75 76 func (ProxyPipe) Skip(ctx *context.Context) bool { 77 return ctx.ModulePath == "" || !ctx.Config.GoMod.Proxy || ctx.Snapshot 78 } 79 80 // Run the ProxyPipe. 81 func (ProxyPipe) Run(ctx *context.Context) error { 82 for i := range ctx.Config.Builds { 83 build := &ctx.Config.Builds[i] 84 if err := proxyBuild(ctx, build); err != nil { 85 return err 86 } 87 } 88 89 return nil 90 } 91 92 const goModTpl = `module {{ .BuildID }}` 93 94 // ErrProxy happens when something goes wrong while proxying the current go module. 95 type ErrProxy struct { 96 err error 97 details string 98 } 99 100 func newErrProxy(err error) error { 101 return ErrProxy{ 102 err: err, 103 } 104 } 105 106 func newDetailedErrProxy(err error, details string) error { 107 return ErrProxy{ 108 err: err, 109 details: details, 110 } 111 } 112 113 func (e ErrProxy) Error() string { 114 out := fmt.Sprintf("failed to proxy module: %v", e.err) 115 if e.details != "" { 116 return fmt.Sprintf("%s: %s", out, e.details) 117 } 118 return out 119 } 120 121 func (e ErrProxy) Unwrap() error { 122 return e.err 123 } 124 125 func proxyBuild(ctx *context.Context, build *config.Build) error { 126 mainPackage := path.Join(ctx.ModulePath, build.Main) 127 if strings.HasSuffix(build.Main, ".go") { 128 pkg := path.Dir(build.Main) 129 log.Warnf("guessing package of '%s' to be '%s', if this is incorrect, setup 'build.%s.main' to be the correct package", build.Main, pkg, build.ID) 130 mainPackage = path.Join(ctx.ModulePath, pkg) 131 } 132 template := tmpl.New(ctx).WithExtraFields(tmpl.Fields{ 133 "Main": mainPackage, 134 "BuildID": build.ID, 135 }) 136 137 log.Infof("proxying %s@%s to build %s", ctx.ModulePath, ctx.Git.CurrentTag, mainPackage) 138 139 mod, err := template.Apply(goModTpl) 140 if err != nil { 141 return newErrProxy(err) 142 } 143 144 dir := filepath.Join(ctx.Config.Dist, "proxy", build.ID) 145 146 log.Debugf("creating needed files") 147 148 if err := os.MkdirAll(dir, 0o755); err != nil { 149 return newErrProxy(err) 150 } 151 152 if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(mod), 0o666); err != nil { 153 return newErrProxy(err) 154 } 155 156 if err := copyGoSum("go.sum", filepath.Join(dir, "go.sum")); err != nil { 157 return newErrProxy(err) 158 } 159 160 log.Debugf("tidying") 161 cmd := exec.CommandContext(ctx, ctx.Config.GoMod.GoBinary, "get", ctx.ModulePath+"@"+ctx.Git.CurrentTag) 162 cmd.Dir = dir 163 cmd.Env = append(ctx.Config.GoMod.Env, os.Environ()...) 164 if out, err := cmd.CombinedOutput(); err != nil { 165 return newDetailedErrProxy(err, string(out)) 166 } 167 168 build.UnproxiedMain = build.Main 169 build.UnproxiedDir = build.Dir 170 build.Main = mainPackage 171 build.Dir = dir 172 return nil 173 } 174 175 func copyGoSum(src, dst string) error { 176 r, err := os.OpenFile(src, os.O_RDONLY, 0o666) 177 if err != nil { 178 if errors.Is(err, os.ErrNotExist) { 179 return nil 180 } 181 return err 182 } 183 defer r.Close() 184 185 w, err := os.Create(dst) 186 if err != nil { 187 return err 188 } 189 defer w.Close() 190 191 _, err = io.Copy(w, r) 192 return err 193 }