github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/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(version string, tmpdir string) (tmpfile string, err 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 if actualDir != "" && !hasPathPrefix(dir, actualDir) { 420 return "", fmt.Errorf("internal error: downloading %v %v: dir=%q but actualDir=%q", r.path, rev, dir, actualDir) 421 } 422 subdir := strings.Trim(strings.TrimPrefix(dir, actualDir), "/") 423 424 // Spool to local file. 425 f, err := ioutil.TempFile(tmpdir, "go-codehost-") 426 if err != nil { 427 dl.Close() 428 return "", err 429 } 430 defer os.Remove(f.Name()) 431 defer f.Close() 432 maxSize := int64(codehost.MaxZipFile) 433 lr := &io.LimitedReader{R: dl, N: maxSize + 1} 434 if _, err := io.Copy(f, lr); err != nil { 435 dl.Close() 436 return "", err 437 } 438 dl.Close() 439 if lr.N <= 0 { 440 return "", fmt.Errorf("downloaded zip file too large") 441 } 442 size := (maxSize + 1) - lr.N 443 if _, err := f.Seek(0, 0); err != nil { 444 return "", err 445 } 446 447 // Translate from zip file we have to zip file we want. 448 zr, err := zip.NewReader(f, size) 449 if err != nil { 450 return "", err 451 } 452 f2, err := ioutil.TempFile(tmpdir, "go-codezip-") 453 if err != nil { 454 return "", err 455 } 456 457 zw := zip.NewWriter(f2) 458 newName := f2.Name() 459 defer func() { 460 f2.Close() 461 if err != nil { 462 os.Remove(newName) 463 } 464 }() 465 if subdir != "" { 466 subdir += "/" 467 } 468 haveLICENSE := false 469 topPrefix := "" 470 haveGoMod := make(map[string]bool) 471 for _, zf := range zr.File { 472 if topPrefix == "" { 473 i := strings.Index(zf.Name, "/") 474 if i < 0 { 475 return "", fmt.Errorf("missing top-level directory prefix") 476 } 477 topPrefix = zf.Name[:i+1] 478 } 479 if !strings.HasPrefix(zf.Name, topPrefix) { 480 return "", fmt.Errorf("zip file contains more than one top-level directory") 481 } 482 dir, file := path.Split(zf.Name) 483 if file == "go.mod" { 484 haveGoMod[dir] = true 485 } 486 } 487 root := topPrefix + subdir 488 inSubmodule := func(name string) bool { 489 for { 490 dir, _ := path.Split(name) 491 if len(dir) <= len(root) { 492 return false 493 } 494 if haveGoMod[dir] { 495 return true 496 } 497 name = dir[:len(dir)-1] 498 } 499 } 500 for _, zf := range zr.File { 501 if topPrefix == "" { 502 i := strings.Index(zf.Name, "/") 503 if i < 0 { 504 return "", fmt.Errorf("missing top-level directory prefix") 505 } 506 topPrefix = zf.Name[:i+1] 507 } 508 if strings.HasSuffix(zf.Name, "/") { // drop directory dummy entries 509 continue 510 } 511 if !strings.HasPrefix(zf.Name, topPrefix) { 512 return "", fmt.Errorf("zip file contains more than one top-level directory") 513 } 514 name := strings.TrimPrefix(zf.Name, topPrefix) 515 if !strings.HasPrefix(name, subdir) { 516 continue 517 } 518 if name == ".hg_archival.txt" { 519 // Inserted by hg archive. 520 // Not correct to drop from other version control systems, but too bad. 521 continue 522 } 523 name = strings.TrimPrefix(name, subdir) 524 if isVendoredPackage(name) { 525 continue 526 } 527 if inSubmodule(zf.Name) { 528 continue 529 } 530 base := path.Base(name) 531 if strings.ToLower(base) == "go.mod" && base != "go.mod" { 532 return "", fmt.Errorf("zip file contains %s, want all lower-case go.mod", zf.Name) 533 } 534 if name == "LICENSE" { 535 haveLICENSE = true 536 } 537 size := int64(zf.UncompressedSize) 538 if size < 0 || maxSize < size { 539 return "", fmt.Errorf("module source tree too big") 540 } 541 maxSize -= size 542 543 rc, err := zf.Open() 544 if err != nil { 545 return "", err 546 } 547 w, err := zw.Create(r.modPrefix(version) + "/" + name) 548 lr := &io.LimitedReader{R: rc, N: size + 1} 549 if _, err := io.Copy(w, lr); err != nil { 550 return "", err 551 } 552 if lr.N <= 0 { 553 return "", fmt.Errorf("individual file too large") 554 } 555 } 556 557 if !haveLICENSE && subdir != "" { 558 data, err := r.code.ReadFile(rev, "LICENSE", codehost.MaxLICENSE) 559 if err == nil { 560 w, err := zw.Create(r.modPrefix(version) + "/LICENSE") 561 if err != nil { 562 return "", err 563 } 564 if _, err := w.Write(data); err != nil { 565 return "", err 566 } 567 } 568 } 569 if err := zw.Close(); err != nil { 570 return "", err 571 } 572 if err := f2.Close(); err != nil { 573 return "", err 574 } 575 576 return f2.Name(), nil 577 } 578 579 // hasPathPrefix reports whether the path s begins with the 580 // elements in prefix. 581 func hasPathPrefix(s, prefix string) bool { 582 switch { 583 default: 584 return false 585 case len(s) == len(prefix): 586 return s == prefix 587 case len(s) > len(prefix): 588 if prefix != "" && prefix[len(prefix)-1] == '/' { 589 return strings.HasPrefix(s, prefix) 590 } 591 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix 592 } 593 } 594 595 func isVendoredPackage(name string) bool { 596 var i int 597 if strings.HasPrefix(name, "vendor/") { 598 i += len("vendor/") 599 } else if j := strings.Index(name, "/vendor/"); j >= 0 { 600 i += len("/vendor/") 601 } else { 602 return false 603 } 604 return strings.Contains(name[i:], "/") 605 }