github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/modfetch/repo.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package modfetch 6 7 import ( 8 "fmt" 9 "io" 10 "io/fs" 11 "os" 12 "strconv" 13 "time" 14 15 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/cfg" 16 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/modfetch/codehost" 17 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/par" 18 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/vcs" 19 web "github.com/bir3/gocompiler/src/cmd/gocmd/internal/web" 20 21 "github.com/bir3/gocompiler/src/xvendor/golang.org/x/mod/module" 22 ) 23 24 const traceRepo = false // trace all repo actions, for debugging 25 26 // A Repo represents a repository storing all versions of a single module. 27 // It must be safe for simultaneous use by multiple goroutines. 28 type Repo interface { 29 // ModulePath returns the module path. 30 ModulePath() string 31 32 // CheckReuse checks whether the validation criteria in the origin 33 // are still satisfied on the server corresponding to this module. 34 // If so, the caller can reuse any cached Versions or RevInfo containing 35 // this origin rather than redownloading those from the server. 36 CheckReuse(old *codehost.Origin) error 37 38 // Versions lists all known versions with the given prefix. 39 // Pseudo-versions are not included. 40 // 41 // Versions should be returned sorted in semver order 42 // (implementations can use semver.Sort). 43 // 44 // Versions returns a non-nil error only if there was a problem 45 // fetching the list of versions: it may return an empty list 46 // along with a nil error if the list of matching versions 47 // is known to be empty. 48 // 49 // If the underlying repository does not exist, 50 // Versions returns an error matching errors.Is(_, os.NotExist). 51 Versions(prefix string) (*Versions, error) 52 53 // Stat returns information about the revision rev. 54 // A revision can be any identifier known to the underlying service: 55 // commit hash, branch, tag, and so on. 56 Stat(rev string) (*RevInfo, error) 57 58 // Latest returns the latest revision on the default branch, 59 // whatever that means in the underlying source code repository. 60 // It is only used when there are no tagged versions. 61 Latest() (*RevInfo, error) 62 63 // GoMod returns the go.mod file for the given version. 64 GoMod(version string) (data []byte, err error) 65 66 // Zip writes a zip file for the given version to dst. 67 Zip(dst io.Writer, version string) error 68 } 69 70 // A Versions describes the available versions in a module repository. 71 type Versions struct { 72 Origin *codehost.Origin `json:",omitempty"` // origin information for reuse 73 74 List []string // semver versions 75 } 76 77 // A RevInfo describes a single revision in a module repository. 78 type RevInfo struct { 79 Version string // suggested version string for this revision 80 Time time.Time // commit time 81 82 // These fields are used for Stat of arbitrary rev, 83 // but they are not recorded when talking about module versions. 84 Name string `json:"-"` // complete ID in underlying repository 85 Short string `json:"-"` // shortened ID, for use in pseudo-version 86 87 Origin *codehost.Origin `json:",omitempty"` // provenance for reuse 88 } 89 90 // Re: module paths, import paths, repository roots, and lookups 91 // 92 // A module is a collection of Go packages stored in a file tree 93 // with a go.mod file at the root of the tree. 94 // The go.mod defines the module path, which is the import path 95 // corresponding to the root of the file tree. 96 // The import path of a directory within that file tree is the module path 97 // joined with the name of the subdirectory relative to the root. 98 // 99 // For example, the module with path rsc.io/qr corresponds to the 100 // file tree in the repository https://github.com/rsc/qr. 101 // That file tree has a go.mod that says "module rsc.io/qr". 102 // The package in the root directory has import path "rsc.io/qr". 103 // The package in the gf256 subdirectory has import path "rsc.io/qr/gf256". 104 // In this example, "rsc.io/qr" is both a module path and an import path. 105 // But "rsc.io/qr/gf256" is only an import path, not a module path: 106 // it names an importable package, but not a module. 107 // 108 // As a special case to incorporate code written before modules were 109 // introduced, if a path p resolves using the pre-module "go get" lookup 110 // to the root of a source code repository without a go.mod file, 111 // that repository is treated as if it had a go.mod in its root directory 112 // declaring module path p. (The go.mod is further considered to 113 // contain requirements corresponding to any legacy version 114 // tracking format such as Gopkg.lock, vendor/vendor.conf, and so on.) 115 // 116 // The presentation so far ignores the fact that a source code repository 117 // has many different versions of a file tree, and those versions may 118 // differ in whether a particular go.mod exists and what it contains. 119 // In fact there is a well-defined mapping only from a module path, version 120 // pair - often written path@version - to a particular file tree. 121 // For example rsc.io/qr@v0.1.0 depends on the "implicit go.mod at root of 122 // repository" rule, while rsc.io/qr@v0.2.0 has an explicit go.mod. 123 // Because the "go get" import paths rsc.io/qr and github.com/rsc/qr 124 // both redirect to the Git repository https://github.com/rsc/qr, 125 // github.com/rsc/qr@v0.1.0 is the same file tree as rsc.io/qr@v0.1.0 126 // but a different module (a different name). In contrast, since v0.2.0 127 // of that repository has an explicit go.mod that declares path rsc.io/qr, 128 // github.com/rsc/qr@v0.2.0 is an invalid module path, version pair. 129 // Before modules, import comments would have had the same effect. 130 // 131 // The set of import paths associated with a given module path is 132 // clearly not fixed: at the least, new directories with new import paths 133 // can always be added. But another potential operation is to split a 134 // subtree out of a module into its own module. If done carefully, 135 // this operation can be done while preserving compatibility for clients. 136 // For example, suppose that we want to split rsc.io/qr/gf256 into its 137 // own module, so that there would be two modules rsc.io/qr and rsc.io/qr/gf256. 138 // Then we can simultaneously issue rsc.io/qr v0.3.0 (dropping the gf256 subdirectory) 139 // and rsc.io/qr/gf256 v0.1.0, including in their respective go.mod 140 // cyclic requirements pointing at each other: rsc.io/qr v0.3.0 requires 141 // rsc.io/qr/gf256 v0.1.0 and vice versa. Then a build can be 142 // using an older rsc.io/qr module that includes the gf256 package, but if 143 // it adds a requirement on either the newer rsc.io/qr or the newer 144 // rsc.io/qr/gf256 module, it will automatically add the requirement 145 // on the complementary half, ensuring both that rsc.io/qr/gf256 is 146 // available for importing by the build and also that it is only defined 147 // by a single module. The gf256 package could move back into the 148 // original by another simultaneous release of rsc.io/qr v0.4.0 including 149 // the gf256 subdirectory and an rsc.io/qr/gf256 v0.2.0 with no code 150 // in its root directory, along with a new requirement cycle. 151 // The ability to shift module boundaries in this way is expected to be 152 // important in large-scale program refactorings, similar to the ones 153 // described in https://talks.golang.org/2016/refactor.article. 154 // 155 // The possibility of shifting module boundaries reemphasizes 156 // that you must know both the module path and its version 157 // to determine the set of packages provided directly by that module. 158 // 159 // On top of all this, it is possible for a single code repository 160 // to contain multiple modules, either in branches or subdirectories, 161 // as a limited kind of monorepo. For example rsc.io/qr/v2, 162 // the v2.x.x continuation of rsc.io/qr, is expected to be found 163 // in v2-tagged commits in https://github.com/rsc/qr, either 164 // in the root or in a v2 subdirectory, disambiguated by go.mod. 165 // Again the precise file tree corresponding to a module 166 // depends on which version we are considering. 167 // 168 // It is also possible for the underlying repository to change over time, 169 // without changing the module path. If I copy the github repo over 170 // to https://bitbucket.org/rsc/qr and update https://rsc.io/qr?go-get=1, 171 // then clients of all versions should start fetching from bitbucket 172 // instead of github. That is, in contrast to the exact file tree, 173 // the location of the source code repository associated with a module path 174 // does not depend on the module version. (This is by design, as the whole 175 // point of these redirects is to allow package authors to establish a stable 176 // name that can be updated as code moves from one service to another.) 177 // 178 // All of this is important background for the lookup APIs defined in this 179 // file. 180 // 181 // The Lookup function takes a module path and returns a Repo representing 182 // that module path. Lookup can do only a little with the path alone. 183 // It can check that the path is well-formed (see semver.CheckPath) 184 // and it can check that the path can be resolved to a target repository. 185 // To avoid version control access except when absolutely necessary, 186 // Lookup does not attempt to connect to the repository itself. 187 188 var lookupCache par.Cache 189 190 type lookupCacheKey struct { 191 proxy, path string 192 } 193 194 // Lookup returns the module with the given module path, 195 // fetched through the given proxy. 196 // 197 // The distinguished proxy "direct" indicates that the path should be fetched 198 // from its origin, and "noproxy" indicates that the patch should be fetched 199 // directly only if GONOPROXY matches the given path. 200 // 201 // For the distinguished proxy "off", Lookup always returns a Repo that returns 202 // a non-nil error for every method call. 203 // 204 // A successful return does not guarantee that the module 205 // has any defined versions. 206 func Lookup(proxy, path string) Repo { 207 if traceRepo { 208 defer logCall("Lookup(%q, %q)", proxy, path)() 209 } 210 211 type cached struct { 212 r Repo 213 } 214 c := lookupCache.Do(lookupCacheKey{proxy, path}, func() any { 215 r := newCachingRepo(path, func() (Repo, error) { 216 r, err := lookup(proxy, path) 217 if err == nil && traceRepo { 218 r = newLoggingRepo(r) 219 } 220 return r, err 221 }) 222 return cached{r} 223 }).(cached) 224 225 return c.r 226 } 227 228 // lookup returns the module with the given module path. 229 func lookup(proxy, path string) (r Repo, err error) { 230 if cfg.BuildMod == "vendor" { 231 return nil, errLookupDisabled 232 } 233 234 if module.MatchPrefixPatterns(cfg.GONOPROXY, path) { 235 switch proxy { 236 case "noproxy", "direct": 237 return lookupDirect(path) 238 default: 239 return nil, errNoproxy 240 } 241 } 242 243 switch proxy { 244 case "off": 245 return errRepo{path, errProxyOff}, nil 246 case "direct": 247 return lookupDirect(path) 248 case "noproxy": 249 return nil, errUseProxy 250 default: 251 return newProxyRepo(proxy, path) 252 } 253 } 254 255 type lookupDisabledError struct{} 256 257 func (lookupDisabledError) Error() string { 258 if cfg.BuildModReason == "" { 259 return fmt.Sprintf("module lookup disabled by -mod=%s", cfg.BuildMod) 260 } 261 return fmt.Sprintf("module lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason) 262 } 263 264 var errLookupDisabled error = lookupDisabledError{} 265 266 var ( 267 errProxyOff = notExistErrorf("module lookup disabled by GOPROXY=off") 268 errNoproxy error = notExistErrorf("disabled by GOPRIVATE/GONOPROXY") 269 errUseProxy error = notExistErrorf("path does not match GOPRIVATE/GONOPROXY") 270 ) 271 272 func lookupDirect(path string) (Repo, error) { 273 security := web.SecureOnly 274 275 if module.MatchPrefixPatterns(cfg.GOINSECURE, path) { 276 security = web.Insecure 277 } 278 rr, err := vcs.RepoRootForImportPath(path, vcs.PreferMod, security) 279 if err != nil { 280 // We don't know where to find code for a module with this path. 281 return nil, notExistError{err: err} 282 } 283 284 if rr.VCS.Name == "mod" { 285 // Fetch module from proxy with base URL rr.Repo. 286 return newProxyRepo(rr.Repo, path) 287 } 288 289 code, err := lookupCodeRepo(rr) 290 if err != nil { 291 return nil, err 292 } 293 return newCodeRepo(code, rr.Root, path) 294 } 295 296 func lookupCodeRepo(rr *vcs.RepoRoot) (codehost.Repo, error) { 297 code, err := codehost.NewRepo(rr.VCS.Cmd, rr.Repo) 298 if err != nil { 299 if _, ok := err.(*codehost.VCSError); ok { 300 return nil, err 301 } 302 return nil, fmt.Errorf("lookup %s: %v", rr.Root, err) 303 } 304 return code, nil 305 } 306 307 // A loggingRepo is a wrapper around an underlying Repo 308 // that prints a log message at the start and end of each call. 309 // It can be inserted when debugging. 310 type loggingRepo struct { 311 r Repo 312 } 313 314 func newLoggingRepo(r Repo) *loggingRepo { 315 return &loggingRepo{r} 316 } 317 318 // logCall prints a log message using format and args and then 319 // also returns a function that will print the same message again, 320 // along with the elapsed time. 321 // Typical usage is: 322 // 323 // defer logCall("hello %s", arg)() 324 // 325 // Note the final (). 326 func logCall(format string, args ...any) func() { 327 start := time.Now() 328 fmt.Fprintf(os.Stderr, "+++ %s\n", fmt.Sprintf(format, args...)) 329 return func() { 330 fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), fmt.Sprintf(format, args...)) 331 } 332 } 333 334 func (l *loggingRepo) ModulePath() string { 335 return l.r.ModulePath() 336 } 337 338 func (l *loggingRepo) CheckReuse(old *codehost.Origin) (err error) { 339 defer func() { 340 logCall("CheckReuse[%s]: %v", l.r.ModulePath(), err) 341 }() 342 return l.r.CheckReuse(old) 343 } 344 345 func (l *loggingRepo) Versions(prefix string) (*Versions, error) { 346 defer logCall("Repo[%s]: Versions(%q)", l.r.ModulePath(), prefix)() 347 return l.r.Versions(prefix) 348 } 349 350 func (l *loggingRepo) Stat(rev string) (*RevInfo, error) { 351 defer logCall("Repo[%s]: Stat(%q)", l.r.ModulePath(), rev)() 352 return l.r.Stat(rev) 353 } 354 355 func (l *loggingRepo) Latest() (*RevInfo, error) { 356 defer logCall("Repo[%s]: Latest()", l.r.ModulePath())() 357 return l.r.Latest() 358 } 359 360 func (l *loggingRepo) GoMod(version string) ([]byte, error) { 361 defer logCall("Repo[%s]: GoMod(%q)", l.r.ModulePath(), version)() 362 return l.r.GoMod(version) 363 } 364 365 func (l *loggingRepo) Zip(dst io.Writer, version string) error { 366 dstName := "_" 367 if dst, ok := dst.(interface{ Name() string }); ok { 368 dstName = strconv.Quote(dst.Name()) 369 } 370 defer logCall("Repo[%s]: Zip(%s, %q)", l.r.ModulePath(), dstName, version)() 371 return l.r.Zip(dst, version) 372 } 373 374 // errRepo is a Repo that returns the same error for all operations. 375 // 376 // It is useful in conjunction with caching, since cache hits will not attempt 377 // the prohibited operations. 378 type errRepo struct { 379 modulePath string 380 err error 381 } 382 383 func (r errRepo) ModulePath() string { return r.modulePath } 384 385 func (r errRepo) CheckReuse(old *codehost.Origin) error { return r.err } 386 func (r errRepo) Versions(prefix string) (*Versions, error) { return nil, r.err } 387 func (r errRepo) Stat(rev string) (*RevInfo, error) { return nil, r.err } 388 func (r errRepo) Latest() (*RevInfo, error) { return nil, r.err } 389 func (r errRepo) GoMod(version string) ([]byte, error) { return nil, r.err } 390 func (r errRepo) Zip(dst io.Writer, version string) error { return r.err } 391 392 // A notExistError is like fs.ErrNotExist, but with a custom message 393 type notExistError struct { 394 err error 395 } 396 397 func notExistErrorf(format string, args ...any) error { 398 return notExistError{fmt.Errorf(format, args...)} 399 } 400 401 func (e notExistError) Error() string { 402 return e.err.Error() 403 } 404 405 func (notExistError) Is(target error) bool { 406 return target == fs.ErrNotExist 407 } 408 409 func (e notExistError) Unwrap() error { 410 return e.err 411 }