github.com/megatontech/mynoteforgo@v0.0.0-20200507084910-5d0c6ea6e890/源码/cmd/go/internal/modfetch/coderepo.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 "archive/zip" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "path" 14 "strings" 15 16 "cmd/go/internal/modfetch/codehost" 17 "cmd/go/internal/modfile" 18 "cmd/go/internal/module" 19 "cmd/go/internal/semver" 20 ) 21 22 // A codeRepo implements modfetch.Repo using an underlying codehost.Repo. 23 type codeRepo struct { 24 modPath string 25 26 code codehost.Repo 27 codeRoot string 28 codeDir string 29 30 path string 31 pathPrefix string 32 pathMajor string 33 pseudoMajor string 34 } 35 36 func newCodeRepo(code codehost.Repo, root, path string) (Repo, error) { 37 if !hasPathPrefix(path, root) { 38 return nil, fmt.Errorf("mismatched repo: found %s for %s", root, path) 39 } 40 pathPrefix, pathMajor, ok := module.SplitPathVersion(path) 41 if !ok { 42 return nil, fmt.Errorf("invalid module path %q", path) 43 } 44 pseudoMajor := "v0" 45 if pathMajor != "" { 46 pseudoMajor = pathMajor[1:] 47 } 48 49 // At this point we might have: 50 // codeRoot = github.com/rsc/foo 51 // path = github.com/rsc/foo/bar/v2 52 // pathPrefix = github.com/rsc/foo/bar 53 // pathMajor = /v2 54 // pseudoMajor = v2 55 // 56 // Compute codeDir = bar, the subdirectory within the repo 57 // corresponding to the module root. 58 codeDir := strings.Trim(strings.TrimPrefix(pathPrefix, root), "/") 59 if strings.HasPrefix(path, "gopkg.in/") { 60 // But gopkg.in is a special legacy case, in which pathPrefix does not start with codeRoot. 61 // For example we might have: 62 // codeRoot = gopkg.in/yaml.v2 63 // pathPrefix = gopkg.in/yaml 64 // pathMajor = .v2 65 // pseudoMajor = v2 66 // codeDir = pathPrefix (because codeRoot is not a prefix of pathPrefix) 67 // Clear codeDir - the module root is the repo root for gopkg.in repos. 68 codeDir = "" 69 } 70 71 r := &codeRepo{ 72 modPath: path, 73 code: code, 74 codeRoot: root, 75 codeDir: codeDir, 76 pathPrefix: pathPrefix, 77 pathMajor: pathMajor, 78 pseudoMajor: pseudoMajor, 79 } 80 81 return r, nil 82 } 83 84 func (r *codeRepo) ModulePath() string { 85 return r.modPath 86 } 87 88 func (r *codeRepo) Versions(prefix string) ([]string, error) { 89 // Special case: gopkg.in/macaroon-bakery.v2-unstable 90 // does not use the v2 tags (those are for macaroon-bakery.v2). 91 // It has no possible tags at all. 92 if strings.HasPrefix(r.modPath, "gopkg.in/") && strings.HasSuffix(r.modPath, "-unstable") { 93 return nil, nil 94 } 95 96 p := prefix 97 if r.codeDir != "" { 98 p = r.codeDir + "/" + p 99 } 100 tags, err := r.code.Tags(p) 101 if err != nil { 102 return nil, err 103 } 104 105 list := []string{} 106 var incompatible []string 107 for _, tag := range tags { 108 if !strings.HasPrefix(tag, p) { 109 continue 110 } 111 v := tag 112 if r.codeDir != "" { 113 v = v[len(r.codeDir)+1:] 114 } 115 if v == "" || v != module.CanonicalVersion(v) || IsPseudoVersion(v) { 116 continue 117 } 118 if !module.MatchPathMajor(v, r.pathMajor) { 119 if r.codeDir == "" && r.pathMajor == "" && semver.Major(v) > "v1" { 120 incompatible = append(incompatible, v) 121 } 122 continue 123 } 124 list = append(list, v) 125 } 126 127 if len(incompatible) > 0 { 128 // Check for later versions that were created not following semantic import versioning, 129 // as indicated by the absence of a go.mod file. Those versions can be addressed 130 // by referring to them with a +incompatible suffix, as in v17.0.0+incompatible. 131 files, err := r.code.ReadFileRevs(incompatible, "go.mod", codehost.MaxGoMod) 132 if err != nil { 133 return nil, err 134 } 135 for _, rev := range incompatible { 136 f := files[rev] 137 if os.IsNotExist(f.Err) { 138 list = append(list, rev+"+incompatible") 139 } 140 } 141 } 142 143 SortVersions(list) 144 return list, nil 145 } 146 147 func (r *codeRepo) Stat(rev string) (*RevInfo, error) { 148 if rev == "latest" { 149 return r.Latest() 150 } 151 codeRev := r.revToRev(rev) 152 if semver.IsValid(codeRev) && r.codeDir != "" { 153 codeRev = r.codeDir + "/" + codeRev 154 } 155 info, err := r.code.Stat(codeRev) 156 if err != nil { 157 return nil, err 158 } 159 return r.convert(info, rev) 160 } 161 162 func (r *codeRepo) Latest() (*RevInfo, error) { 163 info, err := r.code.Latest() 164 if err != nil { 165 return nil, err 166 } 167 return r.convert(info, "") 168 } 169 170 func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, error) { 171 info2 := &RevInfo{ 172 Name: info.Name, 173 Short: info.Short, 174 Time: info.Time, 175 } 176 177 // Determine version. 178 if module.CanonicalVersion(statVers) == statVers && module.MatchPathMajor(statVers, r.pathMajor) { 179 // The original call was repo.Stat(statVers), and requestedVersion is OK, so use it. 180 info2.Version = statVers 181 } else { 182 // Otherwise derive a version from a code repo tag. 183 // Tag must have a prefix matching codeDir. 184 p := "" 185 if r.codeDir != "" { 186 p = r.codeDir + "/" 187 } 188 189 // If this is a plain tag (no dir/ prefix) 190 // and the module path is unversioned, 191 // and if the underlying file tree has no go.mod, 192 // then allow using the tag with a +incompatible suffix. 193 canUseIncompatible := false 194 if r.codeDir == "" && r.pathMajor == "" { 195 _, errGoMod := r.code.ReadFile(info.Name, "go.mod", codehost.MaxGoMod) 196 if errGoMod != nil { 197 canUseIncompatible = true 198 } 199 } 200 201 tagToVersion := func(v string) string { 202 if !strings.HasPrefix(v, p) { 203 return "" 204 } 205 v = v[len(p):] 206 if module.CanonicalVersion(v) != v || IsPseudoVersion(v) { 207 return "" 208 } 209 if module.MatchPathMajor(v, r.pathMajor) { 210 return v 211 } 212 if canUseIncompatible { 213 return v + "+incompatible" 214 } 215 return "" 216 } 217 218 // If info.Version is OK, use it. 219 if v := tagToVersion(info.Version); v != "" { 220 info2.Version = v 221 } else { 222 // Otherwise look through all known tags for latest in semver ordering. 223 for _, tag := range info.Tags { 224 if v := tagToVersion(tag); v != "" && semver.Compare(info2.Version, v) < 0 { 225 info2.Version = v 226 } 227 } 228 // Otherwise make a pseudo-version. 229 if info2.Version == "" { 230 tag, _ := r.code.RecentTag(statVers, p) 231 v = tagToVersion(tag) 232 // TODO: Check that v is OK for r.pseudoMajor or else is OK for incompatible. 233 info2.Version = PseudoVersion(r.pseudoMajor, v, info.Time, info.Short) 234 } 235 } 236 } 237 238 // Do not allow a successful stat of a pseudo-version for a subdirectory 239 // unless the subdirectory actually does have a go.mod. 240 if IsPseudoVersion(info2.Version) && r.codeDir != "" { 241 _, _, _, err := r.findDir(info2.Version) 242 if err != nil { 243 // TODO: It would be nice to return an error like "not a module". 244 // Right now we return "missing go.mod", which is a little confusing. 245 return nil, err 246 } 247 } 248 249 return info2, nil 250 } 251 252 func (r *codeRepo) revToRev(rev string) string { 253 if semver.IsValid(rev) { 254 if IsPseudoVersion(rev) { 255 r, _ := PseudoVersionRev(rev) 256 return r 257 } 258 if semver.Build(rev) == "+incompatible" { 259 rev = rev[:len(rev)-len("+incompatible")] 260 } 261 if r.codeDir == "" { 262 return rev 263 } 264 return r.codeDir + "/" + rev 265 } 266 return rev 267 } 268 269 func (r *codeRepo) versionToRev(version string) (rev string, err error) { 270 if !semver.IsValid(version) { 271 return "", fmt.Errorf("malformed semantic version %q", version) 272 } 273 return r.revToRev(version), nil 274 } 275 276 func (r *codeRepo) findDir(version string) (rev, dir string, gomod []byte, err error) { 277 rev, err = r.versionToRev(version) 278 if err != nil { 279 return "", "", nil, err 280 } 281 282 // Load info about go.mod but delay consideration 283 // (except I/O error) until we rule out v2/go.mod. 284 file1 := path.Join(r.codeDir, "go.mod") 285 gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod) 286 if err1 != nil && !os.IsNotExist(err1) { 287 return "", "", nil, fmt.Errorf("reading %s/%s at revision %s: %v", r.pathPrefix, file1, rev, err1) 288 } 289 mpath1 := modfile.ModulePath(gomod1) 290 found1 := err1 == nil && isMajor(mpath1, r.pathMajor) 291 292 var file2 string 293 if r.pathMajor != "" && !strings.HasPrefix(r.pathMajor, ".") { 294 // Suppose pathMajor is "/v2". 295 // Either go.mod should claim v2 and v2/go.mod should not exist, 296 // or v2/go.mod should exist and claim v2. Not both. 297 // Note that we don't check the full path, just the major suffix, 298 // because of replacement modules. This might be a fork of 299 // the real module, found at a different path, usable only in 300 // a replace directive. 301 dir2 := path.Join(r.codeDir, r.pathMajor[1:]) 302 file2 = path.Join(dir2, "go.mod") 303 gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod) 304 if err2 != nil && !os.IsNotExist(err2) { 305 return "", "", nil, fmt.Errorf("reading %s/%s at revision %s: %v", r.pathPrefix, file2, rev, err2) 306 } 307 mpath2 := modfile.ModulePath(gomod2) 308 found2 := err2 == nil && isMajor(mpath2, r.pathMajor) 309 310 if found1 && found2 { 311 return "", "", nil, fmt.Errorf("%s/%s and ...%s/go.mod both have ...%s module paths at revision %s", r.pathPrefix, file1, r.pathMajor, r.pathMajor, rev) 312 } 313 if found2 { 314 return rev, dir2, gomod2, nil 315 } 316 if err2 == nil { 317 if mpath2 == "" { 318 return "", "", nil, fmt.Errorf("%s/%s is missing module path at revision %s", r.pathPrefix, file2, rev) 319 } 320 return "", "", nil, fmt.Errorf("%s/%s has non-...%s module path %q at revision %s", r.pathPrefix, file2, r.pathMajor, mpath2, rev) 321 } 322 } 323 324 // Not v2/go.mod, so it's either go.mod or nothing. Which is it? 325 if found1 { 326 // Explicit go.mod with matching module path OK. 327 return rev, r.codeDir, gomod1, nil 328 } 329 if err1 == nil { 330 // Explicit go.mod with non-matching module path disallowed. 331 suffix := "" 332 if file2 != "" { 333 suffix = fmt.Sprintf(" (and ...%s/go.mod does not exist)", r.pathMajor) 334 } 335 if mpath1 == "" { 336 return "", "", nil, fmt.Errorf("%s is missing module path%s at revision %s", file1, suffix, rev) 337 } 338 if r.pathMajor != "" { // ".v1", ".v2" for gopkg.in 339 return "", "", nil, fmt.Errorf("%s has non-...%s module path %q%s at revision %s", file1, r.pathMajor, mpath1, suffix, rev) 340 } 341 return "", "", nil, fmt.Errorf("%s has post-%s module path %q%s at revision %s", file1, semver.Major(version), mpath1, suffix, rev) 342 } 343 344 if r.codeDir == "" && (r.pathMajor == "" || strings.HasPrefix(r.pathMajor, ".")) { 345 // Implicit go.mod at root of repo OK for v0/v1 and for gopkg.in. 346 return rev, "", nil, nil 347 } 348 349 // Implicit go.mod below root of repo or at v2+ disallowed. 350 // Be clear about possibility of using either location for v2+. 351 if file2 != "" { 352 return "", "", nil, fmt.Errorf("missing %s/go.mod and ...%s/go.mod at revision %s", r.pathPrefix, r.pathMajor, rev) 353 } 354 return "", "", nil, fmt.Errorf("missing %s/go.mod at revision %s", r.pathPrefix, rev) 355 } 356 357 func isMajor(mpath, pathMajor string) bool { 358 if mpath == "" { 359 return false 360 } 361 if pathMajor == "" { 362 // mpath must NOT have version suffix. 363 i := len(mpath) 364 for i > 0 && '0' <= mpath[i-1] && mpath[i-1] <= '9' { 365 i-- 366 } 367 if i < len(mpath) && i >= 2 && mpath[i-1] == 'v' && mpath[i-2] == '/' { 368 // Found valid suffix. 369 return false 370 } 371 return true 372 } 373 // Otherwise pathMajor is ".v1", ".v2" (gopkg.in), or "/v2", "/v3" etc. 374 return strings.HasSuffix(mpath, pathMajor) 375 } 376 377 func (r *codeRepo) GoMod(version string) (data []byte, err error) { 378 rev, dir, gomod, err := r.findDir(version) 379 if err != nil { 380 return nil, err 381 } 382 if gomod != nil { 383 return gomod, nil 384 } 385 data, err = r.code.ReadFile(rev, path.Join(dir, "go.mod"), codehost.MaxGoMod) 386 if err != nil { 387 if os.IsNotExist(err) { 388 return r.legacyGoMod(rev, dir), nil 389 } 390 return nil, err 391 } 392 return data, nil 393 } 394 395 func (r *codeRepo) legacyGoMod(rev, dir string) []byte { 396 // We used to try to build a go.mod reflecting pre-existing 397 // package management metadata files, but the conversion 398 // was inherently imperfect (because those files don't have 399 // exactly the same semantics as go.mod) and, when done 400 // for dependencies in the middle of a build, impossible to 401 // correct. So we stopped. 402 // Return a fake go.mod that simply declares the module path. 403 return []byte(fmt.Sprintf("module %s\n", modfile.AutoQuote(r.modPath))) 404 } 405 406 func (r *codeRepo) modPrefix(rev string) string { 407 return r.modPath + "@" + rev 408 } 409 410 func (r *codeRepo) Zip(dst io.Writer, version string) error { 411 rev, dir, _, err := r.findDir(version) 412 if err != nil { 413 return err 414 } 415 dl, actualDir, err := r.code.ReadZip(rev, dir, codehost.MaxZipFile) 416 if err != nil { 417 return err 418 } 419 defer dl.Close() 420 if actualDir != "" && !hasPathPrefix(dir, actualDir) { 421 return fmt.Errorf("internal error: downloading %v %v: dir=%q but actualDir=%q", r.path, rev, dir, actualDir) 422 } 423 subdir := strings.Trim(strings.TrimPrefix(dir, actualDir), "/") 424 425 // Spool to local file. 426 f, err := ioutil.TempFile("", "go-codehost-") 427 if err != nil { 428 dl.Close() 429 return err 430 } 431 defer os.Remove(f.Name()) 432 defer f.Close() 433 maxSize := int64(codehost.MaxZipFile) 434 lr := &io.LimitedReader{R: dl, N: maxSize + 1} 435 if _, err := io.Copy(f, lr); err != nil { 436 dl.Close() 437 return err 438 } 439 dl.Close() 440 if lr.N <= 0 { 441 return fmt.Errorf("downloaded zip file too large") 442 } 443 size := (maxSize + 1) - lr.N 444 if _, err := f.Seek(0, 0); err != nil { 445 return err 446 } 447 448 // Translate from zip file we have to zip file we want. 449 zr, err := zip.NewReader(f, size) 450 if err != nil { 451 return err 452 } 453 454 zw := zip.NewWriter(dst) 455 if subdir != "" { 456 subdir += "/" 457 } 458 haveLICENSE := false 459 topPrefix := "" 460 haveGoMod := make(map[string]bool) 461 for _, zf := range zr.File { 462 if topPrefix == "" { 463 i := strings.Index(zf.Name, "/") 464 if i < 0 { 465 return fmt.Errorf("missing top-level directory prefix") 466 } 467 topPrefix = zf.Name[:i+1] 468 } 469 if !strings.HasPrefix(zf.Name, topPrefix) { 470 return fmt.Errorf("zip file contains more than one top-level directory") 471 } 472 dir, file := path.Split(zf.Name) 473 if file == "go.mod" { 474 haveGoMod[dir] = true 475 } 476 } 477 root := topPrefix + subdir 478 inSubmodule := func(name string) bool { 479 for { 480 dir, _ := path.Split(name) 481 if len(dir) <= len(root) { 482 return false 483 } 484 if haveGoMod[dir] { 485 return true 486 } 487 name = dir[:len(dir)-1] 488 } 489 } 490 491 for _, zf := range zr.File { 492 if !zf.FileInfo().Mode().IsRegular() { 493 // Skip symlinks (golang.org/issue/27093). 494 continue 495 } 496 497 if topPrefix == "" { 498 i := strings.Index(zf.Name, "/") 499 if i < 0 { 500 return fmt.Errorf("missing top-level directory prefix") 501 } 502 topPrefix = zf.Name[:i+1] 503 } 504 if strings.HasSuffix(zf.Name, "/") { // drop directory dummy entries 505 continue 506 } 507 if !strings.HasPrefix(zf.Name, topPrefix) { 508 return fmt.Errorf("zip file contains more than one top-level directory") 509 } 510 name := strings.TrimPrefix(zf.Name, topPrefix) 511 if !strings.HasPrefix(name, subdir) { 512 continue 513 } 514 if name == ".hg_archival.txt" { 515 // Inserted by hg archive. 516 // Not correct to drop from other version control systems, but too bad. 517 continue 518 } 519 name = strings.TrimPrefix(name, subdir) 520 if isVendoredPackage(name) { 521 continue 522 } 523 if inSubmodule(zf.Name) { 524 continue 525 } 526 base := path.Base(name) 527 if strings.ToLower(base) == "go.mod" && base != "go.mod" { 528 return fmt.Errorf("zip file contains %s, want all lower-case go.mod", zf.Name) 529 } 530 if name == "LICENSE" { 531 haveLICENSE = true 532 } 533 size := int64(zf.UncompressedSize64) 534 if size < 0 || maxSize < size { 535 return fmt.Errorf("module source tree too big") 536 } 537 maxSize -= size 538 539 rc, err := zf.Open() 540 if err != nil { 541 return err 542 } 543 w, err := zw.Create(r.modPrefix(version) + "/" + name) 544 lr := &io.LimitedReader{R: rc, N: size + 1} 545 if _, err := io.Copy(w, lr); err != nil { 546 return err 547 } 548 if lr.N <= 0 { 549 return fmt.Errorf("individual file too large") 550 } 551 } 552 553 if !haveLICENSE && subdir != "" { 554 data, err := r.code.ReadFile(rev, "LICENSE", codehost.MaxLICENSE) 555 if err == nil { 556 w, err := zw.Create(r.modPrefix(version) + "/LICENSE") 557 if err != nil { 558 return err 559 } 560 if _, err := w.Write(data); err != nil { 561 return err 562 } 563 } 564 } 565 566 return zw.Close() 567 } 568 569 // hasPathPrefix reports whether the path s begins with the 570 // elements in prefix. 571 func hasPathPrefix(s, prefix string) bool { 572 switch { 573 default: 574 return false 575 case len(s) == len(prefix): 576 return s == prefix 577 case len(s) > len(prefix): 578 if prefix != "" && prefix[len(prefix)-1] == '/' { 579 return strings.HasPrefix(s, prefix) 580 } 581 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 582 } 583 } 584 585 func isVendoredPackage(name string) bool { 586 var i int 587 if strings.HasPrefix(name, "vendor/") { 588 i += len("vendor/") 589 } else if j := strings.Index(name, "/vendor/"); j >= 0 { 590 i += len("/vendor/") 591 } else { 592 return false 593 } 594 return strings.Contains(name[i:], "/") 595 }