libgo: update to Go 1.11
[official-gcc.git] / libgo / go / cmd / go / internal / modfetch / coderepo.go
blob9cf0e911508c01eb076a8e881919f55dd8a00a38
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.
5 package modfetch
7 import (
8 "archive/zip"
9 "fmt"
10 "io"
11 "io/ioutil"
12 "os"
13 "path"
14 "strings"
16 "cmd/go/internal/modfetch/codehost"
17 "cmd/go/internal/modfile"
18 "cmd/go/internal/module"
19 "cmd/go/internal/semver"
22 // A codeRepo implements modfetch.Repo using an underlying codehost.Repo.
23 type codeRepo struct {
24 modPath string
26 code codehost.Repo
27 codeRoot string
28 codeDir string
30 path string
31 pathPrefix string
32 pathMajor string
33 pseudoMajor string
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)
40 pathPrefix, pathMajor, ok := module.SplitPathVersion(path)
41 if !ok {
42 return nil, fmt.Errorf("invalid module path %q", path)
44 pseudoMajor := "v0"
45 if pathMajor != "" {
46 pseudoMajor = pathMajor[1:]
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
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 = ""
71 r := &codeRepo{
72 modPath: path,
73 code: code,
74 codeRoot: root,
75 codeDir: codeDir,
76 pathPrefix: pathPrefix,
77 pathMajor: pathMajor,
78 pseudoMajor: pseudoMajor,
81 return r, nil
84 func (r *codeRepo) ModulePath() string {
85 return r.modPath
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
96 p := prefix
97 if r.codeDir != "" {
98 p = r.codeDir + "/" + p
100 tags, err := r.code.Tags(p)
101 if err != nil {
102 return nil, err
105 list := []string{}
106 var incompatible []string
107 for _, tag := range tags {
108 if !strings.HasPrefix(tag, p) {
109 continue
111 v := tag
112 if r.codeDir != "" {
113 v = v[len(r.codeDir)+1:]
115 if v == "" || v != module.CanonicalVersion(v) || IsPseudoVersion(v) {
116 continue
118 if !module.MatchPathMajor(v, r.pathMajor) {
119 if r.codeDir == "" && r.pathMajor == "" && semver.Major(v) > "v1" {
120 incompatible = append(incompatible, v)
122 continue
124 list = append(list, v)
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
135 for _, rev := range incompatible {
136 f := files[rev]
137 if os.IsNotExist(f.Err) {
138 list = append(list, rev+"+incompatible")
143 SortVersions(list)
144 return list, nil
147 func (r *codeRepo) Stat(rev string) (*RevInfo, error) {
148 if rev == "latest" {
149 return r.Latest()
151 codeRev := r.revToRev(rev)
152 if semver.IsValid(codeRev) && r.codeDir != "" {
153 codeRev = r.codeDir + "/" + codeRev
155 info, err := r.code.Stat(codeRev)
156 if err != nil {
157 return nil, err
159 return r.convert(info, rev)
162 func (r *codeRepo) Latest() (*RevInfo, error) {
163 info, err := r.code.Latest()
164 if err != nil {
165 return nil, err
167 return r.convert(info, "")
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,
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 + "/"
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
201 tagToVersion := func(v string) string {
202 if !strings.HasPrefix(v, p) {
203 return ""
205 v = v[len(p):]
206 if module.CanonicalVersion(v) != v || IsPseudoVersion(v) {
207 return ""
209 if module.MatchPathMajor(v, r.pathMajor) {
210 return v
212 if canUseIncompatible {
213 return v + "+incompatible"
215 return ""
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
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)
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
249 return info2, nil
252 func (r *codeRepo) revToRev(rev string) string {
253 if semver.IsValid(rev) {
254 if IsPseudoVersion(rev) {
255 r, _ := PseudoVersionRev(rev)
256 return r
258 if semver.Build(rev) == "+incompatible" {
259 rev = rev[:len(rev)-len("+incompatible")]
261 if r.codeDir == "" {
262 return rev
264 return r.codeDir + "/" + rev
266 return rev
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)
273 return r.revToRev(version), nil
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
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)
289 mpath1 := modfile.ModulePath(gomod1)
290 found1 := err1 == nil && isMajor(mpath1, r.pathMajor)
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)
307 mpath2 := modfile.ModulePath(gomod2)
308 found2 := err2 == nil && isMajor(mpath2, r.pathMajor)
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)
313 if found2 {
314 return rev, dir2, gomod2, nil
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)
320 return "", "", nil, fmt.Errorf("%s/%s has non-...%s module path %q at revision %s", r.pathPrefix, file2, r.pathMajor, mpath2, rev)
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
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)
335 if mpath1 == "" {
336 return "", "", nil, fmt.Errorf("%s is missing module path%s at revision %s", file1, suffix, rev)
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)
341 return "", "", nil, fmt.Errorf("%s has post-%s module path %q%s at revision %s", file1, semver.Major(version), mpath1, suffix, rev)
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
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)
354 return "", "", nil, fmt.Errorf("missing %s/go.mod at revision %s", r.pathPrefix, rev)
357 func isMajor(mpath, pathMajor string) bool {
358 if mpath == "" {
359 return false
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' {
367 if i < len(mpath) && i >= 2 && mpath[i-1] == 'v' && mpath[i-2] == '/' {
368 // Found valid suffix.
369 return false
371 return true
373 // Otherwise pathMajor is ".v1", ".v2" (gopkg.in), or "/v2", "/v3" etc.
374 return strings.HasSuffix(mpath, pathMajor)
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
382 if gomod != nil {
383 return gomod, nil
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
390 return nil, err
392 return data, nil
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)))
406 func (r *codeRepo) modPrefix(rev string) string {
407 return r.modPath + "@" + rev
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
415 dl, actualDir, err := r.code.ReadZip(rev, dir, codehost.MaxZipFile)
416 if err != nil {
417 return "", err
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)
422 subdir := strings.Trim(strings.TrimPrefix(dir, actualDir), "/")
424 // Spool to local file.
425 f, err := ioutil.TempFile(tmpdir, "go-codehost-")
426 if err != nil {
427 dl.Close()
428 return "", err
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
438 dl.Close()
439 if lr.N <= 0 {
440 return "", fmt.Errorf("downloaded zip file too large")
442 size := (maxSize + 1) - lr.N
443 if _, err := f.Seek(0, 0); err != nil {
444 return "", err
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
452 f2, err := ioutil.TempFile(tmpdir, "go-codezip-")
453 if err != nil {
454 return "", err
457 zw := zip.NewWriter(f2)
458 newName := f2.Name()
459 defer func() {
460 f2.Close()
461 if err != nil {
462 os.Remove(newName)
465 if subdir != "" {
466 subdir += "/"
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")
477 topPrefix = zf.Name[:i+1]
479 if !strings.HasPrefix(zf.Name, topPrefix) {
480 return "", fmt.Errorf("zip file contains more than one top-level directory")
482 dir, file := path.Split(zf.Name)
483 if file == "go.mod" {
484 haveGoMod[dir] = true
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
494 if haveGoMod[dir] {
495 return true
497 name = dir[:len(dir)-1]
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")
506 topPrefix = zf.Name[:i+1]
508 if strings.HasSuffix(zf.Name, "/") { // drop directory dummy entries
509 continue
511 if !strings.HasPrefix(zf.Name, topPrefix) {
512 return "", fmt.Errorf("zip file contains more than one top-level directory")
514 name := strings.TrimPrefix(zf.Name, topPrefix)
515 if !strings.HasPrefix(name, subdir) {
516 continue
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
523 name = strings.TrimPrefix(name, subdir)
524 if isVendoredPackage(name) {
525 continue
527 if inSubmodule(zf.Name) {
528 continue
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)
534 if name == "LICENSE" {
535 haveLICENSE = true
537 size := int64(zf.UncompressedSize)
538 if size < 0 || maxSize < size {
539 return "", fmt.Errorf("module source tree too big")
541 maxSize -= size
543 rc, err := zf.Open()
544 if err != nil {
545 return "", err
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
552 if lr.N <= 0 {
553 return "", fmt.Errorf("individual file too large")
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
564 if _, err := w.Write(data); err != nil {
565 return "", err
569 if err := zw.Close(); err != nil {
570 return "", err
572 if err := f2.Close(); err != nil {
573 return "", err
576 return f2.Name(), nil
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)
591 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
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
604 return strings.Contains(name[i:], "/")