From 5756895877492e3427c92e9ec8784eb1f4b01474 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Fri, 8 Jun 2018 13:59:17 -0400 Subject: [PATCH] cmd/go: add dark copy of golang.org/x/vgo This CL corresponds to golang.org/cl/118096 (7fbc8df48a7) in the vgo repo. It copies the bulk of the code from vgo back into the main repo, but completely disabled - vgo.Init is a no-op and vgo.Enabled returns false unconditionally. The point of this CL is to make the two trees easier to diff and to make future syncs smaller. Change-Id: Ic34fd5ddd8272a70c5a3b3437b5169e967d0ed03 Reviewed-on: https://go-review.googlesource.com/118095 Reviewed-by: Bryan C. Mills --- src/cmd/go/go_test.go | 6 +- src/cmd/go/internal/dirhash/hash.go | 103 ++ src/cmd/go/internal/dirhash/hash_test.go | 135 ++ src/cmd/go/internal/envcmd/env.go | 3 + src/cmd/go/internal/fix/fix.go | 11 + src/cmd/go/internal/fmtcmd/fmt.go | 10 + src/cmd/go/internal/generate/generate.go | 10 + src/cmd/go/internal/get/get.go | 16 +- src/cmd/go/internal/help/help.go | 4 + src/cmd/go/internal/imports/build.go | 211 +++ src/cmd/go/internal/imports/read.go | 249 +++ src/cmd/go/internal/imports/read_test.go | 228 +++ src/cmd/go/internal/imports/scan.go | 82 + src/cmd/go/internal/imports/scan_test.go | 67 + src/cmd/go/internal/imports/tags.go | 35 + .../go/internal/imports/testdata/import1/x.go | 3 + .../internal/imports/testdata/import1/x1.go | 9 + .../imports/testdata/import1/x_darwin.go | 3 + .../imports/testdata/import1/x_windows.go | 3 + src/cmd/go/internal/load/path.go | 16 - src/cmd/go/internal/load/pkg.go | 98 +- src/cmd/go/internal/load/search.go | 338 +--- src/cmd/go/internal/modconv/dep.go | 71 + src/cmd/go/internal/modconv/glide.go | 40 + src/cmd/go/internal/modconv/glock.go | 23 + src/cmd/go/internal/modconv/godeps.go | 29 + src/cmd/go/internal/modconv/modconv.go | 25 + src/cmd/go/internal/modconv/modconv_test.go | 69 + .../internal/modconv/testdata/cockroach.glock | 41 + .../internal/modconv/testdata/cockroach.out | 31 + .../modconv/testdata/dockermachine.godeps | 159 ++ .../modconv/testdata/dockermachine.out | 33 + .../internal/modconv/testdata/dockerman.glide | 52 + .../internal/modconv/testdata/dockerman.out | 16 + .../go/internal/modconv/testdata/govmomi.out | 5 + .../modconv/testdata/govmomi.vmanifest | 46 + src/cmd/go/internal/modconv/testdata/juju.out | 106 ++ src/cmd/go/internal/modconv/testdata/juju.tsv | 106 ++ src/cmd/go/internal/modconv/testdata/moby.out | 105 ++ .../go/internal/modconv/testdata/moby.vconf | 149 ++ .../internal/modconv/testdata/panicparse.out | 8 + .../internal/modconv/testdata/panicparse.vyml | 17 + .../internal/modconv/testdata/prometheus.out | 258 +++ .../modconv/testdata/prometheus.vjson | 1605 +++++++++++++++++ .../go/internal/modconv/testdata/upspin.dep | 57 + .../go/internal/modconv/testdata/upspin.out | 8 + src/cmd/go/internal/modconv/tsv.go | 23 + src/cmd/go/internal/modconv/vconf.go | 26 + src/cmd/go/internal/modconv/vjson.go | 28 + src/cmd/go/internal/modconv/vmanifest.go | 28 + src/cmd/go/internal/modconv/vyml.go | 40 + .../go/internal/modfetch/bitbucket/fetch.go | 22 + .../go/internal/modfetch/codehost/codehost.go | 187 ++ src/cmd/go/internal/modfetch/coderepo.go | 576 ++++++ src/cmd/go/internal/modfetch/coderepo_test.go | 688 +++++++ src/cmd/go/internal/modfetch/convert.go | 78 + src/cmd/go/internal/modfetch/convert_test.go | 139 ++ src/cmd/go/internal/modfetch/domain.go | 178 ++ src/cmd/go/internal/modfetch/github/fetch.go | 24 + src/cmd/go/internal/modfetch/gitrepo/fetch.go | 445 +++++ .../internal/modfetch/gitrepo/fetch_test.go | 500 +++++ .../internal/modfetch/googlesource/fetch.go | 25 + src/cmd/go/internal/modfetch/gopkgin.go | 22 + src/cmd/go/internal/modfetch/noweb.go | 24 + src/cmd/go/internal/modfetch/proxy.go | 164 ++ src/cmd/go/internal/modfetch/query.go | 84 + src/cmd/go/internal/modfetch/repo.go | 137 ++ src/cmd/go/internal/modfetch/unzip.go | 100 + src/cmd/go/internal/modfetch/web.go | 31 + src/cmd/go/internal/modfile/gopkgin.go | 47 + src/cmd/go/internal/modfile/print.go | 164 ++ src/cmd/go/internal/modfile/read.go | 699 +++++++ src/cmd/go/internal/modfile/read_test.go | 306 ++++ src/cmd/go/internal/modfile/rule.go | 507 ++++++ .../go/internal/modfile/testdata/block.golden | 29 + src/cmd/go/internal/modfile/testdata/block.in | 29 + .../internal/modfile/testdata/comment.golden | 10 + .../go/internal/modfile/testdata/comment.in | 8 + .../go/internal/modfile/testdata/empty.golden | 0 src/cmd/go/internal/modfile/testdata/empty.in | 0 .../internal/modfile/testdata/gopkg.in.golden | 6 + .../internal/modfile/testdata/module.golden | 1 + .../go/internal/modfile/testdata/module.in | 1 + .../internal/modfile/testdata/replace.golden | 5 + .../go/internal/modfile/testdata/replace.in | 5 + .../internal/modfile/testdata/replace2.golden | 8 + .../go/internal/modfile/testdata/replace2.in | 8 + .../go/internal/modfile/testdata/rule1.golden | 7 + src/cmd/go/internal/modinfo/info.go | 11 + src/cmd/go/internal/module/module.go | 195 ++ src/cmd/go/internal/module/module_test.go | 184 ++ src/cmd/go/internal/mvs/mvs.go | 308 ++++ src/cmd/go/internal/mvs/mvs_test.go | 377 ++++ src/cmd/go/internal/search/search.go | 423 +++++ .../match_test.go => search/search_test.go} | 10 +- src/cmd/go/internal/semver/semver.go | 351 ++++ src/cmd/go/internal/semver/semver_test.go | 123 ++ src/cmd/go/internal/vgo/build.go | 115 ++ src/cmd/go/internal/vgo/fetch.go | 232 +++ src/cmd/go/internal/vgo/get.go | 152 ++ src/cmd/go/internal/vgo/init.go | 411 +++++ src/cmd/go/internal/vgo/list.go | 153 ++ src/cmd/go/internal/vgo/load.go | 575 ++++++ src/cmd/go/internal/vgo/search.go | 196 ++ src/cmd/go/internal/vgo/vendor.go | 156 ++ src/cmd/go/internal/vgo/verify.go | 104 ++ src/cmd/go/internal/web2/web.go | 297 +++ src/cmd/go/internal/web2/web_test.go | 35 + src/cmd/go/internal/webtest/test.go | 314 ++++ src/cmd/go/internal/work/build.go | 3 +- src/cmd/go/internal/work/exec.go | 14 + src/cmd/go/testdata/badmod/go.mod | 1 + src/cmd/go/testdata/badmod/x.go | 4 + src/cmd/go/testdata/vendormod/go.mod | 16 + src/cmd/go/testdata/vendormod/v1.go | 3 + src/cmd/go/testdata/vendormod/v2.go | 5 + src/cmd/go/testdata/vendormod/v3.go | 5 + src/cmd/go/testdata/vendormod/w/go.mod | 1 + src/cmd/go/testdata/vendormod/w/w.go | 1 + src/cmd/go/testdata/vendormod/x/go.mod | 1 + src/cmd/go/testdata/vendormod/x/x.go | 1 + src/cmd/go/testdata/vendormod/y/go.mod | 1 + src/cmd/go/testdata/vendormod/y/y.go | 1 + src/cmd/go/testdata/vendormod/z/go.mod | 1 + src/cmd/go/testdata/vendormod/z/z.go | 1 + 125 files changed, 14504 insertions(+), 385 deletions(-) create mode 100644 src/cmd/go/internal/dirhash/hash.go create mode 100644 src/cmd/go/internal/dirhash/hash_test.go create mode 100644 src/cmd/go/internal/imports/build.go create mode 100644 src/cmd/go/internal/imports/read.go create mode 100644 src/cmd/go/internal/imports/read_test.go create mode 100644 src/cmd/go/internal/imports/scan.go create mode 100644 src/cmd/go/internal/imports/scan_test.go create mode 100644 src/cmd/go/internal/imports/tags.go create mode 100644 src/cmd/go/internal/imports/testdata/import1/x.go create mode 100644 src/cmd/go/internal/imports/testdata/import1/x1.go create mode 100644 src/cmd/go/internal/imports/testdata/import1/x_darwin.go create mode 100644 src/cmd/go/internal/imports/testdata/import1/x_windows.go create mode 100644 src/cmd/go/internal/modconv/dep.go create mode 100644 src/cmd/go/internal/modconv/glide.go create mode 100644 src/cmd/go/internal/modconv/glock.go create mode 100644 src/cmd/go/internal/modconv/godeps.go create mode 100644 src/cmd/go/internal/modconv/modconv.go create mode 100644 src/cmd/go/internal/modconv/modconv_test.go create mode 100644 src/cmd/go/internal/modconv/testdata/cockroach.glock create mode 100644 src/cmd/go/internal/modconv/testdata/cockroach.out create mode 100644 src/cmd/go/internal/modconv/testdata/dockermachine.godeps create mode 100644 src/cmd/go/internal/modconv/testdata/dockermachine.out create mode 100644 src/cmd/go/internal/modconv/testdata/dockerman.glide create mode 100644 src/cmd/go/internal/modconv/testdata/dockerman.out create mode 100644 src/cmd/go/internal/modconv/testdata/govmomi.out create mode 100644 src/cmd/go/internal/modconv/testdata/govmomi.vmanifest create mode 100644 src/cmd/go/internal/modconv/testdata/juju.out create mode 100644 src/cmd/go/internal/modconv/testdata/juju.tsv create mode 100644 src/cmd/go/internal/modconv/testdata/moby.out create mode 100644 src/cmd/go/internal/modconv/testdata/moby.vconf create mode 100644 src/cmd/go/internal/modconv/testdata/panicparse.out create mode 100644 src/cmd/go/internal/modconv/testdata/panicparse.vyml create mode 100644 src/cmd/go/internal/modconv/testdata/prometheus.out create mode 100644 src/cmd/go/internal/modconv/testdata/prometheus.vjson create mode 100644 src/cmd/go/internal/modconv/testdata/upspin.dep create mode 100644 src/cmd/go/internal/modconv/testdata/upspin.out create mode 100644 src/cmd/go/internal/modconv/tsv.go create mode 100644 src/cmd/go/internal/modconv/vconf.go create mode 100644 src/cmd/go/internal/modconv/vjson.go create mode 100644 src/cmd/go/internal/modconv/vmanifest.go create mode 100644 src/cmd/go/internal/modconv/vyml.go create mode 100644 src/cmd/go/internal/modfetch/bitbucket/fetch.go create mode 100644 src/cmd/go/internal/modfetch/codehost/codehost.go create mode 100644 src/cmd/go/internal/modfetch/coderepo.go create mode 100644 src/cmd/go/internal/modfetch/coderepo_test.go create mode 100644 src/cmd/go/internal/modfetch/convert.go create mode 100644 src/cmd/go/internal/modfetch/convert_test.go create mode 100644 src/cmd/go/internal/modfetch/domain.go create mode 100644 src/cmd/go/internal/modfetch/github/fetch.go create mode 100644 src/cmd/go/internal/modfetch/gitrepo/fetch.go create mode 100644 src/cmd/go/internal/modfetch/gitrepo/fetch_test.go create mode 100644 src/cmd/go/internal/modfetch/googlesource/fetch.go create mode 100644 src/cmd/go/internal/modfetch/gopkgin.go create mode 100644 src/cmd/go/internal/modfetch/noweb.go create mode 100644 src/cmd/go/internal/modfetch/proxy.go create mode 100644 src/cmd/go/internal/modfetch/query.go create mode 100644 src/cmd/go/internal/modfetch/repo.go create mode 100644 src/cmd/go/internal/modfetch/unzip.go create mode 100644 src/cmd/go/internal/modfetch/web.go create mode 100644 src/cmd/go/internal/modfile/gopkgin.go create mode 100644 src/cmd/go/internal/modfile/print.go create mode 100644 src/cmd/go/internal/modfile/read.go create mode 100644 src/cmd/go/internal/modfile/read_test.go create mode 100644 src/cmd/go/internal/modfile/rule.go create mode 100644 src/cmd/go/internal/modfile/testdata/block.golden create mode 100644 src/cmd/go/internal/modfile/testdata/block.in create mode 100644 src/cmd/go/internal/modfile/testdata/comment.golden create mode 100644 src/cmd/go/internal/modfile/testdata/comment.in create mode 100644 src/cmd/go/internal/modfile/testdata/empty.golden create mode 100644 src/cmd/go/internal/modfile/testdata/empty.in create mode 100644 src/cmd/go/internal/modfile/testdata/gopkg.in.golden create mode 100644 src/cmd/go/internal/modfile/testdata/module.golden create mode 100644 src/cmd/go/internal/modfile/testdata/module.in create mode 100644 src/cmd/go/internal/modfile/testdata/replace.golden create mode 100644 src/cmd/go/internal/modfile/testdata/replace.in create mode 100644 src/cmd/go/internal/modfile/testdata/replace2.golden create mode 100644 src/cmd/go/internal/modfile/testdata/replace2.in create mode 100644 src/cmd/go/internal/modfile/testdata/rule1.golden create mode 100644 src/cmd/go/internal/modinfo/info.go create mode 100644 src/cmd/go/internal/module/module.go create mode 100644 src/cmd/go/internal/module/module_test.go create mode 100644 src/cmd/go/internal/mvs/mvs.go create mode 100644 src/cmd/go/internal/mvs/mvs_test.go create mode 100644 src/cmd/go/internal/search/search.go rename src/cmd/go/internal/{load/match_test.go => search/search_test.go} (94%) create mode 100644 src/cmd/go/internal/semver/semver.go create mode 100644 src/cmd/go/internal/semver/semver_test.go create mode 100644 src/cmd/go/internal/vgo/build.go create mode 100644 src/cmd/go/internal/vgo/fetch.go create mode 100644 src/cmd/go/internal/vgo/get.go create mode 100644 src/cmd/go/internal/vgo/init.go create mode 100644 src/cmd/go/internal/vgo/list.go create mode 100644 src/cmd/go/internal/vgo/load.go create mode 100644 src/cmd/go/internal/vgo/search.go create mode 100644 src/cmd/go/internal/vgo/vendor.go create mode 100644 src/cmd/go/internal/vgo/verify.go create mode 100644 src/cmd/go/internal/web2/web.go create mode 100644 src/cmd/go/internal/web2/web_test.go create mode 100644 src/cmd/go/internal/webtest/test.go create mode 100644 src/cmd/go/testdata/badmod/go.mod create mode 100644 src/cmd/go/testdata/badmod/x.go create mode 100644 src/cmd/go/testdata/vendormod/go.mod create mode 100644 src/cmd/go/testdata/vendormod/v1.go create mode 100644 src/cmd/go/testdata/vendormod/v2.go create mode 100644 src/cmd/go/testdata/vendormod/v3.go create mode 100644 src/cmd/go/testdata/vendormod/w/go.mod create mode 100644 src/cmd/go/testdata/vendormod/w/w.go create mode 100644 src/cmd/go/testdata/vendormod/x/go.mod create mode 100644 src/cmd/go/testdata/vendormod/x/x.go create mode 100644 src/cmd/go/testdata/vendormod/y/go.mod create mode 100644 src/cmd/go/testdata/vendormod/y/y.go create mode 100644 src/cmd/go/testdata/vendormod/z/go.mod create mode 100644 src/cmd/go/testdata/vendormod/z/z.go diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 21dc9607d5..8d486b7a77 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -1261,14 +1261,14 @@ func TestInternalPackagesInGOROOTAreRespected(t *testing.T) { tg := testgo(t) defer tg.cleanup() tg.runFail("build", "-v", "./testdata/testinternal") - tg.grepBoth(`testinternal(\/|\\)p\.go\:3\:8\: use of internal package not allowed`, "wrong error message for testdata/testinternal") + tg.grepBoth(`testinternal(\/|\\)p\.go\:3\:8\: use of internal package net/http/internal not allowed`, "wrong error message for testdata/testinternal") } func TestInternalPackagesOutsideGOROOTAreRespected(t *testing.T) { tg := testgo(t) defer tg.cleanup() tg.runFail("build", "-v", "./testdata/testinternal2") - tg.grepBoth(`testinternal2(\/|\\)p\.go\:3\:8\: use of internal package not allowed`, "wrote error message for testdata/testinternal2") + tg.grepBoth(`testinternal2(\/|\\)p\.go\:3\:8\: use of internal package .*internal/w not allowed`, "wrote error message for testdata/testinternal2") } func TestRunInternal(t *testing.T) { @@ -1278,7 +1278,7 @@ func TestRunInternal(t *testing.T) { tg.setenv("GOPATH", dir) tg.run("run", filepath.Join(dir, "src/run/good.go")) tg.runFail("run", filepath.Join(dir, "src/run/bad.go")) - tg.grepStderr(`testdata(\/|\\)src(\/|\\)run(\/|\\)bad\.go\:3\:8\: use of internal package not allowed`, "unexpected error for run/bad.go") + tg.grepStderr(`testdata(\/|\\)src(\/|\\)run(\/|\\)bad\.go\:3\:8\: use of internal package run/subdir/internal/private not allowed`, "unexpected error for run/bad.go") } func TestRunPkg(t *testing.T) { diff --git a/src/cmd/go/internal/dirhash/hash.go b/src/cmd/go/internal/dirhash/hash.go new file mode 100644 index 0000000000..61d8face56 --- /dev/null +++ b/src/cmd/go/internal/dirhash/hash.go @@ -0,0 +1,103 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package dirhash defines hashes over directory trees. +package dirhash + +import ( + "archive/zip" + "crypto/sha256" + "encoding/base64" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" +) + +var DefaultHash = Hash1 + +type Hash func(files []string, open func(string) (io.ReadCloser, error)) (string, error) + +func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, error) { + h := sha256.New() + files = append([]string(nil), files...) + sort.Strings(files) + for _, file := range files { + if strings.Contains(file, "\n") { + return "", errors.New("filenames with newlines are not supported") + } + r, err := open(file) + if err != nil { + return "", err + } + hf := sha256.New() + _, err = io.Copy(hf, r) + r.Close() + if err != nil { + return "", err + } + fmt.Fprintf(h, "%x %s\n", hf.Sum(nil), file) + } + return "h1:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil +} + +func HashDir(dir, prefix string, hash Hash) (string, error) { + files, err := DirFiles(dir, prefix) + if err != nil { + return "", err + } + osOpen := func(name string) (io.ReadCloser, error) { + return os.Open(filepath.Join(dir, strings.TrimPrefix(name, prefix))) + } + return hash(files, osOpen) +} + +func DirFiles(dir, prefix string) ([]string, error) { + var files []string + dir = filepath.Clean(dir) + err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + rel := file + if dir != "." { + rel = file[len(dir)+1:] + } + f := filepath.Join(prefix, rel) + files = append(files, filepath.ToSlash(f)) + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} + +func HashZip(zipfile string, hash Hash) (string, error) { + z, err := zip.OpenReader(zipfile) + if err != nil { + return "", err + } + defer z.Close() + var files []string + zfiles := make(map[string]*zip.File) + for _, file := range z.File { + files = append(files, file.Name) + zfiles[file.Name] = file + } + zipOpen := func(name string) (io.ReadCloser, error) { + f := zfiles[name] + if f == nil { + return nil, fmt.Errorf("file %q not found in zip", name) // should never happen + } + return f.Open() + } + return hash(files, zipOpen) +} diff --git a/src/cmd/go/internal/dirhash/hash_test.go b/src/cmd/go/internal/dirhash/hash_test.go new file mode 100644 index 0000000000..ed463c1949 --- /dev/null +++ b/src/cmd/go/internal/dirhash/hash_test.go @@ -0,0 +1,135 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dirhash + +import ( + "archive/zip" + "crypto/sha256" + "encoding/base64" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" +) + +func h(s string) string { + return fmt.Sprintf("%x", sha256.Sum256([]byte(s))) +} + +func htop(k string, s string) string { + sum := sha256.Sum256([]byte(s)) + return k + ":" + base64.StdEncoding.EncodeToString(sum[:]) +} + +func TestHash1(t *testing.T) { + files := []string{"xyz", "abc"} + open := func(name string) (io.ReadCloser, error) { + return ioutil.NopCloser(strings.NewReader("data for " + name)), nil + } + want := htop("h1", fmt.Sprintf("%s %s\n%s %s\n", h("data for abc"), "abc", h("data for xyz"), "xyz")) + out, err := Hash1(files, open) + if err != nil { + t.Fatal(err) + } + if out != want { + t.Errorf("Hash1(...) = %s, want %s", out, want) + } + + _, err = Hash1([]string{"xyz", "a\nbc"}, open) + if err == nil { + t.Error("Hash1: expected error on newline in filenames") + } +} + +func TestHashDir(t *testing.T) { + dir, err := ioutil.TempDir("", "dirhash-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + if err := ioutil.WriteFile(filepath.Join(dir, "xyz"), []byte("data for xyz"), 0666); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(filepath.Join(dir, "abc"), []byte("data for abc"), 0666); err != nil { + t.Fatal(err) + } + want := htop("h1", fmt.Sprintf("%s %s\n%s %s\n", h("data for abc"), "prefix/abc", h("data for xyz"), "prefix/xyz")) + out, err := HashDir(dir, "prefix", Hash1) + if err != nil { + t.Fatalf("HashDir: %v", err) + } + if out != want { + t.Errorf("HashDir(...) = %s, want %s", out, want) + } +} + +func TestHashZip(t *testing.T) { + f, err := ioutil.TempFile("", "dirhash-test-") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + defer f.Close() + + z := zip.NewWriter(f) + w, err := z.Create("prefix/xyz") + if err != nil { + t.Fatal(err) + } + w.Write([]byte("data for xyz")) + w, err = z.Create("prefix/abc") + if err != nil { + t.Fatal(err) + } + w.Write([]byte("data for abc")) + if err := z.Close(); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + + want := htop("h1", fmt.Sprintf("%s %s\n%s %s\n", h("data for abc"), "prefix/abc", h("data for xyz"), "prefix/xyz")) + out, err := HashZip(f.Name(), Hash1) + if err != nil { + t.Fatalf("HashDir: %v", err) + } + if out != want { + t.Errorf("HashDir(...) = %s, want %s", out, want) + } +} + +func TestDirFiles(t *testing.T) { + dir, err := ioutil.TempDir("", "dirfiles-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + if err := ioutil.WriteFile(filepath.Join(dir, "xyz"), []byte("data for xyz"), 0666); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(filepath.Join(dir, "abc"), []byte("data for abc"), 0666); err != nil { + t.Fatal(err) + } + if err := os.Mkdir(filepath.Join(dir, "subdir"), 0777); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(filepath.Join(dir, "subdir", "xyz"), []byte("data for subdir xyz"), 0666); err != nil { + t.Fatal(err) + } + prefix := "foo/bar@v2.3.4" + out, err := DirFiles(dir, prefix) + if err != nil { + t.Fatalf("DirFiles: %v", err) + } + for _, file := range out { + if !strings.HasPrefix(file, prefix) { + t.Errorf("Dir file = %s, want prefix %s", file, prefix) + } + } +} diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index f682c3a789..bd66a98f21 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -16,6 +16,7 @@ import ( "cmd/go/internal/cache" "cmd/go/internal/cfg" "cmd/go/internal/load" + "cmd/go/internal/vgo" "cmd/go/internal/work" ) @@ -56,6 +57,7 @@ func MkEnv() []cfg.EnvVar { {Name: "GOHOSTOS", Value: runtime.GOOS}, {Name: "GOOS", Value: cfg.Goos}, {Name: "GOPATH", Value: cfg.BuildContext.GOPATH}, + {Name: "GOPROXY", Value: os.Getenv("GOPROXY")}, {Name: "GORACE", Value: os.Getenv("GORACE")}, {Name: "GOROOT", Value: cfg.GOROOT}, {Name: "GOTMPDIR", Value: os.Getenv("GOTMPDIR")}, @@ -131,6 +133,7 @@ func ExtraEnvVars() []cfg.EnvVar { {Name: "CGO_LDFLAGS", Value: strings.Join(ldflags, " ")}, {Name: "PKG_CONFIG", Value: b.PkgconfigCmd()}, {Name: "GOGCCFLAGS", Value: strings.Join(cmd[3:], " ")}, + {Name: "VGOMODROOT", Value: vgo.ModRoot}, } } diff --git a/src/cmd/go/internal/fix/fix.go b/src/cmd/go/internal/fix/fix.go index 99c7ca51ac..56f88329ab 100644 --- a/src/cmd/go/internal/fix/fix.go +++ b/src/cmd/go/internal/fix/fix.go @@ -10,6 +10,9 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/load" "cmd/go/internal/str" + "cmd/go/internal/vgo" + "fmt" + "os" ) var CmdFix = &base.Command{ @@ -29,7 +32,15 @@ See also: go fmt, go vet. } func runFix(cmd *base.Command, args []string) { + printed := false for _, pkg := range load.Packages(args) { + if vgo.Enabled() && !pkg.Module.Top { + if !printed { + fmt.Fprintf(os.Stderr, "vgo: not fixing packages in dependency modules\n") + printed = true + } + continue + } // Use pkg.gofiles instead of pkg.Dir so that // the command only applies to this package, // not to packages in subdirectories. diff --git a/src/cmd/go/internal/fmtcmd/fmt.go b/src/cmd/go/internal/fmtcmd/fmt.go index eb96823fa6..c3a90d069c 100644 --- a/src/cmd/go/internal/fmtcmd/fmt.go +++ b/src/cmd/go/internal/fmtcmd/fmt.go @@ -6,6 +6,7 @@ package fmtcmd import ( + "fmt" "os" "path/filepath" "runtime" @@ -16,6 +17,7 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/load" "cmd/go/internal/str" + "cmd/go/internal/vgo" ) func init() { @@ -43,6 +45,7 @@ See also: go fix, go vet. } func runFmt(cmd *base.Command, args []string) { + printed := false gofmt := gofmtPath() procs := runtime.GOMAXPROCS(0) var wg sync.WaitGroup @@ -57,6 +60,13 @@ func runFmt(cmd *base.Command, args []string) { }() } for _, pkg := range load.PackagesAndErrors(args) { + if vgo.Enabled() && !pkg.Module.Top { + if !printed { + fmt.Fprintf(os.Stderr, "vgo: not formatting packages in dependency modules\n") + printed = true + } + continue + } if pkg.Error != nil { if strings.HasPrefix(pkg.Error.Err, "build constraints exclude all Go files") { // Skip this error, as we will format diff --git a/src/cmd/go/internal/generate/generate.go b/src/cmd/go/internal/generate/generate.go index 971844d2ea..365427d442 100644 --- a/src/cmd/go/internal/generate/generate.go +++ b/src/cmd/go/internal/generate/generate.go @@ -21,6 +21,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/load" + "cmd/go/internal/vgo" "cmd/go/internal/work" ) @@ -158,7 +159,16 @@ func runGenerate(cmd *base.Command, args []string) { } } // Even if the arguments are .go files, this loop suffices. + printed := false for _, pkg := range load.Packages(args) { + if vgo.Enabled() && !pkg.Module.Top { + if !printed { + fmt.Fprintf(os.Stderr, "vgo: not generating in packages in dependency modules\n") + printed = true + } + continue + } + pkgName := pkg.Name for _, file := range pkg.InternalGoFiles() { diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go index ffa15c5ba4..6eabc4eabb 100644 --- a/src/cmd/go/internal/get/get.go +++ b/src/cmd/go/internal/get/get.go @@ -16,7 +16,9 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/load" + "cmd/go/internal/search" "cmd/go/internal/str" + "cmd/go/internal/vgo" "cmd/go/internal/web" "cmd/go/internal/work" ) @@ -90,6 +92,10 @@ func init() { } func runGet(cmd *base.Command, args []string) { + if vgo.Enabled() { + base.Fatalf("go get: vgo not implemented") + } + work.BuildInit() if *getF && !*getU { @@ -170,7 +176,7 @@ func runGet(cmd *base.Command, args []string) { // in the hope that we can figure out the repository from the // initial ...-free prefix. func downloadPaths(args []string) []string { - args = load.ImportPathsNoDotExpansion(args) + args = load.ImportPathsForGoGet(args) var out []string for _, a := range args { if strings.Contains(a, "...") { @@ -179,9 +185,9 @@ func downloadPaths(args []string) []string { // warnings. They will be printed by the // eventual call to importPaths instead. if build.IsLocalImport(a) { - expand = load.MatchPackagesInFS(a) + expand = search.MatchPackagesInFS(a) } else { - expand = load.MatchPackages(a) + expand = search.MatchPackages(a) } if len(expand) > 0 { out = append(out, expand...) @@ -271,9 +277,9 @@ func download(arg string, parent *load.Package, stk *load.ImportStack, mode int) // for p has been replaced in the package cache. if wildcardOkay && strings.Contains(arg, "...") { if build.IsLocalImport(arg) { - args = load.MatchPackagesInFS(arg) + args = search.MatchPackagesInFS(arg) } else { - args = load.MatchPackages(arg) + args = search.MatchPackages(arg) } isWildcard = true } diff --git a/src/cmd/go/internal/help/help.go b/src/cmd/go/internal/help/help.go index c79bf8bebb..68b2c940d1 100644 --- a/src/cmd/go/internal/help/help.go +++ b/src/cmd/go/internal/help/help.go @@ -64,6 +64,10 @@ func Help(args []string) { var usageTemplate = `Go is a tool for managing Go source code. +This is vgo, an experimental go command with support for package versioning. +Even though you are invoking it as vgo, most of the messages printed will +still say "go", not "vgo". Sorry. + Usage: go command [arguments] diff --git a/src/cmd/go/internal/imports/build.go b/src/cmd/go/internal/imports/build.go new file mode 100644 index 0000000000..5597870a02 --- /dev/null +++ b/src/cmd/go/internal/imports/build.go @@ -0,0 +1,211 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Copied from Go distribution src/go/build/build.go, syslist.go + +package imports + +import ( + "bytes" + "strings" + "unicode" +) + +var slashslash = []byte("//") + +// ShouldBuild reports whether it is okay to use this file, +// The rule is that in the file's leading run of // comments +// and blank lines, which must be followed by a blank line +// (to avoid including a Go package clause doc comment), +// lines beginning with '// +build' are taken as build directives. +// +// The file is accepted only if each such line lists something +// matching the file. For example: +// +// // +build windows linux +// +// marks the file as applicable only on Windows and Linux. +// +// If tags["*"] is true, then ShouldBuild will consider every +// build tag except "ignore" to be both true and false for +// the purpose of satisfying build tags, in order to estimate +// (conservatively) whether a file could ever possibly be used +// in any build. +// +func ShouldBuild(content []byte, tags map[string]bool) bool { + // Pass 1. Identify leading run of // comments and blank lines, + // which must be followed by a blank line. + end := 0 + p := content + for len(p) > 0 { + line := p + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, p = line[:i], p[i+1:] + } else { + p = p[len(p):] + } + line = bytes.TrimSpace(line) + if len(line) == 0 { // Blank line + end = len(content) - len(p) + continue + } + if !bytes.HasPrefix(line, slashslash) { // Not comment line + break + } + } + content = content[:end] + + // Pass 2. Process each line in the run. + p = content + allok := true + for len(p) > 0 { + line := p + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, p = line[:i], p[i+1:] + } else { + p = p[len(p):] + } + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, slashslash) { + continue + } + line = bytes.TrimSpace(line[len(slashslash):]) + if len(line) > 0 && line[0] == '+' { + // Looks like a comment +line. + f := strings.Fields(string(line)) + if f[0] == "+build" { + ok := false + for _, tok := range f[1:] { + if matchTags(tok, tags) { + ok = true + } + } + if !ok { + allok = false + } + } + } + } + + return allok +} + +// matchTags reports whether the name is one of: +// +// tag (if tags[tag] is true) +// !tag (if tags[tag] is false) +// a comma-separated list of any of these +// +func matchTags(name string, tags map[string]bool) bool { + if name == "" { + return false + } + if i := strings.Index(name, ","); i >= 0 { + // comma-separated list + ok1 := matchTags(name[:i], tags) + ok2 := matchTags(name[i+1:], tags) + return ok1 && ok2 + } + if strings.HasPrefix(name, "!!") { // bad syntax, reject always + return false + } + if strings.HasPrefix(name, "!") { // negation + return len(name) > 1 && matchTag(name[1:], tags, false) + } + return matchTag(name, tags, true) +} + +// matchTag reports whether the tag name is valid and satisfied by tags[name]==want. +func matchTag(name string, tags map[string]bool, want bool) bool { + // Tags must be letters, digits, underscores or dots. + // Unlike in Go identifiers, all digits are fine (e.g., "386"). + for _, c := range name { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { + return false + } + } + + if tags["*"] && name != "" && name != "ignore" { + // Special case for gathering all possible imports: + // if we put * in the tags map then all tags + // except "ignore" are considered both present and not + // (so we return true no matter how 'want' is set). + return true + } + + have := tags[name] + if name == "linux" { + have = have || tags["android"] + } + return have == want +} + +// MatchFile returns false if the name contains a $GOOS or $GOARCH +// suffix which does not match the current system. +// The recognized name formats are: +// +// name_$(GOOS).* +// name_$(GOARCH).* +// name_$(GOOS)_$(GOARCH).* +// name_$(GOOS)_test.* +// name_$(GOARCH)_test.* +// name_$(GOOS)_$(GOARCH)_test.* +// +// An exception: if GOOS=android, then files with GOOS=linux are also matched. +// +// If tags["*"] is true, then MatchFile will consider all possible +// GOOS and GOARCH to be available and will consequently +// always return true. +func MatchFile(name string, tags map[string]bool) bool { + if tags["*"] { + return true + } + if dot := strings.Index(name, "."); dot != -1 { + name = name[:dot] + } + + // Before Go 1.4, a file called "linux.go" would be equivalent to having a + // build tag "linux" in that file. For Go 1.4 and beyond, we require this + // auto-tagging to apply only to files with a non-empty prefix, so + // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating + // systems, such as android, to arrive without breaking existing code with + // innocuous source code in "android.go". The easiest fix: cut everything + // in the name before the initial _. + i := strings.Index(name, "_") + if i < 0 { + return true + } + name = name[i:] // ignore everything before first _ + + l := strings.Split(name, "_") + if n := len(l); n > 0 && l[n-1] == "test" { + l = l[:n-1] + } + n := len(l) + if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] { + return tags[l[n-2]] && tags[l[n-1]] + } + if n >= 1 && knownOS[l[n-1]] { + return tags[l[n-1]] + } + if n >= 1 && knownArch[l[n-1]] { + return tags[l[n-1]] + } + return true +} + +var knownOS = make(map[string]bool) +var knownArch = make(map[string]bool) + +func init() { + for _, v := range strings.Fields(goosList) { + knownOS[v] = true + } + for _, v := range strings.Fields(goarchList) { + knownArch[v] = true + } +} + +const goosList = "android darwin dragonfly freebsd js linux nacl netbsd openbsd plan9 solaris windows zos " +const goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32le ppc riscv riscv64 s390 s390x sparc sparc64 wasm " diff --git a/src/cmd/go/internal/imports/read.go b/src/cmd/go/internal/imports/read.go new file mode 100644 index 0000000000..58c2abdc29 --- /dev/null +++ b/src/cmd/go/internal/imports/read.go @@ -0,0 +1,249 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Copied from Go distribution src/go/build/read.go. + +package imports + +import ( + "bufio" + "errors" + "io" + "unicode/utf8" +) + +type importReader struct { + b *bufio.Reader + buf []byte + peek byte + err error + eof bool + nerr int +} + +func isIdent(c byte) bool { + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf +} + +var ( + errSyntax = errors.New("syntax error") + errNUL = errors.New("unexpected NUL in input") +) + +// syntaxError records a syntax error, but only if an I/O error has not already been recorded. +func (r *importReader) syntaxError() { + if r.err == nil { + r.err = errSyntax + } +} + +// readByte reads the next byte from the input, saves it in buf, and returns it. +// If an error occurs, readByte records the error in r.err and returns 0. +func (r *importReader) readByte() byte { + c, err := r.b.ReadByte() + if err == nil { + r.buf = append(r.buf, c) + if c == 0 { + err = errNUL + } + } + if err != nil { + if err == io.EOF { + r.eof = true + } else if r.err == nil { + r.err = err + } + c = 0 + } + return c +} + +// peekByte returns the next byte from the input reader but does not advance beyond it. +// If skipSpace is set, peekByte skips leading spaces and comments. +func (r *importReader) peekByte(skipSpace bool) byte { + if r.err != nil { + if r.nerr++; r.nerr > 10000 { + panic("go/build: import reader looping") + } + return 0 + } + + // Use r.peek as first input byte. + // Don't just return r.peek here: it might have been left by peekByte(false) + // and this might be peekByte(true). + c := r.peek + if c == 0 { + c = r.readByte() + } + for r.err == nil && !r.eof { + if skipSpace { + // For the purposes of this reader, semicolons are never necessary to + // understand the input and are treated as spaces. + switch c { + case ' ', '\f', '\t', '\r', '\n', ';': + c = r.readByte() + continue + + case '/': + c = r.readByte() + if c == '/' { + for c != '\n' && r.err == nil && !r.eof { + c = r.readByte() + } + } else if c == '*' { + var c1 byte + for (c != '*' || c1 != '/') && r.err == nil { + if r.eof { + r.syntaxError() + } + c, c1 = c1, r.readByte() + } + } else { + r.syntaxError() + } + c = r.readByte() + continue + } + } + break + } + r.peek = c + return r.peek +} + +// nextByte is like peekByte but advances beyond the returned byte. +func (r *importReader) nextByte(skipSpace bool) byte { + c := r.peekByte(skipSpace) + r.peek = 0 + return c +} + +// readKeyword reads the given keyword from the input. +// If the keyword is not present, readKeyword records a syntax error. +func (r *importReader) readKeyword(kw string) { + r.peekByte(true) + for i := 0; i < len(kw); i++ { + if r.nextByte(false) != kw[i] { + r.syntaxError() + return + } + } + if isIdent(r.peekByte(false)) { + r.syntaxError() + } +} + +// readIdent reads an identifier from the input. +// If an identifier is not present, readIdent records a syntax error. +func (r *importReader) readIdent() { + c := r.peekByte(true) + if !isIdent(c) { + r.syntaxError() + return + } + for isIdent(r.peekByte(false)) { + r.peek = 0 + } +} + +// readString reads a quoted string literal from the input. +// If an identifier is not present, readString records a syntax error. +func (r *importReader) readString(save *[]string) { + switch r.nextByte(true) { + case '`': + start := len(r.buf) - 1 + for r.err == nil { + if r.nextByte(false) == '`' { + if save != nil { + *save = append(*save, string(r.buf[start:])) + } + break + } + if r.eof { + r.syntaxError() + } + } + case '"': + start := len(r.buf) - 1 + for r.err == nil { + c := r.nextByte(false) + if c == '"' { + if save != nil { + *save = append(*save, string(r.buf[start:])) + } + break + } + if r.eof || c == '\n' { + r.syntaxError() + } + if c == '\\' { + r.nextByte(false) + } + } + default: + r.syntaxError() + } +} + +// readImport reads an import clause - optional identifier followed by quoted string - +// from the input. +func (r *importReader) readImport(imports *[]string) { + c := r.peekByte(true) + if c == '.' { + r.peek = 0 + } else if isIdent(c) { + r.readIdent() + } + r.readString(imports) +} + +// ReadComments is like ioutil.ReadAll, except that it only reads the leading +// block of comments in the file. +func ReadComments(f io.Reader) ([]byte, error) { + r := &importReader{b: bufio.NewReader(f)} + r.peekByte(true) + if r.err == nil && !r.eof { + // Didn't reach EOF, so must have found a non-space byte. Remove it. + r.buf = r.buf[:len(r.buf)-1] + } + return r.buf, r.err +} + +// ReadImports is like ioutil.ReadAll, except that it expects a Go file as input +// and stops reading the input once the imports have completed. +func ReadImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) { + r := &importReader{b: bufio.NewReader(f)} + + r.readKeyword("package") + r.readIdent() + for r.peekByte(true) == 'i' { + r.readKeyword("import") + if r.peekByte(true) == '(' { + r.nextByte(false) + for r.peekByte(true) != ')' && r.err == nil { + r.readImport(imports) + } + r.nextByte(false) + } else { + r.readImport(imports) + } + } + + // If we stopped successfully before EOF, we read a byte that told us we were done. + // Return all but that last byte, which would cause a syntax error if we let it through. + if r.err == nil && !r.eof { + return r.buf[:len(r.buf)-1], nil + } + + // If we stopped for a syntax error, consume the whole file so that + // we are sure we don't change the errors that go/parser returns. + if r.err == errSyntax && !reportSyntaxError { + r.err = nil + for r.err == nil && !r.eof { + r.readByte() + } + } + + return r.buf, r.err +} diff --git a/src/cmd/go/internal/imports/read_test.go b/src/cmd/go/internal/imports/read_test.go new file mode 100644 index 0000000000..6ea356f1ff --- /dev/null +++ b/src/cmd/go/internal/imports/read_test.go @@ -0,0 +1,228 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Copied from Go distribution src/go/build/read.go. + +package imports + +import ( + "io" + "strings" + "testing" +) + +const quote = "`" + +type readTest struct { + // Test input contains ℙ where readImports should stop. + in string + err string +} + +var readImportsTests = []readTest{ + { + `package p`, + "", + }, + { + `package p; import "x"`, + "", + }, + { + `package p; import . "x"`, + "", + }, + { + `package p; import "x";ℙvar x = 1`, + "", + }, + { + `package p + + // comment + + import "x" + import _ "x" + import a "x" + + /* comment */ + + import ( + "x" /* comment */ + _ "x" + a "x" // comment + ` + quote + `x` + quote + ` + _ /*comment*/ ` + quote + `x` + quote + ` + a ` + quote + `x` + quote + ` + ) + import ( + ) + import () + import()import()import() + import();import();import() + + ℙvar x = 1 + `, + "", + }, +} + +var readCommentsTests = []readTest{ + { + `ℙpackage p`, + "", + }, + { + `ℙpackage p; import "x"`, + "", + }, + { + `ℙpackage p; import . "x"`, + "", + }, + { + `// foo + + /* bar */ + + /* quux */ // baz + + /*/ zot */ + + // asdf + ℙHello, world`, + "", + }, +} + +func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, error)) { + for i, tt := range tests { + var in, testOut string + j := strings.Index(tt.in, "ℙ") + if j < 0 { + in = tt.in + testOut = tt.in + } else { + in = tt.in[:j] + tt.in[j+len("ℙ"):] + testOut = tt.in[:j] + } + r := strings.NewReader(in) + buf, err := read(r) + if err != nil { + if tt.err == "" { + t.Errorf("#%d: err=%q, expected success (%q)", i, err, string(buf)) + continue + } + if !strings.Contains(err.Error(), tt.err) { + t.Errorf("#%d: err=%q, expected %q", i, err, tt.err) + continue + } + continue + } + if err == nil && tt.err != "" { + t.Errorf("#%d: success, expected %q", i, tt.err) + continue + } + + out := string(buf) + if out != testOut { + t.Errorf("#%d: wrong output:\nhave %q\nwant %q\n", i, out, testOut) + } + } +} + +func TestReadImports(t *testing.T) { + testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return ReadImports(r, true, nil) }) +} + +func TestReadComments(t *testing.T) { + testRead(t, readCommentsTests, ReadComments) +} + +var readFailuresTests = []readTest{ + { + `package`, + "syntax error", + }, + { + "package p\n\x00\nimport `math`\n", + "unexpected NUL in input", + }, + { + `package p; import`, + "syntax error", + }, + { + `package p; import "`, + "syntax error", + }, + { + "package p; import ` \n\n", + "syntax error", + }, + { + `package p; import "x`, + "syntax error", + }, + { + `package p; import _`, + "syntax error", + }, + { + `package p; import _ "`, + "syntax error", + }, + { + `package p; import _ "x`, + "syntax error", + }, + { + `package p; import .`, + "syntax error", + }, + { + `package p; import . "`, + "syntax error", + }, + { + `package p; import . "x`, + "syntax error", + }, + { + `package p; import (`, + "syntax error", + }, + { + `package p; import ("`, + "syntax error", + }, + { + `package p; import ("x`, + "syntax error", + }, + { + `package p; import ("x"`, + "syntax error", + }, +} + +func TestReadFailures(t *testing.T) { + // Errors should be reported (true arg to readImports). + testRead(t, readFailuresTests, func(r io.Reader) ([]byte, error) { return ReadImports(r, true, nil) }) +} + +func TestReadFailuresIgnored(t *testing.T) { + // Syntax errors should not be reported (false arg to readImports). + // Instead, entire file should be the output and no error. + // Convert tests not to return syntax errors. + tests := make([]readTest, len(readFailuresTests)) + copy(tests, readFailuresTests) + for i := range tests { + tt := &tests[i] + if !strings.Contains(tt.err, "NUL") { + tt.err = "" + } + } + testRead(t, tests, func(r io.Reader) ([]byte, error) { return ReadImports(r, false, nil) }) +} diff --git a/src/cmd/go/internal/imports/scan.go b/src/cmd/go/internal/imports/scan.go new file mode 100644 index 0000000000..095bb64a8d --- /dev/null +++ b/src/cmd/go/internal/imports/scan.go @@ -0,0 +1,82 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package imports + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" +) + +func ScanDir(dir string, tags map[string]bool) ([]string, []string, error) { + infos, err := ioutil.ReadDir(dir) + if err != nil { + return nil, nil, err + } + var files []string + for _, info := range infos { + name := info.Name() + if info.Mode().IsRegular() && !strings.HasPrefix(name, "_") && strings.HasSuffix(name, ".go") && MatchFile(name, tags) { + files = append(files, filepath.Join(dir, name)) + } + } + return scanFiles(files, tags, false) +} + +func ScanFiles(files []string, tags map[string]bool) ([]string, []string, error) { + return scanFiles(files, tags, true) +} + +func scanFiles(files []string, tags map[string]bool, explicitFiles bool) ([]string, []string, error) { + imports := make(map[string]bool) + testImports := make(map[string]bool) + numFiles := 0 + for _, name := range files { + r, err := os.Open(name) + if err != nil { + return nil, nil, err + } + var list []string + data, err := ReadImports(r, false, &list) + r.Close() + if err != nil { + return nil, nil, fmt.Errorf("reading %s: %v", name, err) + } + if !explicitFiles && !ShouldBuild(data, tags) { + continue + } + numFiles++ + m := imports + if strings.HasSuffix(name, "_test.go") { + m = testImports + } + for _, p := range list { + q, err := strconv.Unquote(p) + if err != nil { + continue + } + m[q] = true + } + } + if numFiles == 0 { + return nil, nil, ErrNoGo + } + return keys(imports), keys(testImports), nil +} + +var ErrNoGo = fmt.Errorf("no Go source files") + +func keys(m map[string]bool) []string { + var list []string + for k := range m { + list = append(list, k) + } + sort.Strings(list) + return list +} diff --git a/src/cmd/go/internal/imports/scan_test.go b/src/cmd/go/internal/imports/scan_test.go new file mode 100644 index 0000000000..6a2ff62ba7 --- /dev/null +++ b/src/cmd/go/internal/imports/scan_test.go @@ -0,0 +1,67 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package imports + +import ( + "internal/testenv" + "path/filepath" + "reflect" + "runtime" + "testing" +) + +func TestScan(t *testing.T) { + testenv.MustHaveGoBuild(t) + + imports, testImports, err := ScanDir(filepath.Join(runtime.GOROOT(), "src/encoding/json"), Tags()) + if err != nil { + t.Fatal(err) + } + foundBase64 := false + for _, p := range imports { + if p == "encoding/base64" { + foundBase64 = true + } + if p == "encoding/binary" { + // A dependency but not an import + t.Errorf("json reported as importing encoding/binary but does not") + } + if p == "net/http" { + // A test import but not an import + t.Errorf("json reported as importing encoding/binary but does not") + } + } + if !foundBase64 { + t.Errorf("json missing import encoding/base64 (%q)", imports) + } + + foundHTTP := false + for _, p := range testImports { + if p == "net/http" { + foundHTTP = true + } + if p == "unicode/utf16" { + // A package import but not a test import + t.Errorf("json reported as test-importing unicode/utf16 but does not") + } + } + if !foundHTTP { + t.Errorf("json missing test import net/http (%q)", testImports) + } +} + +func TestScanStar(t *testing.T) { + testenv.MustHaveGoBuild(t) + + imports, _, err := ScanDir("testdata/import1", map[string]bool{"*": true}) + if err != nil { + t.Fatal(err) + } + + want := []string{"import1", "import2", "import3", "import4"} + if !reflect.DeepEqual(imports, want) { + t.Errorf("ScanDir testdata/import1:\nhave %v\nwant %v", imports, want) + } +} diff --git a/src/cmd/go/internal/imports/tags.go b/src/cmd/go/internal/imports/tags.go new file mode 100644 index 0000000000..ba0ca94535 --- /dev/null +++ b/src/cmd/go/internal/imports/tags.go @@ -0,0 +1,35 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package imports + +import "cmd/go/internal/cfg" + +var tags map[string]bool + +func Tags() map[string]bool { + if tags == nil { + tags = loadTags() + } + return tags +} + +func loadTags() map[string]bool { + tags := map[string]bool{ + cfg.BuildContext.GOOS: true, + cfg.BuildContext.GOARCH: true, + cfg.BuildContext.Compiler: true, + } + if cfg.BuildContext.CgoEnabled { + tags["cgo"] = true + } + // TODO: Should read these out of GOROOT source code? + for _, tag := range cfg.BuildContext.BuildTags { + tags[tag] = true + } + for _, tag := range cfg.BuildContext.ReleaseTags { + tags[tag] = true + } + return tags +} diff --git a/src/cmd/go/internal/imports/testdata/import1/x.go b/src/cmd/go/internal/imports/testdata/import1/x.go new file mode 100644 index 0000000000..98f9191053 --- /dev/null +++ b/src/cmd/go/internal/imports/testdata/import1/x.go @@ -0,0 +1,3 @@ +package x + +import "import1" diff --git a/src/cmd/go/internal/imports/testdata/import1/x1.go b/src/cmd/go/internal/imports/testdata/import1/x1.go new file mode 100644 index 0000000000..6a9594aed0 --- /dev/null +++ b/src/cmd/go/internal/imports/testdata/import1/x1.go @@ -0,0 +1,9 @@ +// +build blahblh +// +build linux +// +build !linux +// +build windows +// +build darwin + +package x + +import "import4" diff --git a/src/cmd/go/internal/imports/testdata/import1/x_darwin.go b/src/cmd/go/internal/imports/testdata/import1/x_darwin.go new file mode 100644 index 0000000000..a0c3fdd21b --- /dev/null +++ b/src/cmd/go/internal/imports/testdata/import1/x_darwin.go @@ -0,0 +1,3 @@ +package xxxx + +import "import3" diff --git a/src/cmd/go/internal/imports/testdata/import1/x_windows.go b/src/cmd/go/internal/imports/testdata/import1/x_windows.go new file mode 100644 index 0000000000..63c508248f --- /dev/null +++ b/src/cmd/go/internal/imports/testdata/import1/x_windows.go @@ -0,0 +1,3 @@ +package x + +import "import2" diff --git a/src/cmd/go/internal/load/path.go b/src/cmd/go/internal/load/path.go index 45a9e7b242..0211b284a4 100644 --- a/src/cmd/go/internal/load/path.go +++ b/src/cmd/go/internal/load/path.go @@ -32,22 +32,6 @@ func hasSubdir(root, dir string) (rel string, ok bool) { return filepath.ToSlash(dir[len(root):]), true } -// hasPathPrefix reports whether the path s begins with the -// elements in prefix. -func hasPathPrefix(s, prefix string) bool { - switch { - default: - return false - case len(s) == len(prefix): - return s == prefix - case len(s) > len(prefix): - if prefix != "" && prefix[len(prefix)-1] == '/' { - return strings.HasPrefix(s, prefix) - } - return s[len(prefix)] == '/' && s[:len(prefix)] == prefix - } -} - // expandPath returns the symlink-expanded form of path. func expandPath(p string) string { x, err := filepath.EvalSymlinks(p) diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 693d862214..400b338a20 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -22,7 +22,10 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" + "cmd/go/internal/modinfo" + "cmd/go/internal/search" "cmd/go/internal/str" + "cmd/go/internal/vgo" ) var IgnoreImports bool // control whether we ignore imports in packages @@ -99,6 +102,8 @@ type PackagePublic struct { TestImports []string `json:",omitempty"` // imports from TestGoFiles XTestGoFiles []string `json:",omitempty"` // _test.go files outside package XTestImports []string `json:",omitempty"` // imports from XTestGoFiles + + Module *modinfo.ModulePublic `json:",omitempty"` // info about package module } // AllFiles returns the names of all the files considered for the package. @@ -148,6 +153,7 @@ type PackageInternal struct { CoverVars map[string]*CoverVar // variables created by coverage analysis OmitDebug bool // tell linker not to write debug information GobinSubdir bool // install target would be subdir of GOBIN + BuildInfo string // add this info to package main TestmainGo *[]byte // content for _testmain.go Asmflags []string // -asmflags for this package @@ -236,7 +242,7 @@ func (p *Package) copyBuild(pp *build.Package) { // TODO? Target p.Goroot = pp.Goroot - p.Standard = p.Goroot && p.ImportPath != "" && isStandardImportPath(p.ImportPath) + p.Standard = p.Goroot && p.ImportPath != "" && search.IsStandardImportPath(p.ImportPath) p.GoFiles = pp.GoFiles p.CgoFiles = pp.CgoFiles p.IgnoredGoFiles = pp.IgnoredGoFiles @@ -270,19 +276,6 @@ func (p *Package) copyBuild(pp *build.Package) { } } -// isStandardImportPath reports whether $GOROOT/src/path should be considered -// part of the standard distribution. For historical reasons we allow people to add -// their own code to $GOROOT instead of using $GOPATH, but we assume that -// code will start with a domain name (dot in the first element). -func isStandardImportPath(path string) bool { - i := strings.Index(path, "/") - if i < 0 { - i = len(path) - } - elem := path[:i] - return !strings.Contains(elem, ".") -} - // A PackageError describes an error loading information about a package. type PackageError struct { ImportStack []string // shortest path from package named on command line to this one @@ -430,8 +423,20 @@ func LoadImport(path, srcDir string, parent *Package, stk *ImportStack, importPo importPath := path origPath := path isLocal := build.IsLocalImport(path) + var vgoDir string + var vgoErr error if isLocal { importPath = dirToImportPath(filepath.Join(srcDir, path)) + } else if vgo.Enabled() { + parentPath := "" + if parent != nil { + parentPath = parent.ImportPath + } + var p string + vgoDir, p, vgoErr = vgo.Lookup(parentPath, path) + if vgoErr == nil { + importPath = p + } } else if mode&ResolveImport != 0 { // We do our own path resolution, because we want to // find out the key to use in packageCache without the @@ -456,17 +461,28 @@ func LoadImport(path, srcDir string, parent *Package, stk *ImportStack, importPo // Load package. // Import always returns bp != nil, even if an error occurs, // in order to return partial information. - buildMode := build.ImportComment - if mode&ResolveImport == 0 || path != origPath { - // Not vendoring, or we already found the vendored path. - buildMode |= build.IgnoreVendor + var bp *build.Package + var err error + if vgoDir != "" { + bp, err = cfg.BuildContext.ImportDir(vgoDir, 0) + } else if vgoErr != nil { + bp = new(build.Package) + err = fmt.Errorf("unknown import path %q: %v", importPath, vgoErr) + } else { + buildMode := build.ImportComment + if mode&ResolveImport == 0 || path != origPath { + // Not vendoring, or we already found the vendored path. + buildMode |= build.IgnoreVendor + } + bp, err = cfg.BuildContext.Import(path, srcDir, buildMode) } - bp, err := cfg.BuildContext.Import(path, srcDir, buildMode) bp.ImportPath = importPath if cfg.GOBIN != "" { bp.BinDir = cfg.GOBIN + } else if vgo.Enabled() { + bp.BinDir = vgo.BinDir() } - if err == nil && !isLocal && bp.ImportComment != "" && bp.ImportComment != path && + if vgoDir == "" && err == nil && !isLocal && bp.ImportComment != "" && bp.ImportComment != path && !strings.Contains(path, "/vendor/") && !strings.HasPrefix(path, "vendor/") { err = fmt.Errorf("code in directory %s expects import %q", bp.Dir, bp.ImportComment) } @@ -475,7 +491,7 @@ func LoadImport(path, srcDir string, parent *Package, stk *ImportStack, importPo p = setErrorPos(p, importPos) } - if origPath != cleanImport(origPath) { + if vgoDir == "" && origPath != cleanImport(origPath) { p.Error = &PackageError{ ImportStack: stk.Copy(), Err: fmt.Sprintf("non-canonical import path: %q should be %q", origPath, pathpkg.Clean(origPath)), @@ -553,6 +569,16 @@ func isDir(path string) bool { // If vendor expansion doesn't trigger, then the path is also subject to // Go 1.11 vgo legacy conversion (golang.org/issue/25069). func ResolveImportPath(parent *Package, path string) (found string) { + if vgo.Enabled() { + parentPath := "" + if parent != nil { + parentPath = parent.ImportPath + } + if _, p, e := vgo.Lookup(parentPath, path); e == nil { + return p + } + return path + } found = VendoredImportPath(parent, path) if found != path { return found @@ -902,7 +928,7 @@ func disallowInternal(srcDir string, p *Package, stk *ImportStack) *Package { perr := *p perr.Error = &PackageError{ ImportStack: stk.Copy(), - Err: "use of internal package not allowed", + Err: "use of internal package " + p.ImportPath + " not allowed", } perr.Incomplete = true return &perr @@ -1130,6 +1156,9 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) { // Install cross-compiled binaries to subdirectories of bin. elem = full } + if p.Internal.Build.BinDir == "" && vgo.Enabled() { + p.Internal.Build.BinDir = vgo.BinDir() + } if p.Internal.Build.BinDir != "" { // Install to GOBIN or bin of GOPATH entry. p.Target = filepath.Join(p.Internal.Build.BinDir, elem) @@ -1380,6 +1409,13 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) { setError(fmt.Sprintf("case-insensitive import collision: %q and %q", p.ImportPath, other)) return } + + if vgo.Enabled() { + p.Module = vgo.PackageModuleInfo(p.ImportPath) + if p.Name == "main" { + p.Internal.BuildInfo = vgo.PackageBuildInfo(p.ImportPath, p.Deps) + } + } } // SafeArg reports whether arg is a "safe" command-line argument, @@ -1654,6 +1690,20 @@ func PackagesAndErrors(args []string) []*Package { return pkgs } +func ImportPaths(args []string) []string { + if cmdlineMatchers == nil { + SetCmdlinePatterns(search.CleanImportPaths(args)) + } + return vgo.ImportPaths(args) +} + +func ImportPathsForGoGet(args []string) []string { + if cmdlineMatchers == nil { + SetCmdlinePatterns(search.CleanImportPaths(args)) + } + return search.ImportPathsNoDotExpansion(args) +} + // packagesForBuild is like 'packages' but fails if any of // the packages or their dependencies have errors // (cannot be built). @@ -1739,6 +1789,8 @@ func GoFilesPackage(gofiles []string) *Package { } ctxt.ReadDir = func(string) ([]os.FileInfo, error) { return dirent, nil } + vgo.AddImports(gofiles) + var err error if dir == "" { dir = base.Cwd @@ -1767,6 +1819,8 @@ func GoFilesPackage(gofiles []string) *Package { } if cfg.GOBIN != "" { pkg.Target = filepath.Join(cfg.GOBIN, exe) + } else if vgo.Enabled() { + pkg.Target = filepath.Join(vgo.BinDir(), exe) } } diff --git a/src/cmd/go/internal/load/search.go b/src/cmd/go/internal/load/search.go index 6494f8e569..49433984a8 100644 --- a/src/cmd/go/internal/load/search.go +++ b/src/cmd/go/internal/load/search.go @@ -5,267 +5,12 @@ package load import ( - "cmd/go/internal/cfg" - "fmt" - "go/build" - "log" - "os" - "path" "path/filepath" - "regexp" "runtime" "strings" -) - -// allPackages returns all the packages that can be found -// under the $GOPATH directories and $GOROOT matching pattern. -// The pattern is either "all" (all packages), "std" (standard packages), -// "cmd" (standard commands), or a path including "...". -func allPackages(pattern string) []string { - pkgs := MatchPackages(pattern) - if len(pkgs) == 0 { - fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) - } - return pkgs -} - -// allPackagesInFS is like allPackages but is passed a pattern -// beginning ./ or ../, meaning it should scan the tree rooted -// at the given directory. There are ... in the pattern too. -func allPackagesInFS(pattern string) []string { - pkgs := MatchPackagesInFS(pattern) - if len(pkgs) == 0 { - fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) - } - return pkgs -} - -// MatchPackages returns a list of package paths matching pattern -// (see go help packages for pattern syntax). -func MatchPackages(pattern string) []string { - match := func(string) bool { return true } - treeCanMatch := func(string) bool { return true } - if !IsMetaPackage(pattern) { - match = matchPattern(pattern) - treeCanMatch = treeCanMatchPattern(pattern) - } - - have := map[string]bool{ - "builtin": true, // ignore pseudo-package that exists only for documentation - } - if !cfg.BuildContext.CgoEnabled { - have["runtime/cgo"] = true // ignore during walk - } - var pkgs []string - for _, src := range cfg.BuildContext.SrcDirs() { - if (pattern == "std" || pattern == "cmd") && src != cfg.GOROOTsrc { - continue - } - src = filepath.Clean(src) + string(filepath.Separator) - root := src - if pattern == "cmd" { - root += "cmd" + string(filepath.Separator) - } - filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { - if err != nil || path == src { - return nil - } - - want := true - // Avoid .foo, _foo, and testdata directory trees. - _, elem := filepath.Split(path) - if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { - want = false - } - - name := filepath.ToSlash(path[len(src):]) - if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") { - // The name "std" is only the standard library. - // If the name is cmd, it's the root of the command tree. - want = false - } - if !treeCanMatch(name) { - want = false - } - - if !fi.IsDir() { - if fi.Mode()&os.ModeSymlink != 0 && want { - if target, err := os.Stat(path); err == nil && target.IsDir() { - fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) - } - } - return nil - } - if !want { - return filepath.SkipDir - } - - if have[name] { - return nil - } - have[name] = true - if !match(name) { - return nil - } - pkg, err := cfg.BuildContext.ImportDir(path, 0) - if err != nil { - if _, noGo := err.(*build.NoGoError); noGo { - return nil - } - } - - // If we are expanding "cmd", skip main - // packages under cmd/vendor. At least as of - // March, 2017, there is one there for the - // vendored pprof tool. - if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" { - return nil - } - - pkgs = append(pkgs, name) - return nil - }) - } - return pkgs -} - -// MatchPackagesInFS returns a list of package paths matching pattern, -// which must begin with ./ or ../ -// (see go help packages for pattern syntax). -func MatchPackagesInFS(pattern string) []string { - // Find directory to begin the scan. - // Could be smarter but this one optimization - // is enough for now, since ... is usually at the - // end of a path. - i := strings.Index(pattern, "...") - dir, _ := path.Split(pattern[:i]) - - // pattern begins with ./ or ../. - // path.Clean will discard the ./ but not the ../. - // We need to preserve the ./ for pattern matching - // and in the returned import paths. - prefix := "" - if strings.HasPrefix(pattern, "./") { - prefix = "./" - } - match := matchPattern(pattern) - - var pkgs []string - filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { - if err != nil || !fi.IsDir() { - return nil - } - if path == dir { - // filepath.Walk starts at dir and recurses. For the recursive case, - // the path is the result of filepath.Join, which calls filepath.Clean. - // The initial case is not Cleaned, though, so we do this explicitly. - // - // This converts a path like "./io/" to "io". Without this step, running - // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io - // package, because prepending the prefix "./" to the unclean path would - // result in "././io", and match("././io") returns false. - path = filepath.Clean(path) - } - - // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". - _, elem := filepath.Split(path) - dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." - if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { - return filepath.SkipDir - } - - name := prefix + filepath.ToSlash(path) - if !match(name) { - return nil - } - - // We keep the directory if we can import it, or if we can't import it - // due to invalid Go source files. This means that directories containing - // parse errors will be built (and fail) instead of being silently skipped - // as not matching the pattern. Go 1.5 and earlier skipped, but that - // behavior means people miss serious mistakes. - // See golang.org/issue/11407. - if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) { - if _, noGo := err.(*build.NoGoError); !noGo { - log.Print(err) - } - return nil - } - pkgs = append(pkgs, name) - return nil - }) - return pkgs -} - -// treeCanMatchPattern(pattern)(name) reports whether -// name or children of name can possibly match pattern. -// Pattern is the same limited glob accepted by matchPattern. -func treeCanMatchPattern(pattern string) func(name string) bool { - wildCard := false - if i := strings.Index(pattern, "..."); i >= 0 { - wildCard = true - pattern = pattern[:i] - } - return func(name string) bool { - return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || - wildCard && strings.HasPrefix(name, pattern) - } -} - -// matchPattern(pattern)(name) reports whether -// name matches pattern. Pattern is a limited glob -// pattern in which '...' means 'any string' and there -// is no other special syntax. -// Unfortunately, there are two special cases. Quoting "go help packages": -// -// First, /... at the end of the pattern can match an empty string, -// so that net/... matches both net and packages in its subdirectories, like net/http. -// Second, any slash-separted pattern element containing a wildcard never -// participates in a match of the "vendor" element in the path of a vendored -// package, so that ./... does not match packages in subdirectories of -// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. -// Note, however, that a directory named vendor that itself contains code -// is not a vendored package: cmd/vendor would be a command named vendor, -// and the pattern cmd/... matches it. -func matchPattern(pattern string) func(name string) bool { - // Convert pattern to regular expression. - // The strategy for the trailing /... is to nest it in an explicit ? expression. - // The strategy for the vendor exclusion is to change the unmatchable - // vendor strings to a disallowed code point (vendorChar) and to use - // "(anything but that codepoint)*" as the implementation of the ... wildcard. - // This is a bit complicated but the obvious alternative, - // namely a hand-written search like in most shell glob matchers, - // is too easy to make accidentally exponential. - // Using package regexp guarantees linear-time matching. - - const vendorChar = "\x00" - - if strings.Contains(pattern, vendorChar) { - return func(name string) bool { return false } - } - - re := regexp.QuoteMeta(pattern) - re = replaceVendor(re, vendorChar) - switch { - case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): - re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` - case re == vendorChar+`/\.\.\.`: - re = `(/vendor|/` + vendorChar + `/\.\.\.)` - case strings.HasSuffix(re, `/\.\.\.`): - re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` - } - re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1) - - reg := regexp.MustCompile(`^` + re + `$`) - - return func(name string) bool { - if strings.Contains(name, vendorChar) { - return false - } - return reg.MatchString(replaceVendor(name, vendorChar)) - } -} + "cmd/go/internal/search" +) // MatchPackage(pattern, cwd)(p) reports whether package p matches pattern in the working directory cwd. func MatchPackage(pattern, cwd string) func(*Package) bool { @@ -284,13 +29,14 @@ func MatchPackage(pattern, cwd string) func(*Package) bool { dir = filepath.Join(cwd, dir) if pattern == "" { return func(p *Package) bool { + // TODO(rsc): This is wrong. See golang.org/issue/25878. if runtime.GOOS != "windows" { return p.Dir == dir } return strings.EqualFold(p.Dir, dir) } } - matchPath := matchPattern(pattern) + matchPath := search.MatchPattern(pattern) return func(p *Package) bool { // Compute relative path to dir and see if it matches the pattern. rel, err := filepath.Rel(dir, p.Dir) @@ -311,81 +57,7 @@ func MatchPackage(pattern, cwd string) func(*Package) bool { case pattern == "cmd": return func(p *Package) bool { return p.Standard && strings.HasPrefix(p.ImportPath, "cmd/") } default: - matchPath := matchPattern(pattern) + matchPath := search.MatchPattern(pattern) return func(p *Package) bool { return matchPath(p.ImportPath) } } } - -// replaceVendor returns the result of replacing -// non-trailing vendor path elements in x with repl. -func replaceVendor(x, repl string) string { - if !strings.Contains(x, "vendor") { - return x - } - elem := strings.Split(x, "/") - for i := 0; i < len(elem)-1; i++ { - if elem[i] == "vendor" { - elem[i] = repl - } - } - return strings.Join(elem, "/") -} - -// ImportPaths returns the import paths to use for the given command line. -func ImportPaths(args []string) []string { - args = ImportPathsNoDotExpansion(args) - var out []string - for _, a := range args { - if strings.Contains(a, "...") { - if build.IsLocalImport(a) { - out = append(out, allPackagesInFS(a)...) - } else { - out = append(out, allPackages(a)...) - } - continue - } - out = append(out, a) - } - return out -} - -// ImportPathsNoDotExpansion returns the import paths to use for the given -// command line, but it does no ... expansion. -func ImportPathsNoDotExpansion(args []string) []string { - if cmdlineMatchers == nil { - SetCmdlinePatterns(args) - } - if len(args) == 0 { - return []string{"."} - } - var out []string - for _, a := range args { - // Arguments are supposed to be import paths, but - // as a courtesy to Windows developers, rewrite \ to / - // in command-line arguments. Handles .\... and so on. - if filepath.Separator == '\\' { - a = strings.Replace(a, `\`, `/`, -1) - } - - // Put argument in canonical form, but preserve leading ./. - if strings.HasPrefix(a, "./") { - a = "./" + path.Clean(a) - if a == "./." { - a = "." - } - } else { - a = path.Clean(a) - } - if IsMetaPackage(a) { - out = append(out, allPackages(a)...) - continue - } - out = append(out, a) - } - return out -} - -// IsMetaPackage checks if name is a reserved package name that expands to multiple packages. -func IsMetaPackage(name string) bool { - return name == "std" || name == "cmd" || name == "all" -} diff --git a/src/cmd/go/internal/modconv/dep.go b/src/cmd/go/internal/modconv/dep.go new file mode 100644 index 0000000000..28dd28a3c2 --- /dev/null +++ b/src/cmd/go/internal/modconv/dep.go @@ -0,0 +1,71 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "fmt" + "strconv" + "strings" + + "cmd/go/internal/module" + "cmd/go/internal/semver" +) + +func ParseGopkgLock(file string, data []byte) ([]module.Version, error) { + var list []module.Version + var r *module.Version + for lineno, line := range strings.Split(string(data), "\n") { + lineno++ + if i := strings.Index(line, "#"); i >= 0 { + line = line[:i] + } + line = strings.TrimSpace(line) + if line == "[[projects]]" { + list = append(list, module.Version{}) + r = &list[len(list)-1] + continue + } + if strings.HasPrefix(line, "[") { + r = nil + continue + } + if r == nil { + continue + } + i := strings.Index(line, "=") + if i < 0 { + continue + } + key := strings.TrimSpace(line[:i]) + val := strings.TrimSpace(line[i+1:]) + if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' { + q, err := strconv.Unquote(val) // Go unquoting, but close enough for now + if err != nil { + return nil, fmt.Errorf("%s:%d: invalid quoted string: %v", file, lineno, err) + } + val = q + } + switch key { + case "name": + r.Path = val + case "revision", "version": + // Note: key "version" should take priority over "revision", + // and it does, because dep writes toml keys in alphabetical order, + // so we see version (if present) second. + if key == "version" { + if !semver.IsValid(val) || semver.Canonical(val) != val { + break + } + } + r.Version = val + } + } + for _, r := range list { + if r.Path == "" || r.Version == "" { + return nil, fmt.Errorf("%s: empty [[projects]] stanza (%s)", file, r.Path) + } + } + return list, nil +} diff --git a/src/cmd/go/internal/modconv/glide.go b/src/cmd/go/internal/modconv/glide.go new file mode 100644 index 0000000000..abe88c4fc2 --- /dev/null +++ b/src/cmd/go/internal/modconv/glide.go @@ -0,0 +1,40 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "cmd/go/internal/module" + "strings" +) + +func ParseGlideLock(file string, data []byte) ([]module.Version, error) { + var list []module.Version + imports := false + name := "" + for lineno, line := range strings.Split(string(data), "\n") { + lineno++ + if line == "" { + continue + } + if strings.HasPrefix(line, "imports:") { + imports = true + } else if line[0] != '-' && line[0] != ' ' && line[0] != '\t' { + imports = false + } + if !imports { + continue + } + if strings.HasPrefix(line, "- name:") { + name = strings.TrimSpace(line[len("- name:"):]) + } + if strings.HasPrefix(line, " version:") { + version := strings.TrimSpace(line[len(" version:"):]) + if name != "" && version != "" { + list = append(list, module.Version{Path: name, Version: version}) + } + } + } + return list, nil +} diff --git a/src/cmd/go/internal/modconv/glock.go b/src/cmd/go/internal/modconv/glock.go new file mode 100644 index 0000000000..57eb66ebf9 --- /dev/null +++ b/src/cmd/go/internal/modconv/glock.go @@ -0,0 +1,23 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "strings" + + "cmd/go/internal/module" +) + +func ParseGLOCKFILE(file string, data []byte) ([]module.Version, error) { + var list []module.Version + for lineno, line := range strings.Split(string(data), "\n") { + lineno++ + f := strings.Fields(line) + if len(f) >= 2 && f[0] != "cmd" { + list = append(list, module.Version{Path: f[0], Version: f[1]}) + } + } + return list, nil +} diff --git a/src/cmd/go/internal/modconv/godeps.go b/src/cmd/go/internal/modconv/godeps.go new file mode 100644 index 0000000000..904fd70ea2 --- /dev/null +++ b/src/cmd/go/internal/modconv/godeps.go @@ -0,0 +1,29 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "encoding/json" + + "cmd/go/internal/module" +) + +func ParseGodepsJSON(file string, data []byte) ([]module.Version, error) { + var cfg struct { + ImportPath string + Deps []struct { + ImportPath string + Rev string + } + } + if err := json.Unmarshal(data, &cfg); err != nil { + return nil, err + } + var list []module.Version + for _, d := range cfg.Deps { + list = append(list, module.Version{Path: d.ImportPath, Version: d.Rev}) + } + return list, nil +} diff --git a/src/cmd/go/internal/modconv/modconv.go b/src/cmd/go/internal/modconv/modconv.go new file mode 100644 index 0000000000..b689b52dee --- /dev/null +++ b/src/cmd/go/internal/modconv/modconv.go @@ -0,0 +1,25 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import "cmd/go/internal/module" + +var Converters = map[string]func(string, []byte) ([]module.Version, error){ + "GLOCKFILE": ParseGLOCKFILE, + "Godeps/Godeps.json": ParseGodepsJSON, + "Gopkg.lock": ParseGopkgLock, + "dependencies.tsv": ParseDependenciesTSV, + "glide.lock": ParseGlideLock, + "vendor.conf": ParseVendorConf, + "vendor.yml": ParseVendorYML, + "vendor/manifest": ParseVendorManifest, + "vendor/vendor.json": ParseVendorJSON, +} + +// Prefix is a line we write at the top of auto-converted go.mod files +// for dependencies before caching them. +// In case of bugs in the converter, if we bump this version number, +// then all the cached copies will be ignored. +const Prefix = "//vgo 0.0.4\n" diff --git a/src/cmd/go/internal/modconv/modconv_test.go b/src/cmd/go/internal/modconv/modconv_test.go new file mode 100644 index 0000000000..04a1db3f84 --- /dev/null +++ b/src/cmd/go/internal/modconv/modconv_test.go @@ -0,0 +1,69 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "bytes" + "fmt" + "internal/testenv" + "io/ioutil" + "path/filepath" + "testing" +) + +var extMap = map[string]string{ + ".dep": "Gopkg.lock", + ".glide": "glide.lock", + ".glock": "GLOCKFILE", + ".godeps": "Godeps/Godeps.json", + ".tsv": "dependencies.tsv", + ".vconf": "vendor.conf", + ".vjson": "vendor/vendor.json", + ".vyml": "vendor.yml", + ".vmanifest": "vendor/manifest", +} + +func Test(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + + tests, _ := filepath.Glob("testdata/*") + if len(tests) == 0 { + t.Fatalf("no tests found") + } + for _, test := range tests { + file := filepath.Base(test) + ext := filepath.Ext(file) + if ext == ".out" { + continue + } + t.Run(file, func(t *testing.T) { + if extMap[ext] == "" { + t.Fatal("unknown extension") + } + if Converters[extMap[ext]] == nil { + t.Fatalf("Converters[%q] == nil", extMap[ext]) + } + data, err := ioutil.ReadFile(test) + if err != nil { + t.Fatal(err) + } + out, err := Converters[extMap[ext]](test, data) + if err != nil { + t.Fatal(err) + } + want, err := ioutil.ReadFile(test[:len(test)-len(ext)] + ".out") + if err != nil { + t.Error(err) + } + var buf bytes.Buffer + for _, r := range out { + fmt.Fprintf(&buf, "%s %s\n", r.Path, r.Version) + } + if !bytes.Equal(buf.Bytes(), want) { + t.Errorf("have:\n%s\nwant:\n%s", buf.Bytes(), want) + } + }) + } +} diff --git a/src/cmd/go/internal/modconv/testdata/cockroach.glock b/src/cmd/go/internal/modconv/testdata/cockroach.glock new file mode 100644 index 0000000000..221c8acdfd --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/cockroach.glock @@ -0,0 +1,41 @@ +cmd github.com/cockroachdb/c-protobuf/cmd/protoc +cmd github.com/cockroachdb/yacc +cmd github.com/gogo/protobuf/protoc-gen-gogo +cmd github.com/golang/lint/golint +cmd github.com/jteeuwen/go-bindata/go-bindata +cmd github.com/kisielk/errcheck +cmd github.com/robfig/glock +cmd github.com/tebeka/go2xunit +cmd golang.org/x/tools/cmd/goimports +cmd golang.org/x/tools/cmd/stringer +github.com/agtorre/gocolorize f42b554bf7f006936130c9bb4f971afd2d87f671 +github.com/biogo/store e1f74b3c58befe661feed7fa4cf52436de753128 +github.com/cockroachdb/c-lz4 6e71f140a365017bbe0904710007f8725fd3f809 +github.com/cockroachdb/c-protobuf 0f9ab7b988ca7474cf76b9a961ab03c0552abcb3 +github.com/cockroachdb/c-rocksdb 7fc876fe79b96de0e25069c9ae27e6444637bd54 +github.com/cockroachdb/c-snappy 618733f9e5bab8463b9049117a335a7a1bfc9fd5 +github.com/cockroachdb/yacc 572e006f8e6b0061ebda949d13744f5108389514 +github.com/coreos/etcd 18ecc297bc913bed6fc093d66b1fa22020dba7dc +github.com/docker/docker 7374852be9def787921aea2ca831771982badecf +github.com/elazarl/go-bindata-assetfs 3dcc96556217539f50599357fb481ac0dc7439b9 +github.com/gogo/protobuf 98e73e511a62a9c232152f94999112c80142a813 +github.com/golang/lint 7b7f4364ff76043e6c3610281525fabc0d90f0e4 +github.com/google/btree cc6329d4279e3f025a53a83c397d2339b5705c45 +github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +github.com/jteeuwen/go-bindata dce55d09e24ac40a6e725c8420902b86554f8046 +github.com/julienschmidt/httprouter 6aacfd5ab513e34f7e64ea9627ab9670371b34e7 +github.com/kisielk/errcheck 50b84cf7fa18ee2985b8c63ba3de5edd604b9259 +github.com/kisielk/gotool d678387370a2eb9b5b0a33218bc8c9d8de15b6be +github.com/lib/pq a8d8d01c4f91602f876bf5aa210274e8203a6b45 +github.com/montanaflynn/stats 44fb56da2a2a67d394dec0e18a82dd316f192529 +github.com/peterh/liner 1bb0d1c1a25ed393d8feb09bab039b2b1b1fbced +github.com/robfig/glock cb3c3ec56de988289cab7bbd284eddc04dfee6c9 +github.com/samalba/dockerclient 12570e600d71374233e5056ba315f657ced496c7 +github.com/spf13/cobra 66816bcd0378e248c613e3c443c020f544c28804 +github.com/spf13/pflag 67cbc198fd11dab704b214c1e629a97af392c085 +github.com/tebeka/go2xunit d45000af2242dd0e7b8c7b07d82a1068adc5fd40 +golang.org/x/crypto cc04154d65fb9296747569b107cfd05380b1ea3e +golang.org/x/net 8bfde94a845cb31000de3266ac83edbda58dab09 +golang.org/x/text d4cc1b1e16b49d6dafc4982403b40fe89c512cd5 +golang.org/x/tools d02228d1857b9f49cd0252788516ff5584266eb6 +gopkg.in/yaml.v1 9f9df34309c04878acc86042b16630b0f696e1de diff --git a/src/cmd/go/internal/modconv/testdata/cockroach.out b/src/cmd/go/internal/modconv/testdata/cockroach.out new file mode 100644 index 0000000000..30cdbb7bf2 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/cockroach.out @@ -0,0 +1,31 @@ +github.com/agtorre/gocolorize f42b554bf7f006936130c9bb4f971afd2d87f671 +github.com/biogo/store e1f74b3c58befe661feed7fa4cf52436de753128 +github.com/cockroachdb/c-lz4 6e71f140a365017bbe0904710007f8725fd3f809 +github.com/cockroachdb/c-protobuf 0f9ab7b988ca7474cf76b9a961ab03c0552abcb3 +github.com/cockroachdb/c-rocksdb 7fc876fe79b96de0e25069c9ae27e6444637bd54 +github.com/cockroachdb/c-snappy 618733f9e5bab8463b9049117a335a7a1bfc9fd5 +github.com/cockroachdb/yacc 572e006f8e6b0061ebda949d13744f5108389514 +github.com/coreos/etcd 18ecc297bc913bed6fc093d66b1fa22020dba7dc +github.com/docker/docker 7374852be9def787921aea2ca831771982badecf +github.com/elazarl/go-bindata-assetfs 3dcc96556217539f50599357fb481ac0dc7439b9 +github.com/gogo/protobuf 98e73e511a62a9c232152f94999112c80142a813 +github.com/golang/lint 7b7f4364ff76043e6c3610281525fabc0d90f0e4 +github.com/google/btree cc6329d4279e3f025a53a83c397d2339b5705c45 +github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +github.com/jteeuwen/go-bindata dce55d09e24ac40a6e725c8420902b86554f8046 +github.com/julienschmidt/httprouter 6aacfd5ab513e34f7e64ea9627ab9670371b34e7 +github.com/kisielk/errcheck 50b84cf7fa18ee2985b8c63ba3de5edd604b9259 +github.com/kisielk/gotool d678387370a2eb9b5b0a33218bc8c9d8de15b6be +github.com/lib/pq a8d8d01c4f91602f876bf5aa210274e8203a6b45 +github.com/montanaflynn/stats 44fb56da2a2a67d394dec0e18a82dd316f192529 +github.com/peterh/liner 1bb0d1c1a25ed393d8feb09bab039b2b1b1fbced +github.com/robfig/glock cb3c3ec56de988289cab7bbd284eddc04dfee6c9 +github.com/samalba/dockerclient 12570e600d71374233e5056ba315f657ced496c7 +github.com/spf13/cobra 66816bcd0378e248c613e3c443c020f544c28804 +github.com/spf13/pflag 67cbc198fd11dab704b214c1e629a97af392c085 +github.com/tebeka/go2xunit d45000af2242dd0e7b8c7b07d82a1068adc5fd40 +golang.org/x/crypto cc04154d65fb9296747569b107cfd05380b1ea3e +golang.org/x/net 8bfde94a845cb31000de3266ac83edbda58dab09 +golang.org/x/text d4cc1b1e16b49d6dafc4982403b40fe89c512cd5 +golang.org/x/tools d02228d1857b9f49cd0252788516ff5584266eb6 +gopkg.in/yaml.v1 9f9df34309c04878acc86042b16630b0f696e1de diff --git a/src/cmd/go/internal/modconv/testdata/dockermachine.godeps b/src/cmd/go/internal/modconv/testdata/dockermachine.godeps new file mode 100644 index 0000000000..a551002a04 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/dockermachine.godeps @@ -0,0 +1,159 @@ +{ + "ImportPath": "github.com/docker/machine", + "GoVersion": "go1.4.2", + "Deps": [ + { + "ImportPath": "code.google.com/p/goauth2/oauth", + "Comment": "weekly-56", + "Rev": "afe77d958c701557ec5dc56f6936fcc194d15520" + }, + { + "ImportPath": "github.com/MSOpenTech/azure-sdk-for-go", + "Comment": "v1.1-17-g515f3ec", + "Rev": "515f3ec74ce6a5b31e934cefae997c97bd0a1b1e" + }, + { + "ImportPath": "github.com/cenkalti/backoff", + "Rev": "9831e1e25c874e0a0601b6dc43641071414eec7a" + }, + { + "ImportPath": "github.com/codegangsta/cli", + "Comment": "1.2.0-64-ge1712f3", + "Rev": "e1712f381785e32046927f64a7c86fe569203196" + }, + { + "ImportPath": "github.com/digitalocean/godo", + "Comment": "v0.5.0", + "Rev": "5478aae80694de1d2d0e02c386bbedd201266234" + }, + { + "ImportPath": "github.com/docker/docker/dockerversion", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/engine", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/archive", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/fileutils", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/ioutils", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/mflag", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/parsers", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/pools", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/promise", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/system", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/term", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/timeutils", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/units", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/pkg/version", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar", + "Comment": "v1.5.0", + "Rev": "a8a31eff10544860d2188dddabdee4d727545796" + }, + { + "ImportPath": "github.com/docker/libtrust", + "Rev": "c54fbb67c1f1e68d7d6f8d2ad7c9360404616a41" + }, + { + "ImportPath": "github.com/google/go-querystring/query", + "Rev": "30f7a39f4a218feb5325f3aebc60c32a572a8274" + }, + { + "ImportPath": "github.com/mitchellh/mapstructure", + "Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf" + }, + { + "ImportPath": "github.com/rackspace/gophercloud", + "Comment": "v1.0.0-558-ce0f487", + "Rev": "ce0f487f6747ab43c4e4404722df25349385bebd" + }, + { + "ImportPath": "github.com/skarademir/naturalsort", + "Rev": "983d4d86054d80f91fd04dd62ec52c1d078ce403" + }, + { + "ImportPath": "github.com/smartystreets/go-aws-auth", + "Rev": "1f0db8c0ee6362470abe06a94e3385927ed72a4b" + }, + { + "ImportPath": "github.com/stretchr/testify/assert", + "Rev": "e4ec8152c15fc46bd5056ce65997a07c7d415325" + }, + { + "ImportPath": "github.com/pyr/egoscale/src/egoscale", + "Rev": "bbaa67324aeeacc90430c1fe0a9c620d3929512e" + }, + { + "ImportPath": "github.com/tent/http-link-go", + "Rev": "ac974c61c2f990f4115b119354b5e0b47550e888" + }, + { + "ImportPath": "github.com/vmware/govcloudair", + "Comment": "v0.0.2", + "Rev": "66a23eaabc61518f91769939ff541886fe1dceef" + }, + { + "ImportPath": "golang.org/x/crypto/ssh", + "Rev": "1fbbd62cfec66bd39d91e97749579579d4d3037e" + }, + { + "ImportPath": "google.golang.org/api/compute/v1", + "Rev": "aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5" + }, + { + "ImportPath": "google.golang.org/api/googleapi", + "Rev": "aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5" + } + ] +} diff --git a/src/cmd/go/internal/modconv/testdata/dockermachine.out b/src/cmd/go/internal/modconv/testdata/dockermachine.out new file mode 100644 index 0000000000..0b39ceaccb --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/dockermachine.out @@ -0,0 +1,33 @@ +code.google.com/p/goauth2/oauth afe77d958c701557ec5dc56f6936fcc194d15520 +github.com/MSOpenTech/azure-sdk-for-go 515f3ec74ce6a5b31e934cefae997c97bd0a1b1e +github.com/cenkalti/backoff 9831e1e25c874e0a0601b6dc43641071414eec7a +github.com/codegangsta/cli e1712f381785e32046927f64a7c86fe569203196 +github.com/digitalocean/godo 5478aae80694de1d2d0e02c386bbedd201266234 +github.com/docker/docker/dockerversion a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/engine a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/archive a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/fileutils a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/ioutils a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/mflag a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/parsers a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/pools a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/promise a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/system a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/term a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/timeutils a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/units a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/pkg/version a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar a8a31eff10544860d2188dddabdee4d727545796 +github.com/docker/libtrust c54fbb67c1f1e68d7d6f8d2ad7c9360404616a41 +github.com/google/go-querystring/query 30f7a39f4a218feb5325f3aebc60c32a572a8274 +github.com/mitchellh/mapstructure 740c764bc6149d3f1806231418adb9f52c11bcbf +github.com/rackspace/gophercloud ce0f487f6747ab43c4e4404722df25349385bebd +github.com/skarademir/naturalsort 983d4d86054d80f91fd04dd62ec52c1d078ce403 +github.com/smartystreets/go-aws-auth 1f0db8c0ee6362470abe06a94e3385927ed72a4b +github.com/stretchr/testify/assert e4ec8152c15fc46bd5056ce65997a07c7d415325 +github.com/pyr/egoscale/src/egoscale bbaa67324aeeacc90430c1fe0a9c620d3929512e +github.com/tent/http-link-go ac974c61c2f990f4115b119354b5e0b47550e888 +github.com/vmware/govcloudair 66a23eaabc61518f91769939ff541886fe1dceef +golang.org/x/crypto/ssh 1fbbd62cfec66bd39d91e97749579579d4d3037e +google.golang.org/api/compute/v1 aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5 +google.golang.org/api/googleapi aa91ac681e18e52b1a0dfe29b9d8354e88c0dcf5 diff --git a/src/cmd/go/internal/modconv/testdata/dockerman.glide b/src/cmd/go/internal/modconv/testdata/dockerman.glide new file mode 100644 index 0000000000..5ec765a4c6 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/dockerman.glide @@ -0,0 +1,52 @@ +hash: ead3ea293a6143fe41069ebec814bf197d8c43a92cc7666b1f7e21a419b46feb +updated: 2016-06-20T21:53:35.420817456Z +imports: +- name: github.com/BurntSushi/toml + version: f0aeabca5a127c4078abb8c8d64298b147264b55 +- name: github.com/cpuguy83/go-md2man + version: a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa + subpackages: + - md2man +- name: github.com/fsnotify/fsnotify + version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8 +- name: github.com/hashicorp/hcl + version: da486364306ed66c218be9b7953e19173447c18b + subpackages: + - hcl/ast + - hcl/parser + - hcl/token + - json/parser + - hcl/scanner + - hcl/strconv + - json/scanner + - json/token +- name: github.com/inconshreveable/mousetrap + version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/magiconair/properties + version: c265cfa48dda6474e208715ca93e987829f572f8 +- name: github.com/mitchellh/mapstructure + version: d2dd0262208475919e1a362f675cfc0e7c10e905 +- name: github.com/russross/blackfriday + version: 1d6b8e9301e720b08a8938b8c25c018285885438 +- name: github.com/shurcooL/sanitized_anchor_name + version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 +- name: github.com/spf13/cast + version: 27b586b42e29bec072fe7379259cc719e1289da6 +- name: github.com/spf13/jwalterweatherman + version: 33c24e77fb80341fe7130ee7c594256ff08ccc46 +- name: github.com/spf13/pflag + version: dabebe21bf790f782ea4c7bbd2efc430de182afd +- name: github.com/spf13/viper + version: c1ccc378a054ea8d4e38d8c67f6938d4760b53dd +- name: golang.org/x/sys + version: 62bee037599929a6e9146f29d10dd5208c43507d + subpackages: + - unix +- name: gopkg.in/yaml.v2 + version: a83829b6f1293c91addabc89d0571c246397bbf4 +- name: github.com/spf13/cobra + repo: https://github.com/dnephin/cobra + subpackages: + - doc + version: v1.3 +devImports: [] diff --git a/src/cmd/go/internal/modconv/testdata/dockerman.out b/src/cmd/go/internal/modconv/testdata/dockerman.out new file mode 100644 index 0000000000..5e6370b31c --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/dockerman.out @@ -0,0 +1,16 @@ +github.com/BurntSushi/toml f0aeabca5a127c4078abb8c8d64298b147264b55 +github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa +github.com/fsnotify/fsnotify 30411dbcefb7a1da7e84f75530ad3abe4011b4f8 +github.com/hashicorp/hcl da486364306ed66c218be9b7953e19173447c18b +github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +github.com/magiconair/properties c265cfa48dda6474e208715ca93e987829f572f8 +github.com/mitchellh/mapstructure d2dd0262208475919e1a362f675cfc0e7c10e905 +github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438 +github.com/shurcooL/sanitized_anchor_name 10ef21a441db47d8b13ebcc5fd2310f636973c77 +github.com/spf13/cast 27b586b42e29bec072fe7379259cc719e1289da6 +github.com/spf13/jwalterweatherman 33c24e77fb80341fe7130ee7c594256ff08ccc46 +github.com/spf13/pflag dabebe21bf790f782ea4c7bbd2efc430de182afd +github.com/spf13/viper c1ccc378a054ea8d4e38d8c67f6938d4760b53dd +golang.org/x/sys 62bee037599929a6e9146f29d10dd5208c43507d +gopkg.in/yaml.v2 a83829b6f1293c91addabc89d0571c246397bbf4 +github.com/spf13/cobra v1.3 diff --git a/src/cmd/go/internal/modconv/testdata/govmomi.out b/src/cmd/go/internal/modconv/testdata/govmomi.out new file mode 100644 index 0000000000..188c458b3d --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/govmomi.out @@ -0,0 +1,5 @@ +github.com/davecgh/go-xdr/xdr2 4930550ba2e22f87187498acfd78348b15f4e7a8 +github.com/google/uuid 6a5e28554805e78ea6141142aba763936c4761c0 +github.com/kr/pretty 2ee9d7453c02ef7fa518a83ae23644eb8872186a +github.com/kr/pty 95d05c1eef33a45bd58676b6ce28d105839b8d0b +github.com/vmware/vmw-guestinfo 25eff159a728be87e103a0b8045e08273f4dbec4 diff --git a/src/cmd/go/internal/modconv/testdata/govmomi.vmanifest b/src/cmd/go/internal/modconv/testdata/govmomi.vmanifest new file mode 100644 index 0000000000..b89e4ab5ee --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/govmomi.vmanifest @@ -0,0 +1,46 @@ +{ + "version": 0, + "dependencies": [ + { + "importpath": "github.com/davecgh/go-xdr/xdr2", + "repository": "https://github.com/rasky/go-xdr", + "vcs": "git", + "revision": "4930550ba2e22f87187498acfd78348b15f4e7a8", + "branch": "improvements", + "path": "/xdr2", + "notests": true + }, + { + "importpath": "github.com/google/uuid", + "repository": "https://github.com/google/uuid", + "vcs": "git", + "revision": "6a5e28554805e78ea6141142aba763936c4761c0", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/kr/pretty", + "repository": "https://github.com/dougm/pretty", + "vcs": "git", + "revision": "2ee9d7453c02ef7fa518a83ae23644eb8872186a", + "branch": "govmomi", + "notests": true + }, + { + "importpath": "github.com/kr/pty", + "repository": "https://github.com/kr/pty", + "vcs": "git", + "revision": "95d05c1eef33a45bd58676b6ce28d105839b8d0b", + "branch": "master", + "notests": true + }, + { + "importpath": "github.com/vmware/vmw-guestinfo", + "repository": "https://github.com/vmware/vmw-guestinfo", + "vcs": "git", + "revision": "25eff159a728be87e103a0b8045e08273f4dbec4", + "branch": "master", + "notests": true + } + ] +} diff --git a/src/cmd/go/internal/modconv/testdata/juju.out b/src/cmd/go/internal/modconv/testdata/juju.out new file mode 100644 index 0000000000..c2430b1e26 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/juju.out @@ -0,0 +1,106 @@ +github.com/Azure/azure-sdk-for-go 902d95d9f311ae585ee98cfd18f418b467d60d5a +github.com/Azure/go-autorest 6f40a8acfe03270d792cb8155e2942c09d7cff95 +github.com/ajstarks/svgo 89e3ac64b5b3e403a5e7c35ea4f98d45db7b4518 +github.com/altoros/gosigma 31228935eec685587914528585da4eb9b073c76d +github.com/beorn7/perks 3ac7bf7a47d159a033b107610db8a1b6575507a4 +github.com/bmizerany/pat c068ca2f0aacee5ac3681d68e4d0a003b7d1fd2c +github.com/coreos/go-systemd 7b2428fec40033549c68f54e26e89e7ca9a9ce31 +github.com/dgrijalva/jwt-go 01aeca54ebda6e0fbfafd0a524d234159c05ec20 +github.com/dustin/go-humanize 145fabdb1ab757076a70a886d092a3af27f66f4c +github.com/godbus/dbus 32c6cc29c14570de4cf6d7e7737d68fb2d01ad15 +github.com/golang/protobuf 4bd1920723d7b7c925de087aa32e2187708897f7 +github.com/google/go-querystring 9235644dd9e52eeae6fa48efd539fdc351a0af53 +github.com/gorilla/schema 08023a0215e7fc27a9aecd8b8c50913c40019478 +github.com/gorilla/websocket 804cb600d06b10672f2fbc0a336a7bee507a428e +github.com/gosuri/uitable 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 +github.com/joyent/gocommon ade826b8b54e81a779ccb29d358a45ba24b7809c +github.com/joyent/gosdc 2f11feadd2d9891e92296a1077c3e2e56939547d +github.com/joyent/gosign 0da0d5f1342065321c97812b1f4ac0c2b0bab56c +github.com/juju/ansiterm b99631de12cf04a906c1d4e4ec54fb86eae5863d +github.com/juju/blobstore 06056004b3d7b54bbb7984d830c537bad00fec21 +github.com/juju/bundlechanges 7725027b95e0d54635e0fb11efc2debdcdf19f75 +github.com/juju/cmd 9425a576247f348b9b40afe3b60085de63470de5 +github.com/juju/description d3742c23561884cd7d759ef7142340af1d22cab0 +github.com/juju/errors 1b5e39b83d1835fa480e0c2ddefb040ee82d58b3 +github.com/juju/gnuflag 4e76c56581859c14d9d87e1ddbe29e1c0f10195f +github.com/juju/go4 40d72ab9641a2a8c36a9c46a51e28367115c8e59 +github.com/juju/gojsonpointer afe8b77aa08f272b49e01b82de78510c11f61500 +github.com/juju/gojsonreference f0d24ac5ee330baa21721cdff56d45e4ee42628e +github.com/juju/gojsonschema e1ad140384f254c82f89450d9a7c8dd38a632838 +github.com/juju/gomaasapi cfbc096bd45f276c17a391efc4db710b60ae3ad7 +github.com/juju/httpprof 14bf14c307672fd2456bdbf35d19cf0ccd3cf565 +github.com/juju/httprequest 266fd1e9debf09c037a63f074d099a2da4559ece +github.com/juju/idmclient 4dc25171f675da4206b71695d3fd80e519ad05c1 +github.com/juju/jsonschema a0ef8b74ebcffeeff9fc374854deb4af388f037e +github.com/juju/loggo 21bc4c63e8b435779a080e39e592969b7b90b889 +github.com/juju/mempool 24974d6c264fe5a29716e7d56ea24c4bd904b7cc +github.com/juju/mutex 59c26ee163447c5c57f63ff71610d433862013de +github.com/juju/persistent-cookiejar 5243747bf8f2d0897f6c7a52799327dc97d585e8 +github.com/juju/pubsub 9dcaca7eb4340dbf685aa7b3ad4cc4f8691a33d4 +github.com/juju/replicaset 6b5becf2232ce76656ea765d8d915d41755a1513 +github.com/juju/retry 62c62032529169c7ec02fa48f93349604c345e1f +github.com/juju/rfc ebdbbdb950cd039a531d15cdc2ac2cbd94f068ee +github.com/juju/romulus 98d6700423d63971f10ca14afea9ecf2b9b99f0f +github.com/juju/schema 075de04f9b7d7580d60a1e12a0b3f50bb18e6998 +github.com/juju/terms-client 9b925afd677234e4146dde3cb1a11e187cbed64e +github.com/juju/testing fce9bc4ebf7a77310c262ac4884e03b778eae06a +github.com/juju/txn 28898197906200d603394d8e4ce537436529f1c5 +github.com/juju/usso 68a59c96c178fbbad65926e7f93db50a2cd14f33 +github.com/juju/utils 9f8aeb9b09e2d8c769be8317ccfa23f7eec62c26 +github.com/juju/version 1f41e27e54f21acccf9b2dddae063a782a8a7ceb +github.com/juju/webbrowser 54b8c57083b4afb7dc75da7f13e2967b2606a507 +github.com/juju/xml eb759a627588d35166bc505fceb51b88500e291e +github.com/juju/zip f6b1e93fa2e29a1d7d49b566b2b51efb060c982a +github.com/julienschmidt/httprouter 77a895ad01ebc98a4dc95d8355bc825ce80a56f6 +github.com/lestrrat/go-jspointer f4881e611bdbe9fb413a7780721ef8400a1f2341 +github.com/lestrrat/go-jsref e452c7b5801d1c6494c9e7e0cbc7498c0f88dfd1 +github.com/lestrrat/go-jsschema b09d7650b822d2ea3dc83d5091a5e2acd8330051 +github.com/lestrrat/go-jsval b1258a10419fe0693f7b35ad65cd5074bc0ba1e5 +github.com/lestrrat/go-pdebug 2e6eaaa5717f81bda41d27070d3c966f40a1e75f +github.com/lestrrat/go-structinfo f74c056fe41f860aa6264478c664a6fff8a64298 +github.com/lunixbochs/vtclean 4fbf7632a2c6d3fbdb9931439bdbbeded02cbe36 +github.com/lxc/lxd 23da0234979fa6299565b91b529a6dbeb42ee36d +github.com/masterzen/azure-sdk-for-go ee4f0065d00cd12b542f18f5bc45799e88163b12 +github.com/masterzen/simplexml 4572e39b1ab9fe03ee513ce6fc7e289e98482190 +github.com/masterzen/winrm 7a535cd943fccaeed196718896beec3fb51aff41 +github.com/masterzen/xmlpath 13f4951698adc0fa9c1dda3e275d489a24201161 +github.com/mattn/go-colorable ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8 +github.com/mattn/go-isatty 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8 +github.com/mattn/go-runewidth d96d1bd051f2bd9e7e43d602782b37b93b1b5666 +github.com/matttproud/golang_protobuf_extensions c12348ce28de40eed0136aa2b644d0ee0650e56c +github.com/nu7hatch/gouuid 179d4d0c4d8d407a32af483c2354df1d2c91e6c3 +github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 +github.com/prometheus/client_golang 575f371f7862609249a1be4c9145f429fe065e32 +github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6 +github.com/prometheus/common dd586c1c5abb0be59e60f942c22af711a2008cb4 +github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 +github.com/rogpeppe/fastuuid 6724a57986aff9bff1a1770e9347036def7c89f6 +github.com/vmware/govmomi c0c7ce63df7edd78e713257b924c89d9a2dac119 +golang.org/x/crypto 8e06e8ddd9629eb88639aba897641bff8031f1d3 +golang.org/x/net ea47fc708ee3e20177f3ca3716217c4ab75942cb +golang.org/x/oauth2 11c60b6f71a6ad48ed6f93c65fa4c6f9b1b5b46a +golang.org/x/sys 7a6e5648d140666db5d920909e082ca00a87ba2c +golang.org/x/text 2910a502d2bf9e43193af9d68ca516529614eed3 +google.golang.org/api 0d3983fb069cb6651353fc44c5cb604e263f2a93 +google.golang.org/cloud f20d6dcccb44ed49de45ae3703312cb46e627db1 +gopkg.in/amz.v3 8c3190dff075bf5442c9eedbf8f8ed6144a099e7 +gopkg.in/check.v1 4f90aeace3a26ad7021961c297b22c42160c7b25 +gopkg.in/errgo.v1 442357a80af5c6bf9b6d51ae791a39c3421004f3 +gopkg.in/goose.v1 ac43167b647feacdd9a1e34ee81e574551bc748d +gopkg.in/ini.v1 776aa739ce9373377cd16f526cdf06cb4c89b40f +gopkg.in/juju/blobstore.v2 51fa6e26128d74e445c72d3a91af555151cc3654 +gopkg.in/juju/charm.v6-unstable 83771c4919d6810bce5b7e63f46bea5fbfed0b93 +gopkg.in/juju/charmrepo.v2-unstable e79aa298df89ea887c9bffec46063c24bfb730f7 +gopkg.in/juju/charmstore.v5-unstable fd1eef3002fc6b6daff5e97efab6f5056d22dcc7 +gopkg.in/juju/environschema.v1 7359fc7857abe2b11b5b3e23811a9c64cb6b01e0 +gopkg.in/juju/jujusvg.v2 d82160011935ef79fc7aca84aba2c6f74700fe75 +gopkg.in/juju/names.v2 0847c26d322a121e52614f969fb82eae2820c715 +gopkg.in/juju/worker.v1 6965b9d826717287bb002e02d1fd4d079978083e +gopkg.in/macaroon-bakery.v1 469b44e6f1f9479e115c8ae879ef80695be624d5 +gopkg.in/macaroon.v1 ab3940c6c16510a850e1c2dd628b919f0f3f1464 +gopkg.in/mgo.v2 f2b6f6c918c452ad107eec89615f074e3bd80e33 +gopkg.in/natefinch/lumberjack.v2 514cbda263a734ae8caac038dadf05f8f3f9f738 +gopkg.in/natefinch/npipe.v2 c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6 +gopkg.in/retry.v1 c09f6b86ba4d5d2cf5bdf0665364aec9fd4815db +gopkg.in/tomb.v1 dd632973f1e7218eb1089048e0798ec9ae7dceb8 +gopkg.in/yaml.v2 a3f3340b5840cee44f372bddb5880fcbc419b46a diff --git a/src/cmd/go/internal/modconv/testdata/juju.tsv b/src/cmd/go/internal/modconv/testdata/juju.tsv new file mode 100644 index 0000000000..0bddcef81c --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/juju.tsv @@ -0,0 +1,106 @@ +github.com/Azure/azure-sdk-for-go git 902d95d9f311ae585ee98cfd18f418b467d60d5a 2016-07-20T05:16:58Z +github.com/Azure/go-autorest git 6f40a8acfe03270d792cb8155e2942c09d7cff95 2016-07-19T23:14:56Z +github.com/ajstarks/svgo git 89e3ac64b5b3e403a5e7c35ea4f98d45db7b4518 2014-10-04T21:11:59Z +github.com/altoros/gosigma git 31228935eec685587914528585da4eb9b073c76d 2015-04-08T14:52:32Z +github.com/beorn7/perks git 3ac7bf7a47d159a033b107610db8a1b6575507a4 2016-02-29T21:34:45Z +github.com/bmizerany/pat git c068ca2f0aacee5ac3681d68e4d0a003b7d1fd2c 2016-02-17T10:32:42Z +github.com/coreos/go-systemd git 7b2428fec40033549c68f54e26e89e7ca9a9ce31 2016-02-02T21:14:25Z +github.com/dgrijalva/jwt-go git 01aeca54ebda6e0fbfafd0a524d234159c05ec20 2016-07-05T20:30:06Z +github.com/dustin/go-humanize git 145fabdb1ab757076a70a886d092a3af27f66f4c 2014-12-28T07:11:48Z +github.com/godbus/dbus git 32c6cc29c14570de4cf6d7e7737d68fb2d01ad15 2016-05-06T22:25:50Z +github.com/golang/protobuf git 4bd1920723d7b7c925de087aa32e2187708897f7 2016-11-09T07:27:36Z +github.com/google/go-querystring git 9235644dd9e52eeae6fa48efd539fdc351a0af53 2016-04-01T23:30:42Z +github.com/gorilla/schema git 08023a0215e7fc27a9aecd8b8c50913c40019478 2016-04-26T23:15:12Z +github.com/gorilla/websocket git 804cb600d06b10672f2fbc0a336a7bee507a428e 2017-02-14T17:41:18Z +github.com/gosuri/uitable git 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 2016-04-04T20:39:58Z +github.com/joyent/gocommon git ade826b8b54e81a779ccb29d358a45ba24b7809c 2016-03-20T19:31:33Z +github.com/joyent/gosdc git 2f11feadd2d9891e92296a1077c3e2e56939547d 2014-05-24T00:08:15Z +github.com/joyent/gosign git 0da0d5f1342065321c97812b1f4ac0c2b0bab56c 2014-05-24T00:07:34Z +github.com/juju/ansiterm git b99631de12cf04a906c1d4e4ec54fb86eae5863d 2016-09-07T23:45:32Z +github.com/juju/blobstore git 06056004b3d7b54bbb7984d830c537bad00fec21 2015-07-29T11:18:58Z +github.com/juju/bundlechanges git 7725027b95e0d54635e0fb11efc2debdcdf19f75 2016-12-15T16:06:52Z +github.com/juju/cmd git 9425a576247f348b9b40afe3b60085de63470de5 2017-03-20T01:37:09Z +github.com/juju/description git d3742c23561884cd7d759ef7142340af1d22cab0 2017-03-20T07:46:40Z +github.com/juju/errors git 1b5e39b83d1835fa480e0c2ddefb040ee82d58b3 2015-09-16T12:56:42Z +github.com/juju/gnuflag git 4e76c56581859c14d9d87e1ddbe29e1c0f10195f 2016-08-09T16:52:14Z +github.com/juju/go4 git 40d72ab9641a2a8c36a9c46a51e28367115c8e59 2016-02-22T16:32:58Z +github.com/juju/gojsonpointer git afe8b77aa08f272b49e01b82de78510c11f61500 2015-02-04T19:46:29Z +github.com/juju/gojsonreference git f0d24ac5ee330baa21721cdff56d45e4ee42628e 2015-02-04T19:46:33Z +github.com/juju/gojsonschema git e1ad140384f254c82f89450d9a7c8dd38a632838 2015-03-12T17:00:16Z +github.com/juju/gomaasapi git cfbc096bd45f276c17a391efc4db710b60ae3ad7 2017-02-27T07:51:07Z +github.com/juju/httpprof git 14bf14c307672fd2456bdbf35d19cf0ccd3cf565 2014-12-17T16:00:36Z +github.com/juju/httprequest git 266fd1e9debf09c037a63f074d099a2da4559ece 2016-10-06T15:09:09Z +github.com/juju/idmclient git 4dc25171f675da4206b71695d3fd80e519ad05c1 2017-02-09T16:27:49Z +github.com/juju/jsonschema git a0ef8b74ebcffeeff9fc374854deb4af388f037e 2016-11-02T18:19:19Z +github.com/juju/loggo git 21bc4c63e8b435779a080e39e592969b7b90b889 2017-02-22T12:20:47Z +github.com/juju/mempool git 24974d6c264fe5a29716e7d56ea24c4bd904b7cc 2016-02-05T10:49:27Z +github.com/juju/mutex git 59c26ee163447c5c57f63ff71610d433862013de 2016-06-17T01:09:07Z +github.com/juju/persistent-cookiejar git 5243747bf8f2d0897f6c7a52799327dc97d585e8 2016-11-15T13:33:28Z +github.com/juju/pubsub git 9dcaca7eb4340dbf685aa7b3ad4cc4f8691a33d4 2016-07-28T03:00:34Z +github.com/juju/replicaset git 6b5becf2232ce76656ea765d8d915d41755a1513 2016-11-25T16:08:49Z +github.com/juju/retry git 62c62032529169c7ec02fa48f93349604c345e1f 2015-10-29T02:48:21Z +github.com/juju/rfc git ebdbbdb950cd039a531d15cdc2ac2cbd94f068ee 2016-07-11T02:42:13Z +github.com/juju/romulus git 98d6700423d63971f10ca14afea9ecf2b9b99f0f 2017-01-23T14:29:29Z +github.com/juju/schema git 075de04f9b7d7580d60a1e12a0b3f50bb18e6998 2016-04-20T04:42:03Z +github.com/juju/terms-client git 9b925afd677234e4146dde3cb1a11e187cbed64e 2016-08-09T13:19:00Z +github.com/juju/testing git fce9bc4ebf7a77310c262ac4884e03b778eae06a 2017-02-22T09:01:19Z +github.com/juju/txn git 28898197906200d603394d8e4ce537436529f1c5 2016-11-16T04:07:55Z +github.com/juju/usso git 68a59c96c178fbbad65926e7f93db50a2cd14f33 2016-04-01T10:44:24Z +github.com/juju/utils git 9f8aeb9b09e2d8c769be8317ccfa23f7eec62c26 2017-02-15T08:19:00Z +github.com/juju/version git 1f41e27e54f21acccf9b2dddae063a782a8a7ceb 2016-10-31T05:19:06Z +github.com/juju/webbrowser git 54b8c57083b4afb7dc75da7f13e2967b2606a507 2016-03-09T14:36:29Z +github.com/juju/xml git eb759a627588d35166bc505fceb51b88500e291e 2015-04-13T13:11:21Z +github.com/juju/zip git f6b1e93fa2e29a1d7d49b566b2b51efb060c982a 2016-02-05T10:52:21Z +github.com/julienschmidt/httprouter git 77a895ad01ebc98a4dc95d8355bc825ce80a56f6 2015-10-13T22:55:20Z +github.com/lestrrat/go-jspointer git f4881e611bdbe9fb413a7780721ef8400a1f2341 2016-02-29T02:13:54Z +github.com/lestrrat/go-jsref git e452c7b5801d1c6494c9e7e0cbc7498c0f88dfd1 2016-06-01T01:32:40Z +github.com/lestrrat/go-jsschema git b09d7650b822d2ea3dc83d5091a5e2acd8330051 2016-09-03T13:19:57Z +github.com/lestrrat/go-jsval git b1258a10419fe0693f7b35ad65cd5074bc0ba1e5 2016-10-12T04:57:17Z +github.com/lestrrat/go-pdebug git 2e6eaaa5717f81bda41d27070d3c966f40a1e75f 2016-08-17T06:33:33Z +github.com/lestrrat/go-structinfo git f74c056fe41f860aa6264478c664a6fff8a64298 2016-03-08T13:11:05Z +github.com/lunixbochs/vtclean git 4fbf7632a2c6d3fbdb9931439bdbbeded02cbe36 2016-01-25T03:51:06Z +github.com/lxc/lxd git 23da0234979fa6299565b91b529a6dbeb42ee36d 2017-02-16T05:29:42Z +github.com/masterzen/azure-sdk-for-go git ee4f0065d00cd12b542f18f5bc45799e88163b12 2016-10-14T13:56:28Z +github.com/masterzen/simplexml git 4572e39b1ab9fe03ee513ce6fc7e289e98482190 2016-06-08T18:30:07Z +github.com/masterzen/winrm git 7a535cd943fccaeed196718896beec3fb51aff41 2016-10-14T15:10:40Z +github.com/masterzen/xmlpath git 13f4951698adc0fa9c1dda3e275d489a24201161 2014-02-18T18:59:01Z +github.com/mattn/go-colorable git ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8 2016-07-31T23:54:17Z +github.com/mattn/go-isatty git 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8 2016-08-06T12:27:52Z +github.com/mattn/go-runewidth git d96d1bd051f2bd9e7e43d602782b37b93b1b5666 2015-11-18T07:21:59Z +github.com/matttproud/golang_protobuf_extensions git c12348ce28de40eed0136aa2b644d0ee0650e56c 2016-04-24T11:30:07Z +github.com/nu7hatch/gouuid git 179d4d0c4d8d407a32af483c2354df1d2c91e6c3 2013-12-21T20:05:32Z +github.com/pkg/errors git 839d9e913e063e28dfd0e6c7b7512793e0a48be9 2016-10-02T05:25:12Z +github.com/prometheus/client_golang git 575f371f7862609249a1be4c9145f429fe065e32 2016-11-24T15:57:32Z +github.com/prometheus/client_model git fa8ad6fec33561be4280a8f0514318c79d7f6cb6 2015-02-12T10:17:44Z +github.com/prometheus/common git dd586c1c5abb0be59e60f942c22af711a2008cb4 2016-05-03T22:05:32Z +github.com/prometheus/procfs git abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 2016-04-11T19:08:41Z +github.com/rogpeppe/fastuuid git 6724a57986aff9bff1a1770e9347036def7c89f6 2015-01-06T09:32:20Z +github.com/vmware/govmomi git c0c7ce63df7edd78e713257b924c89d9a2dac119 2016-06-30T15:37:42Z +golang.org/x/crypto git 8e06e8ddd9629eb88639aba897641bff8031f1d3 2016-09-22T17:06:29Z +golang.org/x/net git ea47fc708ee3e20177f3ca3716217c4ab75942cb 2015-08-29T23:03:18Z +golang.org/x/oauth2 git 11c60b6f71a6ad48ed6f93c65fa4c6f9b1b5b46a 2015-03-25T02:00:22Z +golang.org/x/sys git 7a6e5648d140666db5d920909e082ca00a87ba2c 2017-02-01T05:12:45Z +golang.org/x/text git 2910a502d2bf9e43193af9d68ca516529614eed3 2016-07-26T16:48:57Z +google.golang.org/api git 0d3983fb069cb6651353fc44c5cb604e263f2a93 2014-12-10T23:51:26Z +google.golang.org/cloud git f20d6dcccb44ed49de45ae3703312cb46e627db1 2015-03-19T22:36:35Z +gopkg.in/amz.v3 git 8c3190dff075bf5442c9eedbf8f8ed6144a099e7 2016-12-15T13:08:49Z +gopkg.in/check.v1 git 4f90aeace3a26ad7021961c297b22c42160c7b25 2016-01-05T16:49:36Z +gopkg.in/errgo.v1 git 442357a80af5c6bf9b6d51ae791a39c3421004f3 2016-12-22T12:58:16Z +gopkg.in/goose.v1 git ac43167b647feacdd9a1e34ee81e574551bc748d 2017-02-15T01:56:23Z +gopkg.in/ini.v1 git 776aa739ce9373377cd16f526cdf06cb4c89b40f 2016-02-22T23:24:41Z +gopkg.in/juju/blobstore.v2 git 51fa6e26128d74e445c72d3a91af555151cc3654 2016-01-25T02:37:03Z +gopkg.in/juju/charm.v6-unstable git 83771c4919d6810bce5b7e63f46bea5fbfed0b93 2016-10-03T20:31:18Z +gopkg.in/juju/charmrepo.v2-unstable git e79aa298df89ea887c9bffec46063c24bfb730f7 2016-11-17T15:25:28Z +gopkg.in/juju/charmstore.v5-unstable git fd1eef3002fc6b6daff5e97efab6f5056d22dcc7 2016-09-16T10:09:07Z +gopkg.in/juju/environschema.v1 git 7359fc7857abe2b11b5b3e23811a9c64cb6b01e0 2015-11-04T11:58:10Z +gopkg.in/juju/jujusvg.v2 git d82160011935ef79fc7aca84aba2c6f74700fe75 2016-06-09T10:52:15Z +gopkg.in/juju/names.v2 git 0847c26d322a121e52614f969fb82eae2820c715 2016-11-02T13:43:03Z +gopkg.in/juju/worker.v1 git 6965b9d826717287bb002e02d1fd4d079978083e 2017-03-08T00:24:58Z +gopkg.in/macaroon-bakery.v1 git 469b44e6f1f9479e115c8ae879ef80695be624d5 2016-06-22T12:14:21Z +gopkg.in/macaroon.v1 git ab3940c6c16510a850e1c2dd628b919f0f3f1464 2015-01-21T11:42:31Z +gopkg.in/mgo.v2 git f2b6f6c918c452ad107eec89615f074e3bd80e33 2016-08-18T01:52:18Z +gopkg.in/natefinch/lumberjack.v2 git 514cbda263a734ae8caac038dadf05f8f3f9f738 2016-01-25T11:17:49Z +gopkg.in/natefinch/npipe.v2 git c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6 2016-06-21T03:49:01Z +gopkg.in/retry.v1 git c09f6b86ba4d5d2cf5bdf0665364aec9fd4815db 2016-10-25T18:14:30Z +gopkg.in/tomb.v1 git dd632973f1e7218eb1089048e0798ec9ae7dceb8 2014-10-24T13:56:13Z +gopkg.in/yaml.v2 git a3f3340b5840cee44f372bddb5880fcbc419b46a 2017-02-08T14:18:51Z diff --git a/src/cmd/go/internal/modconv/testdata/moby.out b/src/cmd/go/internal/modconv/testdata/moby.out new file mode 100644 index 0000000000..2cb2e056a8 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/moby.out @@ -0,0 +1,105 @@ +github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 +github.com/Microsoft/hcsshim v0.6.5 +github.com/Microsoft/go-winio v0.4.5 +github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 +github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a +github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 +github.com/gorilla/context v1.1 +github.com/gorilla/mux v1.1 +github.com/Microsoft/opengcs v0.3.4 +github.com/kr/pty 5cf931ef8f +github.com/mattn/go-shellwords v1.0.3 +github.com/sirupsen/logrus v1.0.3 +github.com/tchap/go-patricia v2.2.6 +github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 +golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6 +golang.org/x/sys 07c182904dbd53199946ba614a412c61d3c548f5 +github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 +github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d +golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756 +github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987 +github.com/pmezard/go-difflib v1.0.0 +github.com/gotestyourself/gotestyourself v1.1.0 +github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 +github.com/imdario/mergo 0.2.1 +golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0 +github.com/containerd/continuity 22694c680ee48fb8f50015b44618517e2bde77e8 +github.com/moby/buildkit aaff9d591ef128560018433fe61beb802e149de8 +github.com/tonistiigi/fsutil dea3a0da73aee887fc02142d995be764106ac5e2 +github.com/docker/libnetwork 68f1039f172434709a4550fe92e3e058406c74ce +github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 +github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 +github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec +github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b +github.com/hashicorp/memberlist v0.1.0 +github.com/sean-/seed e2103e2c35297fb7e17febb81e49b312087a2372 +github.com/hashicorp/go-sockaddr acd314c5781ea706c710d9ea70069fd2e110d61d +github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e +github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 +github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef +github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25 +github.com/vishvananda/netlink bd6d5de5ccef2d66b0a26177928d0d8895d7f969 +github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060 +github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374 +github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d +github.com/coreos/etcd v3.2.1 +github.com/coreos/go-semver v0.2.0 +github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065 +github.com/hashicorp/consul v0.5.2 +github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904 +github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 +github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c +github.com/vbatts/tar-split v0.10.1 +github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb +github.com/mistifyio/go-zfs 22c9b32c84eb0d0c6f4043b6e90fc94073de92fa +github.com/pborman/uuid v1.0 +google.golang.org/grpc v1.3.0 +github.com/opencontainers/runc 0351df1c5a66838d0c392b4ac4cf9450de844e2d +github.com/opencontainers/image-spec 372ad780f63454fbbbbcc7cf80e5b90245c13e13 +github.com/opencontainers/runtime-spec v1.0.0 +github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 +github.com/coreos/go-systemd v4 +github.com/godbus/dbus v4.0.0 +github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852 +github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4 +github.com/Graylog2/go-gelf v2 +github.com/fluent/fluent-logger-golang v1.2.1 +github.com/philhofer/fwd 98c11a7a6ec829d672b03833c3d69a7fae1ca972 +github.com/tinylib/msgp 75ee40d2601edf122ef667e2a07d600d4c44490c +github.com/fsnotify/fsnotify v1.4.2 +github.com/aws/aws-sdk-go v1.4.22 +github.com/go-ini/ini 060d7da055ba6ec5ea7a31f116332fe5efa04ce0 +github.com/jmespath/go-jmespath 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74 +github.com/bsphere/le_go 7a984a84b5492ae539b79b62fb4a10afc63c7bcf +golang.org/x/oauth2 96382aa079b72d8c014eb0c50f6c223d1e6a2de0 +google.golang.org/api 3cc2e591b550923a2c5f0ab5a803feda924d5823 +cloud.google.com/go 9d965e63e8cceb1b5d7977a202f0fcb8866d6525 +github.com/googleapis/gax-go da06d194a00e19ce00d9011a13931c3f6f6887c7 +google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 +github.com/containerd/containerd 06b9cb35161009dcb7123345749fef02f7cea8e0 +github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4 +github.com/docker/swarmkit 872861d2ae46958af7ead1d5fffb092c73afbaf0 +github.com/gogo/protobuf v0.4 +github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a +github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e +golang.org/x/crypto 558b6879de74bc843225cde5686419267ff707ca +golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb +github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad +github.com/hashicorp/go-immutable-radix 8e8ed81f8f0bf1bdd829593fdd5c29922c1ea990 +github.com/hashicorp/golang-lru a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 +github.com/coreos/pkg fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8 +github.com/pivotal-golang/clock 3fd3c1944c59d9742e1cd333672181cd1a6f9fa0 +github.com/prometheus/client_golang 52437c81da6b127a9925d17eb3a382a2e5fd395e +github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 +github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6 +github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8 +github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 +github.com/matttproud/golang_protobuf_extensions v1.0.0 +github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 +github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 +github.com/spf13/cobra v1.5.1 +github.com/spf13/pflag 9ff6c6923cfffbcd502984b8e0c80539a94968b7 +github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c +github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18 +github.com/opencontainers/selinux v1.0.0-rc1 diff --git a/src/cmd/go/internal/modconv/testdata/moby.vconf b/src/cmd/go/internal/modconv/testdata/moby.vconf new file mode 100644 index 0000000000..53b90d1e37 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/moby.vconf @@ -0,0 +1,149 @@ +# the following lines are in sorted order, FYI +github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 +github.com/Microsoft/hcsshim v0.6.5 +github.com/Microsoft/go-winio v0.4.5 +github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 +github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a +github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git +github.com/gorilla/context v1.1 +github.com/gorilla/mux v1.1 +github.com/Microsoft/opengcs v0.3.4 +github.com/kr/pty 5cf931ef8f +github.com/mattn/go-shellwords v1.0.3 +github.com/sirupsen/logrus v1.0.3 +github.com/tchap/go-patricia v2.2.6 +github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 +golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6 +golang.org/x/sys 07c182904dbd53199946ba614a412c61d3c548f5 +github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 +github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d +golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756 +github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987 +github.com/pmezard/go-difflib v1.0.0 +github.com/gotestyourself/gotestyourself v1.1.0 + +github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 +github.com/imdario/mergo 0.2.1 +golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0 + +github.com/containerd/continuity 22694c680ee48fb8f50015b44618517e2bde77e8 +github.com/moby/buildkit aaff9d591ef128560018433fe61beb802e149de8 +github.com/tonistiigi/fsutil dea3a0da73aee887fc02142d995be764106ac5e2 + +#get libnetwork packages +github.com/docker/libnetwork 68f1039f172434709a4550fe92e3e058406c74ce +github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 +github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 +github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec +github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b +github.com/hashicorp/memberlist v0.1.0 +github.com/sean-/seed e2103e2c35297fb7e17febb81e49b312087a2372 +github.com/hashicorp/go-sockaddr acd314c5781ea706c710d9ea70069fd2e110d61d +github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e +github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 +github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef +github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25 +github.com/vishvananda/netlink bd6d5de5ccef2d66b0a26177928d0d8895d7f969 +github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060 +github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374 +github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d +github.com/coreos/etcd v3.2.1 +github.com/coreos/go-semver v0.2.0 +github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065 +github.com/hashicorp/consul v0.5.2 +github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904 +github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 + +# get graph and distribution packages +github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c +github.com/vbatts/tar-split v0.10.1 +github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb + +# get go-zfs packages +github.com/mistifyio/go-zfs 22c9b32c84eb0d0c6f4043b6e90fc94073de92fa +github.com/pborman/uuid v1.0 + +google.golang.org/grpc v1.3.0 + +# When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly +github.com/opencontainers/runc 0351df1c5a66838d0c392b4ac4cf9450de844e2d +github.com/opencontainers/image-spec 372ad780f63454fbbbbcc7cf80e5b90245c13e13 +github.com/opencontainers/runtime-spec v1.0.0 + +github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 + +# libcontainer deps (see src/github.com/opencontainers/runc/Godeps/Godeps.json) +github.com/coreos/go-systemd v4 +github.com/godbus/dbus v4.0.0 +github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852 +github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4 + +# gelf logging driver deps +github.com/Graylog2/go-gelf v2 + +github.com/fluent/fluent-logger-golang v1.2.1 +# fluent-logger-golang deps +github.com/philhofer/fwd 98c11a7a6ec829d672b03833c3d69a7fae1ca972 +github.com/tinylib/msgp 75ee40d2601edf122ef667e2a07d600d4c44490c + +# fsnotify +github.com/fsnotify/fsnotify v1.4.2 + +# awslogs deps +github.com/aws/aws-sdk-go v1.4.22 +github.com/go-ini/ini 060d7da055ba6ec5ea7a31f116332fe5efa04ce0 +github.com/jmespath/go-jmespath 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74 + +# logentries +github.com/bsphere/le_go 7a984a84b5492ae539b79b62fb4a10afc63c7bcf + +# gcplogs deps +golang.org/x/oauth2 96382aa079b72d8c014eb0c50f6c223d1e6a2de0 +google.golang.org/api 3cc2e591b550923a2c5f0ab5a803feda924d5823 +cloud.google.com/go 9d965e63e8cceb1b5d7977a202f0fcb8866d6525 +github.com/googleapis/gax-go da06d194a00e19ce00d9011a13931c3f6f6887c7 +google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 + +# containerd +github.com/containerd/containerd 06b9cb35161009dcb7123345749fef02f7cea8e0 +github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4 + +# cluster +github.com/docker/swarmkit 872861d2ae46958af7ead1d5fffb092c73afbaf0 +github.com/gogo/protobuf v0.4 +github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a +github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e +golang.org/x/crypto 558b6879de74bc843225cde5686419267ff707ca +golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb +github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad +github.com/hashicorp/go-immutable-radix 8e8ed81f8f0bf1bdd829593fdd5c29922c1ea990 +github.com/hashicorp/golang-lru a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 +github.com/coreos/pkg fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8 +github.com/pivotal-golang/clock 3fd3c1944c59d9742e1cd333672181cd1a6f9fa0 +github.com/prometheus/client_golang 52437c81da6b127a9925d17eb3a382a2e5fd395e +github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 +github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6 +github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8 +github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 +github.com/matttproud/golang_protobuf_extensions v1.0.0 +github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 +github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 + +# cli +github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git +github.com/spf13/pflag 9ff6c6923cfffbcd502984b8e0c80539a94968b7 +github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty + +# metrics +github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18 + +github.com/opencontainers/selinux v1.0.0-rc1 + +# archive/tar +# mkdir -p ./vendor/archive +# git clone git://github.com/tonistiigi/go-1.git ./go +# git --git-dir ./go/.git --work-tree ./go checkout revert-prefix-ignore +# cp -a go/src/archive/tar ./vendor/archive/tar +# rm -rf ./go +# vndr diff --git a/src/cmd/go/internal/modconv/testdata/panicparse.out b/src/cmd/go/internal/modconv/testdata/panicparse.out new file mode 100644 index 0000000000..8830033c6b --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/panicparse.out @@ -0,0 +1,8 @@ +github.com/kr/pretty 737b74a46c4bf788349f72cb256fed10aea4d0ac +github.com/kr/text 7cafcd837844e784b526369c9bce262804aebc60 +github.com/maruel/ut a9c9f15ccfa6f8b90182a53df32f4745586fbae3 +github.com/mattn/go-colorable 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59 +github.com/mattn/go-isatty 56b76bdf51f7708750eac80fa38b952bb9f32639 +github.com/mgutz/ansi c286dcecd19ff979eeb73ea444e479b903f2cfcb +github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2 +golang.org/x/sys a646d33e2ee3172a661fc09bca23bb4889a41bc8 diff --git a/src/cmd/go/internal/modconv/testdata/panicparse.vyml b/src/cmd/go/internal/modconv/testdata/panicparse.vyml new file mode 100644 index 0000000000..ff3d43f5f2 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/panicparse.vyml @@ -0,0 +1,17 @@ +vendors: +- path: github.com/kr/pretty + rev: 737b74a46c4bf788349f72cb256fed10aea4d0ac +- path: github.com/kr/text + rev: 7cafcd837844e784b526369c9bce262804aebc60 +- path: github.com/maruel/ut + rev: a9c9f15ccfa6f8b90182a53df32f4745586fbae3 +- path: github.com/mattn/go-colorable + rev: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59 +- path: github.com/mattn/go-isatty + rev: 56b76bdf51f7708750eac80fa38b952bb9f32639 +- path: github.com/mgutz/ansi + rev: c286dcecd19ff979eeb73ea444e479b903f2cfcb +- path: github.com/pmezard/go-difflib + rev: 792786c7400a136282c1664665ae0a8db921c6c2 +- path: golang.org/x/sys + rev: a646d33e2ee3172a661fc09bca23bb4889a41bc8 diff --git a/src/cmd/go/internal/modconv/testdata/prometheus.out b/src/cmd/go/internal/modconv/testdata/prometheus.out new file mode 100644 index 0000000000..d11b8ecc72 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/prometheus.out @@ -0,0 +1,258 @@ +cloud.google.com/go/compute/metadata c589d0c9f0d81640c518354c7bcae77d99820aa3 +cloud.google.com/go/internal c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/Azure/azure-sdk-for-go/arm/compute bd73d950fa4440dae889bd9917bff7cef539f86e +github.com/Azure/azure-sdk-for-go/arm/network bd73d950fa4440dae889bd9917bff7cef539f86e +github.com/Azure/go-autorest/autorest 8a25372bbfec739b8719a9e3987400d15ef9e179 +github.com/Azure/go-autorest/autorest/azure 8a25372bbfec739b8719a9e3987400d15ef9e179 +github.com/Azure/go-autorest/autorest/date 8a25372bbfec739b8719a9e3987400d15ef9e179 +github.com/Azure/go-autorest/autorest/to 8a25372bbfec739b8719a9e3987400d15ef9e179 +github.com/Azure/go-autorest/autorest/validation 8a25372bbfec739b8719a9e3987400d15ef9e179 +github.com/PuerkitoBio/purell c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/PuerkitoBio/urlesc c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/asaskevich/govalidator 7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877 +github.com/aws/aws-sdk-go/aws 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/awserr 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/awsutil 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/client 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/client/metadata 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/corehandlers 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/credentials 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/credentials/endpointcreds 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/credentials/stscreds 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/defaults 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/ec2metadata 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/request 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/session 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/aws/signer/v4 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/private/endpoints 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/private/protocol 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/private/protocol/ec2query 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/private/protocol/query 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/private/protocol/query/queryutil 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/private/protocol/rest 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/private/waiter 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/service/ec2 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/aws/aws-sdk-go/service/sts 707203bc55114ed114446bf57949c5c211d8b7c0 +github.com/beorn7/perks/quantile 3ac7bf7a47d159a033b107610db8a1b6575507a4 +github.com/blang/semver c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/coreos/go-oidc/http c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/coreos/go-oidc/jose c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/coreos/go-oidc/key c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/coreos/go-oidc/oauth2 c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/coreos/go-oidc/oidc c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/coreos/pkg/health c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/coreos/pkg/httputil c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/coreos/pkg/timeutil c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/davecgh/go-spew/spew c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/dgrijalva/jwt-go 9ed569b5d1ac936e6494082958d63a6aa4fff99a +github.com/docker/distribution/digest c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/docker/distribution/reference c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/emicklei/go-restful c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/emicklei/go-restful/log c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/emicklei/go-restful/swagger c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/ghodss/yaml c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/go-ini/ini 6e4869b434bd001f6983749881c7ead3545887d8 +github.com/go-openapi/jsonpointer c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/go-openapi/jsonreference c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/go-openapi/spec c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/go-openapi/swag c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/gogo/protobuf/proto c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/gogo/protobuf/sortkeys c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/golang/glog c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/golang/protobuf/proto 98fa357170587e470c5f27d3c3ea0947b71eb455 +github.com/golang/snappy d9eb7a3d35ec988b8585d4a0068e462c27d28380 +github.com/google/gofuzz c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/hashicorp/consul/api daacc4be8bee214e3fc4b32a6dd385f5ef1b4c36 +github.com/hashicorp/go-cleanhttp ad28ea4487f05916463e2423a55166280e8254b5 +github.com/hashicorp/serf/coordinate 1d4fa605f6ff3ed628d7ae5eda7c0e56803e72a5 +github.com/influxdb/influxdb/client 291aaeb9485b43b16875c238482b2f7d0a22a13b +github.com/influxdb/influxdb/tsdb 291aaeb9485b43b16875c238482b2f7d0a22a13b +github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d +github.com/jonboulle/clockwork c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/juju/ratelimit c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/julienschmidt/httprouter 109e267447e95ad1bb48b758e40dd7453eb7b039 +github.com/mailru/easyjson/buffer c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/mailru/easyjson/jlexer c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/mailru/easyjson/jwriter c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/matttproud/golang_protobuf_extensions/pbutil fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a +github.com/miekg/dns 58f52c57ce9df13460ac68200cef30a008b9c468 +github.com/pborman/uuid c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/pmezard/go-difflib/difflib d77da356e56a7428ad25149ca77381849a6a5232 +github.com/prometheus/client_golang/prometheus c5b7fccd204277076155f10851dad72b76a49317 +github.com/prometheus/client_model/go fa8ad6fec33561be4280a8f0514318c79d7f6cb6 +github.com/prometheus/common/expfmt 85637ea67b04b5c3bb25e671dacded2977f8f9f6 +github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg 85637ea67b04b5c3bb25e671dacded2977f8f9f6 +github.com/prometheus/common/log 85637ea67b04b5c3bb25e671dacded2977f8f9f6 +github.com/prometheus/common/model 85637ea67b04b5c3bb25e671dacded2977f8f9f6 +github.com/prometheus/common/route 85637ea67b04b5c3bb25e671dacded2977f8f9f6 +github.com/prometheus/common/version 85637ea67b04b5c3bb25e671dacded2977f8f9f6 +github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 +github.com/samuel/go-zookeeper/zk 177002e16a0061912f02377e2dd8951a8b3551bc +github.com/spf13/pflag c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/stretchr/testify/assert d77da356e56a7428ad25149ca77381849a6a5232 +github.com/stretchr/testify/require d77da356e56a7428ad25149ca77381849a6a5232 +github.com/syndtr/goleveldb/leveldb 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/cache 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/comparer 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/errors 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/filter 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/iterator 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/journal 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/memdb 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/opt 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/storage 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/table 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/syndtr/goleveldb/leveldb/util 6b4daa5362b502898ddf367c5c11deb9e7a5c727 +github.com/ugorji/go/codec c589d0c9f0d81640c518354c7bcae77d99820aa3 +github.com/vaughan0/go-ini a98ad7ee00ec53921f08832bc06ecf7fd600e6a1 +golang.org/x/net/context b336a971b799939dd16ae9b1df8334cb8b977c4d +golang.org/x/net/context/ctxhttp b336a971b799939dd16ae9b1df8334cb8b977c4d +golang.org/x/net/http2 c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/net/http2/hpack c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/net/idna c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/net/internal/timeseries 6250b412798208e6c90b03b7c4f226de5aa299e2 +golang.org/x/net/lex/httplex c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/net/netutil bc3663df0ac92f928d419e31e0d2af22e683a5a2 +golang.org/x/oauth2 65a8d08c6292395d47053be10b3c5e91960def76 +golang.org/x/oauth2/google 65a8d08c6292395d47053be10b3c5e91960def76 +golang.org/x/oauth2/internal 65a8d08c6292395d47053be10b3c5e91960def76 +golang.org/x/oauth2/jws 65a8d08c6292395d47053be10b3c5e91960def76 +golang.org/x/oauth2/jwt 65a8d08c6292395d47053be10b3c5e91960def76 +golang.org/x/sys/unix c200b10b5d5e122be351b67af224adc6128af5bf +golang.org/x/sys/windows c200b10b5d5e122be351b67af224adc6128af5bf +golang.org/x/sys/windows/registry c200b10b5d5e122be351b67af224adc6128af5bf +golang.org/x/sys/windows/svc/eventlog c200b10b5d5e122be351b67af224adc6128af5bf +golang.org/x/text/cases c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/text/internal/tag c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/text/language c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/text/runes c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/text/secure/bidirule c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/text/secure/precis c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/text/transform c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/text/unicode/bidi c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/text/unicode/norm c589d0c9f0d81640c518354c7bcae77d99820aa3 +golang.org/x/text/width c589d0c9f0d81640c518354c7bcae77d99820aa3 +google.golang.org/api/compute/v1 63ade871fd3aec1225809d496e81ec91ab76ea29 +google.golang.org/api/gensupport 63ade871fd3aec1225809d496e81ec91ab76ea29 +google.golang.org/api/googleapi 63ade871fd3aec1225809d496e81ec91ab76ea29 +google.golang.org/api/googleapi/internal/uritemplates 63ade871fd3aec1225809d496e81ec91ab76ea29 +google.golang.org/appengine 267c27e7492265b84fc6719503b14a1e17975d79 +google.golang.org/appengine/internal 267c27e7492265b84fc6719503b14a1e17975d79 +google.golang.org/appengine/internal/app_identity 267c27e7492265b84fc6719503b14a1e17975d79 +google.golang.org/appengine/internal/base 267c27e7492265b84fc6719503b14a1e17975d79 +google.golang.org/appengine/internal/datastore 267c27e7492265b84fc6719503b14a1e17975d79 +google.golang.org/appengine/internal/log 267c27e7492265b84fc6719503b14a1e17975d79 +google.golang.org/appengine/internal/modules 267c27e7492265b84fc6719503b14a1e17975d79 +google.golang.org/appengine/internal/remote_api 4f7eeb5305a4ba1966344836ba4af9996b7b4e05 +google.golang.org/appengine/internal/urlfetch 267c27e7492265b84fc6719503b14a1e17975d79 +google.golang.org/appengine/urlfetch 267c27e7492265b84fc6719503b14a1e17975d79 +google.golang.org/cloud/compute/metadata 0a83eba2cadb60eb22123673c8fb6fca02b03c94 +google.golang.org/cloud/internal 0a83eba2cadb60eb22123673c8fb6fca02b03c94 +gopkg.in/fsnotify.v1 30411dbcefb7a1da7e84f75530ad3abe4011b4f8 +gopkg.in/inf.v0 c589d0c9f0d81640c518354c7bcae77d99820aa3 +gopkg.in/yaml.v2 7ad95dd0798a40da1ccdff6dff35fd177b5edf40 +k8s.io/client-go/1.5/discovery c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/apps/v1alpha1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/authentication/v1beta1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/authorization/v1beta1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/autoscaling/v1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/batch/v1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/certificates/v1alpha1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/core/v1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/extensions/v1beta1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/policy/v1alpha1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/rbac/v1alpha1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/kubernetes/typed/storage/v1beta1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/api c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/api/errors c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/api/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/api/meta c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/api/meta/metatypes c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/api/resource c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/api/unversioned c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/api/v1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/api/validation/path c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apimachinery c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apimachinery/announced c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apimachinery/registered c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/apps c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/apps/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/apps/v1alpha1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/authentication c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/authentication/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/authentication/v1beta1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/authorization c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/authorization/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/authorization/v1beta1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/autoscaling c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/autoscaling/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/autoscaling/v1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/batch c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/batch/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/batch/v1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/batch/v2alpha1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/certificates c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/certificates/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/certificates/v1alpha1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/extensions c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/extensions/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/policy c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/policy/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/policy/v1alpha1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/rbac c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/rbac/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/rbac/v1alpha1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/storage c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/storage/install c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/apis/storage/v1beta1 c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/auth/user c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/conversion c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/conversion/queryparams c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/fields c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/genericapiserver/openapi/common c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/labels c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/runtime c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/runtime/serializer c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/runtime/serializer/json c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/runtime/serializer/protobuf c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/runtime/serializer/recognizer c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/runtime/serializer/streaming c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/runtime/serializer/versioning c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/selection c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/third_party/forked/golang/reflect c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/types c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/cert c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/clock c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/errors c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/flowcontrol c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/framer c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/integer c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/intstr c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/json c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/labels c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/net c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/parsers c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/rand c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/runtime c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/sets c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/uuid c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/validation c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/validation/field c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/wait c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/util/yaml c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/version c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/watch c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/pkg/watch/versioned c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/plugin/pkg/client/auth c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/plugin/pkg/client/auth/gcp c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/plugin/pkg/client/auth/oidc c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/rest c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/tools/cache c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/tools/clientcmd/api c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/tools/metrics c589d0c9f0d81640c518354c7bcae77d99820aa3 +k8s.io/client-go/1.5/transport c589d0c9f0d81640c518354c7bcae77d99820aa3 diff --git a/src/cmd/go/internal/modconv/testdata/prometheus.vjson b/src/cmd/go/internal/modconv/testdata/prometheus.vjson new file mode 100644 index 0000000000..648bec4260 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/prometheus.vjson @@ -0,0 +1,1605 @@ +{ + "comment": "", + "ignore": "test appengine", + "package": [ + { + "checksumSHA1": "Cslv4/ITyQmgjSUhNXFu8q5bqOU=", + "origin": "k8s.io/client-go/1.5/vendor/cloud.google.com/go/compute/metadata", + "path": "cloud.google.com/go/compute/metadata", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "hiJXjkFEGy+sDFf6O58Ocdy9Rnk=", + "origin": "k8s.io/client-go/1.5/vendor/cloud.google.com/go/internal", + "path": "cloud.google.com/go/internal", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "oIt4tXgFYnZJBsCac1BQLnTWALM=", + "path": "github.com/Azure/azure-sdk-for-go/arm/compute", + "revision": "bd73d950fa4440dae889bd9917bff7cef539f86e", + "revisionTime": "2016-10-28T18:31:11Z" + }, + { + "checksumSHA1": "QKi6LiSyD5GnRK8ExpMgZl4XiMI=", + "path": "github.com/Azure/azure-sdk-for-go/arm/network", + "revision": "bd73d950fa4440dae889bd9917bff7cef539f86e", + "revisionTime": "2016-10-28T18:31:11Z" + }, + { + "checksumSHA1": "eVSHe6GIHj9/ziFrQLZ1SC7Nn6k=", + "path": "github.com/Azure/go-autorest/autorest", + "revision": "8a25372bbfec739b8719a9e3987400d15ef9e179", + "revisionTime": "2016-10-25T18:07:34Z" + }, + { + "checksumSHA1": "0sYi0JprevG/PZjtMbOh8h0pt0g=", + "path": "github.com/Azure/go-autorest/autorest/azure", + "revision": "8a25372bbfec739b8719a9e3987400d15ef9e179", + "revisionTime": "2016-10-25T18:07:34Z" + }, + { + "checksumSHA1": "q9Qz8PAxK5FTOZwgYKe5Lj38u4c=", + "path": "github.com/Azure/go-autorest/autorest/date", + "revision": "8a25372bbfec739b8719a9e3987400d15ef9e179", + "revisionTime": "2016-10-25T18:07:34Z" + }, + { + "checksumSHA1": "Ev8qCsbFjDlMlX0N2tYAhYQFpUc=", + "path": "github.com/Azure/go-autorest/autorest/to", + "revision": "8a25372bbfec739b8719a9e3987400d15ef9e179", + "revisionTime": "2016-10-25T18:07:34Z" + }, + { + "checksumSHA1": "oBixceM+55gdk47iff8DSEIh3po=", + "path": "github.com/Azure/go-autorest/autorest/validation", + "revision": "8a25372bbfec739b8719a9e3987400d15ef9e179", + "revisionTime": "2016-10-25T18:07:34Z" + }, + { + "checksumSHA1": "IatnluZB5jTVUncMN134e4VOV34=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/PuerkitoBio/purell", + "path": "github.com/PuerkitoBio/purell", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "E/Tz8z0B/gaR551g+XqPKAhcteM=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/PuerkitoBio/urlesc", + "path": "github.com/PuerkitoBio/urlesc", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "BdLdZP/C2uOO3lqk9X3NCKFpXa4=", + "path": "github.com/asaskevich/govalidator", + "revision": "7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877", + "revisionTime": "2016-10-01T16:31:30Z" + }, + { + "checksumSHA1": "WNfR3yhLjRC5/uccgju/bwrdsxQ=", + "path": "github.com/aws/aws-sdk-go/aws", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "Y9W+4GimK4Fuxq+vyIskVYFRnX4=", + "path": "github.com/aws/aws-sdk-go/aws/awserr", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "+q4vdl3l1Wom8K1wfIpJ4jlFsbY=", + "path": "github.com/aws/aws-sdk-go/aws/awsutil", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "/232RBWA3KnT7U+wciPS2+wmvR0=", + "path": "github.com/aws/aws-sdk-go/aws/client", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "ieAJ+Cvp/PKv1LpUEnUXpc3OI6E=", + "path": "github.com/aws/aws-sdk-go/aws/client/metadata", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "c1N3Loy3AS9zD+m5CzpPNAED39U=", + "path": "github.com/aws/aws-sdk-go/aws/corehandlers", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "zu5C95rmCZff6NYZb62lEaT5ibE=", + "path": "github.com/aws/aws-sdk-go/aws/credentials", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "KQiUK/zr3mqnAXD7x/X55/iNme0=", + "path": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "NUJUTWlc1sV8b7WjfiYc4JZbXl0=", + "path": "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "4Ipx+5xN0gso+cENC2MHMWmQlR4=", + "path": "github.com/aws/aws-sdk-go/aws/credentials/stscreds", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "DwhFsNluCFEwqzyp3hbJR3q2Wqs=", + "path": "github.com/aws/aws-sdk-go/aws/defaults", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "8E0fEBUJY/1lJOyVxzTxMGQGInk=", + "path": "github.com/aws/aws-sdk-go/aws/ec2metadata", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "5Ac22YMTBmrX/CXaEIXzWljr8UY=", + "path": "github.com/aws/aws-sdk-go/aws/request", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "eOo6evLMAxQfo7Qkc5/h5euN1Sw=", + "path": "github.com/aws/aws-sdk-go/aws/session", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "diXvBs1LRC0RJ9WK6sllWKdzC04=", + "path": "github.com/aws/aws-sdk-go/aws/signer/v4", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "Esab5F8KswqkTdB4TtjSvZgs56k=", + "path": "github.com/aws/aws-sdk-go/private/endpoints", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "wk7EyvDaHwb5qqoOP/4d3cV0708=", + "path": "github.com/aws/aws-sdk-go/private/protocol", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "1QmQ3FqV37w0Zi44qv8pA1GeR0A=", + "path": "github.com/aws/aws-sdk-go/private/protocol/ec2query", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "ZqY5RWavBLWTo6j9xqdyBEaNFRk=", + "path": "github.com/aws/aws-sdk-go/private/protocol/query", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "5xzix1R8prUyWxgLnzUQoxTsfik=", + "path": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "TW/7U+/8ormL7acf6z2rv2hDD+s=", + "path": "github.com/aws/aws-sdk-go/private/protocol/rest", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "eUEkjyMPAuekKBE4ou+nM9tXEas=", + "path": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "Eo9yODN5U99BK0pMzoqnBm7PCrY=", + "path": "github.com/aws/aws-sdk-go/private/waiter", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "6h4tJ9wVtbYb9wG4srtUxyPoAYM=", + "path": "github.com/aws/aws-sdk-go/service/ec2", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "ouwhxcAsIYQ6oJbMRdLW/Ys/iyg=", + "path": "github.com/aws/aws-sdk-go/service/sts", + "revision": "707203bc55114ed114446bf57949c5c211d8b7c0", + "revisionTime": "2016-11-02T21:59:28Z" + }, + { + "checksumSHA1": "4QnLdmB1kG3N+KlDd1N+G9TWAGQ=", + "path": "github.com/beorn7/perks/quantile", + "revision": "3ac7bf7a47d159a033b107610db8a1b6575507a4", + "revisionTime": "2016-02-29T21:34:45Z" + }, + { + "checksumSHA1": "n+s4YwtzpMWW5Rt0dEaQa7NHDGQ=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/blang/semver", + "path": "github.com/blang/semver", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Z2AOGSmDKKvI6nuxa+UPjQWpIeM=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/coreos/go-oidc/http", + "path": "github.com/coreos/go-oidc/http", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "8yvt1xKCgNwuuavJdxRnvaIjrIc=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/coreos/go-oidc/jose", + "path": "github.com/coreos/go-oidc/jose", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "zhXKrWBSSJLqZxVE/Xsw0M9ynFQ=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/coreos/go-oidc/key", + "path": "github.com/coreos/go-oidc/key", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "bkW0mnXvmHQwHprW/6wrbpP7lAk=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/coreos/go-oidc/oauth2", + "path": "github.com/coreos/go-oidc/oauth2", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "E1x2k5FdhJ+dzFrh3kCmC6aJfVw=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/coreos/go-oidc/oidc", + "path": "github.com/coreos/go-oidc/oidc", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "O0UMBRCOD9ItMayDqLQ2MJEjkVE=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/coreos/pkg/health", + "path": "github.com/coreos/pkg/health", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "74vyZz/d49FZXMbFaHOfCGvSLj0=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/coreos/pkg/httputil", + "path": "github.com/coreos/pkg/httputil", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "etBdQ0LN6ojGunfvUt6B5C3FNrQ=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/coreos/pkg/timeutil", + "path": "github.com/coreos/pkg/timeutil", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "SdSd7pyjONWWTHc5XE3AhglLo34=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/davecgh/go-spew/spew", + "path": "github.com/davecgh/go-spew/spew", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "2Fy1Y6Z3lRRX1891WF/+HT4XS2I=", + "path": "github.com/dgrijalva/jwt-go", + "revision": "9ed569b5d1ac936e6494082958d63a6aa4fff99a", + "revisionTime": "2016-11-01T19:39:35Z" + }, + { + "checksumSHA1": "f1wARLDzsF/JoyN01yoxXEwFIp8=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/docker/distribution/digest", + "path": "github.com/docker/distribution/digest", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "PzXRTLmmqWXxmDqdIXLcRYBma18=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/docker/distribution/reference", + "path": "github.com/docker/distribution/reference", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "1vQR+ZyudsjKio6RNKmWhwzGTb0=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/emicklei/go-restful", + "path": "github.com/emicklei/go-restful", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "3xWz4fZ9xW+CfADpYoPFcZCYJ4E=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/emicklei/go-restful/log", + "path": "github.com/emicklei/go-restful/log", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "J7CtF9gIs2yH9A7lPQDDrhYxiRk=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/emicklei/go-restful/swagger", + "path": "github.com/emicklei/go-restful/swagger", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "ww7LVo7jNJ1o6sfRcromEHKyY+o=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/ghodss/yaml", + "path": "github.com/ghodss/yaml", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "cVyhKIRI2gQrgpn5qrBeAqErmWM=", + "path": "github.com/go-ini/ini", + "revision": "6e4869b434bd001f6983749881c7ead3545887d8", + "revisionTime": "2016-08-27T06:11:18Z" + }, + { + "checksumSHA1": "NaZnW0tKj/b0k5WzcMD0twrLbrE=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/go-openapi/jsonpointer", + "path": "github.com/go-openapi/jsonpointer", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "3LJXjMDxPY+veIqzQtiAvK3hXnY=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/go-openapi/jsonreference", + "path": "github.com/go-openapi/jsonreference", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "faeB3fny260hQ/gEfEXa1ZQTGtk=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/go-openapi/spec", + "path": "github.com/go-openapi/spec", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "wGpZwJ5HZtReou8A3WEV1Gdxs6k=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/go-openapi/swag", + "path": "github.com/go-openapi/swag", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "BIyZQL97iG7mzZ2UMR3XpiXbZdc=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/gogo/protobuf/proto", + "path": "github.com/gogo/protobuf/proto", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "e6cMbpJj41MpihS5eP4SIliRBK4=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/gogo/protobuf/sortkeys", + "path": "github.com/gogo/protobuf/sortkeys", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "URsJa4y/sUUw/STmbeYx9EKqaYE=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/golang/glog", + "path": "github.com/golang/glog", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "yDh5kmmr0zEF1r+rvYqbZcR7iLs=", + "path": "github.com/golang/protobuf/proto", + "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", + "revisionTime": "2016-10-12T20:53:35Z" + }, + { + "checksumSHA1": "2a/SsTUBMKtcM6VtpbdPGO+c6c8=", + "path": "github.com/golang/snappy", + "revision": "d9eb7a3d35ec988b8585d4a0068e462c27d28380", + "revisionTime": "2016-05-29T05:00:41Z" + }, + { + "checksumSHA1": "/yFfUp3tGt6cK22UVzbq8SjPDCU=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/google/gofuzz", + "path": "github.com/google/gofuzz", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "LclVLJYrBi03PBjsVPpgoMbUDQ8=", + "path": "github.com/hashicorp/consul/api", + "revision": "daacc4be8bee214e3fc4b32a6dd385f5ef1b4c36", + "revisionTime": "2016-10-28T04:06:46Z" + }, + { + "checksumSHA1": "Uzyon2091lmwacNsl1hCytjhHtg=", + "path": "github.com/hashicorp/go-cleanhttp", + "revision": "ad28ea4487f05916463e2423a55166280e8254b5", + "revisionTime": "2016-04-07T17:41:26Z" + }, + { + "checksumSHA1": "E3Xcanc9ouQwL+CZGOUyA/+giLg=", + "path": "github.com/hashicorp/serf/coordinate", + "revision": "1d4fa605f6ff3ed628d7ae5eda7c0e56803e72a5", + "revisionTime": "2016-10-07T00:41:22Z" + }, + { + "path": "github.com/influxdb/influxdb/client", + "revision": "291aaeb9485b43b16875c238482b2f7d0a22a13b", + "revisionTime": "2015-09-16T14:41:53+02:00" + }, + { + "path": "github.com/influxdb/influxdb/tsdb", + "revision": "291aaeb9485b43b16875c238482b2f7d0a22a13b", + "revisionTime": "2015-09-16T14:41:53+02:00" + }, + { + "checksumSHA1": "0ZrwvB6KoGPj2PoDNSEJwxQ6Mog=", + "path": "github.com/jmespath/go-jmespath", + "revision": "bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d", + "revisionTime": "2016-08-03T19:07:31Z" + }, + { + "checksumSHA1": "9ZVOEbIXnTuYpVqce4en8rwlkPE=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/jonboulle/clockwork", + "path": "github.com/jonboulle/clockwork", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "gA95N2LM2hEJLoqrTPaFsSWDJ2Y=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/juju/ratelimit", + "path": "github.com/juju/ratelimit", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Farach1xcmsQYrhiUfkwF2rbIaE=", + "path": "github.com/julienschmidt/httprouter", + "revision": "109e267447e95ad1bb48b758e40dd7453eb7b039", + "revisionTime": "2015-09-05T19:25:33+02:00" + }, + { + "checksumSHA1": "urY45++NYCue4nh4k8OjUFnIGfU=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/mailru/easyjson/buffer", + "path": "github.com/mailru/easyjson/buffer", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "yTDKAM4KBgOvXRsZC50zg0OChvM=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/mailru/easyjson/jlexer", + "path": "github.com/mailru/easyjson/jlexer", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "4+d+6rhM1pei6lBguhqSEW7LaXs=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/mailru/easyjson/jwriter", + "path": "github.com/mailru/easyjson/jwriter", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Q2vw4HZBbnU8BLFt8VrzStwqSJg=", + "path": "github.com/matttproud/golang_protobuf_extensions/pbutil", + "revision": "fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a", + "revisionTime": "2015-04-06T19:39:34+02:00" + }, + { + "checksumSHA1": "Wahi4g/9XiHhSLAJ+8jskg71PCU=", + "path": "github.com/miekg/dns", + "revision": "58f52c57ce9df13460ac68200cef30a008b9c468", + "revisionTime": "2016-10-18T06:08:08Z" + }, + { + "checksumSHA1": "3YJklSuzSE1Rt8A+2dhiWSmf/fw=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/pborman/uuid", + "path": "github.com/pborman/uuid", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "zKKp5SZ3d3ycKe4EKMNT0BqAWBw=", + "origin": "github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib", + "path": "github.com/pmezard/go-difflib/difflib", + "revision": "d77da356e56a7428ad25149ca77381849a6a5232", + "revisionTime": "2016-06-15T09:26:46Z" + }, + { + "checksumSHA1": "KkB+77Ziom7N6RzSbyUwYGrmDeU=", + "path": "github.com/prometheus/client_golang/prometheus", + "revision": "c5b7fccd204277076155f10851dad72b76a49317", + "revisionTime": "2016-08-17T15:48:24Z" + }, + { + "checksumSHA1": "DvwvOlPNAgRntBzt3b3OSRMS2N4=", + "path": "github.com/prometheus/client_model/go", + "revision": "fa8ad6fec33561be4280a8f0514318c79d7f6cb6", + "revisionTime": "2015-02-12T10:17:44Z" + }, + { + "checksumSHA1": "mHyjbJ3BWOfUV6q9f5PBt0gaY1k=", + "path": "github.com/prometheus/common/expfmt", + "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", + "revisionTime": "2016-10-02T21:02:34Z" + }, + { + "checksumSHA1": "GWlM3d2vPYyNATtTFgftS10/A9w=", + "path": "github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg", + "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", + "revisionTime": "2016-10-02T21:02:34Z" + }, + { + "checksumSHA1": "UU6hIfhVjnAYDADQEfE/3T7Ddm8=", + "path": "github.com/prometheus/common/log", + "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", + "revisionTime": "2016-10-02T21:02:34Z" + }, + { + "checksumSHA1": "nFie+rxcX5WdIv1diZ+fu3aj6lE=", + "path": "github.com/prometheus/common/model", + "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", + "revisionTime": "2016-10-02T21:02:34Z" + }, + { + "checksumSHA1": "QQKJYoGcY10nIHxhBEHwjwUZQzk=", + "path": "github.com/prometheus/common/route", + "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", + "revisionTime": "2016-10-02T21:02:34Z" + }, + { + "checksumSHA1": "91KYK0SpvkaMJJA2+BcxbVnyRO0=", + "path": "github.com/prometheus/common/version", + "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", + "revisionTime": "2016-10-02T21:02:34Z" + }, + { + "checksumSHA1": "W218eJZPXJG783fUr/z6IaAZyes=", + "path": "github.com/prometheus/procfs", + "revision": "abf152e5f3e97f2fafac028d2cc06c1feb87ffa5", + "revisionTime": "2016-04-11T19:08:41Z" + }, + { + "checksumSHA1": "+49Vr4Me28p3cR+gxX5SUQHbbas=", + "path": "github.com/samuel/go-zookeeper/zk", + "revision": "177002e16a0061912f02377e2dd8951a8b3551bc", + "revisionTime": "2015-08-17T10:50:50-07:00" + }, + { + "checksumSHA1": "YuPBOVkkE3uuBh4RcRUTF0n+frs=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/spf13/pflag", + "path": "github.com/spf13/pflag", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "iydUphwYqZRq3WhstEdGsbvBAKs=", + "path": "github.com/stretchr/testify/assert", + "revision": "d77da356e56a7428ad25149ca77381849a6a5232", + "revisionTime": "2016-06-15T09:26:46Z" + }, + { + "checksumSHA1": "P9FJpir2c4G5PA46qEkaWy3l60U=", + "path": "github.com/stretchr/testify/require", + "revision": "d77da356e56a7428ad25149ca77381849a6a5232", + "revisionTime": "2016-06-15T09:26:46Z" + }, + { + "checksumSHA1": "VhcnDY37sYAnL8WjfYQN9YYl+W4=", + "path": "github.com/syndtr/goleveldb/leveldb", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "EKIow7XkgNdWvR/982ffIZxKG8Y=", + "path": "github.com/syndtr/goleveldb/leveldb/cache", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "5KPgnvCPlR0ysDAqo6jApzRQ3tw=", + "path": "github.com/syndtr/goleveldb/leveldb/comparer", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "1DRAxdlWzS4U0xKN/yQ/fdNN7f0=", + "path": "github.com/syndtr/goleveldb/leveldb/errors", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "eqKeD6DS7eNCtxVYZEHHRKkyZrw=", + "path": "github.com/syndtr/goleveldb/leveldb/filter", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "8dXuAVIsbtaMiGGuHjzGR6Ny/5c=", + "path": "github.com/syndtr/goleveldb/leveldb/iterator", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "gJY7bRpELtO0PJpZXgPQ2BYFJ88=", + "path": "github.com/syndtr/goleveldb/leveldb/journal", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "j+uaQ6DwJ50dkIdfMQu1TXdlQcY=", + "path": "github.com/syndtr/goleveldb/leveldb/memdb", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "UmQeotV+m8/FduKEfLOhjdp18rs=", + "path": "github.com/syndtr/goleveldb/leveldb/opt", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "/Wvv9HeJTN9UUjdjwUlz7X4ioIo=", + "path": "github.com/syndtr/goleveldb/leveldb/storage", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "JTJA+u8zk7EXy1UUmpFPNGvtO2A=", + "path": "github.com/syndtr/goleveldb/leveldb/table", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "4zil8Gwg8VPkDn1YzlgCvtukJFU=", + "path": "github.com/syndtr/goleveldb/leveldb/util", + "revision": "6b4daa5362b502898ddf367c5c11deb9e7a5c727", + "revisionTime": "2016-10-11T05:00:08Z" + }, + { + "checksumSHA1": "f6Aew+ZA+HBAXCw6/xTST3mB0Lw=", + "origin": "k8s.io/client-go/1.5/vendor/github.com/ugorji/go/codec", + "path": "github.com/ugorji/go/codec", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "sFD8LpJPQtWLwGda3edjf5mNUbs=", + "path": "github.com/vaughan0/go-ini", + "revision": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1", + "revisionTime": "2013-09-23T16:52:12+02:00" + }, + { + "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", + "path": "golang.org/x/net/context", + "revision": "b336a971b799939dd16ae9b1df8334cb8b977c4d", + "revisionTime": "2016-10-27T19:58:04Z" + }, + { + "checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=", + "path": "golang.org/x/net/context/ctxhttp", + "revision": "b336a971b799939dd16ae9b1df8334cb8b977c4d", + "revisionTime": "2016-10-27T19:58:04Z" + }, + { + "checksumSHA1": "SPYGC6DQrH9jICccUsOfbvvhB4g=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/net/http2", + "path": "golang.org/x/net/http2", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "EYNaHp7XdLWRydUCE0amEkKAtgk=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/net/http2/hpack", + "path": "golang.org/x/net/http2/hpack", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "gXiSniT8fevWOVPVKopYgrdzi60=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/net/idna", + "path": "golang.org/x/net/idna", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", + "path": "golang.org/x/net/internal/timeseries", + "revision": "6250b412798208e6c90b03b7c4f226de5aa299e2", + "revisionTime": "2016-08-24T22:20:41Z" + }, + { + "checksumSHA1": "yhndhWXMs/VSEDLks4dNyFMQStA=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/net/lex/httplex", + "path": "golang.org/x/net/lex/httplex", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "7WASrg0PEueWDDRHkFhEEN6Qrms=", + "path": "golang.org/x/net/netutil", + "revision": "bc3663df0ac92f928d419e31e0d2af22e683a5a2", + "revisionTime": "2016-06-21T20:48:10Z" + }, + { + "checksumSHA1": "mktBVED98G2vv+OKcSgtnFVZC1Y=", + "path": "golang.org/x/oauth2", + "revision": "65a8d08c6292395d47053be10b3c5e91960def76", + "revisionTime": "2016-06-07T03:33:14Z" + }, + { + "checksumSHA1": "2rk6lthfQa5Rfydj8j7+dilKGbo=", + "path": "golang.org/x/oauth2/google", + "revision": "65a8d08c6292395d47053be10b3c5e91960def76", + "revisionTime": "2016-06-07T03:33:14Z" + }, + { + "checksumSHA1": "W/GiDqzsagBnR7/yEvxatMhUDBs=", + "path": "golang.org/x/oauth2/internal", + "revision": "65a8d08c6292395d47053be10b3c5e91960def76", + "revisionTime": "2016-06-07T03:33:14Z" + }, + { + "checksumSHA1": "CPTYHWrVL4jA0B1IuC0hvgcE2AQ=", + "path": "golang.org/x/oauth2/jws", + "revision": "65a8d08c6292395d47053be10b3c5e91960def76", + "revisionTime": "2016-06-07T03:33:14Z" + }, + { + "checksumSHA1": "xifBSq0Pn6pIoPA/o3tyzq8X4Ds=", + "path": "golang.org/x/oauth2/jwt", + "revision": "65a8d08c6292395d47053be10b3c5e91960def76", + "revisionTime": "2016-06-07T03:33:14Z" + }, + { + "checksumSHA1": "aVgPDgwY3/t4J/JOw9H3FVMHqh0=", + "path": "golang.org/x/sys/unix", + "revision": "c200b10b5d5e122be351b67af224adc6128af5bf", + "revisionTime": "2016-10-22T18:22:21Z" + }, + { + "checksumSHA1": "fpW2dhGFC6SrVzipJx7fjg2DIH8=", + "path": "golang.org/x/sys/windows", + "revision": "c200b10b5d5e122be351b67af224adc6128af5bf", + "revisionTime": "2016-10-22T18:22:21Z" + }, + { + "checksumSHA1": "PjYlbMS0ttyZYlaevvjA/gV3g1c=", + "path": "golang.org/x/sys/windows/registry", + "revision": "c200b10b5d5e122be351b67af224adc6128af5bf", + "revisionTime": "2016-10-22T18:22:21Z" + }, + { + "checksumSHA1": "uVlUSSKplihZG7N+QJ6fzDZ4Kh8=", + "path": "golang.org/x/sys/windows/svc/eventlog", + "revision": "c200b10b5d5e122be351b67af224adc6128af5bf", + "revisionTime": "2016-10-22T18:22:21Z" + }, + { + "checksumSHA1": "QQpKbWuqvhmxVr/hfEYdWzzcXRM=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/cases", + "path": "golang.org/x/text/cases", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "iAsGo/kxvnwILbJVUCd0ZcqZO/Q=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/internal/tag", + "path": "golang.org/x/text/internal/tag", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "mQ6PCGHY7K0oPjKbYD8wsTjm/P8=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/language", + "path": "golang.org/x/text/language", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "WpeH2TweiuiZAQVTJNO5vyZAQQA=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/runes", + "path": "golang.org/x/text/runes", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "1VjEPyjdi0xOiIN/Alkqiad/B/c=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/secure/bidirule", + "path": "golang.org/x/text/secure/bidirule", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "FcK7VslktIAWj5jnWVnU2SesBq0=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/secure/precis", + "path": "golang.org/x/text/secure/precis", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "nwlu7UTwYbCj9l5f3a7t2ROwNzM=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/transform", + "path": "golang.org/x/text/transform", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "nWJ9R1+Xw41f/mM3b7BYtv77CfI=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/unicode/bidi", + "path": "golang.org/x/text/unicode/bidi", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "BAZ96wCGUj6HdY9sG60Yw09KWA4=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/unicode/norm", + "path": "golang.org/x/text/unicode/norm", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "AZMILKWqLP99UilLgbGZ+uzIVrM=", + "origin": "k8s.io/client-go/1.5/vendor/golang.org/x/text/width", + "path": "golang.org/x/text/width", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "AjdmRXf0fiy6Bec9mNlsGsmZi1k=", + "path": "google.golang.org/api/compute/v1", + "revision": "63ade871fd3aec1225809d496e81ec91ab76ea29", + "revisionTime": "2016-05-31T06:42:46Z" + }, + { + "checksumSHA1": "OtsMVXY89Hc/bBXdDp84atFQawM=", + "path": "google.golang.org/api/gensupport", + "revision": "63ade871fd3aec1225809d496e81ec91ab76ea29", + "revisionTime": "2016-05-31T06:42:46Z" + }, + { + "checksumSHA1": "yQREK/OWrz9PLljbr127+xFk6J0=", + "path": "google.golang.org/api/googleapi", + "revision": "63ade871fd3aec1225809d496e81ec91ab76ea29", + "revisionTime": "2016-05-31T06:42:46Z" + }, + { + "checksumSHA1": "ii4ET3JHk3vkMUEcg+9t/1RZSUU=", + "path": "google.golang.org/api/googleapi/internal/uritemplates", + "revision": "63ade871fd3aec1225809d496e81ec91ab76ea29", + "revisionTime": "2016-05-31T06:42:46Z" + }, + { + "checksumSHA1": "N3KZEuQ9O1QwJXcCJbe7Czwroo4=", + "path": "google.golang.org/appengine", + "revision": "267c27e7492265b84fc6719503b14a1e17975d79", + "revisionTime": "2016-06-21T05:59:22Z" + }, + { + "checksumSHA1": "G9Xp1ScdsfcKsw+PcWunivRRP3o=", + "path": "google.golang.org/appengine/internal", + "revision": "267c27e7492265b84fc6719503b14a1e17975d79", + "revisionTime": "2016-06-21T05:59:22Z" + }, + { + "checksumSHA1": "x6Thdfyasqd68dWZWqzWWeIfAfI=", + "path": "google.golang.org/appengine/internal/app_identity", + "revision": "267c27e7492265b84fc6719503b14a1e17975d79", + "revisionTime": "2016-06-21T05:59:22Z" + }, + { + "checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=", + "path": "google.golang.org/appengine/internal/base", + "revision": "267c27e7492265b84fc6719503b14a1e17975d79", + "revisionTime": "2016-06-21T05:59:22Z" + }, + { + "checksumSHA1": "5QsV5oLGSfKZqTCVXP6NRz5T4Tw=", + "path": "google.golang.org/appengine/internal/datastore", + "revision": "267c27e7492265b84fc6719503b14a1e17975d79", + "revisionTime": "2016-06-21T05:59:22Z" + }, + { + "checksumSHA1": "Gep2T9zmVYV8qZfK2gu3zrmG6QE=", + "path": "google.golang.org/appengine/internal/log", + "revision": "267c27e7492265b84fc6719503b14a1e17975d79", + "revisionTime": "2016-06-21T05:59:22Z" + }, + { + "checksumSHA1": "eLZVX1EHLclFtQnjDIszsdyWRHo=", + "path": "google.golang.org/appengine/internal/modules", + "revision": "267c27e7492265b84fc6719503b14a1e17975d79", + "revisionTime": "2016-06-21T05:59:22Z" + }, + { + "checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=", + "path": "google.golang.org/appengine/internal/remote_api", + "revision": "4f7eeb5305a4ba1966344836ba4af9996b7b4e05", + "revisionTime": "2016-08-19T23:33:10Z" + }, + { + "checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=", + "path": "google.golang.org/appengine/internal/urlfetch", + "revision": "267c27e7492265b84fc6719503b14a1e17975d79", + "revisionTime": "2016-06-21T05:59:22Z" + }, + { + "checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=", + "path": "google.golang.org/appengine/urlfetch", + "revision": "267c27e7492265b84fc6719503b14a1e17975d79", + "revisionTime": "2016-06-21T05:59:22Z" + }, + { + "checksumSHA1": "Wp8g9MHRmK8SwcyGVCoGtPx+5Lo=", + "path": "google.golang.org/cloud/compute/metadata", + "revision": "0a83eba2cadb60eb22123673c8fb6fca02b03c94", + "revisionTime": "2016-06-21T15:59:29Z" + }, + { + "checksumSHA1": "U7dGDNwEHORvJFMoNSXErKE7ITg=", + "path": "google.golang.org/cloud/internal", + "revision": "0a83eba2cadb60eb22123673c8fb6fca02b03c94", + "revisionTime": "2016-06-21T15:59:29Z" + }, + { + "checksumSHA1": "JfVmsMwyeeepbdw4q4wpN07BuFg=", + "path": "gopkg.in/fsnotify.v1", + "revision": "30411dbcefb7a1da7e84f75530ad3abe4011b4f8", + "revisionTime": "2016-04-12T13:37:56Z" + }, + { + "checksumSHA1": "pfQwQtWlFezJq0Viroa/L+v+yDM=", + "origin": "k8s.io/client-go/1.5/vendor/gopkg.in/inf.v0", + "path": "gopkg.in/inf.v0", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "KgT+peLCcuh0/m2mpoOZXuxXmwc=", + "path": "gopkg.in/yaml.v2", + "revision": "7ad95dd0798a40da1ccdff6dff35fd177b5edf40", + "revisionTime": "2015-06-24T11:29:02+01:00" + }, + { + "checksumSHA1": "st0Nbu4zwLcP3mz03lDOJVZtn8Y=", + "path": "k8s.io/client-go/1.5/discovery", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "S+OzpkipMb46LGZoWuveqSLAcoM=", + "path": "k8s.io/client-go/1.5/kubernetes", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "yCBn8ig1TUMrk+ljtK0nDr7E5Vo=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/apps/v1alpha1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "ZRnUz5NrpvJsXAjtnRdEv5UYhSI=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/authentication/v1beta1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "TY55Np20olmPMzXgfVlIUIyqv04=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/authorization/v1beta1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "FRByJsFff/6lPH20FtJPaK1NPWI=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/autoscaling/v1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "3Cy2as7HnQ2FDcvpNbatpFWx0P4=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/batch/v1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "RUKywApIbSLLsfkYxXzifh7HIvs=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/certificates/v1alpha1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "4+Lsxu+sYgzsS2JOHP7CdrZLSKc=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/core/v1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "H8jzevN03YUfmf2krJt0qj2P9sU=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/extensions/v1beta1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "hrpA6xxtwj3oMcQbFxI2cDhO2ZA=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/policy/v1alpha1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "B2+F12NeMwrOHvHK2ALyEcr3UGA=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/rbac/v1alpha1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "h2eSNUym87RWPlez7UKujShwrUQ=", + "path": "k8s.io/client-go/1.5/kubernetes/typed/storage/v1beta1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "+oIykJ3A0wYjAWbbrGo0jNnMLXw=", + "path": "k8s.io/client-go/1.5/pkg/api", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "UsUsIdhuy5Ej2vI0hbmSsrimoaQ=", + "path": "k8s.io/client-go/1.5/pkg/api/errors", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Eo6LLHFqG6YznIAKr2mVjuqUj6k=", + "path": "k8s.io/client-go/1.5/pkg/api/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "dYznkLcCEai21z1dX8kZY7uDsck=", + "path": "k8s.io/client-go/1.5/pkg/api/meta", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "b06esG4xMj/YNFD85Lqq00cx+Yo=", + "path": "k8s.io/client-go/1.5/pkg/api/meta/metatypes", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "L9svak1yut0Mx8r9VLDOwpqZzBk=", + "path": "k8s.io/client-go/1.5/pkg/api/resource", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "m7jGshKDLH9kdokfa6MwAqzxRQk=", + "path": "k8s.io/client-go/1.5/pkg/api/unversioned", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "iI6s5WAexr1PEfqrbvuscB+oVik=", + "path": "k8s.io/client-go/1.5/pkg/api/v1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "ikac34qI/IkTWHnfi8pPl9irPyo=", + "path": "k8s.io/client-go/1.5/pkg/api/validation/path", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "MJyygSPp8N6z+7SPtcROz4PEwas=", + "path": "k8s.io/client-go/1.5/pkg/apimachinery", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "EGb4IcSTQ1VXCmX0xcyG5GpWId8=", + "path": "k8s.io/client-go/1.5/pkg/apimachinery/announced", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "vhSyuINHQhCsDKTyBmvJT1HzDHI=", + "path": "k8s.io/client-go/1.5/pkg/apimachinery/registered", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "rXeBnwLg8ZFe6m5/Ki7tELVBYDk=", + "path": "k8s.io/client-go/1.5/pkg/apis/apps", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "KzHaG858KV1tBh5cuLInNcm+G5s=", + "path": "k8s.io/client-go/1.5/pkg/apis/apps/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "fynWdchlRbPaxuST2oGDKiKLTqE=", + "path": "k8s.io/client-go/1.5/pkg/apis/apps/v1alpha1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "hreIYssoH4Ef/+Aglpitn3GNLR4=", + "path": "k8s.io/client-go/1.5/pkg/apis/authentication", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "EgUqJH4CqB9vXVg6T8II2OEt5LE=", + "path": "k8s.io/client-go/1.5/pkg/apis/authentication/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Z3DKgomzRPGcBv/8hlL6pfnIpXI=", + "path": "k8s.io/client-go/1.5/pkg/apis/authentication/v1beta1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "GpuScB2Z+NOT4WIQg1mVvVSDUts=", + "path": "k8s.io/client-go/1.5/pkg/apis/authorization", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "+u3UD+HY9lBH+PFi/2B4W564JEw=", + "path": "k8s.io/client-go/1.5/pkg/apis/authorization/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "zIFzgWjmlWNLHGHMpCpDCvoLtKY=", + "path": "k8s.io/client-go/1.5/pkg/apis/authorization/v1beta1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "tdpzQFQyVkt5kCLTvtKTVqT+maE=", + "path": "k8s.io/client-go/1.5/pkg/apis/autoscaling", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "nb6LbYGS5tv8H8Ovptg6M7XuDZ4=", + "path": "k8s.io/client-go/1.5/pkg/apis/autoscaling/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "DNb1/nl/5RDdckRrJoXBRagzJXs=", + "path": "k8s.io/client-go/1.5/pkg/apis/autoscaling/v1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "4bLhH2vNl5l4Qp6MjLhWyWVAPE0=", + "path": "k8s.io/client-go/1.5/pkg/apis/batch", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "RpAAEynmxlvOlLLZK1KEUQRnYzk=", + "path": "k8s.io/client-go/1.5/pkg/apis/batch/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "uWJ2BHmjL/Gq4FFlNkqiN6vvPyM=", + "path": "k8s.io/client-go/1.5/pkg/apis/batch/v1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "mHWt/p724dKeP1vqLtWQCye7zaE=", + "path": "k8s.io/client-go/1.5/pkg/apis/batch/v2alpha1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "6dJ1dGfXkB3A42TOtMaY/rvv4N8=", + "path": "k8s.io/client-go/1.5/pkg/apis/certificates", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Bkrhm6HbFYANwtzUE8eza9SWBk0=", + "path": "k8s.io/client-go/1.5/pkg/apis/certificates/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "nRRPIBQ5O3Ad24kscNtK+gPC+fk=", + "path": "k8s.io/client-go/1.5/pkg/apis/certificates/v1alpha1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "KUMhoaOg9GXHN/aAVvSLO18SgqU=", + "path": "k8s.io/client-go/1.5/pkg/apis/extensions", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "eSo2VhNAYtesvmpEPqn05goW4LY=", + "path": "k8s.io/client-go/1.5/pkg/apis/extensions/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "DunWIPrCC5iGMWzkaaugMOxD+hg=", + "path": "k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "rVGYi2ko0E7vL5OZSMYX+NAGPYw=", + "path": "k8s.io/client-go/1.5/pkg/apis/policy", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "llJHd2H0LzABGB6BcletzIHnexo=", + "path": "k8s.io/client-go/1.5/pkg/apis/policy/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "j44bqyY13ldnuCtysYE8nRkMD7o=", + "path": "k8s.io/client-go/1.5/pkg/apis/policy/v1alpha1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "vT7rFxowcKMTYc55mddePqUFRgE=", + "path": "k8s.io/client-go/1.5/pkg/apis/rbac", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "r1MzUXsG+Zyn30aU8I5R5dgrJPA=", + "path": "k8s.io/client-go/1.5/pkg/apis/rbac/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "aNfO8xn8VDO3fM9CpVCe6EIB+GA=", + "path": "k8s.io/client-go/1.5/pkg/apis/rbac/v1alpha1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "rQCxrbisCXmj2wymlYG63kcTL9I=", + "path": "k8s.io/client-go/1.5/pkg/apis/storage", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "wZyxh5nt5Eh6kF7YNAIYukKWWy0=", + "path": "k8s.io/client-go/1.5/pkg/apis/storage/install", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "P8ANOt/I4Cs3QtjVXWmDA/gpQdg=", + "path": "k8s.io/client-go/1.5/pkg/apis/storage/v1beta1", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "qnVPwzvNLz2mmr3BXdU9qIhQXXU=", + "path": "k8s.io/client-go/1.5/pkg/auth/user", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "KrIchxhapSs242yAy8yrTS1XlZo=", + "path": "k8s.io/client-go/1.5/pkg/conversion", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "weZqKFcOhcnF47eDDHXzluCKSF0=", + "path": "k8s.io/client-go/1.5/pkg/conversion/queryparams", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "T3EMfyXZX5939/OOQ1JU+Nmbk4k=", + "path": "k8s.io/client-go/1.5/pkg/fields", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "2v11s3EBH8UBl2qfImT29tQN2kM=", + "path": "k8s.io/client-go/1.5/pkg/genericapiserver/openapi/common", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "GvBlph6PywK3zguou/T9kKNNdoQ=", + "path": "k8s.io/client-go/1.5/pkg/labels", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Vtrgy827r0rWzIAgvIWY4flu740=", + "path": "k8s.io/client-go/1.5/pkg/runtime", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "SEcZqRATexhgHvDn+eHvMc07UJs=", + "path": "k8s.io/client-go/1.5/pkg/runtime/serializer", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "qzYKG9YZSj8l/W1QVTOrGAry/BM=", + "path": "k8s.io/client-go/1.5/pkg/runtime/serializer/json", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "F7h+8zZ0JPLYkac4KgSVljguBE4=", + "path": "k8s.io/client-go/1.5/pkg/runtime/serializer/protobuf", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "CvySOL8C85e3y7EWQ+Au4cwUZJM=", + "path": "k8s.io/client-go/1.5/pkg/runtime/serializer/recognizer", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "eCitoKeIun+lJzYFhAfdSIIicSM=", + "path": "k8s.io/client-go/1.5/pkg/runtime/serializer/streaming", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "kVWvZuLGltJ4YqQsiaCLRRLDDK0=", + "path": "k8s.io/client-go/1.5/pkg/runtime/serializer/versioning", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "m51+LAeQ9RK1KHX+l2iGcwbVCKs=", + "path": "k8s.io/client-go/1.5/pkg/selection", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "dp4IWcC3U6a0HeOdVCDQWODWCbw=", + "path": "k8s.io/client-go/1.5/pkg/third_party/forked/golang/reflect", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "ER898XJD1ox4d71gKZD8TLtTSpM=", + "path": "k8s.io/client-go/1.5/pkg/types", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "BVdXtnLDlmBQksRPfHOIG+qdeVg=", + "path": "k8s.io/client-go/1.5/pkg/util", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "nnh8Sa4dCupxRI4bbKaozGp1d/A=", + "path": "k8s.io/client-go/1.5/pkg/util/cert", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "S32d5uduNlwouM8+mIz+ALpliUQ=", + "path": "k8s.io/client-go/1.5/pkg/util/clock", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Y6rWC0TUw2/uUeUjJ7kazyEUzBQ=", + "path": "k8s.io/client-go/1.5/pkg/util/errors", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "C7IfEAdCOePw3/IraaZCNXuYXLw=", + "path": "k8s.io/client-go/1.5/pkg/util/flowcontrol", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "EuslQHnhBSRXaWimYqLEqhMPV48=", + "path": "k8s.io/client-go/1.5/pkg/util/framer", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "ByO18NbZwiifFr8qtLyfJAHXguA=", + "path": "k8s.io/client-go/1.5/pkg/util/integer", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "ww+RfsoIlUBDwThg2oqC5QVz33Y=", + "path": "k8s.io/client-go/1.5/pkg/util/intstr", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "7E8f8dLlXW7u6r9sggMjvB4HEiw=", + "path": "k8s.io/client-go/1.5/pkg/util/json", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "d0pFZxMJG9j95acNmaIM1l+X+QU=", + "path": "k8s.io/client-go/1.5/pkg/util/labels", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "wCN7u1lE+25neM9jXeI7aE8EAfk=", + "path": "k8s.io/client-go/1.5/pkg/util/net", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "g+kBkxcb+tYmFtRRly+VE+JAIfw=", + "path": "k8s.io/client-go/1.5/pkg/util/parsers", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "S4wUnE5VkaWWrkLbgPL/1oNLJ4g=", + "path": "k8s.io/client-go/1.5/pkg/util/rand", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "8j9c2PqTKybtnymXbStNYRexRj8=", + "path": "k8s.io/client-go/1.5/pkg/util/runtime", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "aAz4e8hLGs0+ZAz1TdA5tY/9e1A=", + "path": "k8s.io/client-go/1.5/pkg/util/sets", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "P/fwh6QZ5tsjVyHTaASDWL3WaGs=", + "path": "k8s.io/client-go/1.5/pkg/util/uuid", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "P9Bq/1qbF4SvnN9HyCTRpbUz7sQ=", + "path": "k8s.io/client-go/1.5/pkg/util/validation", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "D0JIEjlP69cuPOZEdsSKeFgsnI8=", + "path": "k8s.io/client-go/1.5/pkg/util/validation/field", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "T7ba8t8i+BtgClMgL+aMZM94fcI=", + "path": "k8s.io/client-go/1.5/pkg/util/wait", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "6RCTv/KDiw7as4KeyrgU3XrUSQI=", + "path": "k8s.io/client-go/1.5/pkg/util/yaml", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "OwKlsSeKtz1FBVC9cQ5gWRL5pKc=", + "path": "k8s.io/client-go/1.5/pkg/version", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Oil9WGw/dODbpBopn6LWQGS3DYg=", + "path": "k8s.io/client-go/1.5/pkg/watch", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "r5alnRCbLaPsbTeJjjTVn/bt6uw=", + "path": "k8s.io/client-go/1.5/pkg/watch/versioned", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "X1+ltyfHui/XCwDupXIf39+9gWQ=", + "path": "k8s.io/client-go/1.5/plugin/pkg/client/auth", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "KYy+js37AS0ZT08g5uBr1ZoMPmE=", + "path": "k8s.io/client-go/1.5/plugin/pkg/client/auth/gcp", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "wQ9G5++lbQpejqCzGHo037N3YcY=", + "path": "k8s.io/client-go/1.5/plugin/pkg/client/auth/oidc", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "ABe8YfZVEDoRpAUqp2BKP8o1VIA=", + "path": "k8s.io/client-go/1.5/rest", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "Gbe0Vs9hkI7X5hhbXUuWdRFffSI=", + "path": "k8s.io/client-go/1.5/tools/cache", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "K/oOznXABjqSS1c2Fs407c5F8KA=", + "path": "k8s.io/client-go/1.5/tools/clientcmd/api", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "c1PQ4WJRfpA9BYcFHW2+46hu5IE=", + "path": "k8s.io/client-go/1.5/tools/metrics", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + }, + { + "checksumSHA1": "e4W2q+6wvjejv3V0UCI1mewTTro=", + "path": "k8s.io/client-go/1.5/transport", + "revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3", + "revisionTime": "2016-09-30T00:14:02Z" + } + ], + "rootPath": "github.com/prometheus/prometheus" +} diff --git a/src/cmd/go/internal/modconv/testdata/upspin.dep b/src/cmd/go/internal/modconv/testdata/upspin.dep new file mode 100644 index 0000000000..be77bcb928 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/upspin.dep @@ -0,0 +1,57 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "bazil.org/fuse" + packages = [".","fs","fuseutil"] + revision = "371fbbdaa8987b715bdd21d6adc4c9b20155f748" + +[[projects]] + branch = "master" + name = "github.com/NYTimes/gziphandler" + packages = ["."] + revision = "97ae7fbaf81620fe97840685304a78a306a39c64" + +[[projects]] + branch = "master" + name = "github.com/golang/protobuf" + packages = ["proto"] + revision = "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" + +[[projects]] + branch = "master" + name = "github.com/russross/blackfriday" + packages = ["."] + revision = "6d1ef893fcb01b4f50cb6e57ed7df3e2e627b6b2" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = ["acme","acme/autocert","hkdf"] + revision = "13931e22f9e72ea58bb73048bc752b48c6d4d4ac" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = ["context"] + revision = "4b14673ba32bee7f5ac0f990a48f033919fd418b" + +[[projects]] + branch = "master" + name = "golang.org/x/text" + packages = ["cases","internal","internal/gen","internal/tag","internal/triegen","internal/ucd","language","runes","secure/bidirule","secure/precis","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable","width"] + revision = "6eab0e8f74e86c598ec3b6fad4888e0c11482d48" + +[[projects]] + branch = "v2" + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "2246e647ba1c78b0b9f948f9fb072fff1467284fb138709c063e99736f646b90" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/src/cmd/go/internal/modconv/testdata/upspin.out b/src/cmd/go/internal/modconv/testdata/upspin.out new file mode 100644 index 0000000000..00597db848 --- /dev/null +++ b/src/cmd/go/internal/modconv/testdata/upspin.out @@ -0,0 +1,8 @@ +bazil.org/fuse 371fbbdaa8987b715bdd21d6adc4c9b20155f748 +github.com/NYTimes/gziphandler 97ae7fbaf81620fe97840685304a78a306a39c64 +github.com/golang/protobuf 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 +github.com/russross/blackfriday 6d1ef893fcb01b4f50cb6e57ed7df3e2e627b6b2 +golang.org/x/crypto 13931e22f9e72ea58bb73048bc752b48c6d4d4ac +golang.org/x/net 4b14673ba32bee7f5ac0f990a48f033919fd418b +golang.org/x/text 6eab0e8f74e86c598ec3b6fad4888e0c11482d48 +gopkg.in/yaml.v2 eb3733d160e74a9c7e442f435eb3bea458e1d19f diff --git a/src/cmd/go/internal/modconv/tsv.go b/src/cmd/go/internal/modconv/tsv.go new file mode 100644 index 0000000000..fd3364934f --- /dev/null +++ b/src/cmd/go/internal/modconv/tsv.go @@ -0,0 +1,23 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "strings" + + "cmd/go/internal/module" +) + +func ParseDependenciesTSV(file string, data []byte) ([]module.Version, error) { + var list []module.Version + for lineno, line := range strings.Split(string(data), "\n") { + lineno++ + f := strings.Split(line, "\t") + if len(f) >= 3 { + list = append(list, module.Version{Path: f[0], Version: f[2]}) + } + } + return list, nil +} diff --git a/src/cmd/go/internal/modconv/vconf.go b/src/cmd/go/internal/modconv/vconf.go new file mode 100644 index 0000000000..5d3cd3c917 --- /dev/null +++ b/src/cmd/go/internal/modconv/vconf.go @@ -0,0 +1,26 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "strings" + + "cmd/go/internal/module" +) + +func ParseVendorConf(file string, data []byte) ([]module.Version, error) { + var list []module.Version + for lineno, line := range strings.Split(string(data), "\n") { + lineno++ + if i := strings.Index(line, "#"); i >= 0 { + line = line[:i] + } + f := strings.Fields(line) + if len(f) >= 2 { + list = append(list, module.Version{Path: f[0], Version: f[1]}) + } + } + return list, nil +} diff --git a/src/cmd/go/internal/modconv/vjson.go b/src/cmd/go/internal/modconv/vjson.go new file mode 100644 index 0000000000..38b0a685ad --- /dev/null +++ b/src/cmd/go/internal/modconv/vjson.go @@ -0,0 +1,28 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "encoding/json" + + "cmd/go/internal/module" +) + +func ParseVendorJSON(file string, data []byte) ([]module.Version, error) { + var cfg struct { + Package []struct { + Path string + Revision string + } + } + if err := json.Unmarshal(data, &cfg); err != nil { + return nil, err + } + var list []module.Version + for _, d := range cfg.Package { + list = append(list, module.Version{Path: d.Path, Version: d.Revision}) + } + return list, nil +} diff --git a/src/cmd/go/internal/modconv/vmanifest.go b/src/cmd/go/internal/modconv/vmanifest.go new file mode 100644 index 0000000000..f2cf0f58f2 --- /dev/null +++ b/src/cmd/go/internal/modconv/vmanifest.go @@ -0,0 +1,28 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "encoding/json" + + "cmd/go/internal/module" +) + +func ParseVendorManifest(file string, data []byte) ([]module.Version, error) { + var cfg struct { + Dependencies []struct { + ImportPath string + Revision string + } + } + if err := json.Unmarshal(data, &cfg); err != nil { + return nil, err + } + var list []module.Version + for _, d := range cfg.Dependencies { + list = append(list, module.Version{Path: d.ImportPath, Version: d.Revision}) + } + return list, nil +} diff --git a/src/cmd/go/internal/modconv/vyml.go b/src/cmd/go/internal/modconv/vyml.go new file mode 100644 index 0000000000..e2ea9e3e9c --- /dev/null +++ b/src/cmd/go/internal/modconv/vyml.go @@ -0,0 +1,40 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modconv + +import ( + "cmd/go/internal/module" + "strings" +) + +func ParseVendorYML(file string, data []byte) ([]module.Version, error) { + var list []module.Version + vendors := false + path := "" + for lineno, line := range strings.Split(string(data), "\n") { + lineno++ + if line == "" { + continue + } + if strings.HasPrefix(line, "vendors:") { + vendors = true + } else if line[0] != '-' && line[0] != ' ' && line[0] != '\t' { + vendors = false + } + if !vendors { + continue + } + if strings.HasPrefix(line, "- path:") { + path = strings.TrimSpace(line[len("- path:"):]) + } + if strings.HasPrefix(line, " rev:") { + rev := strings.TrimSpace(line[len(" rev:"):]) + if path != "" && rev != "" { + list = append(list, module.Version{Path: path, Version: rev}) + } + } + } + return list, nil +} diff --git a/src/cmd/go/internal/modfetch/bitbucket/fetch.go b/src/cmd/go/internal/modfetch/bitbucket/fetch.go new file mode 100644 index 0000000000..c077a3eb74 --- /dev/null +++ b/src/cmd/go/internal/modfetch/bitbucket/fetch.go @@ -0,0 +1,22 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bitbucket + +import ( + "fmt" + "strings" + + "cmd/go/internal/modfetch/codehost" + "cmd/go/internal/modfetch/gitrepo" +) + +func Lookup(path string) (codehost.Repo, error) { + f := strings.Split(path, "/") + if len(f) < 3 || f[0] != "bitbucket.org" { + return nil, fmt.Errorf("bitbucket repo must be bitbucket.org/org/project") + } + path = f[0] + "/" + f[1] + "/" + f[2] + return gitrepo.Repo("https://"+path, path) +} diff --git a/src/cmd/go/internal/modfetch/codehost/codehost.go b/src/cmd/go/internal/modfetch/codehost/codehost.go new file mode 100644 index 0000000000..0e3bb7d7c3 --- /dev/null +++ b/src/cmd/go/internal/modfetch/codehost/codehost.go @@ -0,0 +1,187 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package codehost defines the interface implemented by a code hosting source, +// along with support code for use by implementations. +package codehost + +import ( + "bytes" + "crypto/sha256" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "cmd/go/internal/cfg" + "cmd/go/internal/str" +) + +// Downloaded size limits. +const ( + MaxGoMod = 16 << 20 // maximum size of go.mod file + MaxLICENSE = 16 << 20 // maximum size of LICENSE file + MaxZipFile = 500 << 20 // maximum size of downloaded zip file +) + +// A Repo represents a code hosting source. +// Typical implementations include local version control repositories, +// remote version control servers, and code hosting sites. +type Repo interface { + // Root returns the import path of the root directory of the repository. + Root() string + + // List lists all tags with the given prefix. + Tags(prefix string) (tags []string, err error) + + // Stat returns information about the revision rev. + // A revision can be any identifier known to the underlying service: + // commit hash, branch, tag, and so on. + Stat(rev string) (*RevInfo, error) + + // Latest returns the latest revision on the default branch, + // whatever that means in the underlying implementation. + Latest() (*RevInfo, error) + + // ReadFile reads the given file in the file tree corresponding to revision rev. + // It should refuse to read more than maxSize bytes. + ReadFile(rev, file string, maxSize int64) (data []byte, err error) + + // ReadZip downloads a zip file for the subdir subdirectory + // of the given revision to a new file in a given temporary directory. + // It should refuse to read more than maxSize bytes. + // It returns a ReadCloser for a streamed copy of the zip file, + // along with the actual subdirectory (possibly shorter than subdir) + // contained in the zip file. All files in the zip file are expected to be + // nested in a single top-level directory, whose name is not specified. + ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, actualSubdir string, err error) +} + +// A Rev describes a single revision in a source code repository. +type RevInfo struct { + Name string // complete ID in underlying repository + Short string // shortened ID, for use in pseudo-version + Version string // TODO what is this? + Time time.Time // commit time +} + +// AllHex reports whether the revision rev is entirely lower-case hexadecimal digits. +func AllHex(rev string) bool { + for i := 0; i < len(rev); i++ { + c := rev[i] + if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' { + continue + } + return false + } + return true +} + +// ShortenSHA1 shortens a SHA1 hash (40 hex digits) to the canonical length +// used in pseudo-versions (12 hex digits). +func ShortenSHA1(rev string) string { + if AllHex(rev) && len(rev) == 40 { + return rev[:12] + } + return rev +} + +// WorkRoot is the root of the cached work directory. +// It is set by cmd/go/internal/vgo.InitMod. +var WorkRoot string + +// WorkDir returns the name of the cached work directory to use for the +// given repository type and name. +func WorkDir(typ, name string) (string, error) { + if WorkRoot == "" { + return "", fmt.Errorf("codehost.WorkRoot not set") + } + + // We name the work directory for the SHA256 hash of the type and name. + // We intentionally avoid the actual name both because of possible + // conflicts with valid file system paths and because we want to ensure + // that one checkout is never nested inside another. That nesting has + // led to security problems in the past. + if strings.Contains(typ, ":") { + return "", fmt.Errorf("codehost.WorkDir: type cannot contain colon") + } + key := typ + ":" + name + dir := filepath.Join(WorkRoot, fmt.Sprintf("%x", sha256.Sum256([]byte(key)))) + data, err := ioutil.ReadFile(dir + ".info") + if err == nil { + have := strings.TrimSuffix(string(data), "\n") + if have != key { + return "", fmt.Errorf("%s exists with wrong content (have %q want %q)", dir+".info", have, key) + } + _, err := os.Stat(dir) + if err != nil { + return "", fmt.Errorf("%s exists but %s does not", dir+".info", dir) + } + if cfg.BuildX { + fmt.Fprintf(os.Stderr, "# %s for %s %s\n", dir, typ, name) + } + return dir, nil + } + + if cfg.BuildX { + fmt.Fprintf(os.Stderr, "mkdir -p %s # %s %s\n", dir, typ, name) + } + os.RemoveAll(dir) + if err := os.MkdirAll(dir, 0777); err != nil { + return "", err + } + if err := ioutil.WriteFile(dir+".info", []byte(key), 0666); err != nil { + os.RemoveAll(dir) + return "", err + } + return dir, nil +} + +type RunError struct { + Cmd string + Err error + Stderr []byte +} + +func (e *RunError) Error() string { + text := e.Cmd + ": " + e.Err.Error() + stderr := bytes.TrimRight(e.Stderr, "\n") + if len(stderr) > 0 { + text += ":\n\t" + strings.Replace(string(stderr), "\n", "\n\t", -1) + } + return text +} + +// Run runs the command line in the given directory +// (an empty dir means the current directory). +// It returns the standard output and, for a non-zero exit, +// a *RunError indicating the command, exit status, and standard error. +// Standard error is unavailable for commands that exit successfully. +func Run(dir string, cmdline ...interface{}) ([]byte, error) { + cmd := str.StringList(cmdline...) + if cfg.BuildX { + var cd string + if dir != "" { + cd = "cd " + dir + "; " + } + fmt.Fprintf(os.Stderr, "%s%s\n", cd, strings.Join(cmd, " ")) + } + // TODO: Impose limits on command output size. + // TODO: Set environment to get English error messages. + var stderr bytes.Buffer + var stdout bytes.Buffer + c := exec.Command(cmd[0], cmd[1:]...) + c.Dir = dir + c.Stderr = &stderr + c.Stdout = &stdout + err := c.Run() + if err != nil { + err = &RunError{Cmd: strings.Join(cmd, " ") + " in " + dir, Stderr: stderr.Bytes(), Err: err} + } + return stdout.Bytes(), err +} diff --git a/src/cmd/go/internal/modfetch/coderepo.go b/src/cmd/go/internal/modfetch/coderepo.go new file mode 100644 index 0000000000..bb6e8ac179 --- /dev/null +++ b/src/cmd/go/internal/modfetch/coderepo.go @@ -0,0 +1,576 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfetch + +import ( + "archive/zip" + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + + "cmd/go/internal/modconv" + "cmd/go/internal/modfetch/codehost" + "cmd/go/internal/modfile" + "cmd/go/internal/module" + "cmd/go/internal/semver" +) + +// A codeRepo implements modfetch.Repo using an underlying codehost.Repo. +type codeRepo struct { + modPath string + code codehost.Repo + codeRoot string + codeDir string + + path string + pathPrefix string + pathMajor string + pseudoMajor string +} + +func newCodeRepo(code codehost.Repo, path string) (Repo, error) { + codeRoot := code.Root() + if !hasPathPrefix(path, codeRoot) { + return nil, fmt.Errorf("mismatched repo: found %s for %s", codeRoot, path) + } + pathPrefix, pathMajor, ok := module.SplitPathVersion(path) + if !ok { + return nil, fmt.Errorf("invalid module path %q", path) + } + pseudoMajor := "v0" + if pathMajor != "" { + pseudoMajor = pathMajor[1:] + } + + // At this point we might have: + // codeRoot = github.com/rsc/foo + // path = github.com/rsc/foo/bar/v2 + // pathPrefix = github.com/rsc/foo/bar + // pathMajor = /v2 + // pseudoMajor = v2 + // + // Compute codeDir = bar, the subdirectory within the repo + // corresponding to the module root. + codeDir := strings.Trim(strings.TrimPrefix(pathPrefix, codeRoot), "/") + if strings.HasPrefix(path, "gopkg.in/") { + // But gopkg.in is a special legacy case, in which pathPrefix does not start with codeRoot. + // For example we might have: + // codeRoot = gopkg.in/yaml.v2 + // pathPrefix = gopkg.in/yaml + // pathMajor = .v2 + // pseudoMajor = v2 + // codeDir = pathPrefix (because codeRoot is not a prefix of pathPrefix) + // Clear codeDir - the module root is the repo root for gopkg.in repos. + codeDir = "" + } + + r := &codeRepo{ + modPath: path, + code: code, + codeRoot: codeRoot, + codeDir: codeDir, + pathPrefix: pathPrefix, + pathMajor: pathMajor, + pseudoMajor: pseudoMajor, + } + + return r, nil +} + +func (r *codeRepo) ModulePath() string { + return r.modPath +} + +func (r *codeRepo) Versions(prefix string) ([]string, error) { + p := prefix + if r.codeDir != "" { + p = r.codeDir + "/" + p + } + tags, err := r.code.Tags(p) + if err != nil { + return nil, err + } + list := []string{} + for _, tag := range tags { + if !strings.HasPrefix(tag, p) { + continue + } + v := tag + if r.codeDir != "" { + v = v[len(r.codeDir)+1:] + } + if !semver.IsValid(v) || v != semver.Canonical(v) || isPseudoVersion(v) || !module.MatchPathMajor(v, r.pathMajor) { + continue + } + list = append(list, v) + } + SortVersions(list) + return list, nil +} + +func (r *codeRepo) Stat(rev string) (*RevInfo, error) { + if rev == "latest" { + return r.Latest() + } + codeRev := r.revToRev(rev) + if semver.IsValid(codeRev) && r.codeDir != "" { + codeRev = r.codeDir + "/" + codeRev + } + info, err := r.code.Stat(codeRev) + if err != nil { + return nil, err + } + return r.convert(info) +} + +func (r *codeRepo) Latest() (*RevInfo, error) { + info, err := r.code.Latest() + if err != nil { + return nil, err + } + return r.convert(info) +} + +func (r *codeRepo) convert(info *codehost.RevInfo) (*RevInfo, error) { + versionOK := func(v string) bool { + return semver.IsValid(v) && v == semver.Canonical(v) && !isPseudoVersion(v) && module.MatchPathMajor(v, r.pathMajor) + } + v := info.Version + if r.codeDir == "" { + if !versionOK(v) { + v = PseudoVersion(r.pseudoMajor, info.Time, info.Short) + } + } else { + p := r.codeDir + "/" + if strings.HasPrefix(v, p) && versionOK(v[len(p):]) { + v = v[len(p):] + } else { + v = PseudoVersion(r.pseudoMajor, info.Time, info.Short) + } + } + + info2 := &RevInfo{ + Name: info.Name, + Short: info.Short, + Time: info.Time, + Version: v, + } + return info2, nil +} + +func (r *codeRepo) revToRev(rev string) string { + if semver.IsValid(rev) { + if isPseudoVersion(rev) { + i := strings.Index(rev, "-") + j := strings.Index(rev[i+1:], "-") + return rev[i+1+j+1:] + } + if r.codeDir == "" { + return rev + } + return r.codeDir + "/" + rev + } + return rev +} + +func (r *codeRepo) versionToRev(version string) (rev string, err error) { + if !semver.IsValid(version) { + return "", fmt.Errorf("malformed semantic version %q", version) + } + return r.revToRev(version), nil +} + +func (r *codeRepo) findDir(version string) (rev, dir string, gomod []byte, err error) { + rev, err = r.versionToRev(version) + if err != nil { + return "", "", nil, err + } + if r.pathMajor == "" || strings.HasPrefix(r.pathMajor, ".") { + if r.codeDir == "" { + return rev, "", nil, nil + } + file1 := path.Join(r.codeDir, "go.mod") + gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod) + if err1 != nil { + return "", "", nil, fmt.Errorf("missing go.mod") + } + return rev, r.codeDir, gomod1, nil + } + + // Suppose pathMajor is "/v2". + // Either go.mod should claim v2 and v2/go.mod should not exist, + // or v2/go.mod should exist and claim v2. Not both. + // Note that we don't check the full path, just the major suffix, + // because of replacement modules. This might be a fork of + // the real module, found at a different path, usable only in + // a replace directive. + file1 := path.Join(r.codeDir, "go.mod") + file2 := path.Join(r.codeDir, r.pathMajor[1:], "go.mod") + gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod) + gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod) + found1 := err1 == nil && isMajor(gomod1, r.pathMajor) + found2 := err2 == nil && isMajor(gomod2, r.pathMajor) + + if err2 == nil && !found2 { + return "", "", nil, fmt.Errorf("%s has non-...%s module path", file2, r.pathMajor) + } + if found1 && found2 { + return "", "", nil, fmt.Errorf("both %s and %s claim ...%s module", file1, file2, r.pathMajor) + } + if found2 { + return rev, filepath.Join(r.codeDir, r.pathMajor), gomod2, nil + } + if found1 { + return rev, r.codeDir, gomod1, nil + } + return "", "", nil, fmt.Errorf("missing or invalid go.mod") +} + +func isMajor(gomod []byte, pathMajor string) bool { + return strings.HasSuffix(modPath(gomod), pathMajor) +} + +var moduleStr = []byte("module") + +func modPath(mod []byte) string { + for len(mod) > 0 { + line := mod + mod = nil + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, mod = line[:i], line[i+1:] + } + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, moduleStr) { + continue + } + line = line[len(moduleStr):] + n := len(line) + line = bytes.TrimSpace(line) + if len(line) == n || len(line) == 0 { + continue + } + + if line[0] == '"' || line[0] == '`' { + p, err := strconv.Unquote(string(line)) + if err != nil { + return "" // malformed quoted string or multiline module path + } + return p + } + + return string(line) + } + return "" // missing module path +} + +func (r *codeRepo) GoMod(version string) (data []byte, err error) { + rev, dir, gomod, err := r.findDir(version) + if err != nil { + return nil, err + } + if gomod != nil { + return gomod, nil + } + data, err = r.code.ReadFile(rev, path.Join(dir, "go.mod"), codehost.MaxGoMod) + if err != nil { + if e := strings.ToLower(err.Error()); strings.Contains(e, "not found") || strings.Contains(e, "404") { // TODO + return r.legacyGoMod(rev, dir), nil + } + return nil, err + } + return data, nil +} + +var altConfigs = []string{ + "Gopkg.lock", + + "GLOCKFILE", + "Godeps/Godeps.json", + "dependencies.tsv", + "glide.lock", + "vendor.conf", + "vendor.yml", + "vendor/manifest", + "vendor/vendor.json", +} + +func (r *codeRepo) legacyGoMod(rev, dir string) []byte { + mf := new(modfile.File) + mf.AddModuleStmt(r.modPath) + for _, file := range altConfigs { + data, err := r.code.ReadFile(rev, path.Join(dir, file), codehost.MaxGoMod) + if err != nil { + continue + } + convert := modconv.Converters[file] + if convert == nil { + continue + } + if err := ConvertLegacyConfig(mf, file, data); err != nil { + continue + } + break + } + data, err := mf.Format() + if err != nil { + return []byte(fmt.Sprintf("%s\nmodule %q\n", modconv.Prefix, r.modPath)) + } + return append([]byte(modconv.Prefix+"\n"), data...) +} + +func (r *codeRepo) modPrefix(rev string) string { + return r.modPath + "@" + rev +} + +func (r *codeRepo) Zip(version string, tmpdir string) (tmpfile string, err error) { + rev, dir, _, err := r.findDir(version) + if err != nil { + return "", err + } + dl, actualDir, err := r.code.ReadZip(rev, dir, codehost.MaxZipFile) + if err != nil { + return "", err + } + if actualDir != "" && !hasPathPrefix(dir, actualDir) { + return "", fmt.Errorf("internal error: downloading %v %v: dir=%q but actualDir=%q", r.path, rev, dir, actualDir) + } + subdir := strings.Trim(strings.TrimPrefix(dir, actualDir), "/") + + // Spool to local file. + f, err := ioutil.TempFile(tmpdir, "vgo-codehost-") + if err != nil { + dl.Close() + return "", err + } + defer os.Remove(f.Name()) + defer f.Close() + maxSize := int64(codehost.MaxZipFile) + lr := &io.LimitedReader{R: dl, N: maxSize + 1} + if _, err := io.Copy(f, lr); err != nil { + dl.Close() + return "", err + } + dl.Close() + if lr.N <= 0 { + return "", fmt.Errorf("downloaded zip file too large") + } + size := (maxSize + 1) - lr.N + if _, err := f.Seek(0, 0); err != nil { + return "", err + } + + // Translate from zip file we have to zip file we want. + zr, err := zip.NewReader(f, size) + if err != nil { + return "", err + } + f2, err := ioutil.TempFile(tmpdir, "vgo-") + if err != nil { + return "", err + } + + zw := zip.NewWriter(f2) + newName := f2.Name() + defer func() { + f2.Close() + if err != nil { + os.Remove(newName) + } + }() + if subdir != "" { + subdir += "/" + } + haveLICENSE := false + topPrefix := "" + haveGoMod := make(map[string]bool) + for _, zf := range zr.File { + if topPrefix == "" { + i := strings.Index(zf.Name, "/") + if i < 0 { + return "", fmt.Errorf("missing top-level directory prefix") + } + topPrefix = zf.Name[:i+1] + } + if !strings.HasPrefix(zf.Name, topPrefix) { + return "", fmt.Errorf("zip file contains more than one top-level directory") + } + dir, file := path.Split(zf.Name) + if file == "go.mod" { + haveGoMod[dir] = true + } + } + root := topPrefix + subdir + inSubmodule := func(name string) bool { + for { + dir, _ := path.Split(name) + if len(dir) <= len(root) { + return false + } + if haveGoMod[dir] { + return true + } + name = dir[:len(dir)-1] + } + } + for _, zf := range zr.File { + if topPrefix == "" { + i := strings.Index(zf.Name, "/") + if i < 0 { + return "", fmt.Errorf("missing top-level directory prefix") + } + topPrefix = zf.Name[:i+1] + } + if strings.HasSuffix(zf.Name, "/") { // drop directory dummy entries + continue + } + if !strings.HasPrefix(zf.Name, topPrefix) { + return "", fmt.Errorf("zip file contains more than one top-level directory") + } + name := strings.TrimPrefix(zf.Name, topPrefix) + if !strings.HasPrefix(name, subdir) { + continue + } + name = strings.TrimPrefix(name, subdir) + if isVendoredPackage(name) { + continue + } + if inSubmodule(zf.Name) { + continue + } + base := path.Base(name) + if strings.ToLower(base) == "go.mod" && base != "go.mod" { + return "", fmt.Errorf("zip file contains %s, want all lower-case go.mod", zf.Name) + } + if name == "LICENSE" { + haveLICENSE = true + } + size := int64(zf.UncompressedSize) + if size < 0 || maxSize < size { + return "", fmt.Errorf("module source tree too big") + } + maxSize -= size + + rc, err := zf.Open() + if err != nil { + return "", err + } + w, err := zw.Create(r.modPrefix(version) + "/" + name) + lr := &io.LimitedReader{R: rc, N: size + 1} + if _, err := io.Copy(w, lr); err != nil { + return "", err + } + if lr.N <= 0 { + return "", fmt.Errorf("individual file too large") + } + } + + if !haveLICENSE && subdir != "" { + if data, err := r.code.ReadFile(rev, "LICENSE", codehost.MaxLICENSE); err == nil { + w, err := zw.Create(r.modPrefix(version) + "/LICENSE") + if err != nil { + return "", err + } + if _, err := w.Write(data); err != nil { + return "", err + } + } + } + if err := zw.Close(); err != nil { + return "", err + } + if err := f2.Close(); err != nil { + return "", err + } + + return f2.Name(), nil +} + +// hasPathPrefix reports whether the path s begins with the +// elements in prefix. +func hasPathPrefix(s, prefix string) bool { + switch { + default: + return false + case len(s) == len(prefix): + return s == prefix + case len(s) > len(prefix): + if prefix != "" && prefix[len(prefix)-1] == '/' { + return strings.HasPrefix(s, prefix) + } + return s[len(prefix)] == '/' && s[:len(prefix)] == prefix + } +} + +func isVendoredPackage(name string) bool { + var i int + if strings.HasPrefix(name, "vendor/") { + i += len("vendor/") + } else if j := strings.Index(name, "/vendor/"); j >= 0 { + i += len("/vendor/") + } else { + return false + } + return strings.Contains(name[i:], "/") +} + +func PseudoVersion(major string, t time.Time, rev string) string { + if major == "" { + major = "v0" + } + return fmt.Sprintf("%s.0.0-%s-%s", major, t.UTC().Format("20060102150405"), rev) +} + +var ErrNotPseudoVersion = errors.New("not a pseudo-version") + +/* +func ParsePseudoVersion(repo Repo, version string) (rev string, err error) { + major := semver.Major(version) + if major == "" { + return "", ErrNotPseudoVersion + } + majorPrefix := major + ".0.0-" + if !strings.HasPrefix(version, majorPrefix) || !strings.Contains(version[len(majorPrefix):], "-") { + return "", ErrNotPseudoVersion + } + versionSuffix := version[len(majorPrefix):] + for i := 0; versionSuffix[i] != '-'; i++ { + c := versionSuffix[i] + if c < '0' || '9' < c { + return "", ErrNotPseudoVersion + } + } + rev = versionSuffix[strings.Index(versionSuffix, "-")+1:] + if rev == "" { + return "", ErrNotPseudoVersion + } + if proxyURL != "" { + return version, nil + } + fullRev, t, err := repo.CommitInfo(rev) + if err != nil { + return "", fmt.Errorf("unknown pseudo-version %s: loading %v: %v", version, rev, err) + } + v := PseudoVersion(major, t, repo.ShortRev(fullRev)) + if v != version { + return "", fmt.Errorf("unknown pseudo-version %s: %v is %v", version, rev, v) + } + return fullRev, nil +} +*/ + +var pseudoVersionRE = regexp.MustCompile(`^v[0-9]+\.0\.0-[0-9]{14}-[A-Za-z0-9]+$`) + +func isPseudoVersion(v string) bool { + return pseudoVersionRE.MatchString(v) +} diff --git a/src/cmd/go/internal/modfetch/coderepo_test.go b/src/cmd/go/internal/modfetch/coderepo_test.go new file mode 100644 index 0000000000..e3b106e195 --- /dev/null +++ b/src/cmd/go/internal/modfetch/coderepo_test.go @@ -0,0 +1,688 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfetch + +import ( + "archive/zip" + "internal/testenv" + "io" + "io/ioutil" + "log" + "os" + "reflect" + "strings" + "testing" + "time" + + "cmd/go/internal/modfetch/codehost" +) + +func init() { + isTest = true +} + +func TestMain(m *testing.M) { + os.Exit(testMain(m)) +} + +func testMain(m *testing.M) int { + dir, err := ioutil.TempDir("", "gitrepo-test-") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(dir) + + codehost.WorkRoot = dir + return m.Run() +} + +var codeRepoTests = []struct { + path string + lookerr string + mpath string + rev string + err string + version string + name string + short string + time time.Time + gomod string + gomoderr string + zip []string + ziperr string +}{ + { + path: "github.com/rsc/vgotest1", + rev: "v0.0.0", + version: "v0.0.0", + name: "80d85c5d4d17598a0e9055e7c175a32b415d6128", + short: "80d85c5d4d17", + time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC), + zip: []string{ + "LICENSE", + "README.md", + "pkg/p.go", + }, + }, + { + path: "github.com/rsc/vgotest1", + rev: "v1.0.0", + version: "v1.0.0", + name: "80d85c5d4d17598a0e9055e7c175a32b415d6128", + short: "80d85c5d4d17", + time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC), + zip: []string{ + "LICENSE", + "README.md", + "pkg/p.go", + }, + }, + { + path: "github.com/rsc/vgotest1/v2", + rev: "v2.0.0", + version: "v2.0.0", + name: "80d85c5d4d17598a0e9055e7c175a32b415d6128", + short: "80d85c5d4d17", + time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC), + ziperr: "missing go.mod", + }, + { + path: "github.com/rsc/vgotest1", + rev: "80d85", + version: "v0.0.0-20180219231006-80d85c5d4d17", + name: "80d85c5d4d17598a0e9055e7c175a32b415d6128", + short: "80d85c5d4d17", + time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC), + zip: []string{ + "LICENSE", + "README.md", + "pkg/p.go", + }, + }, + { + path: "github.com/rsc/vgotest1", + rev: "mytag", + version: "v0.0.0-20180219231006-80d85c5d4d17", + name: "80d85c5d4d17598a0e9055e7c175a32b415d6128", + short: "80d85c5d4d17", + time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC), + zip: []string{ + "LICENSE", + "README.md", + "pkg/p.go", + }, + }, + { + path: "github.com/rsc/vgotest1/v2", + rev: "80d85", + version: "v2.0.0-20180219231006-80d85c5d4d17", + name: "80d85c5d4d17598a0e9055e7c175a32b415d6128", + short: "80d85c5d4d17", + time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC), + gomoderr: "missing go.mod", + ziperr: "missing go.mod", + }, + { + path: "github.com/rsc/vgotest1/v54321", + rev: "80d85", + version: "v54321.0.0-20180219231006-80d85c5d4d17", + name: "80d85c5d4d17598a0e9055e7c175a32b415d6128", + short: "80d85c5d4d17", + time: time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC), + ziperr: "missing go.mod", + }, + { + path: "github.com/rsc/vgotest1/submod", + rev: "v1.0.0", + err: "unknown revision \"submod/v1.0.0\"", + }, + { + path: "github.com/rsc/vgotest1/submod", + rev: "v1.0.3", + err: "unknown revision \"submod/v1.0.3\"", + }, + { + path: "github.com/rsc/vgotest1/submod", + rev: "v1.0.4", + version: "v1.0.4", + name: "8afe2b2efed96e0880ecd2a69b98a53b8c2738b6", + short: "8afe2b2efed9", + time: time.Date(2018, 2, 19, 23, 12, 7, 0, time.UTC), + gomod: "module \"github.com/vgotest1/submod\" // submod/go.mod\n", + zip: []string{ + "go.mod", + "pkg/p.go", + "LICENSE", + }, + }, + { + path: "github.com/rsc/vgotest1", + rev: "v1.1.0", + version: "v1.1.0", + name: "b769f2de407a4db81af9c5de0a06016d60d2ea09", + short: "b769f2de407a", + time: time.Date(2018, 2, 19, 23, 13, 36, 0, time.UTC), + gomod: "module \"github.com/rsc/vgotest1\" // root go.mod\nrequire \"github.com/rsc/vgotest1/submod\" v1.0.5\n", + zip: []string{ + "LICENSE", + "README.md", + "go.mod", + "pkg/p.go", + }, + }, + { + path: "github.com/rsc/vgotest1/v2", + rev: "v2.0.1", + version: "v2.0.1", + name: "ea65f87c8f52c15ea68f3bdd9925ef17e20d91e9", + short: "ea65f87c8f52", + time: time.Date(2018, 2, 19, 23, 14, 23, 0, time.UTC), + gomod: "module \"github.com/rsc/vgotest1/v2\" // root go.mod\n", + }, + { + path: "github.com/rsc/vgotest1/v2", + rev: "v2.0.3", + version: "v2.0.3", + name: "f18795870fb14388a21ef3ebc1d75911c8694f31", + short: "f18795870fb1", + time: time.Date(2018, 2, 19, 23, 16, 4, 0, time.UTC), + gomoderr: "v2/go.mod has non-.../v2 module path", + }, + { + path: "github.com/rsc/vgotest1/v2", + rev: "v2.0.4", + version: "v2.0.4", + name: "1f863feb76bc7029b78b21c5375644838962f88d", + short: "1f863feb76bc", + time: time.Date(2018, 2, 20, 0, 3, 38, 0, time.UTC), + gomoderr: "both go.mod and v2/go.mod claim .../v2 module", + }, + { + path: "github.com/rsc/vgotest1/v2", + rev: "v2.0.5", + version: "v2.0.5", + name: "2f615117ce481c8efef46e0cc0b4b4dccfac8fea", + short: "2f615117ce48", + time: time.Date(2018, 2, 20, 0, 3, 59, 0, time.UTC), + gomod: "module \"github.com/rsc/vgotest1/v2\" // v2/go.mod\n", + }, + { + path: "go.googlesource.com/scratch", + rev: "0f302529858", + version: "v0.0.0-20180220024720-0f3025298580", + name: "0f30252985809011f026b5a2d5cf456e021623da", + short: "0f3025298580", + time: time.Date(2018, 2, 20, 2, 47, 20, 0, time.UTC), + gomod: "//vgo 0.0.4\n\nmodule go.googlesource.com/scratch\n", + }, + { + path: "go.googlesource.com/scratch/rsc", + rev: "0f302529858", + version: "v0.0.0-20180220024720-0f3025298580", + name: "0f30252985809011f026b5a2d5cf456e021623da", + short: "0f3025298580", + time: time.Date(2018, 2, 20, 2, 47, 20, 0, time.UTC), + gomod: "", + }, + { + path: "go.googlesource.com/scratch/cbro", + rev: "0f302529858", + version: "v0.0.0-20180220024720-0f3025298580", + name: "0f30252985809011f026b5a2d5cf456e021623da", + short: "0f3025298580", + time: time.Date(2018, 2, 20, 2, 47, 20, 0, time.UTC), + gomoderr: "missing go.mod", + }, + { + // redirect to github + path: "rsc.io/quote", + rev: "v1.0.0", + version: "v1.0.0", + name: "f488df80bcdbd3e5bafdc24ad7d1e79e83edd7e6", + short: "f488df80bcdb", + time: time.Date(2018, 2, 14, 0, 45, 20, 0, time.UTC), + gomod: "module \"rsc.io/quote\"\n", + }, + { + // redirect to static hosting proxy + path: "swtch.com/testmod", + rev: "v1.0.0", + version: "v1.0.0", + name: "v1.0.0", + short: "v1.0.0", + time: time.Date(1972, 7, 18, 12, 34, 56, 0, time.UTC), + gomod: "module \"swtch.com/testmod\"\n", + }, + { + // redirect to googlesource + path: "golang.org/x/text", + rev: "4e4a3210bb", + version: "v0.0.0-20180208041248-4e4a3210bb54", + name: "4e4a3210bb54bb31f6ab2cdca2edcc0b50c420c1", + short: "4e4a3210bb54", + time: time.Date(2018, 2, 8, 4, 12, 48, 0, time.UTC), + }, + { + path: "github.com/pkg/errors", + rev: "v0.8.0", + version: "v0.8.0", + name: "645ef00459ed84a119197bfb8d8205042c6df63d", + short: "645ef00459ed", + time: time.Date(2016, 9, 29, 1, 48, 1, 0, time.UTC), + }, + { + // package in subdirectory - custom domain + path: "golang.org/x/net/context", + lookerr: "module root is \"golang.org/x/net\"", + }, + { + // package in subdirectory - github + path: "github.com/rsc/quote/buggy", + rev: "c4d4236f", + version: "v0.0.0-20180214154420-c4d4236f9242", + name: "c4d4236f92427c64bfbcf1cc3f8142ab18f30b22", + short: "c4d4236f9242", + time: time.Date(2018, 2, 14, 15, 44, 20, 0, time.UTC), + gomoderr: "missing go.mod", + }, + { + path: "gopkg.in/yaml.v2", + rev: "d670f940", + version: "v2.0.0-20180109114331-d670f9405373", + name: "d670f9405373e636a5a2765eea47fac0c9bc91a4", + short: "d670f9405373", + time: time.Date(2018, 1, 9, 11, 43, 31, 0, time.UTC), + gomod: "//vgo 0.0.4\n\nmodule gopkg.in/yaml.v2\n", + }, + { + path: "gopkg.in/check.v1", + rev: "20d25e280405", + version: "v1.0.0-20161208181325-20d25e280405", + name: "20d25e2804050c1cd24a7eea1e7a6447dd0e74ec", + short: "20d25e280405", + time: time.Date(2016, 12, 8, 18, 13, 25, 0, time.UTC), + gomod: "//vgo 0.0.4\n\nmodule gopkg.in/check.v1\n", + }, + { + path: "gopkg.in/yaml.v2", + rev: "v2", + version: "v2.0.0-20180328195020-5420a8b6744d", + name: "5420a8b6744d3b0345ab293f6fcba19c978f1183", + short: "5420a8b6744d", + time: time.Date(2018, 3, 28, 19, 50, 20, 0, time.UTC), + gomod: "module \"gopkg.in/yaml.v2\"\n\nrequire (\n\t\"gopkg.in/check.v1\" v0.0.0-20161208181325-20d25e280405\n)\n", + }, + { + path: "vcs-test.golang.org/go/mod/gitrepo1", + rev: "master", + version: "v0.0.0-20180417194322-ede458df7cd0", + name: "ede458df7cd0fdca520df19a33158086a8a68e81", + short: "ede458df7cd0", + time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), + gomod: "//vgo 0.0.4\n\nmodule vcs-test.golang.org/go/mod/gitrepo1\n", + }, + { + path: "gopkg.in/natefinch/lumberjack.v2", + rev: "latest", + version: "v2.0.0-20170531160350-a96e63847dc3", + name: "a96e63847dc3c67d17befa69c303767e2f84e54f", + short: "a96e63847dc3", + time: time.Date(2017, 5, 31, 16, 3, 50, 0, time.UTC), + gomod: "//vgo 0.0.4\n\nmodule gopkg.in/natefinch/lumberjack.v2\n", + }, + { + path: "gopkg.in/natefinch/lumberjack.v2", + // This repo has a v2.1 tag. + // We only allow semver references to tags that are fully qualified, as in v2.1.0. + // Because we can't record v2.1.0 (the actual tag is v2.1), we record a pseudo-version + // instead, same as if the tag were any other non-version-looking string. + // We use a v2 pseudo-version here because of the .v2 in the path, not because + // of the v2 in the rev. + rev: "v2.1", // non-canonical semantic version turns into pseudo-version + version: "v2.0.0-20170531160350-a96e63847dc3", + name: "a96e63847dc3c67d17befa69c303767e2f84e54f", + short: "a96e63847dc3", + time: time.Date(2017, 5, 31, 16, 3, 50, 0, time.UTC), + gomod: "//vgo 0.0.4\n\nmodule gopkg.in/natefinch/lumberjack.v2\n", + }, +} + +func TestCodeRepo(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + + tmpdir, err := ioutil.TempDir("", "vgo-modfetch-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + for _, tt := range codeRepoTests { + t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.rev, func(t *testing.T) { + repo, err := Lookup(tt.path) + if err != nil { + if tt.lookerr != "" { + if err.Error() == tt.lookerr { + return + } + t.Errorf("Lookup(%q): %v, want error %q", tt.path, err, tt.lookerr) + } + t.Fatalf("Lookup(%q): %v", tt.path, err) + } + if tt.mpath == "" { + tt.mpath = tt.path + } + if mpath := repo.ModulePath(); mpath != tt.mpath { + t.Errorf("repo.ModulePath() = %q, want %q", mpath, tt.mpath) + } + info, err := repo.Stat(tt.rev) + if err != nil { + if tt.err != "" { + if !strings.Contains(err.Error(), tt.err) { + t.Fatalf("repoStat(%q): %v, wanted %q", tt.rev, err, tt.err) + } + return + } + t.Fatalf("repo.Stat(%q): %v", tt.rev, err) + } + if tt.err != "" { + t.Errorf("repo.Stat(%q): success, wanted error", tt.rev) + } + if info.Version != tt.version { + t.Errorf("info.Version = %q, want %q", info.Version, tt.version) + } + if info.Name != tt.name { + t.Errorf("info.Name = %q, want %q", info.Name, tt.name) + } + if info.Short != tt.short { + t.Errorf("info.Short = %q, want %q", info.Short, tt.short) + } + if !info.Time.Equal(tt.time) { + t.Errorf("info.Time = %v, want %v", info.Time, tt.time) + } + if tt.gomod != "" || tt.gomoderr != "" { + data, err := repo.GoMod(tt.version) + if err != nil && tt.gomoderr == "" { + t.Errorf("repo.GoMod(%q): %v", tt.version, err) + } else if err != nil && tt.gomoderr != "" { + if err.Error() != tt.gomoderr { + t.Errorf("repo.GoMod(%q): %v, want %q", tt.version, err, tt.gomoderr) + } + } else if tt.gomoderr != "" { + t.Errorf("repo.GoMod(%q) = %q, want error %q", tt.version, data, tt.gomoderr) + } else if string(data) != tt.gomod { + t.Errorf("repo.GoMod(%q) = %q, want %q", tt.version, data, tt.gomod) + } + } + if tt.zip != nil || tt.ziperr != "" { + zipfile, err := repo.Zip(tt.version, tmpdir) + if err != nil { + if tt.ziperr != "" { + if err.Error() == tt.ziperr { + return + } + t.Fatalf("repo.Zip(%q): %v, want error %q", tt.version, err, tt.ziperr) + } + t.Fatalf("repo.Zip(%q): %v", tt.version, err) + } + if tt.ziperr != "" { + t.Errorf("repo.Zip(%q): success, want error %q", tt.version, tt.ziperr) + } + prefix := tt.path + "@" + tt.version + "/" + z, err := zip.OpenReader(zipfile) + if err != nil { + t.Fatalf("open zip %s: %v", zipfile, err) + } + var names []string + for _, file := range z.File { + if !strings.HasPrefix(file.Name, prefix) { + t.Errorf("zip entry %v does not start with prefix %v", file.Name, prefix) + continue + } + names = append(names, file.Name[len(prefix):]) + } + z.Close() + if !reflect.DeepEqual(names, tt.zip) { + t.Fatalf("zip = %v\nwant %v\n", names, tt.zip) + } + } + }) + } +} + +var importTests = []struct { + path string + mpath string + err string +}{ + { + path: "golang.org/x/net/context", + mpath: "golang.org/x/net", + }, + { + path: "github.com/rsc/quote/buggy", + mpath: "github.com/rsc/quote", + }, + { + path: "golang.org/x/net", + mpath: "golang.org/x/net", + }, + { + path: "github.com/rsc/quote", + mpath: "github.com/rsc/quote", + }, + { + path: "golang.org/x/foo/bar", + err: "unknown module golang.org/x/foo/bar: no go-import tags", + }, +} + +func TestImport(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + + for _, tt := range importTests { + t.Run(strings.Replace(tt.path, "/", "_", -1), func(t *testing.T) { + repo, info, err := Import(tt.path, nil) + if err != nil { + if tt.err != "" { + if err.Error() == tt.err { + return + } + t.Errorf("Import(%q): %v, want error %q", tt.path, err, tt.err) + } + t.Fatalf("Lookup(%q): %v", tt.path, err) + } + if mpath := repo.ModulePath(); mpath != tt.mpath { + t.Errorf("repo.ModulePath() = %q (%v), want %q", mpath, info.Version, tt.mpath) + } + }) + } +} + +var codeRepoVersionsTests = []struct { + path string + prefix string + versions []string +}{ + // TODO: Why do we allow a prefix here at all? + { + path: "github.com/rsc/vgotest1", + versions: []string{"v0.0.0", "v0.0.1", "v1.0.0", "v1.0.1", "v1.0.2", "v1.0.3", "v1.1.0"}, + }, + { + path: "github.com/rsc/vgotest1", + prefix: "v1.0", + versions: []string{"v1.0.0", "v1.0.1", "v1.0.2", "v1.0.3"}, + }, + { + path: "github.com/rsc/vgotest1/v2", + versions: []string{"v2.0.0", "v2.0.1", "v2.0.2", "v2.0.3", "v2.0.4", "v2.0.5", "v2.0.6"}, + }, + { + path: "swtch.com/testmod", + versions: []string{"v1.0.0", "v1.1.1"}, + }, + { + path: "gopkg.in/russross/blackfriday.v2", + versions: []string{"v2.0.0"}, + }, + { + path: "gopkg.in/natefinch/lumberjack.v2", + versions: []string{}, + }, +} + +func TestCodeRepoVersions(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + + tmpdir, err := ioutil.TempDir("", "vgo-modfetch-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + for _, tt := range codeRepoVersionsTests { + t.Run(strings.Replace(tt.path, "/", "_", -1), func(t *testing.T) { + repo, err := Lookup(tt.path) + if err != nil { + t.Fatalf("Lookup(%q): %v", tt.path, err) + } + list, err := repo.Versions(tt.prefix) + if err != nil { + t.Fatalf("Versions(%q): %v", tt.prefix, err) + } + if !reflect.DeepEqual(list, tt.versions) { + t.Fatalf("Versions(%q):\nhave %v\nwant %v", tt.prefix, list, tt.versions) + } + }) + } +} + +var latestTests = []struct { + path string + version string + err string +}{ + { + path: "github.com/rsc/empty", + err: "no commits", + }, + { + path: "github.com/rsc/vgotest1", + version: "v0.0.0-20180219223237-a08abb797a67", + }, + { + path: "swtch.com/testmod", + version: "v1.1.1", + }, +} + +func TestLatest(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + + tmpdir, err := ioutil.TempDir("", "vgo-modfetch-test-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + for _, tt := range latestTests { + name := strings.Replace(tt.path, "/", "_", -1) + t.Run(name, func(t *testing.T) { + repo, err := Lookup(tt.path) + if err != nil { + t.Fatalf("Lookup(%q): %v", tt.path, err) + } + info, err := repo.Latest() + if err != nil { + if tt.err != "" { + if err.Error() == tt.err { + return + } + t.Fatalf("Latest(): %v, want %q", err, tt.err) + } + t.Fatalf("Latest(): %v", err) + } + if info.Version != tt.version { + t.Fatalf("Latest() = %v, want %v", info.Version, tt.version) + } + }) + } +} + +// fixedTagsRepo is a fake codehost.Repo that returns a fixed list of tags +type fixedTagsRepo struct { + root string + tags []string +} + +func (ch *fixedTagsRepo) Tags(string) ([]string, error) { return ch.tags, nil } +func (ch *fixedTagsRepo) Root() string { return ch.root } +func (ch *fixedTagsRepo) Latest() (*codehost.RevInfo, error) { panic("not impl") } +func (ch *fixedTagsRepo) ReadFile(string, string, int64) ([]byte, error) { panic("not impl") } +func (ch *fixedTagsRepo) ReadZip(string, string, int64) (io.ReadCloser, string, error) { + panic("not impl") +} +func (ch *fixedTagsRepo) Stat(string) (*codehost.RevInfo, error) { panic("not impl") } + +func TestNonCanonicalSemver(t *testing.T) { + root := "golang.org/x/issue24476" + ch := &fixedTagsRepo{ + root: root, + tags: []string{ + "", "huh?", "1.0.1", + // what about "version 1 dot dogcow"? + "v1.🐕.🐄", + "v1", "v0.1", + // and one normal one that should pass through + "v1.0.1", + }, + } + + cr, err := newCodeRepo(ch, root) + if err != nil { + t.Fatal(err) + } + + v, err := cr.Versions("") + if err != nil { + t.Fatal(err) + } + if len(v) != 1 || v[0] != "v1.0.1" { + t.Fatal("unexpected versions returned:", v) + } +} + +var modPathTests = []struct { + input []byte + expected string +}{ + {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, + {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, + {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, + {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, + {input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"}, + {input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"}, + {input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"}, + {input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"}, + {input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"}, + {input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""}, + {input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"}, + {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""}, + {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""}, + {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""}, +} + +func TestModPath(t *testing.T) { + for _, test := range modPathTests { + t.Run(string(test.input), func(t *testing.T) { + result := modPath(test.input) + if result != test.expected { + t.Fatalf("modPath(%s): %s, want %s", string(test.input), result, test.expected) + } + }) + } +} diff --git a/src/cmd/go/internal/modfetch/convert.go b/src/cmd/go/internal/modfetch/convert.go new file mode 100644 index 0000000000..5c482be003 --- /dev/null +++ b/src/cmd/go/internal/modfetch/convert.go @@ -0,0 +1,78 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfetch + +import ( + "fmt" + "os" + "sort" + "strings" + + "cmd/go/internal/modconv" + "cmd/go/internal/modfile" + "cmd/go/internal/semver" +) + +// ConvertLegacyConfig converts legacy config to modfile. +// The file argument is slash-delimited. +func ConvertLegacyConfig(f *modfile.File, file string, data []byte) error { + i := strings.LastIndex(file, "/") + j := -2 + if i >= 0 { + j = strings.LastIndex(file[:i], "/") + } + convert := modconv.Converters[file[i+1:]] + if convert == nil && j != -2 { + convert = modconv.Converters[file[j+1:]] + } + if convert == nil { + return fmt.Errorf("unknown legacy config file %s", file) + } + require, err := convert(file, data) + if err != nil { + return fmt.Errorf("parsing %s: %v", file, err) + } + + // Convert requirements block, which may use raw SHA1 hashes as versions, + // to valid semver requirement list, respecting major versions. + need := make(map[string]string) + for _, r := range require { + if r.Path == "" { + continue + } + + // TODO: Something better here. + if strings.HasPrefix(r.Path, "github.com/") || strings.HasPrefix(r.Path, "golang.org/x/") { + f := strings.Split(r.Path, "/") + if len(f) > 3 { + r.Path = strings.Join(f[:3], "/") + } + } + + repo, err := Lookup(r.Path) + if err != nil { + fmt.Fprintf(os.Stderr, "vgo: lookup %s: %v\n", r.Path, err) + continue + } + info, err := repo.Stat(r.Version) + if err != nil { + fmt.Fprintf(os.Stderr, "vgo: stat %s@%s: %v\n", r.Path, r.Version, err) + continue + } + path := repo.ModulePath() + need[path] = semver.Max(need[path], info.Version) + } + + var paths []string + for path := range need { + paths = append(paths, path) + } + sort.Strings(paths) + for _, path := range paths { + f.AddRequire(path, need[path]) + } + + return nil +} diff --git a/src/cmd/go/internal/modfetch/convert_test.go b/src/cmd/go/internal/modfetch/convert_test.go new file mode 100644 index 0000000000..03ae1569f0 --- /dev/null +++ b/src/cmd/go/internal/modfetch/convert_test.go @@ -0,0 +1,139 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfetch + +import ( + "bytes" + "internal/testenv" + "strings" + "testing" + + "cmd/go/internal/cfg" + "cmd/go/internal/modconv" + "cmd/go/internal/modfile" +) + +func TestConvertLegacyConfig(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + + if testing.Verbose() { + old := cfg.BuildX + defer func() { + cfg.BuildX = old + }() + cfg.BuildX = true + } + + var tests = []struct { + path string + vers string + gomod string + }{ + { + // Gopkg.lock parsing. + "github.com/golang/dep", "v0.4.0", + `module github.com/golang/dep + + require ( + github.com/Masterminds/semver v0.0.0-20170726230514-a93e51b5a57e + github.com/Masterminds/vcs v1.11.1 + github.com/armon/go-radix v0.0.0-20160115234725-4239b77079c7 + github.com/boltdb/bolt v1.3.1 + github.com/go-yaml/yaml v0.0.0-20170407172122-cd8b52f8269e + github.com/golang/protobuf v0.0.0-20170901042739-5afd06f9d81a + github.com/jmank88/nuts v0.3.0 + github.com/nightlyone/lockfile v0.0.0-20170707060451-e83dc5e7bba0 + github.com/pelletier/go-toml v0.0.0-20171218135716-b8b5e7696574 + github.com/pkg/errors v0.8.0 + github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 + golang.org/x/net v0.0.0-20170828231752-66aacef3dd8a + golang.org/x/sync v0.0.0-20170517211232-f52d1811a629 + golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea + )`, + }, + + // TODO: https://github.com/docker/distribution uses vendor.conf + + { + // Godeps.json parsing. + // TODO: Should v2.0.0 work here too? + "github.com/docker/distribution", "v0.0.0-20150410205453-85de3967aa93", + `module github.com/docker/distribution + + require ( + github.com/AdRoll/goamz v0.0.0-20150130162828-d3664b76d905 + github.com/MSOpenTech/azure-sdk-for-go v0.0.0-20150323223030-d90753bcad2e + github.com/Sirupsen/logrus v0.0.0-20150409230825-55eb11d21d2a + github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd + github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b + github.com/bugsnag/panicwrap v0.0.0-20141110184334-e5f9854865b9 + github.com/codegangsta/cli v0.0.0-20150131031259-6086d7927ec3 + github.com/docker/docker v0.0.0-20150204013315-165ea5c158cf + github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 + github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 + github.com/gorilla/context v0.0.0-20140604161150-14f550f51af5 + github.com/gorilla/handlers v0.0.0-20140825150757-0e84b7d810c1 + github.com/gorilla/mux v0.0.0-20140926153814-e444e69cbd2e + github.com/jlhawn/go-crypto v0.0.0-20150401213827-cd738dde20f0 + github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 + github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 + github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f + golang.org/x/net v0.0.0-20150202051010-1dfe7915deaf + gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789 + gopkg.in/yaml.v2 v2.0.0-20150116202057-bef53efd0c76 + )`, + }, + + { + // golang.org/issue/24585 - confusion about v2.0.0 tag in legacy non-v2 module + "github.com/fishy/gcsbucket", "v0.0.0-20150410205453-618d60fe84e0", + `module github.com/fishy/gcsbucket + + require ( + cloud.google.com/go v0.18.0 + github.com/fishy/fsdb v0.0.0-20180217030800-5527ded01371 + github.com/golang/protobuf v1.0.0 + github.com/googleapis/gax-go v0.0.0-20170915024731-317e0006254c + golang.org/x/net v0.0.0-20180216171745-136a25c244d3 + golang.org/x/oauth2 v0.0.0-20180207181906-543e37812f10 + golang.org/x/text v0.0.0-20180208041248-4e4a3210bb54 + google.golang.org/api v0.0.0-20180217000815-c7a403bb5fe1 + google.golang.org/appengine v1.0.0 + google.golang.org/genproto v0.0.0-20180206005123-2b5a72b8730b + google.golang.org/grpc v1.10.0 + )`, + }, + } + + for _, tt := range tests { + t.Run(strings.Replace(tt.path, "/", "_", -1)+"_"+tt.vers, func(t *testing.T) { + f, err := modfile.Parse("golden", []byte(tt.gomod), nil) + if err != nil { + t.Fatal(err) + } + want, err := f.Format() + if err != nil { + t.Fatal(err) + } + repo, err := Lookup(tt.path) + if err != nil { + t.Fatal(err) + } + + out, err := repo.GoMod(tt.vers) + if err != nil { + t.Fatal(err) + } + prefix := modconv.Prefix + "\n" + if !bytes.HasPrefix(out, []byte(prefix)) { + t.Fatalf("go.mod missing prefix %q:\n%s", prefix, out) + } + out = out[len(prefix):] + if !bytes.Equal(out, want) { + t.Fatalf("final go.mod:\n%s\n\nwant:\n%s", out, want) + } + }) + } +} diff --git a/src/cmd/go/internal/modfetch/domain.go b/src/cmd/go/internal/modfetch/domain.go new file mode 100644 index 0000000000..2494f80ab0 --- /dev/null +++ b/src/cmd/go/internal/modfetch/domain.go @@ -0,0 +1,178 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Support for custom domains. + +package modfetch + +import ( + "encoding/xml" + "fmt" + "io" + "net/url" + "os" + "strings" + + "cmd/go/internal/modfetch/codehost" + "cmd/go/internal/modfetch/gitrepo" +) + +// metaImport represents the parsed tags from HTML files. +type metaImport struct { + Prefix, VCS, RepoRoot string +} + +func lookupCustomDomain(path string) (Repo, error) { + dom := path + if i := strings.Index(dom, "/"); i >= 0 { + dom = dom[:i] + } + if !strings.Contains(dom, ".") { + return nil, fmt.Errorf("unknown module %s: not a domain name", path) + } + var body io.ReadCloser + err := webGetGoGet("https://"+path+"?go-get=1", &body) + if body != nil { + defer body.Close() + } + if err != nil { + fmt.Fprintf(os.Stderr, "FindRepo: %v\n", err) + return nil, err + } + // Note: accepting a non-200 OK here, so people can serve a + // meta import in their http 404 page. + imports, err := parseMetaGoImports(body) + if err != nil { + fmt.Fprintf(os.Stderr, "findRepo: %v\n", err) + return nil, err + } + if len(imports) == 0 { + return nil, fmt.Errorf("unknown module %s: no go-import tags", path) + } + + // First look for new module definition. + for _, imp := range imports { + if path == imp.Prefix || strings.HasPrefix(path, imp.Prefix+"/") { + if imp.VCS == "mod" { + u, err := url.Parse(imp.RepoRoot) + if err != nil { + return nil, fmt.Errorf("invalid module URL %q", imp.RepoRoot) + } else if u.Scheme != "https" { + // TODO: Allow -insecure flag as a build flag? + return nil, fmt.Errorf("invalid module URL %q: must be HTTPS", imp.RepoRoot) + } + return newProxyRepo(imp.RepoRoot, imp.Prefix), nil + } + } + } + + // Fall back to redirections to known version control systems. + for _, imp := range imports { + if path == imp.Prefix { + if !strings.HasPrefix(imp.RepoRoot, "https://") { + // TODO: Allow -insecure flag as a build flag? + return nil, fmt.Errorf("invalid server URL %q: must be HTTPS", imp.RepoRoot) + } + if imp.VCS == "git" { + code, err := gitrepo.Repo(imp.RepoRoot, imp.Prefix) + if err != nil { + return nil, err + } + return newCodeRepo(code, path) + } + return nil, fmt.Errorf("unknown VCS, Repo: %s, %s", imp.VCS, imp.RepoRoot) + } + } + + // Check for redirect to repo root. + for _, imp := range imports { + if strings.HasPrefix(path, imp.Prefix+"/") { + return nil, &ModuleSubdirError{imp.Prefix} + } + } + + return nil, fmt.Errorf("unknown module %s: no matching go-import tags", path) +} + +type ModuleSubdirError struct { + ModulePath string +} + +func (e *ModuleSubdirError) Error() string { + return fmt.Sprintf("module root is %q", e.ModulePath) +} + +type customPrefix struct { + codehost.Repo + root string +} + +func (c *customPrefix) Root() string { + return c.root +} + +// parseMetaGoImports returns meta imports from the HTML in r. +// Parsing ends at the end of the section or the beginning of the . +func parseMetaGoImports(r io.Reader) (imports []metaImport, err error) { + d := xml.NewDecoder(r) + d.CharsetReader = charsetReader + d.Strict = false + var t xml.Token + for { + t, err = d.RawToken() + if err != nil { + if err == io.EOF || len(imports) > 0 { + err = nil + } + return + } + if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") { + return + } + if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") { + return + } + e, ok := t.(xml.StartElement) + if !ok || !strings.EqualFold(e.Name.Local, "meta") { + continue + } + if attrValue(e.Attr, "name") != "go-import" { + continue + } + if f := strings.Fields(attrValue(e.Attr, "content")); len(f) == 3 { + imports = append(imports, metaImport{ + Prefix: f[0], + VCS: f[1], + RepoRoot: f[2], + }) + } + } +} + +// attrValue returns the attribute value for the case-insensitive key +// `name', or the empty string if nothing is found. +func attrValue(attrs []xml.Attr, name string) string { + for _, a := range attrs { + if strings.EqualFold(a.Name.Local, name) { + return a.Value + } + } + return "" +} + +// charsetReader returns a reader for the given charset. Currently +// it only supports UTF-8 and ASCII. Otherwise, it returns a meaningful +// error which is printed by go get, so the user can find why the package +// wasn't downloaded if the encoding is not supported. Note that, in +// order to reduce potential errors, ASCII is treated as UTF-8 (i.e. characters +// greater than 0x7f are not rejected). +func charsetReader(charset string, input io.Reader) (io.Reader, error) { + switch strings.ToLower(charset) { + case "ascii": + return input, nil + default: + return nil, fmt.Errorf("can't decode XML document using charset %q", charset) + } +} diff --git a/src/cmd/go/internal/modfetch/github/fetch.go b/src/cmd/go/internal/modfetch/github/fetch.go new file mode 100644 index 0000000000..a2a90f166c --- /dev/null +++ b/src/cmd/go/internal/modfetch/github/fetch.go @@ -0,0 +1,24 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "strings" + + "cmd/go/internal/modfetch/codehost" + "cmd/go/internal/modfetch/gitrepo" +) + +// Lookup returns the code repository enclosing the given module path, +// which must begin with github.com/. +func Lookup(path string) (codehost.Repo, error) { + f := strings.Split(path, "/") + if len(f) < 3 || f[0] != "github.com" { + return nil, fmt.Errorf("github repo must be github.com/org/project") + } + path = f[0] + "/" + f[1] + "/" + f[2] + return gitrepo.Repo("https://"+path, path) +} diff --git a/src/cmd/go/internal/modfetch/gitrepo/fetch.go b/src/cmd/go/internal/modfetch/gitrepo/fetch.go new file mode 100644 index 0000000000..0d6eb4b7e9 --- /dev/null +++ b/src/cmd/go/internal/modfetch/gitrepo/fetch.go @@ -0,0 +1,445 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gitrepo provides a Git-based implementation of codehost.Repo. +package gitrepo + +import ( + "archive/zip" + "bytes" + "cmd/go/internal/modfetch/codehost" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "time" +) + +// Repo returns the code repository at the given Git remote reference. +// The returned repo reports the given root as its module root. +func Repo(remote, root string) (codehost.Repo, error) { + return newRepo(remote, root, false) +} + +// LocalRepo is like Repo but accepts both Git remote references +// and paths to repositories on the local file system. +// The returned repo reports the given root as its module root. +func LocalRepo(remote, root string) (codehost.Repo, error) { + return newRepo(remote, root, true) +} + +const workDirType = "git2" + +func newRepo(remote, root string, localOK bool) (codehost.Repo, error) { + r := &repo{remote: remote, root: root, canArchive: true} + if strings.Contains(remote, "://") { + // This is a remote path. + dir, err := codehost.WorkDir(workDirType, r.remote) + if err != nil { + return nil, err + } + r.dir = dir + if _, err := os.Stat(filepath.Join(dir, "objects")); err != nil { + if _, err := codehost.Run(dir, "git", "init", "--bare"); err != nil { + os.RemoveAll(dir) + return nil, err + } + // We could just say git fetch https://whatever later, + // but this lets us say git fetch origin instead, which + // is a little nicer. More importantly, using a named remote + // avoids a problem with Git LFS. See golang.org/issue/25605. + if _, err := codehost.Run(dir, "git", "remote", "add", "origin", r.remote); err != nil { + os.RemoveAll(dir) + return nil, err + } + r.remote = "origin" + } + } else { + // Local path. + // Disallow colon (not in ://) because sometimes + // that's rcp-style host:path syntax and sometimes it's not (c:\work). + // The go command has always insisted on URL syntax for ssh. + if strings.Contains(remote, ":") { + return nil, fmt.Errorf("git remote cannot use host:path syntax") + } + if !localOK { + return nil, fmt.Errorf("git remote must not be local directory") + } + r.local = true + info, err := os.Stat(remote) + if err != nil { + return nil, err + } + if !info.IsDir() { + return nil, fmt.Errorf("%s exists but is not a directory", remote) + } + r.dir = remote + } + return r, nil +} + +type repo struct { + remote string + local bool + root string + dir string + canArchive bool + + refsOnce sync.Once + refs map[string]string + refsErr error +} + +func (r *repo) Root() string { + return r.root +} + +// loadRefs loads heads and tags references from the remote into the map r.refs. +// Should only be called as r.refsOnce.Do(r.loadRefs). +func (r *repo) loadRefs() { + // The git protocol sends all known refs and ls-remote filters them on the client side, + // so we might as well record both heads and tags in one shot. + // Most of the time we only care about tags but sometimes we care about heads too. + out, err := codehost.Run(r.dir, "git", "ls-remote", "-q", r.remote) + if err != nil { + r.refsErr = err + return + } + + r.refs = make(map[string]string) + for _, line := range strings.Split(string(out), "\n") { + f := strings.Fields(line) + if len(f) != 2 { + continue + } + if f[1] == "HEAD" || strings.HasPrefix(f[1], "refs/heads/") || strings.HasPrefix(f[1], "refs/tags/") { + r.refs[f[1]] = f[0] + } + } + for ref, hash := range r.refs { + if strings.HasSuffix(ref, "^{}") { // record unwrapped annotated tag as value of tag + r.refs[strings.TrimSuffix(ref, "^{}")] = hash + delete(r.refs, ref) + } + } +} + +func (r *repo) Tags(prefix string) ([]string, error) { + r.refsOnce.Do(r.loadRefs) + if r.refsErr != nil { + return nil, r.refsErr + } + + tags := []string{} + for ref := range r.refs { + if !strings.HasPrefix(ref, "refs/tags/") { + continue + } + tag := ref[len("refs/tags/"):] + if !strings.HasPrefix(tag, prefix) { + continue + } + tags = append(tags, tag) + } + sort.Strings(tags) + return tags, nil +} + +func (r *repo) Latest() (*codehost.RevInfo, error) { + r.refsOnce.Do(r.loadRefs) + if r.refsErr != nil { + return nil, r.refsErr + } + if r.refs["HEAD"] == "" { + return nil, fmt.Errorf("no commits") + } + return r.Stat(r.refs["HEAD"]) +} + +// findRef finds some ref name for the given hash, +// for use when the server requires giving a ref instead of a hash. +// There may be multiple ref names for a given hash, +// in which case this returns some name - it doesn't matter which. +func (r *repo) findRef(hash string) (ref string, ok bool) { + r.refsOnce.Do(r.loadRefs) + for ref, h := range r.refs { + if h == hash { + return ref, true + } + } + return "", false +} + +func unshallow(gitDir string) []string { + if _, err := os.Stat(filepath.Join(gitDir, "shallow")); err == nil { + return []string{"--unshallow"} + } + return []string{} +} + +// statOrArchive tries to stat the given rev in the local repository, +// or else it tries to obtain an archive at the rev with the given arguments, +// or else it falls back to aggressive fetching and then a local stat. +// The archive step is an optimization for servers that support it +// (most do not, but maybe that will change), to let us minimize +// the amount of code downloaded. +func (r *repo) statOrArchive(rev string, archiveArgs ...string) (info *codehost.RevInfo, archive []byte, err error) { + // Do we have this rev? + r.refsOnce.Do(r.loadRefs) + var hash string + if k := "refs/tags/" + rev; r.refs[k] != "" { + hash = r.refs[k] + } else if k := "refs/heads/" + rev; r.refs[k] != "" { + hash = r.refs[k] + rev = hash + } else if rev == "HEAD" && r.refs["HEAD"] != "" { + hash = r.refs["HEAD"] + rev = hash + } else if len(rev) >= 5 && len(rev) <= 40 && codehost.AllHex(rev) { + hash = rev + } else { + return nil, nil, fmt.Errorf("unknown revision %q", rev) + } + + out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash) + if err == nil { + hash = strings.TrimSpace(string(out)) + goto Found + } + + // We don't have the rev. Can we fetch it? + if r.local { + return nil, nil, fmt.Errorf("unknown revision %q", rev) + } + + if r.canArchive { + // git archive with --remote requires a ref, not a hash. + // Proceed only if we know a ref for this hash. + if ref, ok := r.findRef(hash); ok { + out, err := codehost.Run(r.dir, "git", "archive", "--format=zip", "--remote="+r.remote, "--prefix=prefix/", ref, archiveArgs) + if err == nil { + return &codehost.RevInfo{Version: rev}, out, nil + } + if bytes.Contains(err.(*codehost.RunError).Stderr, []byte("did not match any files")) { + return nil, nil, fmt.Errorf("file not found") + } + if bytes.Contains(err.(*codehost.RunError).Stderr, []byte("Operation not supported by protocol")) { + r.canArchive = false + } + } + } + + // Maybe it's a prefix of a ref we know. + // Iterating through all the refs is faster than doing unnecessary fetches. + // This is not strictly correct, in that the short ref might be ambiguous + // in the git repo as a whole, but not ambiguous in the list of named refs, + // so that we will resolve it where the git server would not. + // But this check avoids great expense, and preferring a known ref does + // not seem like such a bad failure mode. + if len(hash) >= 5 && len(hash) < 40 { + var full string + for _, h := range r.refs { + if strings.HasPrefix(h, hash) { + if full != "" { + // Prefix is ambiguous even in the ref list! + full = "" + break + } + full = h + } + } + if full != "" { + hash = full + } + } + + // Fetch it. + if len(hash) == 40 { + name := hash + if ref, ok := r.findRef(hash); ok { + name = ref + } + if _, err = codehost.Run(r.dir, "git", "fetch", "--depth=1", r.remote, name); err == nil { + goto Found + } + if !strings.Contains(err.Error(), "unadvertised object") && !strings.Contains(err.Error(), "no such remote ref") && !strings.Contains(err.Error(), "does not support shallow") { + return nil, nil, err + } + } + + // It's a prefix, and we don't have a way to make the server resolve the prefix for us, + // or it's a full hash but also an unadvertised object. + // Download progressively more of the repo to look for it. + + // Fetch the main branch (non-shallow). + if _, err := codehost.Run(r.dir, "git", "fetch", unshallow(r.dir), r.remote); err != nil { + return nil, nil, err + } + if out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash); err == nil { + hash = strings.TrimSpace(string(out)) + goto Found + } + + // Fetch all tags (non-shallow). + if _, err := codehost.Run(r.dir, "git", "fetch", unshallow(r.dir), "-f", "--tags", r.remote); err != nil { + return nil, nil, err + } + if out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash); err == nil { + hash = strings.TrimSpace(string(out)) + goto Found + } + + // Fetch all branches (non-shallow). + if _, err := codehost.Run(r.dir, "git", "fetch", unshallow(r.dir), "-f", r.remote, "refs/heads/*:refs/heads/*"); err != nil { + return nil, nil, err + } + if out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash); err == nil { + hash = strings.TrimSpace(string(out)) + goto Found + } + + // Fetch all refs (non-shallow). + if _, err := codehost.Run(r.dir, "git", "fetch", unshallow(r.dir), "-f", r.remote, "refs/*:refs/*"); err != nil { + return nil, nil, err + } + if out, err := codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%H", hash); err == nil { + hash = strings.TrimSpace(string(out)) + goto Found + } + return nil, nil, fmt.Errorf("cannot find hash %s", hash) +Found: + + if strings.HasPrefix(hash, rev) { + rev = hash + } + + out, err = codehost.Run(r.dir, "git", "log", "-n1", "--format=format:%ct", hash) + if err != nil { + return nil, nil, err + } + t, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64) + if err != nil { + return nil, nil, fmt.Errorf("invalid time from git log: %q", out) + } + + info = &codehost.RevInfo{ + Name: hash, + Short: codehost.ShortenSHA1(hash), + Time: time.Unix(t, 0).UTC(), + Version: rev, + } + return info, nil, nil +} + +func (r *repo) Stat(rev string) (*codehost.RevInfo, error) { + // If the server will give us a git archive, we can pull the + // commit ID and the commit time out of the archive. + // We want an archive as small as possible (for speed), + // but we have to specify a pattern that matches at least one file name. + // The pattern here matches README, .gitignore, .gitattributes, + // and go.mod (and some other incidental file names); + // hopefully most repos will have at least one of these. + info, archive, err := r.statOrArchive(rev, "[Rg.][Ego][A.i][Dmt][Miao][Edgt]*") + if err != nil { + return nil, err + } + if archive != nil { + return zip2info(archive, info.Version) + } + return info, nil +} + +func (r *repo) ReadFile(rev, file string, maxSize int64) ([]byte, error) { + info, archive, err := r.statOrArchive(rev, file) + if err != nil { + return nil, err + } + if archive != nil { + return zip2file(archive, file, maxSize) + } + out, err := codehost.Run(r.dir, "git", "cat-file", "blob", info.Name+":"+file) + if err != nil { + return nil, fmt.Errorf("file not found") + } + return out, nil +} + +func (r *repo) ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, actualSubdir string, err error) { + // TODO: Use maxSize or drop it. + args := []string{} + if subdir != "" { + args = append(args, "--", subdir) + } + info, archive, err := r.statOrArchive(rev, args...) + if err != nil { + return nil, "", err + } + if archive == nil { + archive, err = codehost.Run(r.dir, "git", "archive", "--format=zip", "--prefix=prefix/", info.Name, args) + if err != nil { + if bytes.Contains(err.(*codehost.RunError).Stderr, []byte("did not match any files")) { + return nil, "", fmt.Errorf("file not found") + } + return nil, "", err + } + } + + return ioutil.NopCloser(bytes.NewReader(archive)), "", nil +} + +func zip2info(archive []byte, rev string) (*codehost.RevInfo, error) { + r, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive))) + if err != nil { + return nil, err + } + if r.Comment == "" { + return nil, fmt.Errorf("missing commit ID in git zip comment") + } + hash := r.Comment + if len(hash) != 40 || !codehost.AllHex(hash) { + return nil, fmt.Errorf("invalid commit ID in git zip comment") + } + if len(r.File) == 0 { + return nil, fmt.Errorf("git zip has no files") + } + info := &codehost.RevInfo{ + Name: hash, + Short: codehost.ShortenSHA1(hash), + Time: r.File[0].Modified.UTC(), + Version: rev, + } + return info, nil +} + +func zip2file(archive []byte, file string, maxSize int64) ([]byte, error) { + r, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive))) + if err != nil { + return nil, err + } + for _, f := range r.File { + if f.Name != "prefix/"+file { + continue + } + rc, err := f.Open() + if err != nil { + return nil, err + } + defer rc.Close() + l := &io.LimitedReader{R: rc, N: maxSize + 1} + data, err := ioutil.ReadAll(l) + if err != nil { + return nil, err + } + if l.N <= 0 { + return nil, fmt.Errorf("file %s too large", file) + } + return data, nil + } + return nil, fmt.Errorf("incomplete git zip archive: cannot find %s", file) +} diff --git a/src/cmd/go/internal/modfetch/gitrepo/fetch_test.go b/src/cmd/go/internal/modfetch/gitrepo/fetch_test.go new file mode 100644 index 0000000000..ca932808e8 --- /dev/null +++ b/src/cmd/go/internal/modfetch/gitrepo/fetch_test.go @@ -0,0 +1,500 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gitrepo + +import ( + "archive/zip" + "bytes" + "fmt" + "internal/testenv" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "reflect" + "strings" + "testing" + "time" + + "cmd/go/internal/modfetch/codehost" +) + +func TestMain(m *testing.M) { + os.Exit(testMain(m)) +} + +const gitrepo1 = "https://vcs-test.golang.org/git/gitrepo1" + +// localGitRepo is like gitrepo1 but allows archive access. +var localGitRepo string + +func testMain(m *testing.M) int { + if _, err := exec.LookPath("git"); err != nil { + fmt.Fprintln(os.Stderr, "skipping because git binary not found") + fmt.Println("PASS") + return 0 + } + + dir, err := ioutil.TempDir("", "gitrepo-test-") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(dir) + codehost.WorkRoot = dir + + if testenv.HasExternalNetwork() && testenv.HasExec() { + // Clone gitrepo1 into a local directory. + // If we use a file:// URL to access the local directory, + // then git starts up all the usual protocol machinery, + // which will let us test remote git archive invocations. + localGitRepo = filepath.Join(dir, "gitrepo2") + if _, err := codehost.Run("", "git", "clone", "--mirror", gitrepo1, localGitRepo); err != nil { + log.Fatal(err) + } + if _, err := codehost.Run(localGitRepo, "git", "config", "daemon.uploadarch", "true"); err != nil { + log.Fatal(err) + } + } + + return m.Run() +} + +func testRepo(remote string) (codehost.Repo, error) { + if remote == "localGitRepo" { + remote = "file://" + filepath.ToSlash(localGitRepo) + } + // Re ?root: nothing should care about the second argument, + // so use a string that will be distinctive if it does show up. + return LocalRepo(remote, "?root") +} + +var tagsTests = []struct { + repo string + prefix string + tags []string +}{ + {gitrepo1, "xxx", []string{}}, + {gitrepo1, "", []string{"v1.2.3", "v1.2.4-annotated", "v2.0.1", "v2.0.2", "v2.3"}}, + {gitrepo1, "v", []string{"v1.2.3", "v1.2.4-annotated", "v2.0.1", "v2.0.2", "v2.3"}}, + {gitrepo1, "v1", []string{"v1.2.3", "v1.2.4-annotated"}}, + {gitrepo1, "2", []string{}}, +} + +func TestTags(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + testenv.MustHaveExec(t) + + for _, tt := range tagsTests { + f := func(t *testing.T) { + r, err := testRepo(tt.repo) + if err != nil { + t.Fatal(err) + } + tags, err := r.Tags(tt.prefix) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(tags, tt.tags) { + t.Errorf("Tags: incorrect tags\nhave %v\nwant %v", tags, tt.tags) + } + } + t.Run(tt.repo+"/"+tt.prefix, f) + if tt.repo == gitrepo1 { + tt.repo = "localGitRepo" + t.Run(tt.repo+"/"+tt.prefix, f) + } + } +} + +var latestTests = []struct { + repo string + info *codehost.RevInfo +}{ + { + gitrepo1, + &codehost.RevInfo{ + Name: "ede458df7cd0fdca520df19a33158086a8a68e81", + Short: "ede458df7cd0", + Version: "ede458df7cd0fdca520df19a33158086a8a68e81", + Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), + }, + }, +} + +func TestLatest(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + testenv.MustHaveExec(t) + + for _, tt := range latestTests { + f := func(t *testing.T) { + r, err := testRepo(tt.repo) + if err != nil { + t.Fatal(err) + } + info, err := r.Latest() + if err != nil { + t.Fatal(err) + } + if *info != *tt.info { + t.Errorf("Latest: incorrect info\nhave %+v\nwant %+v", *info, *tt.info) + } + } + t.Run(tt.repo, f) + if tt.repo == gitrepo1 { + tt.repo = "localGitRepo" + t.Run(tt.repo, f) + } + } +} + +var readFileTests = []struct { + repo string + rev string + file string + err string + data string +}{ + { + repo: gitrepo1, + rev: "HEAD", + file: "README", + data: "", + }, + { + repo: gitrepo1, + rev: "v2", + file: "another.txt", + data: "another\n", + }, + { + repo: gitrepo1, + rev: "v2.3.4", + file: "another.txt", + err: "file not found", + }, +} + +func TestReadFile(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + testenv.MustHaveExec(t) + + for _, tt := range readFileTests { + f := func(t *testing.T) { + r, err := testRepo(tt.repo) + if err != nil { + t.Fatal(err) + } + data, err := r.ReadFile(tt.rev, tt.file, 100) + if err != nil { + if tt.err == "" { + t.Fatalf("ReadFile: unexpected error %v", err) + } + if !strings.Contains(err.Error(), tt.err) { + t.Fatalf("ReadFile: wrong error %q, want %q", err, tt.err) + } + if len(data) != 0 { + t.Errorf("ReadFile: non-empty data %q with error %v", data, err) + } + return + } + if tt.err != "" { + t.Fatalf("ReadFile: no error, wanted %v", tt.err) + } + if string(data) != tt.data { + t.Errorf("ReadFile: incorrect data\nhave %q\nwant %q", data, tt.data) + } + } + t.Run(tt.repo+"/"+tt.rev+"/"+tt.file, f) + if tt.repo == gitrepo1 { + tt.repo = "localGitRepo" + t.Run(tt.repo+"/"+tt.rev+"/"+tt.file, f) + } + } +} + +var readZipTests = []struct { + repo string + rev string + subdir string + actualSubdir string + err string + files map[string]uint64 +}{ + { + repo: gitrepo1, + rev: "v2.3.4", + subdir: "", + files: map[string]uint64{ + "prefix/": 0, + "prefix/README": 0, + "prefix/v2": 3, + }, + }, + { + repo: gitrepo1, + rev: "v2", + subdir: "", + files: map[string]uint64{ + "prefix/": 0, + "prefix/README": 0, + "prefix/v2": 3, + "prefix/another.txt": 8, + "prefix/foo.txt": 13, + }, + }, + { + repo: gitrepo1, + rev: "v3", + subdir: "", + files: map[string]uint64{ + "prefix/": 0, + "prefix/v3/": 0, + "prefix/v3/sub/": 0, + "prefix/v3/sub/dir/": 0, + "prefix/v3/sub/dir/file.txt": 16, + "prefix/README": 0, + }, + }, + { + repo: gitrepo1, + rev: "v3", + subdir: "v3/sub/dir", + files: map[string]uint64{ + "prefix/": 0, + "prefix/v3/": 0, + "prefix/v3/sub/": 0, + "prefix/v3/sub/dir/": 0, + "prefix/v3/sub/dir/file.txt": 16, + }, + }, + { + repo: gitrepo1, + rev: "v3", + subdir: "v3/sub", + files: map[string]uint64{ + "prefix/": 0, + "prefix/v3/": 0, + "prefix/v3/sub/": 0, + "prefix/v3/sub/dir/": 0, + "prefix/v3/sub/dir/file.txt": 16, + }, + }, + { + repo: gitrepo1, + rev: "aaaaaaaaab", + subdir: "", + err: "cannot find hash", + }, + { + repo: "https://github.com/rsc/vgotest1", + rev: "submod/v1.0.4", + subdir: "submod", + files: map[string]uint64{ + "prefix/": 0, + "prefix/submod/": 0, + "prefix/submod/go.mod": 53, + "prefix/submod/pkg/": 0, + "prefix/submod/pkg/p.go": 31, + }, + }, +} + +type zipFile struct { + name string + size int64 +} + +func TestReadZip(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + testenv.MustHaveExec(t) + + for _, tt := range readZipTests { + f := func(t *testing.T) { + r, err := testRepo(tt.repo) + if err != nil { + t.Fatal(err) + } + rc, actualSubdir, err := r.ReadZip(tt.rev, tt.subdir, 100000) + if err != nil { + if tt.err == "" { + t.Fatalf("ReadZip: unexpected error %v", err) + } + if !strings.Contains(err.Error(), tt.err) { + t.Fatalf("ReadZip: wrong error %q, want %q", err, tt.err) + } + if rc != nil { + t.Errorf("ReadZip: non-nil io.ReadCloser with error %v", err) + } + return + } + defer rc.Close() + if tt.err != "" { + t.Fatalf("ReadZip: no error, wanted %v", tt.err) + } + if actualSubdir != tt.actualSubdir { + t.Fatalf("ReadZip: actualSubdir = %q, want %q", actualSubdir, tt.actualSubdir) + } + zipdata, err := ioutil.ReadAll(rc) + if err != nil { + t.Fatal(err) + } + z, err := zip.NewReader(bytes.NewReader(zipdata), int64(len(zipdata))) + if err != nil { + t.Fatalf("ReadZip: cannot read zip file: %v", err) + } + have := make(map[string]bool) + for _, f := range z.File { + size, ok := tt.files[f.Name] + if !ok { + t.Errorf("ReadZip: unexpected file %s", f.Name) + continue + } + have[f.Name] = true + if f.UncompressedSize64 != size { + t.Errorf("ReadZip: file %s has unexpected size %d != %d", f.Name, f.UncompressedSize64, size) + } + } + for name := range tt.files { + if !have[name] { + t.Errorf("ReadZip: missing file %s", name) + } + } + } + t.Run(tt.repo+"/"+tt.rev+"/"+tt.subdir, f) + if tt.repo == gitrepo1 { + tt.repo = "localGitRepo" + t.Run(tt.repo+"/"+tt.rev+"/"+tt.subdir, f) + } + } +} + +var statTests = []struct { + repo string + rev string + err string + info *codehost.RevInfo +}{ + { + repo: gitrepo1, + rev: "HEAD", + info: &codehost.RevInfo{ + Name: "ede458df7cd0fdca520df19a33158086a8a68e81", + Short: "ede458df7cd0", + Version: "ede458df7cd0fdca520df19a33158086a8a68e81", + Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), + }, + }, + { + repo: gitrepo1, + rev: "v2", // branch + info: &codehost.RevInfo{ + Name: "9d02800338b8a55be062c838d1f02e0c5780b9eb", + Short: "9d02800338b8", + Version: "9d02800338b8a55be062c838d1f02e0c5780b9eb", + Time: time.Date(2018, 4, 17, 20, 00, 32, 0, time.UTC), + }, + }, + { + repo: gitrepo1, + rev: "v2.3.4", // badly-named branch (semver should be a tag) + info: &codehost.RevInfo{ + Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f", + Short: "76a00fb249b7", + Version: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f", + Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC), + }, + }, + { + repo: gitrepo1, + rev: "v2.3", // badly-named tag (we only respect full semver v2.3.0) + info: &codehost.RevInfo{ + Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f", + Short: "76a00fb249b7", + Version: "v2.3", + Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC), + }, + }, + { + repo: gitrepo1, + rev: "v1.2.3", // tag + info: &codehost.RevInfo{ + Name: "ede458df7cd0fdca520df19a33158086a8a68e81", + Short: "ede458df7cd0", + Version: "v1.2.3", + Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), + }, + }, + { + repo: gitrepo1, + rev: "ede458df", // hash prefix in refs + info: &codehost.RevInfo{ + Name: "ede458df7cd0fdca520df19a33158086a8a68e81", + Short: "ede458df7cd0", + Version: "ede458df7cd0fdca520df19a33158086a8a68e81", + Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), + }, + }, + { + repo: gitrepo1, + rev: "97f6aa59", // hash prefix not in refs + info: &codehost.RevInfo{ + Name: "97f6aa59c81c623494825b43d39e445566e429a4", + Short: "97f6aa59c81c", + Version: "97f6aa59c81c623494825b43d39e445566e429a4", + Time: time.Date(2018, 4, 17, 20, 0, 19, 0, time.UTC), + }, + }, + { + repo: gitrepo1, + rev: "v1.2.4-annotated", // annotated tag uses unwrapped commit hash + info: &codehost.RevInfo{ + Name: "ede458df7cd0fdca520df19a33158086a8a68e81", + Short: "ede458df7cd0", + Version: "v1.2.4-annotated", + Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), + }, + }, + { + repo: gitrepo1, + rev: "aaaaaaaaab", + err: "cannot find hash", + }, +} + +func TestStat(t *testing.T) { + testenv.MustHaveExternalNetwork(t) + testenv.MustHaveExec(t) + + for _, tt := range statTests { + f := func(t *testing.T) { + r, err := testRepo(tt.repo) + if err != nil { + t.Fatal(err) + } + info, err := r.Stat(tt.rev) + if err != nil { + if tt.err == "" { + t.Fatalf("Stat: unexpected error %v", err) + } + if !strings.Contains(err.Error(), tt.err) { + t.Fatalf("Stat: wrong error %q, want %q", err, tt.err) + } + if info != nil { + t.Errorf("Stat: non-nil info with error %q", err) + } + return + } + if *info != *tt.info { + t.Errorf("Stat: incorrect info\nhave %+v\nwant %+v", *info, *tt.info) + } + } + t.Run(filepath.Base(tt.repo)+"/"+tt.rev, f) + if tt.repo == gitrepo1 { + tt.repo = "localGitRepo" + t.Run(filepath.Base(tt.repo)+"/"+tt.rev, f) + } + } +} diff --git a/src/cmd/go/internal/modfetch/googlesource/fetch.go b/src/cmd/go/internal/modfetch/googlesource/fetch.go new file mode 100644 index 0000000000..8317ac3e29 --- /dev/null +++ b/src/cmd/go/internal/modfetch/googlesource/fetch.go @@ -0,0 +1,25 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package googlesource + +import ( + "fmt" + "strings" + + "cmd/go/internal/modfetch/codehost" + "cmd/go/internal/modfetch/gitrepo" +) + +func Lookup(path string) (codehost.Repo, error) { + i := strings.Index(path, "/") + if i+1 == len(path) || !strings.HasSuffix(path[:i+1], ".googlesource.com/") { + return nil, fmt.Errorf("not *.googlesource.com/*") + } + j := strings.Index(path[i+1:], "/") + if j >= 0 { + path = path[:i+1+j] + } + return gitrepo.Repo("https://"+path, path) +} diff --git a/src/cmd/go/internal/modfetch/gopkgin.go b/src/cmd/go/internal/modfetch/gopkgin.go new file mode 100644 index 0000000000..d49b60b3f0 --- /dev/null +++ b/src/cmd/go/internal/modfetch/gopkgin.go @@ -0,0 +1,22 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// TODO: Figure out what gopkg.in should do. + +package modfetch + +import ( + "cmd/go/internal/modfetch/codehost" + "cmd/go/internal/modfetch/gitrepo" + "cmd/go/internal/modfile" + "fmt" +) + +func gopkginLookup(path string) (codehost.Repo, error) { + root, _, _, _, ok := modfile.ParseGopkgIn(path) + if !ok { + return nil, fmt.Errorf("invalid gopkg.in/ path: %q", path) + } + return gitrepo.Repo("https://"+root, root) +} diff --git a/src/cmd/go/internal/modfetch/noweb.go b/src/cmd/go/internal/modfetch/noweb.go new file mode 100644 index 0000000000..9d713dcc66 --- /dev/null +++ b/src/cmd/go/internal/modfetch/noweb.go @@ -0,0 +1,24 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build cmd_go_bootstrap + +package modfetch + +import ( + "fmt" + "io" +) + +func webGetGoGet(url string, body *io.ReadCloser) error { + return fmt.Errorf("no network in go_bootstrap") +} + +func webGetBytes(url string, body *[]byte) error { + return fmt.Errorf("no network in go_bootstrap") +} + +func webGetBody(url string, body *io.ReadCloser) error { + return fmt.Errorf("no network in go_bootstrap") +} diff --git a/src/cmd/go/internal/modfetch/proxy.go b/src/cmd/go/internal/modfetch/proxy.go new file mode 100644 index 0000000000..419323bb3b --- /dev/null +++ b/src/cmd/go/internal/modfetch/proxy.go @@ -0,0 +1,164 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfetch + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "strings" + "time" + + "cmd/go/internal/modfetch/codehost" + "cmd/go/internal/semver" +) + +var proxyURL = os.Getenv("GOPROXY") + +func lookupProxy(path string) (Repo, error) { + u, err := url.Parse(proxyURL) + if err != nil || u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "file" { + // Don't echo $GOPROXY back in case it has user:password in it (sigh). + return nil, fmt.Errorf("invalid $GOPROXY setting") + } + return newProxyRepo(u.String(), path), nil +} + +type proxyRepo struct { + url string + path string +} + +func newProxyRepo(baseURL, path string) Repo { + return &proxyRepo{strings.TrimSuffix(baseURL, "/") + "/" + pathEscape(path), path} +} + +func (p *proxyRepo) ModulePath() string { + return p.path +} + +func (p *proxyRepo) Versions(prefix string) ([]string, error) { + var data []byte + err := webGetBytes(p.url+"/@v/list", &data) + if err != nil { + return nil, err + } + var list []string + for _, line := range strings.Split(string(data), "\n") { + f := strings.Fields(line) + if len(f) >= 1 && semver.IsValid(f[0]) && strings.HasPrefix(f[0], prefix) { + list = append(list, f[0]) + } + } + return list, nil +} + +func (p *proxyRepo) latest() (*RevInfo, error) { + var data []byte + err := webGetBytes(p.url+"/@v/list", &data) + if err != nil { + return nil, err + } + var best time.Time + var bestVersion string + for _, line := range strings.Split(string(data), "\n") { + f := strings.Fields(line) + if len(f) >= 2 && semver.IsValid(f[0]) { + ft, err := time.Parse(time.RFC3339, f[1]) + if err == nil && best.Before(ft) { + best = ft + bestVersion = f[0] + } + } + } + if bestVersion == "" { + return nil, fmt.Errorf("no commits") + } + info := &RevInfo{ + Version: bestVersion, + Name: bestVersion, + Short: bestVersion, + Time: best, + } + return info, nil +} + +func (p *proxyRepo) Stat(rev string) (*RevInfo, error) { + var data []byte + err := webGetBytes(p.url+"/@v/"+pathEscape(rev)+".info", &data) + if err != nil { + return nil, err + } + info := new(RevInfo) + if err := json.Unmarshal(data, info); err != nil { + return nil, err + } + return info, nil +} + +func (p *proxyRepo) Latest() (*RevInfo, error) { + var data []byte + u := p.url + "/@latest" + err := webGetBytes(u, &data) + if err != nil { + // TODO return err if not 404 + return p.latest() + } + info := new(RevInfo) + if err := json.Unmarshal(data, info); err != nil { + return nil, err + } + return info, nil +} + +func (p *proxyRepo) GoMod(version string) ([]byte, error) { + var data []byte + err := webGetBytes(p.url+"/@v/"+pathEscape(version)+".mod", &data) + if err != nil { + return nil, err + } + return data, nil +} + +func (p *proxyRepo) Zip(version string, tmpdir string) (tmpfile string, err error) { + var body io.ReadCloser + err = webGetBody(p.url+"/@v/"+pathEscape(version)+".zip", &body) + if err != nil { + return "", err + } + defer body.Close() + + // Spool to local file. + f, err := ioutil.TempFile(tmpdir, "vgo-proxy-download-") + if err != nil { + return "", err + } + defer f.Close() + maxSize := int64(codehost.MaxZipFile) + lr := &io.LimitedReader{R: body, N: maxSize + 1} + if _, err := io.Copy(f, lr); err != nil { + os.Remove(f.Name()) + return "", err + } + if lr.N <= 0 { + os.Remove(f.Name()) + return "", fmt.Errorf("downloaded zip file too large") + } + if err := f.Close(); err != nil { + os.Remove(f.Name()) + return "", err + } + return f.Name(), nil +} + +// pathEscape escapes s so it can be used in a path. +// That is, it escapes things like ? and # (which really shouldn't appear anyway). +// It does not escape / to %2F: our REST API is designed so that / can be left as is. +func pathEscape(s string) string { + return strings.Replace(url.PathEscape(s), "%2F", "/", -1) +} diff --git a/src/cmd/go/internal/modfetch/query.go b/src/cmd/go/internal/modfetch/query.go new file mode 100644 index 0000000000..5e9d86c411 --- /dev/null +++ b/src/cmd/go/internal/modfetch/query.go @@ -0,0 +1,84 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfetch + +import ( + "cmd/go/internal/module" + "cmd/go/internal/semver" + "fmt" + "strings" +) + +// Query looks up a revision of a given module given a version query string. +// The module must be a complete module path. +// The version must take one of the following forms: +// +// - the literal string "latest", denoting the latest available tagged version +// - v1.2.3, a semantic version string +// - v1 or v1.2, an abbreviated semantic version string completed by adding zeroes (v1.0.0 or v1.2.0); +// - >v1.2.3, denoting the earliest available version after v1.2.3 +// - ") || strings.HasPrefix(vers, "<") || vers == "latest" { + var op string + if vers != "latest" { + if !semver.IsValid(vers[1:]) { + return nil, fmt.Errorf("invalid semantic version in range %s", vers) + } + op, vers = vers[:1], vers[1:] + } + versions, err := repo.Versions("") + if err != nil { + return nil, err + } + if len(versions) == 0 && vers == "latest" { + return repo.Latest() + } + if vers == "latest" { + for i := len(versions) - 1; i >= 0; i-- { + if allowed == nil || allowed(module.Version{Path: path, Version: versions[i]}) { + return repo.Stat(versions[i]) + } + } + } else if op == "<" { + for i := len(versions) - 1; i >= 0; i-- { + if semver.Compare(versions[i], vers) < 0 && (allowed == nil || allowed(module.Version{Path: path, Version: versions[i]})) { + return repo.Stat(versions[i]) + } + } + } else { + for i := 0; i < len(versions); i++ { + if semver.Compare(versions[i], vers) > 0 && (allowed == nil || allowed(module.Version{Path: path, Version: versions[i]})) { + return repo.Stat(versions[i]) + } + } + } + return nil, fmt.Errorf("no matching versions for %s%s", op, vers) + } + // TODO: Time queries, maybe. + + return repo.Stat(vers) +} diff --git a/src/cmd/go/internal/modfetch/repo.go b/src/cmd/go/internal/modfetch/repo.go new file mode 100644 index 0000000000..6e21a41777 --- /dev/null +++ b/src/cmd/go/internal/modfetch/repo.go @@ -0,0 +1,137 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfetch + +import ( + "errors" + pathpkg "path" + "sort" + "strings" + "time" + + "cmd/go/internal/modfetch/bitbucket" + "cmd/go/internal/modfetch/codehost" + "cmd/go/internal/modfetch/github" + "cmd/go/internal/modfetch/googlesource" + "cmd/go/internal/module" + "cmd/go/internal/semver" +) + +// A Repo represents a repository storing all versions of a single module. +type Repo interface { + // ModulePath returns the module path. + ModulePath() string + + // Versions lists all known versions with the given prefix. + // Pseudo-versions are not included. + // Versions should be returned sorted in semver order + // (implementations can use SortVersions). + Versions(prefix string) (tags []string, err error) + + // Stat returns information about the revision rev. + // A revision can be any identifier known to the underlying service: + // commit hash, branch, tag, and so on. + Stat(rev string) (*RevInfo, error) + + // Latest returns the latest revision on the default branch, + // whatever that means in the underlying source code repository. + // It is only used when there are no tagged versions. + Latest() (*RevInfo, error) + + // GoMod returns the go.mod file for the given version. + GoMod(version string) (data []byte, err error) + + // Zip downloads a zip file for the given version + // to a new file in a given temporary directory. + // It returns the name of the new file. + // The caller should remove the file when finished with it. + Zip(version, tmpdir string) (tmpfile string, err error) +} + +// A Rev describes a single revision in a module repository. +type RevInfo struct { + Version string // version string + Name string // complete ID in underlying repository + Short string // shortened ID, for use in pseudo-version + Time time.Time // commit time +} + +// Lookup returns the module with the given module path. +func Lookup(path string) (Repo, error) { + if proxyURL != "" { + return lookupProxy(path) + } + if code, err := lookupCodeHost(path, false); err != errNotHosted { + if err != nil { + return nil, err + } + return newCodeRepo(code, path) + } + return lookupCustomDomain(path) +} + +func Import(path string, allowed func(module.Version) bool) (Repo, *RevInfo, error) { + try := func(path string) (Repo, *RevInfo, error) { + r, err := Lookup(path) + if err != nil { + return nil, nil, err + } + info, err := Query(path, "latest", allowed) + if err != nil { + return nil, nil, err + } + _, err = r.GoMod(info.Version) + if err != nil { + return nil, nil, err + } + return r, info, nil + } + + var firstErr error + for { + r, info, err := try(path) + if err == nil { + return r, info, nil + } + if firstErr == nil { + firstErr = err + } + p := pathpkg.Dir(path) + if p == "." { + break + } + path = p + } + return nil, nil, firstErr +} + +var errNotHosted = errors.New("not hosted") + +var isTest bool + +func lookupCodeHost(path string, customDomain bool) (codehost.Repo, error) { + switch { + case strings.HasPrefix(path, "github.com/"): + return github.Lookup(path) + case strings.HasPrefix(path, "bitbucket.org/"): + return bitbucket.Lookup(path) + case customDomain && strings.HasSuffix(path[:strings.Index(path, "/")+1], ".googlesource.com/") || + isTest && strings.HasPrefix(path, "go.googlesource.com/scratch"): + return googlesource.Lookup(path) + case strings.HasPrefix(path, "gopkg.in/"): + return gopkginLookup(path) + } + return nil, errNotHosted +} + +func SortVersions(list []string) { + sort.Slice(list, func(i, j int) bool { + cmp := semver.Compare(list[i], list[j]) + if cmp != 0 { + return cmp < 0 + } + return list[i] < list[j] + }) +} diff --git a/src/cmd/go/internal/modfetch/unzip.go b/src/cmd/go/internal/modfetch/unzip.go new file mode 100644 index 0000000000..9d9e29889e --- /dev/null +++ b/src/cmd/go/internal/modfetch/unzip.go @@ -0,0 +1,100 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfetch + +import ( + "archive/zip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "cmd/go/internal/modfetch/codehost" +) + +func Unzip(dir, zipfile, prefix string, maxSize int64) error { + if maxSize == 0 { + maxSize = codehost.MaxZipFile + } + + // Directory can exist, but must be empty. + // except maybe + files, _ := ioutil.ReadDir(dir) + if len(files) > 0 { + return fmt.Errorf("target directory %v exists and is not empty", dir) + } + if err := os.MkdirAll(dir, 0777); err != nil { + return err + } + + f, err := os.Open(zipfile) + if err != nil { + return err + } + defer f.Close() + info, err := f.Stat() + if err != nil { + return err + } + + z, err := zip.NewReader(f, info.Size()) + if err != nil { + return fmt.Errorf("unzip %v: %s", zipfile, err) + } + + // Check total size. + var size int64 + for _, zf := range z.File { + if !strings.HasPrefix(zf.Name, prefix) { + return fmt.Errorf("unzip %v: unexpected file name %s", zipfile, zf.Name) + } + if strings.HasSuffix(zf.Name, "/") { + continue + } + s := int64(zf.UncompressedSize64) + if s < 0 || maxSize-size < s { + return fmt.Errorf("unzip %v: content too large", zipfile) + } + size += s + } + + // Unzip, enforcing sizes checked earlier. + for _, zf := range z.File { + if strings.HasSuffix(zf.Name, "/") { + continue + } + dst := filepath.Join(dir, zf.Name[len(prefix):]) + if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil { + return err + } + w, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0444) + if err != nil { + return fmt.Errorf("unzip %v: %v", zipfile, err) + } + r, err := zf.Open() + if err != nil { + r.Close() + w.Close() + return fmt.Errorf("unzip %v: %v", zipfile, err) + } + lr := &io.LimitedReader{R: r, N: int64(zf.UncompressedSize64) + 1} + _, err = io.Copy(w, lr) + r.Close() + if err != nil { + w.Close() + return fmt.Errorf("unzip %v: %v", zipfile, err) + } + if err := w.Close(); err != nil { + return fmt.Errorf("unzip %v: %v", zipfile, err) + } + if lr.N <= 0 { + return fmt.Errorf("unzip %v: content too large", zipfile) + } + } + + return nil +} diff --git a/src/cmd/go/internal/modfetch/web.go b/src/cmd/go/internal/modfetch/web.go new file mode 100644 index 0000000000..b327bf293d --- /dev/null +++ b/src/cmd/go/internal/modfetch/web.go @@ -0,0 +1,31 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !cmd_go_bootstrap + +package modfetch + +import ( + "io" + + web "cmd/go/internal/web2" +) + +// webGetGoGet fetches a go-get=1 URL and returns the body in *body. +// It allows non-200 responses, as usual for these URLs. +func webGetGoGet(url string, body *io.ReadCloser) error { + return web.Get(url, web.Non200OK(), web.Body(body)) +} + +// webGetBytes returns the body returned by an HTTP GET, as a []byte. +// It insists on a 200 response. +func webGetBytes(url string, body *[]byte) error { + return web.Get(url, web.ReadAllBody(body)) +} + +// webGetBody returns the body returned by an HTTP GET, as a io.ReadCloser. +// It insists on a 200 response. +func webGetBody(url string, body *io.ReadCloser) error { + return web.Get(url, web.Body(body)) +} diff --git a/src/cmd/go/internal/modfile/gopkgin.go b/src/cmd/go/internal/modfile/gopkgin.go new file mode 100644 index 0000000000..c94b3848a0 --- /dev/null +++ b/src/cmd/go/internal/modfile/gopkgin.go @@ -0,0 +1,47 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// TODO: Figure out what gopkg.in should do. + +package modfile + +import "strings" + +// ParseGopkgIn splits gopkg.in import paths into their constituent parts +func ParseGopkgIn(path string) (root, repo, major, subdir string, ok bool) { + if !strings.HasPrefix(path, "gopkg.in/") { + return + } + f := strings.Split(path, "/") + if len(f) >= 2 { + if elem, v, ok := dotV(f[1]); ok { + root = strings.Join(f[:2], "/") + repo = "github.com/go-" + elem + "/" + elem + major = v + subdir = strings.Join(f[2:], "/") + return root, repo, major, subdir, true + } + } + if len(f) >= 3 { + if elem, v, ok := dotV(f[2]); ok { + root = strings.Join(f[:3], "/") + repo = "github.com/" + f[1] + "/" + elem + major = v + subdir = strings.Join(f[3:], "/") + return root, repo, major, subdir, true + } + } + return +} + +func dotV(name string) (elem, v string, ok bool) { + i := len(name) - 1 + for i >= 0 && '0' <= name[i] && name[i] <= '9' { + i-- + } + if i <= 2 || i+1 >= len(name) || name[i-1] != '.' || name[i] != 'v' || name[i+1] == '0' && len(name) != i+2 { + return "", "", false + } + return name[:i-1], name[i:], true +} diff --git a/src/cmd/go/internal/modfile/print.go b/src/cmd/go/internal/modfile/print.go new file mode 100644 index 0000000000..cefc43b141 --- /dev/null +++ b/src/cmd/go/internal/modfile/print.go @@ -0,0 +1,164 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Module file printer. + +package modfile + +import ( + "bytes" + "fmt" + "strings" +) + +func Format(f *FileSyntax) []byte { + pr := &printer{} + pr.file(f) + return pr.Bytes() +} + +// A printer collects the state during printing of a file or expression. +type printer struct { + bytes.Buffer // output buffer + comment []Comment // pending end-of-line comments + margin int // left margin (indent), a number of tabs +} + +// printf prints to the buffer. +func (p *printer) printf(format string, args ...interface{}) { + fmt.Fprintf(p, format, args...) +} + +// indent returns the position on the current line, in bytes, 0-indexed. +func (p *printer) indent() int { + b := p.Bytes() + n := 0 + for n < len(b) && b[len(b)-1-n] != '\n' { + n++ + } + return n +} + +// newline ends the current line, flushing end-of-line comments. +func (p *printer) newline() { + if len(p.comment) > 0 { + p.printf(" ") + for i, com := range p.comment { + if i > 0 { + p.trim() + p.printf("\n") + for i := 0; i < p.margin; i++ { + p.printf("\t") + } + } + p.printf("%s", strings.TrimSpace(com.Token)) + } + p.comment = p.comment[:0] + } + + p.trim() + p.printf("\n") + for i := 0; i < p.margin; i++ { + p.printf("\t") + } +} + +// trim removes trailing spaces and tabs from the current line. +func (p *printer) trim() { + // Remove trailing spaces and tabs from line we're about to end. + b := p.Bytes() + n := len(b) + for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') { + n-- + } + p.Truncate(n) +} + +// file formats the given file into the print buffer. +func (p *printer) file(f *FileSyntax) { + for _, com := range f.Before { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + + for i, stmt := range f.Stmt { + switch x := stmt.(type) { + case *CommentBlock: + // comments already handled + p.expr(x) + + default: + p.expr(x) + p.newline() + } + + for _, com := range stmt.Comment().After { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + + if i+1 < len(f.Stmt) { + p.newline() + } + } +} + +func (p *printer) expr(x Expr) { + // Emit line-comments preceding this expression. + if before := x.Comment().Before; len(before) > 0 { + // Want to print a line comment. + // Line comments must be at the current margin. + p.trim() + if p.indent() > 0 { + // There's other text on the line. Start a new line. + p.printf("\n") + } + // Re-indent to margin. + for i := 0; i < p.margin; i++ { + p.printf("\t") + } + for _, com := range before { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + } + + switch x := x.(type) { + default: + panic(fmt.Errorf("printer: unexpected type %T", x)) + + case *CommentBlock: + // done + + case *LParen: + p.printf("(") + case *RParen: + p.printf(")") + + case *Line: + sep := "" + for _, tok := range x.Token { + p.printf("%s%s", sep, tok) + sep = " " + } + + case *LineBlock: + for _, tok := range x.Token { + p.printf("%s ", tok) + } + p.expr(&x.LParen) + p.margin++ + for _, l := range x.Line { + p.newline() + p.expr(l) + } + p.margin-- + p.newline() + p.expr(&x.RParen) + } + + // Queue end-of-line comments for printing when we + // reach the end of the line. + p.comment = append(p.comment, x.Comment().Suffix...) +} diff --git a/src/cmd/go/internal/modfile/read.go b/src/cmd/go/internal/modfile/read.go new file mode 100644 index 0000000000..a0c88d6ca9 --- /dev/null +++ b/src/cmd/go/internal/modfile/read.go @@ -0,0 +1,699 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Module file parser. +// This is a simplified copy of Google's buildifier parser. + +package modfile + +import ( + "bytes" + "fmt" + "os" + "strings" + "unicode" + "unicode/utf8" +) + +// A Position describes the position between two bytes of input. +type Position struct { + Line int // line in input (starting at 1) + LineRune int // rune in line (starting at 1) + Byte int // byte in input (starting at 0) +} + +// add returns the position at the end of s, assuming it starts at p. +func (p Position) add(s string) Position { + p.Byte += len(s) + if n := strings.Count(s, "\n"); n > 0 { + p.Line += n + s = s[strings.LastIndex(s, "\n")+1:] + p.LineRune = 1 + } + p.LineRune += utf8.RuneCountInString(s) + return p +} + +// An Expr represents an input element. +type Expr interface { + // Span returns the start and end position of the expression, + // excluding leading or trailing comments. + Span() (start, end Position) + + // Comment returns the comments attached to the expression. + // This method would normally be named 'Comments' but that + // would interfere with embedding a type of the same name. + Comment() *Comments +} + +// A Comment represents a single // comment. +type Comment struct { + Start Position + Token string // without trailing newline + Suffix bool // an end of line (not whole line) comment +} + +// Comments collects the comments associated with an expression. +type Comments struct { + Before []Comment // whole-line comments before this expression + Suffix []Comment // end-of-line comments after this expression + + // For top-level expressions only, After lists whole-line + // comments following the expression. + After []Comment +} + +// Comment returns the receiver. This isn't useful by itself, but +// a Comments struct is embedded into all the expression +// implementation types, and this gives each of those a Comment +// method to satisfy the Expr interface. +func (c *Comments) Comment() *Comments { + return c +} + +// A FileSyntax represents an entire go.mod file. +type FileSyntax struct { + Name string // file path + Comments + Stmt []Expr +} + +func (x *FileSyntax) Span() (start, end Position) { + if len(x.Stmt) == 0 { + return + } + start, _ = x.Stmt[0].Span() + _, end = x.Stmt[len(x.Stmt)-1].Span() + return start, end +} + +// A CommentBlock represents a top-level block of comments separate +// from any rule. +type CommentBlock struct { + Comments + Start Position +} + +func (x *CommentBlock) Span() (start, end Position) { + return x.Start, x.Start +} + +// A Line is a single line of tokens. +type Line struct { + Comments + Start Position + Token []string + End Position +} + +func (x *Line) Span() (start, end Position) { + return x.Start, x.End +} + +// A LineBlock is a factored block of lines, like +// +// require ( +// "x" +// "y" +// ) +// +type LineBlock struct { + Comments + Start Position + LParen LParen + Token []string + Line []*Line + RParen RParen +} + +func (x *LineBlock) Span() (start, end Position) { + return x.Start, x.RParen.Pos.add(")") +} + +// An LParen represents the beginning of a parenthesized line block. +// It is a place to store suffix comments. +type LParen struct { + Comments + Pos Position +} + +func (x *LParen) Span() (start, end Position) { + return x.Pos, x.Pos.add(")") +} + +// An RParen represents the end of a parenthesized line block. +// It is a place to store whole-line (before) comments. +type RParen struct { + Comments + Pos Position +} + +func (x *RParen) Span() (start, end Position) { + return x.Pos, x.Pos.add(")") +} + +// An input represents a single input file being parsed. +type input struct { + // Lexing state. + filename string // name of input file, for errors + complete []byte // entire input + remaining []byte // remaining input + token []byte // token being scanned + lastToken string // most recently returned token, for error messages + pos Position // current input position + comments []Comment // accumulated comments + endRule int // position of end of current rule + + // Parser state. + file *FileSyntax // returned top-level syntax tree + parseError error // error encountered during parsing + + // Comment assignment state. + pre []Expr // all expressions, in preorder traversal + post []Expr // all expressions, in postorder traversal +} + +func newInput(filename string, data []byte) *input { + return &input{ + filename: filename, + complete: data, + remaining: data, + pos: Position{Line: 1, LineRune: 1, Byte: 0}, + } +} + +// parse parses the input file. +func parse(file string, data []byte) (f *FileSyntax, err error) { + in := newInput(file, data) + // The parser panics for both routine errors like syntax errors + // and for programmer bugs like array index errors. + // Turn both into error returns. Catching bug panics is + // especially important when processing many files. + defer func() { + if e := recover(); e != nil { + if e == in.parseError { + err = in.parseError + } else { + err = fmt.Errorf("%s:%d:%d: internal error: %v", in.filename, in.pos.Line, in.pos.LineRune, e) + } + } + }() + + // Invoke the parser. + in.parseFile() + if in.parseError != nil { + return nil, in.parseError + } + in.file.Name = in.filename + + // Assign comments to nearby syntax. + in.assignComments() + + return in.file, nil +} + +// Error is called to report an error. +// The reason s is often "syntax error". +// Error does not return: it panics. +func (in *input) Error(s string) { + if s == "syntax error" && in.lastToken != "" { + s += " near " + in.lastToken + } + in.parseError = fmt.Errorf("%s:%d:%d: %v", in.filename, in.pos.Line, in.pos.LineRune, s) + panic(in.parseError) +} + +// eof reports whether the input has reached end of file. +func (in *input) eof() bool { + return len(in.remaining) == 0 +} + +// peekRune returns the next rune in the input without consuming it. +func (in *input) peekRune() int { + if len(in.remaining) == 0 { + return 0 + } + r, _ := utf8.DecodeRune(in.remaining) + return int(r) +} + +// peekPrefix reports whether the remaining input begins with the given prefix. +func (in *input) peekPrefix(prefix string) bool { + // This is like bytes.HasPrefix(in.remaining, []byte(prefix)) + // but without the allocation of the []byte copy of prefix. + for i := 0; i < len(prefix); i++ { + if i >= len(in.remaining) || in.remaining[i] != prefix[i] { + return false + } + } + return true +} + +// readRune consumes and returns the next rune in the input. +func (in *input) readRune() int { + if len(in.remaining) == 0 { + in.Error("internal lexer error: readRune at EOF") + } + r, size := utf8.DecodeRune(in.remaining) + in.remaining = in.remaining[size:] + if r == '\n' { + in.pos.Line++ + in.pos.LineRune = 1 + } else { + in.pos.LineRune++ + } + in.pos.Byte += size + return int(r) +} + +type symType struct { + pos Position + endPos Position + text string +} + +// startToken marks the beginning of the next input token. +// It must be followed by a call to endToken, once the token has +// been consumed using readRune. +func (in *input) startToken(sym *symType) { + in.token = in.remaining + sym.text = "" + sym.pos = in.pos +} + +// endToken marks the end of an input token. +// It records the actual token string in sym.text if the caller +// has not done that already. +func (in *input) endToken(sym *symType) { + if sym.text == "" { + tok := string(in.token[:len(in.token)-len(in.remaining)]) + sym.text = tok + in.lastToken = sym.text + } + sym.endPos = in.pos +} + +// lex is called from the parser to obtain the next input token. +// It returns the token value (either a rune like '+' or a symbolic token _FOR) +// and sets val to the data associated with the token. +// For all our input tokens, the associated data is +// val.Pos (the position where the token begins) +// and val.Token (the input string corresponding to the token). +func (in *input) lex(sym *symType) int { + // Skip past spaces, stopping at non-space or EOF. + countNL := 0 // number of newlines we've skipped past + for !in.eof() { + // Skip over spaces. Count newlines so we can give the parser + // information about where top-level blank lines are, + // for top-level comment assignment. + c := in.peekRune() + if c == ' ' || c == '\t' || c == '\r' { + in.readRune() + continue + } + + // Comment runs to end of line. + if in.peekPrefix("//") { + in.startToken(sym) + + // Is this comment the only thing on its line? + // Find the last \n before this // and see if it's all + // spaces from there to here. + i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n")) + suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0 + in.readRune() + in.readRune() + + // Consume comment. + for len(in.remaining) > 0 && in.readRune() != '\n' { + } + in.endToken(sym) + + sym.text = strings.TrimRight(sym.text, "\n") + in.lastToken = "comment" + + // If we are at top level (not in a statement), hand the comment to + // the parser as a _COMMENT token. The grammar is written + // to handle top-level comments itself. + if !suffix { + // Not in a statement. Tell parser about top-level comment. + return _COMMENT + } + + // Otherwise, save comment for later attachment to syntax tree. + if countNL > 1 { + in.comments = append(in.comments, Comment{sym.pos, "", false}) + } + in.comments = append(in.comments, Comment{sym.pos, sym.text, suffix}) + countNL = 1 + return _EOL + } + + if in.peekPrefix("/*") { + in.Error(fmt.Sprintf("mod files must use // comments (not /* */ comments)")) + } + + // Found non-space non-comment. + break + } + + // Found the beginning of the next token. + in.startToken(sym) + defer in.endToken(sym) + + // End of file. + if in.eof() { + in.lastToken = "EOF" + return _EOF + } + + // Punctuation tokens. + switch c := in.peekRune(); c { + case '\n': + in.readRune() + return c + + case '(': + in.readRune() + return c + + case ')': + in.readRune() + return c + + case '"', '`': // quoted string + quote := c + in.readRune() + for { + if in.eof() { + in.pos = sym.pos + in.Error("unexpected EOF in string") + } + if in.peekRune() == '\n' { + in.Error("unexpected newline in string") + } + c := in.readRune() + if c == quote { + break + } + if c == '\\' && quote != '`' { + if in.eof() { + in.pos = sym.pos + in.Error("unexpected EOF in string") + } + in.readRune() + } + } + in.endToken(sym) + return _STRING + } + + // Checked all punctuation. Must be identifier token. + if c := in.peekRune(); !isIdent(c) { + in.Error(fmt.Sprintf("unexpected input character %#q", c)) + } + + // Scan over identifier. + for isIdent(in.peekRune()) { + if in.peekPrefix("//") { + break + } + if in.peekPrefix("/*") { + in.Error(fmt.Sprintf("mod files must use // comments (not /* */ comments)")) + } + in.readRune() + } + return _IDENT +} + +// isIdent reports whether c is an identifier rune. +// We treat nearly all runes as identifier runes. +func isIdent(c int) bool { + return c != 0 && !unicode.IsSpace(rune(c)) +} + +// Comment assignment. +// We build two lists of all subexpressions, preorder and postorder. +// The preorder list is ordered by start location, with outer expressions first. +// The postorder list is ordered by end location, with outer expressions last. +// We use the preorder list to assign each whole-line comment to the syntax +// immediately following it, and we use the postorder list to assign each +// end-of-line comment to the syntax immediately preceding it. + +// order walks the expression adding it and its subexpressions to the +// preorder and postorder lists. +func (in *input) order(x Expr) { + if x != nil { + in.pre = append(in.pre, x) + } + switch x := x.(type) { + default: + panic(fmt.Errorf("order: unexpected type %T", x)) + case nil: + // nothing + case *LParen, *RParen: + // nothing + case *CommentBlock: + // nothing + case *Line: + // nothing + case *FileSyntax: + for _, stmt := range x.Stmt { + in.order(stmt) + } + case *LineBlock: + in.order(&x.LParen) + for _, l := range x.Line { + in.order(l) + } + in.order(&x.RParen) + } + if x != nil { + in.post = append(in.post, x) + } +} + +// assignComments attaches comments to nearby syntax. +func (in *input) assignComments() { + const debug = false + + // Generate preorder and postorder lists. + in.order(in.file) + + // Split into whole-line comments and suffix comments. + var line, suffix []Comment + for _, com := range in.comments { + if com.Suffix { + suffix = append(suffix, com) + } else { + line = append(line, com) + } + } + + if debug { + for _, c := range line { + fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte) + } + } + + // Assign line comments to syntax immediately following. + for _, x := range in.pre { + start, _ := x.Span() + if debug { + fmt.Printf("pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte) + } + xcom := x.Comment() + for len(line) > 0 && start.Byte >= line[0].Start.Byte { + if debug { + fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte) + } + xcom.Before = append(xcom.Before, line[0]) + line = line[1:] + } + } + + // Remaining line comments go at end of file. + in.file.After = append(in.file.After, line...) + + if debug { + for _, c := range suffix { + fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte) + } + } + + // Assign suffix comments to syntax immediately before. + for i := len(in.post) - 1; i >= 0; i-- { + x := in.post[i] + + start, end := x.Span() + if debug { + fmt.Printf("post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte) + } + + // Do not assign suffix comments to end of line block or whole file. + // Instead assign them to the last element inside. + switch x.(type) { + case *FileSyntax: + continue + } + + // Do not assign suffix comments to something that starts + // on an earlier line, so that in + // + // x ( y + // z ) // comment + // + // we assign the comment to z and not to x ( ... ). + if start.Line != end.Line { + continue + } + xcom := x.Comment() + for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte { + if debug { + fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte) + } + xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1]) + suffix = suffix[:len(suffix)-1] + } + } + + // We assigned suffix comments in reverse. + // If multiple suffix comments were appended to the same + // expression node, they are now in reverse. Fix that. + for _, x := range in.post { + reverseComments(x.Comment().Suffix) + } + + // Remaining suffix comments go at beginning of file. + in.file.Before = append(in.file.Before, suffix...) +} + +// reverseComments reverses the []Comment list. +func reverseComments(list []Comment) { + for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { + list[i], list[j] = list[j], list[i] + } +} + +func (in *input) parseFile() { + in.file = new(FileSyntax) + var sym symType + var cb *CommentBlock + for { + tok := in.lex(&sym) + switch tok { + case '\n': + if cb != nil { + in.file.Stmt = append(in.file.Stmt, cb) + cb = nil + } + case _COMMENT: + if cb == nil { + cb = &CommentBlock{Start: sym.pos} + } + com := cb.Comment() + com.Before = append(com.Before, Comment{Start: sym.pos, Token: sym.text}) + case _EOF: + if cb != nil { + in.file.Stmt = append(in.file.Stmt, cb) + } + return + default: + in.parseStmt(&sym) + if cb != nil { + in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before + cb = nil + } + } + } +} + +func (in *input) parseStmt(sym *symType) { + start := sym.pos + end := sym.endPos + token := []string{sym.text} + for { + tok := in.lex(sym) + switch tok { + case '\n', _EOF, _EOL: + in.file.Stmt = append(in.file.Stmt, &Line{ + Start: start, + Token: token, + End: end, + }) + return + case '(': + in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, token, sym)) + return + default: + token = append(token, sym.text) + end = sym.endPos + } + } +} + +func (in *input) parseLineBlock(start Position, token []string, sym *symType) *LineBlock { + x := &LineBlock{ + Start: start, + Token: token, + LParen: LParen{Pos: sym.pos}, + } + var comments []Comment + for { + tok := in.lex(sym) + switch tok { + case _EOL: + // ignore + case '\n': + if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" { + comments = append(comments, Comment{}) + } + case _COMMENT: + comments = append(comments, Comment{Start: sym.pos, Token: sym.text}) + case _EOF: + in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune)) + case ')': + x.RParen.Before = comments + x.RParen.Pos = sym.pos + tok = in.lex(sym) + if tok != '\n' && tok != _EOF && tok != _EOL { + in.Error("syntax error (expected newline after closing paren)") + } + return x + default: + l := in.parseLine(sym) + x.Line = append(x.Line, l) + l.Comment().Before = comments + comments = nil + } + } +} + +func (in *input) parseLine(sym *symType) *Line { + start := sym.pos + end := sym.endPos + token := []string{sym.text} + for { + tok := in.lex(sym) + switch tok { + case '\n', _EOF, _EOL: + return &Line{ + Start: start, + Token: token, + End: end, + } + default: + token = append(token, sym.text) + end = sym.endPos + } + } +} + +const ( + _EOF = -(1 + iota) + _EOL + _IDENT + _STRING + _COMMENT +) diff --git a/src/cmd/go/internal/modfile/read_test.go b/src/cmd/go/internal/modfile/read_test.go new file mode 100644 index 0000000000..2c617b82ae --- /dev/null +++ b/src/cmd/go/internal/modfile/read_test.go @@ -0,0 +1,306 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfile + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "reflect" + "strings" + "testing" +) + +// exists reports whether the named file exists. +func exists(name string) bool { + _, err := os.Stat(name) + return err == nil +} + +// Test that reading and then writing the golden files +// does not change their output. +func TestPrintGolden(t *testing.T) { + outs, err := filepath.Glob("testdata/*.golden") + if err != nil { + t.Fatal(err) + } + for _, out := range outs { + testPrint(t, out, out) + } +} + +// testPrint is a helper for testing the printer. +// It reads the file named in, reformats it, and compares +// the result to the file named out. +func testPrint(t *testing.T, in, out string) { + data, err := ioutil.ReadFile(in) + if err != nil { + t.Error(err) + return + } + + golden, err := ioutil.ReadFile(out) + if err != nil { + t.Error(err) + return + } + + base := "testdata/" + filepath.Base(in) + f, err := parse(in, data) + if err != nil { + t.Error(err) + return + } + + ndata := Format(f) + + if !bytes.Equal(ndata, golden) { + t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) + tdiff(t, string(golden), string(ndata)) + return + } +} + +// Test that when files in the testdata directory are parsed +// and printed and parsed again, we get the same parse tree +// both times. +func TestPrintParse(t *testing.T) { + outs, err := filepath.Glob("testdata/*") + if err != nil { + t.Fatal(err) + } + for _, out := range outs { + data, err := ioutil.ReadFile(out) + if err != nil { + t.Error(err) + continue + } + + base := "testdata/" + filepath.Base(out) + f, err := parse(base, data) + if err != nil { + t.Errorf("parsing original: %v", err) + continue + } + + ndata := Format(f) + f2, err := parse(base, ndata) + if err != nil { + t.Errorf("parsing reformatted: %v", err) + continue + } + + eq := eqchecker{file: base} + if err := eq.check(f, f2); err != nil { + t.Errorf("not equal (parse/Format/parse): %v", err) + } + + pf1, err := Parse(base, data, nil) + if err != nil { + switch base { + case "testdata/replace2.in", "testdata/gopkg.in.golden": + t.Errorf("should parse %v: %v", base, err) + } + } + if err == nil { + pf2, err := Parse(base, ndata, nil) + if err != nil { + t.Errorf("Parsing reformatted: %v", err) + continue + } + eq := eqchecker{file: base} + if err := eq.check(pf1, pf2); err != nil { + t.Errorf("not equal (parse/Format/Parse): %v", err) + } + + ndata2, err := pf1.Format() + if err != nil { + t.Errorf("reformat: %v", err) + } + pf3, err := Parse(base, ndata2, nil) + if err != nil { + t.Errorf("Parsing reformatted2: %v", err) + continue + } + eq = eqchecker{file: base} + if err := eq.check(pf1, pf3); err != nil { + t.Errorf("not equal (Parse/Format/Parse): %v", err) + } + ndata = ndata2 + } + + if strings.HasSuffix(out, ".in") { + golden, err := ioutil.ReadFile(strings.TrimSuffix(out, ".in") + ".golden") + if err != nil { + t.Error(err) + continue + } + if !bytes.Equal(ndata, golden) { + t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) + tdiff(t, string(golden), string(ndata)) + return + } + } + } +} + +// An eqchecker holds state for checking the equality of two parse trees. +type eqchecker struct { + file string + pos Position +} + +// errorf returns an error described by the printf-style format and arguments, +// inserting the current file position before the error text. +func (eq *eqchecker) errorf(format string, args ...interface{}) error { + return fmt.Errorf("%s:%d: %s", eq.file, eq.pos.Line, + fmt.Sprintf(format, args...)) +} + +// check checks that v and w represent the same parse tree. +// If not, it returns an error describing the first difference. +func (eq *eqchecker) check(v, w interface{}) error { + return eq.checkValue(reflect.ValueOf(v), reflect.ValueOf(w)) +} + +var ( + posType = reflect.TypeOf(Position{}) + commentsType = reflect.TypeOf(Comments{}) +) + +// checkValue checks that v and w represent the same parse tree. +// If not, it returns an error describing the first difference. +func (eq *eqchecker) checkValue(v, w reflect.Value) error { + // inner returns the innermost expression for v. + // if v is a non-nil interface value, it returns the concrete + // value in the interface. + inner := func(v reflect.Value) reflect.Value { + for { + if v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + continue + } + break + } + return v + } + + v = inner(v) + w = inner(w) + if v.Kind() == reflect.Invalid && w.Kind() == reflect.Invalid { + return nil + } + if v.Kind() == reflect.Invalid { + return eq.errorf("nil interface became %s", w.Type()) + } + if w.Kind() == reflect.Invalid { + return eq.errorf("%s became nil interface", v.Type()) + } + + if v.Type() != w.Type() { + return eq.errorf("%s became %s", v.Type(), w.Type()) + } + + if p, ok := v.Interface().(Expr); ok { + eq.pos, _ = p.Span() + } + + switch v.Kind() { + default: + return eq.errorf("unexpected type %s", v.Type()) + + case reflect.Bool, reflect.Int, reflect.String: + vi := v.Interface() + wi := w.Interface() + if vi != wi { + return eq.errorf("%v became %v", vi, wi) + } + + case reflect.Slice: + vl := v.Len() + wl := w.Len() + for i := 0; i < vl || i < wl; i++ { + if i >= vl { + return eq.errorf("unexpected %s", w.Index(i).Type()) + } + if i >= wl { + return eq.errorf("missing %s", v.Index(i).Type()) + } + if err := eq.checkValue(v.Index(i), w.Index(i)); err != nil { + return err + } + } + + case reflect.Struct: + // Fields in struct must match. + t := v.Type() + n := t.NumField() + for i := 0; i < n; i++ { + tf := t.Field(i) + switch { + default: + if err := eq.checkValue(v.Field(i), w.Field(i)); err != nil { + return err + } + + case tf.Type == posType: // ignore positions + case tf.Type == commentsType: // ignore comment assignment + } + } + + case reflect.Ptr, reflect.Interface: + if v.IsNil() != w.IsNil() { + if v.IsNil() { + return eq.errorf("unexpected %s", w.Elem().Type()) + } + return eq.errorf("missing %s", v.Elem().Type()) + } + if err := eq.checkValue(v.Elem(), w.Elem()); err != nil { + return err + } + } + return nil +} + +// diff returns the output of running diff on b1 and b2. +func diff(b1, b2 []byte) (data []byte, err error) { + f1, err := ioutil.TempFile("", "testdiff") + if err != nil { + return nil, err + } + defer os.Remove(f1.Name()) + defer f1.Close() + + f2, err := ioutil.TempFile("", "testdiff") + if err != nil { + return nil, err + } + defer os.Remove(f2.Name()) + defer f2.Close() + + f1.Write(b1) + f2.Write(b2) + + data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() + if len(data) > 0 { + // diff exits with a non-zero status when the files don't match. + // Ignore that failure as long as we get output. + err = nil + } + return +} + +// tdiff logs the diff output to t.Error. +func tdiff(t *testing.T, a, b string) { + data, err := diff([]byte(a), []byte(b)) + if err != nil { + t.Error(err) + return + } + t.Error(string(data)) +} diff --git a/src/cmd/go/internal/modfile/rule.go b/src/cmd/go/internal/modfile/rule.go new file mode 100644 index 0000000000..5a784a3a33 --- /dev/null +++ b/src/cmd/go/internal/modfile/rule.go @@ -0,0 +1,507 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfile + +import ( + "bytes" + "errors" + "fmt" + "path/filepath" + "sort" + "strconv" + "strings" + "unicode" + + "cmd/go/internal/module" + "cmd/go/internal/semver" +) + +type File struct { + Module *Module + Require []*Require + Exclude []*Exclude + Replace []*Replace + + Syntax *FileSyntax +} + +type Module struct { + Mod module.Version + Major string +} + +type Require struct { + Mod module.Version + Syntax *Line +} + +type Exclude struct { + Mod module.Version + Syntax *Line +} + +type Replace struct { + Old module.Version + New module.Version + + Syntax *Line +} + +func (f *File) AddModuleStmt(path string) { + f.Module = &Module{ + Mod: module.Version{Path: path}, + } + if f.Syntax == nil { + f.Syntax = new(FileSyntax) + } + f.Syntax.Stmt = append(f.Syntax.Stmt, &Line{ + Token: []string{"module", AutoQuote(path)}, + }) +} + +func (f *File) AddComment(text string) { + if f.Syntax == nil { + f.Syntax = new(FileSyntax) + } + f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{ + Comments: Comments{ + Before: []Comment{ + { + Token: text, + }, + }, + }, + }) +} + +type VersionFixer func(path, version string) (string, error) + +func Parse(file string, data []byte, fix VersionFixer) (*File, error) { + fs, err := parse(file, data) + if err != nil { + return nil, err + } + f := &File{ + Syntax: fs, + } + + var errs bytes.Buffer + for _, x := range fs.Stmt { + switch x := x.(type) { + case *Line: + f.add(&errs, x, x.Token[0], x.Token[1:], fix) + + case *LineBlock: + if len(x.Token) > 1 { + fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " ")) + continue + } + switch x.Token[0] { + default: + fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " ")) + continue + case "module", "require", "exclude", "replace": + for _, l := range x.Line { + f.add(&errs, l, x.Token[0], l.Token, fix) + } + } + } + } + + if errs.Len() > 0 { + return nil, errors.New(strings.TrimRight(errs.String(), "\n")) + } + return f, nil +} + +func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer) { + // TODO: We should pass in a flag saying whether this module is a dependency. + // If so, we should ignore all unknown directives and not attempt to parse + // replace and exclude either. They don't matter, and it will work better for + // forward compatibility if we can depend on modules that have local changes. + + // TODO: For the target module (not dependencies), maybe we should + // relax the semver requirement and rewrite the file with updated info + // after resolving any versions. That would let people type commit hashes + // or tags or branch names, and then vgo would fix them. + + switch verb { + default: + fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb) + case "module": + if f.Module != nil { + fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line) + return + } + f.Module = new(Module) + if len(args) != 1 { + + fmt.Fprintf(errs, "%s:%d: usage: module module/path [version]\n", f.Syntax.Name, line.Start.Line) + return + } + s, err := parseString(&args[0]) + if err != nil { + fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) + return + } + f.Module.Mod = module.Version{Path: s} + case "require", "exclude": + if len(args) != 2 { + fmt.Fprintf(errs, "%s:%d: usage: %s module/path v1.2.3\n", f.Syntax.Name, line.Start.Line, verb) + return + } + s, err := parseString(&args[0]) + if err != nil { + fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) + return + } + old := args[1] + v, err := parseVersion(s, &args[1], fix) + if err != nil { + fmt.Fprintf(errs, "%s:%d: invalid module version %q: %v\n", f.Syntax.Name, line.Start.Line, old, err) + return + } + v1, err := moduleMajorVersion(s) + if err != nil { + fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) + return + } + if v2 := semver.Major(v); v1 != v2 && (v1 != "v1" || v2 != "v0") { + fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, v1, v2, v) + return + } + if verb == "require" { + f.Require = append(f.Require, &Require{ + Mod: module.Version{Path: s, Version: v}, + Syntax: line, + }) + } else { + f.Exclude = append(f.Exclude, &Exclude{ + Mod: module.Version{Path: s, Version: v}, + Syntax: line, + }) + } + case "replace": + if len(args) < 4 || len(args) > 5 || args[2] != "=>" { + fmt.Fprintf(errs, "%s:%d: usage: %s module/path v1.2.3 => other/module v1.4\n\t or %s module/path v1.2.3 => ../local/directory", f.Syntax.Name, line.Start.Line, verb, verb) + return + } + s, err := parseString(&args[0]) + if err != nil { + fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) + return + } + old := args[1] + v, err := parseVersion(s, &args[1], fix) + if err != nil { + fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err) + return + } + v1, err := moduleMajorVersion(s) + if err != nil { + fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err) + return + } + if v2 := semver.Major(v); v1 != v2 && (v1 != "v1" || v2 != "v0") { + fmt.Fprintf(errs, "%s:%d: invalid module: %s should be %s, not %s (%s)\n", f.Syntax.Name, line.Start.Line, s, v1, v2, v) + return + } + ns, err := parseString(&args[3]) + if err != nil { + fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err) + return + } + nv := "" + if len(args) == 4 { + if !isDirectoryPath(ns) { + fmt.Fprintf(errs, "%s:%d: replacement module without version must be directory path (rooted or starting with ./ or ../)", f.Syntax.Name, line.Start.Line) + return + } + if filepath.Separator == '/' && strings.Contains(ns, `\`) { + fmt.Fprintf(errs, "%s:%d: replacement directory appears to be Windows path (on a non-windows system)", f.Syntax.Name, line.Start.Line) + return + } + } + if len(args) == 5 { + old := args[4] + nv, err = parseVersion(ns, &args[4], fix) + if err != nil { + fmt.Fprintf(errs, "%s:%d: invalid module version %v: %v\n", f.Syntax.Name, line.Start.Line, old, err) + return + } + if isDirectoryPath(ns) { + fmt.Fprintf(errs, "%s:%d: replacement module directory path %q cannot have version", f.Syntax.Name, line.Start.Line, ns) + return + } + } + // TODO: More sanity checks about directories vs module paths. + f.Replace = append(f.Replace, &Replace{ + Old: module.Version{Path: s, Version: v}, + New: module.Version{Path: ns, Version: nv}, + Syntax: line, + }) + } +} + +func isDirectoryPath(ns string) bool { + // Because go.mod files can move from one system to another, + // we check all known path syntaxes, both Unix and Windows. + return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") || + strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) || + len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':' +} + +func mustQuote(t string) bool { + for _, r := range t { + if !unicode.IsPrint(r) || r == ' ' || r == '"' || r == '\'' || r == '`' { + return true + } + } + return t == "" || strings.Contains(t, "//") || strings.Contains(t, "/*") +} + +// AutoQuote returns s or, if quoting is required for s to appear in a go.mod, +// the quotation of s. +func AutoQuote(s string) string { + if mustQuote(s) { + return strconv.Quote(s) + } + return s +} + +func parseString(s *string) (string, error) { + t := *s + if strings.HasPrefix(t, `"`) { + var err error + if t, err = strconv.Unquote(t); err != nil { + return "", err + } + } else if strings.ContainsAny(t, "\"'`") { + // Other quotes are reserved both for possible future expansion + // and to avoid confusion. For example if someone types 'x' + // we want that to be a syntax error and not a literal x in literal quotation marks. + return "", fmt.Errorf("unquoted string cannot contain quote") + } + *s = AutoQuote(t) + return t, nil +} + +func parseVersion(path string, s *string, fix VersionFixer) (string, error) { + t, err := parseString(s) + if err != nil { + return "", err + } + if fix != nil { + var err error + t, err = fix(path, t) + if err != nil { + return "", err + } + } + if semver.IsValid(t) { + *s = semver.Canonical(t) + return *s, nil + } + return "", fmt.Errorf("version must be of the form v1.2.3") +} + +func moduleMajorVersion(p string) (string, error) { + if _, _, major, _, ok := ParseGopkgIn(p); ok { + return major, nil + } + + start := strings.LastIndex(p, "/") + 1 + v := p[start:] + if !isMajorVersion(v) { + return "v1", nil + } + if v[1] == '0' || v == "v1" { + return "", fmt.Errorf("module path has invalid version number %s", v) + } + return v, nil +} + +func isMajorVersion(v string) bool { + if len(v) < 2 || v[0] != 'v' { + return false + } + for i := 1; i < len(v); i++ { + if v[i] < '0' || '9' < v[i] { + return false + } + } + return true +} + +func (f *File) Format() ([]byte, error) { + return Format(f.Syntax), nil +} + +func (x *File) AddRequire(path, vers string) { + var syntax *Line + + for i, stmt := range x.Syntax.Stmt { + switch stmt := stmt.(type) { + case *LineBlock: + if len(stmt.Token) > 0 && stmt.Token[0] == "require" { + syntax = &Line{Token: []string{AutoQuote(path), vers}} + stmt.Line = append(stmt.Line, syntax) + goto End + } + case *Line: + if len(stmt.Token) > 0 && stmt.Token[0] == "require" { + stmt.Token = stmt.Token[1:] + syntax = &Line{Token: []string{AutoQuote(path), vers}} + x.Syntax.Stmt[i] = &LineBlock{ + Comments: stmt.Comments, + Token: []string{"require"}, + Line: []*Line{ + stmt, + syntax, + }, + } + goto End + } + } + } + + syntax = &Line{Token: []string{"require", AutoQuote(path), vers}} + x.Syntax.Stmt = append(x.Syntax.Stmt, syntax) + +End: + x.Require = append(x.Require, &Require{module.Version{Path: path, Version: vers}, syntax}) +} + +func (f *File) SetRequire(req []module.Version) { + need := make(map[string]string) + for _, m := range req { + need[m.Path] = m.Version + } + + for _, r := range f.Require { + if v, ok := need[r.Mod.Path]; ok { + r.Mod.Version = v + } + } + + var newStmts []Expr + for _, stmt := range f.Syntax.Stmt { + switch stmt := stmt.(type) { + case *LineBlock: + if len(stmt.Token) > 0 && stmt.Token[0] == "require" { + var newLines []*Line + for _, line := range stmt.Line { + if p, err := strconv.Unquote(line.Token[0]); err == nil && need[p] != "" { + line.Token[1] = need[p] + delete(need, p) + newLines = append(newLines, line) + } + } + if len(newLines) == 0 { + continue // drop stmt + } + stmt.Line = newLines + } + + case *Line: + if len(stmt.Token) > 0 && stmt.Token[0] == "require" { + if p, err := strconv.Unquote(stmt.Token[1]); err == nil && need[p] != "" { + stmt.Token[2] = need[p] + delete(need, p) + } else { + continue // drop stmt + } + } + } + newStmts = append(newStmts, stmt) + } + f.Syntax.Stmt = newStmts + + for path, vers := range need { + f.AddRequire(path, vers) + } + f.SortBlocks() +} + +func (f *File) SortBlocks() { + f.removeDups() // otherwise sorting is unsafe + + for _, stmt := range f.Syntax.Stmt { + block, ok := stmt.(*LineBlock) + if !ok { + continue + } + sort.Slice(block.Line, func(i, j int) bool { + li := block.Line[i] + lj := block.Line[j] + for k := 0; k < len(li.Token) && k < len(lj.Token); k++ { + if li.Token[k] != lj.Token[k] { + return li.Token[k] < lj.Token[k] + } + } + return len(li.Token) < len(lj.Token) + }) + } +} + +func (f *File) removeDups() { + have := make(map[module.Version]bool) + kill := make(map[*Line]bool) + for _, x := range f.Exclude { + if have[x.Mod] { + kill[x.Syntax] = true + continue + } + have[x.Mod] = true + } + var excl []*Exclude + for _, x := range f.Exclude { + if !kill[x.Syntax] { + excl = append(excl, x) + } + } + f.Exclude = excl + + have = make(map[module.Version]bool) + // Later replacements take priority over earlier ones. + for i := len(f.Replace) - 1; i >= 0; i-- { + x := f.Replace[i] + if have[x.Old] { + kill[x.Syntax] = true + continue + } + have[x.Old] = true + } + var repl []*Replace + for _, x := range f.Replace { + if !kill[x.Syntax] { + repl = append(repl, x) + } + } + f.Replace = repl + + var stmts []Expr + for _, stmt := range f.Syntax.Stmt { + switch stmt := stmt.(type) { + case *Line: + if kill[stmt] { + continue + } + case *LineBlock: + var lines []*Line + for _, line := range stmt.Line { + if !kill[line] { + lines = append(lines, line) + } + } + stmt.Line = lines + if len(lines) == 0 { + continue + } + } + stmts = append(stmts, stmt) + } + f.Syntax.Stmt = stmts +} diff --git a/src/cmd/go/internal/modfile/testdata/block.golden b/src/cmd/go/internal/modfile/testdata/block.golden new file mode 100644 index 0000000000..4aa2d634fc --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/block.golden @@ -0,0 +1,29 @@ +// comment +x "y" z + +// block +block ( // block-eol + // x-before-line + + "x" ( y // x-eol + "x1" + "x2" + // line + "x3" + "x4" + + "x5" + + // y-line + "y" // y-eol + + "z" // z-eol +) // block-eol2 + +block2 ( + x + y + z +) + +// eof diff --git a/src/cmd/go/internal/modfile/testdata/block.in b/src/cmd/go/internal/modfile/testdata/block.in new file mode 100644 index 0000000000..1dfae65f5c --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/block.in @@ -0,0 +1,29 @@ +// comment +x "y" z + +// block +block ( // block-eol + // x-before-line + + "x" ( y // x-eol + "x1" + "x2" + // line + "x3" + "x4" + + "x5" + + // y-line + "y" // y-eol + + "z" // z-eol +) // block-eol2 + + +block2 (x + y + z +) + +// eof diff --git a/src/cmd/go/internal/modfile/testdata/comment.golden b/src/cmd/go/internal/modfile/testdata/comment.golden new file mode 100644 index 0000000000..75f3b84478 --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/comment.golden @@ -0,0 +1,10 @@ +// comment +module "x" // eol + +// mid comment + +// comment 2 +// comment 2 line 2 +module "y" // eoy + +// comment 3 diff --git a/src/cmd/go/internal/modfile/testdata/comment.in b/src/cmd/go/internal/modfile/testdata/comment.in new file mode 100644 index 0000000000..bfc2492b26 --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/comment.in @@ -0,0 +1,8 @@ +// comment +module "x" // eol +// mid comment + +// comment 2 +// comment 2 line 2 +module "y" // eoy +// comment 3 diff --git a/src/cmd/go/internal/modfile/testdata/empty.golden b/src/cmd/go/internal/modfile/testdata/empty.golden new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/cmd/go/internal/modfile/testdata/empty.in b/src/cmd/go/internal/modfile/testdata/empty.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/cmd/go/internal/modfile/testdata/gopkg.in.golden b/src/cmd/go/internal/modfile/testdata/gopkg.in.golden new file mode 100644 index 0000000000..41669b3a73 --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/gopkg.in.golden @@ -0,0 +1,6 @@ +module x + +require ( + gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528 + gopkg.in/yaml.v2 v2.2.1 +) diff --git a/src/cmd/go/internal/modfile/testdata/module.golden b/src/cmd/go/internal/modfile/testdata/module.golden new file mode 100644 index 0000000000..78ba94398c --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/module.golden @@ -0,0 +1 @@ +module abc diff --git a/src/cmd/go/internal/modfile/testdata/module.in b/src/cmd/go/internal/modfile/testdata/module.in new file mode 100644 index 0000000000..08f383623f --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/module.in @@ -0,0 +1 @@ +module "abc" diff --git a/src/cmd/go/internal/modfile/testdata/replace.golden b/src/cmd/go/internal/modfile/testdata/replace.golden new file mode 100644 index 0000000000..5d6abcfcda --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/replace.golden @@ -0,0 +1,5 @@ +module abc + +replace xyz v1.2.3 => /tmp/z + +replace xyz v1.3.4 => my/xyz v1.3.4-me diff --git a/src/cmd/go/internal/modfile/testdata/replace.in b/src/cmd/go/internal/modfile/testdata/replace.in new file mode 100644 index 0000000000..685249946a --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/replace.in @@ -0,0 +1,5 @@ +module "abc" + +replace "xyz" v1.2.3 => "/tmp/z" + +replace "xyz" v1.3.4 => "my/xyz" v1.3.4-me diff --git a/src/cmd/go/internal/modfile/testdata/replace2.golden b/src/cmd/go/internal/modfile/testdata/replace2.golden new file mode 100644 index 0000000000..1d18a3b461 --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/replace2.golden @@ -0,0 +1,8 @@ +module abc + +replace ( + xyz v1.2.3 => /tmp/z + xyz v1.3.4 => my/xyz v1.3.4-me + xyz v1.4.5 => "/tmp/my dir" + xyz v1.5.6 => my/xyz v1.5.6 +) diff --git a/src/cmd/go/internal/modfile/testdata/replace2.in b/src/cmd/go/internal/modfile/testdata/replace2.in new file mode 100644 index 0000000000..78c46694a2 --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/replace2.in @@ -0,0 +1,8 @@ +module "abc" + +replace ( + "xyz" v1.2.3 => "/tmp/z" + "xyz" v1.3.4 => "my/xyz" "v1.3.4-me" + xyz "v1.4.5" => "/tmp/my dir" + xyz v1.5.6 => my/xyz v1.5.6 +) diff --git a/src/cmd/go/internal/modfile/testdata/rule1.golden b/src/cmd/go/internal/modfile/testdata/rule1.golden new file mode 100644 index 0000000000..8a5c725894 --- /dev/null +++ b/src/cmd/go/internal/modfile/testdata/rule1.golden @@ -0,0 +1,7 @@ +module "x" + +module "y" + +require "x" + +require x diff --git a/src/cmd/go/internal/modinfo/info.go b/src/cmd/go/internal/modinfo/info.go new file mode 100644 index 0000000000..6dff2d2c26 --- /dev/null +++ b/src/cmd/go/internal/modinfo/info.go @@ -0,0 +1,11 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modinfo + +type ModulePublic struct { + Top bool + Path string + Version string +} diff --git a/src/cmd/go/internal/module/module.go b/src/cmd/go/internal/module/module.go new file mode 100644 index 0000000000..1e8f74c1af --- /dev/null +++ b/src/cmd/go/internal/module/module.go @@ -0,0 +1,195 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package module defines the module.Version type +// along with support code. +package module + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" + + "cmd/go/internal/semver" +) + +// A Version is defined by a module path and version pair. +type Version struct { + Path string + Version string +} + +// Check checks that a given module path, version pair is valid. +// In addition to the path being a valid module path +// and the version being a valid semantic version, +// the two must correspond. +// For example, the path "yaml/v2" only corresponds to +// semantic versions beginning with "v2.". +func Check(path, version string) error { + if err := CheckPath(path); err != nil { + return err + } + if !semver.IsValid(version) { + return fmt.Errorf("malformed semantic version %v", version) + } + vm := semver.Major(version) + _, pathVersion, _ := SplitPathVersion(path) + + if strings.HasPrefix(pathVersion, ".") { + // Special-case gopkg.in path requirements. + pathVersion = pathVersion[1:] // cut . + if vm == pathVersion { + return nil + } + } else { + // Standard path requirements. + if pathVersion != "" { + pathVersion = pathVersion[1:] // cut / + } + if vm == "v0" || vm == "v1" { + vm = "" + } + if vm == pathVersion { + return nil + } + if pathVersion == "" { + pathVersion = "v0 or v1" + } + } + return fmt.Errorf("mismatched module path %v and version %v (want %v)", path, version, pathVersion) +} + +// firstPathOK reports whether r can appear in the first element of a module path. +// The first element of the path must be an LDH domain name, at least for now. +func firstPathOK(r rune) bool { + return r == '-' || r == '.' || + '0' <= r && r <= '9' || + 'A' <= r && r <= 'Z' || + 'a' <= r && r <= 'z' +} + +// pathOK reports whether r can appear in a module path. +// Paths must avoid potentially problematic ASCII punctuation +// and control characters but otherwise can be any Unicode printable character, +// as defined by Go's IsPrint. +func pathOK(r rune) bool { + if r < utf8.RuneSelf { + return r == '+' || r == ',' || r == '-' || r == '.' || r == '/' || r == '_' || r == '~' || + '0' <= r && r <= '9' || + 'A' <= r && r <= 'Z' || + 'a' <= r && r <= 'z' + } + return unicode.IsPrint(r) +} + +// CheckPath checks that a module path is valid. +func CheckPath(path string) error { + if !utf8.ValidString(path) { + return fmt.Errorf("malformed module path %q: invalid UTF-8", path) + } + if path == "" { + return fmt.Errorf("malformed module path %q: empty string", path) + } + + i := strings.Index(path, "/") + if i < 0 { + i = len(path) + } + if i == 0 { + return fmt.Errorf("malformed module path %q: leading slash", path) + } + if !strings.Contains(path[:i], ".") { + return fmt.Errorf("malformed module path %q: missing dot in first path element", path) + } + if path[i-1] == '.' { + return fmt.Errorf("malformed module path %q: trailing dot in first path element", path) + } + if path[0] == '.' { + return fmt.Errorf("malformed module path %q: leading dot in first path element", path) + } + if path[0] == '-' { + return fmt.Errorf("malformed module path %q: leading dash in first path element", path) + } + if strings.Contains(path, "..") { + return fmt.Errorf("malformed module path %q: double dot", path) + } + if strings.Contains(path, "//") { + return fmt.Errorf("malformed module path %q: double slash", path) + } + for _, r := range path[:i] { + if !firstPathOK(r) { + return fmt.Errorf("malformed module path %q: invalid char %q in first path element", path, r) + } + } + if path[len(path)-1] == '/' { + return fmt.Errorf("malformed module path %q: trailing slash", path) + } + for _, r := range path { + if !pathOK(r) { + return fmt.Errorf("malformed module path %q: invalid char %q", path, r) + } + } + if _, _, ok := SplitPathVersion(path); !ok { + return fmt.Errorf("malformed module path %q: invalid version %s", path, path[strings.LastIndex(path, "/")+1:]) + } + return nil +} + +// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path +// and version is either empty or "/vN" for N >= 2. +// As a special case, gopkg.in paths are recognized directly; +// they require ".vN" instead of "/vN", and for all N, not just N >= 2. +func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) { + if strings.HasPrefix(path, "gopkg.in/") { + return splitGopkgIn(path) + } + + i := len(path) + dot := false + for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') { + if path[i-1] == '.' { + dot = true + } + i-- + } + if i <= 1 || path[i-1] != 'v' || path[i-2] != '/' { + return path, "", true + } + prefix, pathMajor = path[:i-2], path[i-2:] + if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" { + return path, "", false + } + return prefix, pathMajor, true +} + +// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths. +func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) { + if !strings.HasPrefix(path, "gopkg.in/") { + return path, "", false + } + i := len(path) + for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') { + i-- + } + if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' { + // All gopkg.in paths must end in vN for some N. + return path, "", false + } + prefix, pathMajor = path[:i-2], path[i-2:] + if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" { + return path, "", false + } + return prefix, pathMajor, true +} + +// MatchPathMajor reports whether the semantic version v +// matches the path major version pathMajor. +func MatchPathMajor(v, pathMajor string) bool { + m := semver.Major(v) + if pathMajor == "" { + return m == "v0" || m == "v1" + } + return (pathMajor[0] == '/' || pathMajor[0] == '.') && m == pathMajor[1:] +} diff --git a/src/cmd/go/internal/module/module_test.go b/src/cmd/go/internal/module/module_test.go new file mode 100644 index 0000000000..6142a9e048 --- /dev/null +++ b/src/cmd/go/internal/module/module_test.go @@ -0,0 +1,184 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package module + +import "testing" + +var checkTests = []struct { + path string + version string + ok bool +}{ + {"rsc.io/quote", "0.1.0", false}, + {"rsc io/quote", "v1.0.0", false}, + + {"github.com/go-yaml/yaml", "v0.8.0", true}, + {"github.com/go-yaml/yaml", "v1.0.0", true}, + {"github.com/go-yaml/yaml", "v2.0.0", false}, + {"github.com/go-yaml/yaml", "v2.1.5", false}, + {"github.com/go-yaml/yaml", "v3.0.0", false}, + + {"github.com/go-yaml/yaml/v2", "v1.0.0", false}, + {"github.com/go-yaml/yaml/v2", "v2.0.0", true}, + {"github.com/go-yaml/yaml/v2", "v2.1.5", true}, + {"github.com/go-yaml/yaml/v2", "v3.0.0", false}, + + {"gopkg.in/yaml.v0", "v0.8.0", true}, + {"gopkg.in/yaml.v0", "v1.0.0", false}, + {"gopkg.in/yaml.v0", "v2.0.0", false}, + {"gopkg.in/yaml.v0", "v2.1.5", false}, + {"gopkg.in/yaml.v0", "v3.0.0", false}, + + {"gopkg.in/yaml.v1", "v0.8.0", false}, + {"gopkg.in/yaml.v1", "v1.0.0", true}, + {"gopkg.in/yaml.v1", "v2.0.0", false}, + {"gopkg.in/yaml.v1", "v2.1.5", false}, + {"gopkg.in/yaml.v1", "v3.0.0", false}, + + {"gopkg.in/yaml.v2", "v1.0.0", false}, + {"gopkg.in/yaml.v2", "v2.0.0", true}, + {"gopkg.in/yaml.v2", "v2.1.5", true}, + {"gopkg.in/yaml.v2", "v3.0.0", false}, +} + +func TestCheck(t *testing.T) { + for _, tt := range checkTests { + err := Check(tt.path, tt.version) + if tt.ok && err != nil { + t.Errorf("Check(%q, %q) = %v, wanted nil error", tt.path, tt.version, err) + } else if !tt.ok && err == nil { + t.Errorf("Check(%q, %q) succeeded, wanted error", tt.path, tt.version) + } + } +} + +var checkPathTests = []struct { + path string + ok bool +}{ + {"x.y/z", true}, + {"x.y", true}, + + {"", false}, + {"x.y/\xFFz", false}, + {"/x.y/z", false}, + {"x./z", false}, + {".x/z", false}, + {"-x/z", false}, + {"x..y/z", false}, + {"x.y/z/../../w", false}, + {"x.y//z", false}, + {"x.y/z//w", false}, + {"x.y/z/", false}, + + {"x.y/z/v0", false}, + {"x.y/z/v1", false}, + {"x.y/z/v2", true}, + {"x.y/z/v2.0", false}, + + {"!x.y/z", false}, + {"_x.y/z", false}, + {"x.y!/z", false}, + {"x.y\"/z", false}, + {"x.y#/z", false}, + {"x.y$/z", false}, + {"x.y%/z", false}, + {"x.y&/z", false}, + {"x.y'/z", false}, + {"x.y(/z", false}, + {"x.y)/z", false}, + {"x.y*/z", false}, + {"x.y+/z", false}, + {"x.y,/z", false}, + {"x.y-/z", true}, + {"x.y./zt", false}, + {"x.y:/z", false}, + {"x.y;/z", false}, + {"x.y/z", false}, + {"x.y?/z", false}, + {"x.y@/z", false}, + {"x.y[/z", false}, + {"x.y\\/z", false}, + {"x.y]/z", false}, + {"x.y^/z", false}, + {"x.y_/z", false}, + {"x.y`/z", false}, + {"x.y{/z", false}, + {"x.y}/z", false}, + {"x.y~/z", false}, + {"x.y/z!", false}, + {"x.y/z\"", false}, + {"x.y/z#", false}, + {"x.y/z$", false}, + {"x.y/z%", false}, + {"x.y/z&", false}, + {"x.y/z'", false}, + {"x.y/z(", false}, + {"x.y/z)", false}, + {"x.y/z*", false}, + {"x.y/z+", true}, + {"x.y/z,", true}, + {"x.y/z-", true}, + {"x.y/z.t", true}, + {"x.y/z/t", true}, + {"x.y/z:", false}, + {"x.y/z;", false}, + {"x.y/z<", false}, + {"x.y/z=", false}, + {"x.y/z>", false}, + {"x.y/z?", false}, + {"x.y/z@", false}, + {"x.y/z[", false}, + {"x.y/z\\", false}, + {"x.y/z]", false}, + {"x.y/z^", false}, + {"x.y/z_", true}, + {"x.y/z`", false}, + {"x.y/z{", false}, + {"x.y/z}", false}, + {"x.y/z~", true}, +} + +func TestCheckPath(t *testing.T) { + for _, tt := range checkPathTests { + err := CheckPath(tt.path) + if tt.ok && err != nil { + t.Errorf("CheckPath(%q) = %v, wanted nil error", tt.path, err) + } else if !tt.ok && err == nil { + t.Errorf("CheckPath(%q) succeeded, wanted error", tt.path) + } + } +} + +var splitPathVersionTests = []struct { + pathPrefix string + version string +}{ + {"x.y/z", ""}, + {"x.y/z", "/v2"}, + {"x.y/z", "/v3"}, + {"gopkg.in/yaml", ".v0"}, + {"gopkg.in/yaml", ".v1"}, + {"gopkg.in/yaml", ".v2"}, + {"gopkg.in/yaml", ".v3"}, +} + +func TestSplitPathVersion(t *testing.T) { + for _, tt := range splitPathVersionTests { + pathPrefix, version, ok := SplitPathVersion(tt.pathPrefix + tt.version) + if pathPrefix != tt.pathPrefix || version != tt.version || !ok { + t.Errorf("SplitPathVersion(%q) = %q, %q, %v, want %q, %q, true", tt.pathPrefix+tt.version, pathPrefix, version, ok, tt.pathPrefix, tt.version) + } + } + + for _, tt := range checkPathTests { + pathPrefix, version, ok := SplitPathVersion(tt.path) + if pathPrefix+version != tt.path { + t.Errorf("SplitPathVersion(%q) = %q, %q, %v, doesn't add to input", tt.path, pathPrefix, version, ok) + } + } +} diff --git a/src/cmd/go/internal/mvs/mvs.go b/src/cmd/go/internal/mvs/mvs.go new file mode 100644 index 0000000000..47670ff5a6 --- /dev/null +++ b/src/cmd/go/internal/mvs/mvs.go @@ -0,0 +1,308 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package mvs implements Minimal Version Selection. +// See https://research.swtch.com/vgo-mvs. +package mvs + +import ( + "fmt" + "sort" + + "cmd/go/internal/module" +) + +type Reqs interface { + Required(m module.Version) ([]module.Version, error) + Max(v1, v2 string) string + Latest(path string) (module.Version, error) + Previous(m module.Version) (module.Version, error) +} + +type MissingModuleError struct { + Module module.Version +} + +func (e *MissingModuleError) Error() string { + return fmt.Sprintf("missing module: %v", e.Module) +} + +// BuildList returns the build list for the target module. +func BuildList(target module.Version, reqs Reqs) ([]module.Version, error) { + return buildList(target, reqs, nil, nil) +} + +func buildList(target module.Version, reqs Reqs, uses map[module.Version][]module.Version, vers map[string]string) ([]module.Version, error) { + var ( + min = map[string]string{target.Path: target.Version} + todo = []module.Version{target} + seen = map[module.Version]bool{target: true} + ) + for len(todo) > 0 { + m := todo[len(todo)-1] + todo = todo[:len(todo)-1] + required, _ := reqs.Required(m) + for _, r := range required { + if uses != nil { + uses[r] = append(uses[r], m) + } + if !seen[r] { + if v, ok := min[r.Path]; !ok { + min[r.Path] = r.Version + } else if max := reqs.Max(v, r.Version); max != v { + min[r.Path] = max + } + todo = append(todo, r) + seen[r] = true + } + } + } + + if min[target.Path] != target.Version { + panic("unbuildable") // TODO + } + + if vers == nil { + vers = make(map[string]string) + } + list := []module.Version{target} + for i := 0; i < len(list); i++ { + m := list[i] + required, err := reqs.Required(m) + if err != nil { + // TODO: Check error is decent. + return nil, err + } + for _, r := range required { + v := min[r.Path] + if reqs.Max(v, r.Version) != v { + panic("mistake") // TODO + } + if _, ok := vers[r.Path]; !ok { + vers[r.Path] = v + list = append(list, module.Version{Path: r.Path, Version: v}) + } + } + } + tail := list[1:] + sort.Slice(tail, func(i, j int) bool { + return tail[i].Path < tail[j].Path + }) + return list, nil +} + +// Req returns the minimal requirement list for the target module +// that result in the given build list. +func Req(target module.Version, list []module.Version, reqs Reqs) ([]module.Version, error) { + // Compute postorder, cache requirements. + var postorder []module.Version + reqCache := map[module.Version][]module.Version{} + reqCache[target] = nil + var walk func(module.Version) error + walk = func(m module.Version) error { + _, ok := reqCache[m] + if ok { + return nil + } + required, err := reqs.Required(m) + if err != nil { + return err + } + reqCache[m] = required + for _, m1 := range required { + if err := walk(m1); err != nil { + return err + } + } + postorder = append(postorder, m) + return nil + } + for _, m := range list { + if err := walk(m); err != nil { + return nil, err + } + } + + // Walk modules in reverse post-order, only adding those not implied already. + have := map[string]string{} + walk = func(m module.Version) error { + if v, ok := have[m.Path]; ok && reqs.Max(m.Version, v) == v { + return nil + } + have[m.Path] = m.Version + for _, m1 := range reqCache[m] { + walk(m1) + } + return nil + } + max := map[string]string{} + for _, m := range list { + if max[m.Path] == "" { + max[m.Path] = m.Version + } else { + max[m.Path] = reqs.Max(m.Version, max[m.Path]) + } + } + var min []module.Version + for i := len(postorder) - 1; i >= 0; i-- { + m := postorder[i] + if max[m.Path] != m.Version { + // Older version. + continue + } + if have[m.Path] != m.Version { + min = append(min, m) + walk(m) + } + } + sort.Slice(min, func(i, j int) bool { + return min[i].Path < min[j].Path + }) + return min, nil +} + +// UpgradeAll returns a build list for the target module +// in which every module is upgraded to its latest version. +func UpgradeAll(target module.Version, reqs Reqs) ([]module.Version, error) { + have := map[string]bool{target.Path: true} + list := []module.Version{target} + for i := 0; i < len(list); i++ { + m := list[i] + required, err := reqs.Required(m) + if err != nil { + panic(err) // TODO + } + for _, r := range required { + latest, err := reqs.Latest(r.Path) + if err != nil { + panic(err) // TODO + } + if reqs.Max(latest.Version, r.Version) != latest.Version { + panic("mistake") // TODO + } + if !have[r.Path] { + have[r.Path] = true + list = append(list, module.Version{Path: r.Path, Version: latest.Version}) + } + } + } + tail := list[1:] + sort.Slice(tail, func(i, j int) bool { + return tail[i].Path < tail[j].Path + }) + return list, nil +} + +// Upgrade returns a build list for the target module +// in which the given additional modules are upgraded. +func Upgrade(target module.Version, reqs Reqs, upgrade ...module.Version) ([]module.Version, error) { + list, err := reqs.Required(target) + if err != nil { + panic(err) // TODO + } + // TODO: Maybe if an error is given, + // rerun with BuildList(upgrade[0], reqs) etc + // to find which ones are the buggy ones. + list = append([]module.Version(nil), list...) + list = append(list, upgrade...) + return BuildList(target, &override{target, list, reqs}) +} + +// Downgrade returns a build list for the target module +// in which the given additional modules are downgraded. +func Downgrade(target module.Version, reqs Reqs, downgrade ...module.Version) ([]module.Version, error) { + list, err := reqs.Required(target) + if err != nil { + panic(err) // TODO + } + max := make(map[string]string) + for _, r := range list { + max[r.Path] = r.Version + } + for _, d := range downgrade { + if v, ok := max[d.Path]; !ok || reqs.Max(v, d.Version) != d.Version { + max[d.Path] = d.Version + } + } + + var ( + added = make(map[module.Version]bool) + rdeps = make(map[module.Version][]module.Version) + excluded = make(map[module.Version]bool) + ) + var exclude func(module.Version) + exclude = func(m module.Version) { + if excluded[m] { + return + } + excluded[m] = true + for _, p := range rdeps[m] { + exclude(p) + } + } + var add func(module.Version) + add = func(m module.Version) { + if added[m] { + return + } + added[m] = true + if v, ok := max[m.Path]; ok && reqs.Max(m.Version, v) != v { + exclude(m) + return + } + list, err := reqs.Required(m) + if err != nil { + panic(err) // TODO + } + for _, r := range list { + add(r) + if excluded[r] { + exclude(m) + return + } + rdeps[r] = append(rdeps[r], m) + } + } + + var out []module.Version + out = append(out, target) +List: + for _, r := range list { + add(r) + for excluded[r] { + p, err := reqs.Previous(r) + if err != nil { + return nil, err // TODO + } + // If the target version is a pseudo-version, it may not be + // included when iterating over prior versions using reqs.Previous. + // Insert it into the right place in the iteration. + // If v is excluded, p should be returned again by reqs.Previous on the next iteration. + if v := max[r.Path]; reqs.Max(v, r.Version) != v && reqs.Max(p.Version, v) != p.Version { + p.Version = v + } + if p.Version == "none" { + continue List + } + add(p) + r = p + } + out = append(out, r) + } + + return out, nil +} + +type override struct { + target module.Version + list []module.Version + Reqs +} + +func (r *override) Required(m module.Version) ([]module.Version, error) { + if m == r.target { + return r.list, nil + } + return r.Reqs.Required(m) +} diff --git a/src/cmd/go/internal/mvs/mvs_test.go b/src/cmd/go/internal/mvs/mvs_test.go new file mode 100644 index 0000000000..0fd55e4e45 --- /dev/null +++ b/src/cmd/go/internal/mvs/mvs_test.go @@ -0,0 +1,377 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mvs + +import ( + "reflect" + "strings" + "testing" + + "cmd/go/internal/module" +) + +var tests = ` +# Scenario from blog. +name: blog +A: B1 C2 +B1: D3 +C1: D2 +C2: D4 +C3: D5 +C4: G1 +D2: E1 +D3: E2 +D4: E2 F1 +D5: E2 +G1: C4 +A2: B1 C4 D4 +build A: A B1 C2 D4 E2 F1 +upgrade* A: A B1 C4 D5 E2 G1 +upgrade A C4: A B1 C4 D4 E2 F1 G1 +downgrade A2 D2: A2 C4 D2 + +name: trim +A: B1 C2 +B1: D3 +C2: B2 +B2: +build A: A B2 C2 + +# Cross-dependency between D and E. +# No matter how it arises, should get result of merging all build lists via max, +# which leads to including both D2 and E2. + +name: cross1 +A: B C +B: D1 +C: D2 +D1: E2 +D2: E1 +build A: A B C D2 E2 + +name: cross1V +A: B2 C D2 E1 +B1: +B2: D1 +C: D2 +D1: E2 +D2: E1 +build A: A B2 C D2 E2 + +name: cross1U +A: B1 C +B1: +B2: D1 +C: D2 +D1: E2 +D2: E1 +build A: A B1 C D2 E1 +upgrade A B2: A B2 C D2 E2 + +name: cross1R +A: B C +B: D2 +C: D1 +D1: E2 +D2: E1 +build A: A B C D2 E2 + +name: cross1X +A: B C +B: D1 E2 +C: D2 +D1: E2 +D2: E1 +build A: A B C D2 E2 + +name: cross2 +A: B D2 +B: D1 +D1: E2 +D2: E1 +build A: A B D2 E2 + +name: cross2X +A: B D2 +B: D1 E2 +C: D2 +D1: E2 +D2: E1 +build A: A B D2 E2 + +name: cross3 +A: B D2 E1 +B: D1 +D1: E2 +D2: E1 +build A: A B D2 E2 + +name: cross3X +A: B D2 E1 +B: D1 E2 +D1: E2 +D2: E1 +build A: A B D2 E2 + +# Should not get E2 here, because B has been updated +# not to depend on D1 anymore. +name: cross4 +A1: B1 D2 +A2: B2 D2 +B1: D1 +B2: D2 +D1: E2 +D2: E1 +build A1: A1 B1 D2 E2 +build A2: A2 B2 D2 E1 + +# But the upgrade from A1 preserves the E2 dep explicitly. +upgrade A1 B2: A1 B2 D2 E2 +upgradereq A1 B2: B2 E2 + +name: cross5 +A: D1 +D1: E2 +D2: E1 +build A: A D1 E2 +upgrade* A: A D2 E2 +upgrade A D2: A D2 E2 +upgradereq A D2: D2 E2 + +name: cross6 +A: D2 +D1: E2 +D2: E1 +build A: A D2 E1 +upgrade* A: A D2 E2 +upgrade A E2: A D2 E2 + +name: cross7 +A: B C +B: D1 +C: E1 +D1: E2 +E1: D2 +build A: A B C D2 E2 + +name: down1 +A: B2 +B1: C1 +B2: C2 +build A: A B2 C2 +downgrade A C1: A B1 + +name: down2 +A: B2 E2 +B1: +B2: C2 F2 +C1: +D1: +C2: D2 E2 +D2: B2 +E2: D2 +E1: +F1: +downgrade A F1: A B1 E1 + +name: down3 +A: + +# golang.org/issue/25542. +name: noprev1 +A: B4 C2 +B2.hidden: +C2: +downgrade A B2.hidden: A B2.hidden C2 + +name: noprev2 +A: B4 C2 +B2.hidden: +B1: +C2: +downgrade A B2.hidden: A B2.hidden C2 + +name: noprev3 +A: B4 C2 +B3: +B2.hidden: +C2: +downgrade A B2.hidden: A B2.hidden C2 +` + +func Test(t *testing.T) { + var ( + name string + reqs reqsMap + fns []func(*testing.T) + ) + flush := func() { + if name != "" { + t.Run(name, func(t *testing.T) { + for _, fn := range fns { + fn(t) + } + }) + } + } + m := func(s string) module.Version { + return module.Version{Path: s[:1], Version: s[1:]} + } + ms := func(list []string) []module.Version { + var mlist []module.Version + for _, s := range list { + mlist = append(mlist, m(s)) + } + return mlist + } + checkList := func(t *testing.T, desc string, list []module.Version, err error, val string) { + if err != nil { + t.Fatalf("%s: %v", desc, err) + } + vs := ms(strings.Fields(val)) + if !reflect.DeepEqual(list, vs) { + t.Errorf("%s = %v, want %v", desc, list, vs) + } + } + + for _, line := range strings.Split(tests, "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "#") || line == "" { + continue + } + i := strings.Index(line, ":") + if i < 0 { + t.Fatalf("missing colon: %q", line) + } + key := strings.TrimSpace(line[:i]) + val := strings.TrimSpace(line[i+1:]) + if key == "" { + t.Fatalf("missing key: %q", line) + } + kf := strings.Fields(key) + switch kf[0] { + case "name": + if len(kf) != 1 { + t.Fatalf("name takes no arguments: %q", line) + } + flush() + reqs = make(reqsMap) + fns = nil + name = val + continue + case "build": + if len(kf) != 2 { + t.Fatalf("build takes one argument: %q", line) + } + fns = append(fns, func(t *testing.T) { + list, err := BuildList(m(kf[1]), reqs) + checkList(t, key, list, err, val) + }) + continue + case "upgrade*": + if len(kf) != 2 { + t.Fatalf("upgrade* takes one argument: %q", line) + } + fns = append(fns, func(t *testing.T) { + list, err := UpgradeAll(m(kf[1]), reqs) + checkList(t, key, list, err, val) + }) + continue + case "upgradereq": + if len(kf) < 2 { + t.Fatalf("upgrade takes at least one arguments: %q", line) + } + fns = append(fns, func(t *testing.T) { + list, err := Upgrade(m(kf[1]), reqs, ms(kf[2:])...) + if err == nil { + list, err = Req(m(kf[1]), list, reqs) + } + checkList(t, key, list, err, val) + }) + continue + case "upgrade": + if len(kf) < 2 { + t.Fatalf("upgrade takes at least one arguments: %q", line) + } + fns = append(fns, func(t *testing.T) { + list, err := Upgrade(m(kf[1]), reqs, ms(kf[2:])...) + checkList(t, key, list, err, val) + }) + continue + case "downgrade": + if len(kf) < 2 { + t.Fatalf("downgrade takes at least one arguments: %q", line) + } + fns = append(fns, func(t *testing.T) { + list, err := Downgrade(m(kf[1]), reqs, ms(kf[1:])...) + checkList(t, key, list, err, val) + }) + continue + } + if len(kf) == 1 && 'A' <= key[0] && key[0] <= 'Z' { + var rs []module.Version + for _, f := range strings.Fields(val) { + r := m(f) + if reqs[r] == nil { + reqs[r] = []module.Version{} + } + rs = append(rs, r) + } + reqs[m(key)] = rs + continue + } + t.Fatalf("bad line: %q", line) + } + flush() +} + +type reqsMap map[module.Version][]module.Version + +func (r reqsMap) Max(v1, v2 string) string { + if v1 == "none" { + return v2 + } + if v2 == "none" { + return v1 + } + if v1 < v2 { + return v2 + } + return v1 +} + +func (r reqsMap) Latest(path string) (module.Version, error) { + var m module.Version + for k := range r { + if k.Path == path && m.Version < k.Version { + m = k + } + } + if m.Path == "" { + return module.Version{}, &MissingModuleError{module.Version{Path: path, Version: ""}} + } + return m, nil +} + +func (r reqsMap) Previous(m module.Version) (module.Version, error) { + var p module.Version + for k := range r { + if k.Path == m.Path && p.Version < k.Version && k.Version < m.Version && !strings.HasSuffix(k.Version, ".hidden") { + p = k + } + } + if p.Path == "" { + return module.Version{Path: m.Path, Version: "none"}, nil + } + return p, nil +} + +func (r reqsMap) Required(m module.Version) ([]module.Version, error) { + rr, ok := r[m] + if !ok { + return nil, &MissingModuleError{m} + } + return rr, nil +} diff --git a/src/cmd/go/internal/search/search.go b/src/cmd/go/internal/search/search.go new file mode 100644 index 0000000000..42a6be0520 --- /dev/null +++ b/src/cmd/go/internal/search/search.go @@ -0,0 +1,423 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package search + +import ( + "cmd/go/internal/base" + "cmd/go/internal/cfg" + "fmt" + "go/build" + "log" + "os" + "path" + "path/filepath" + "regexp" + "strings" +) + +// AllPackages returns all the packages that can be found +// under the $GOPATH directories and $GOROOT matching pattern. +// The pattern is either "all" (all packages), "std" (standard packages), +// "cmd" (standard commands), or a path including "...". +func AllPackages(pattern string) []string { + pkgs := MatchPackages(pattern) + if len(pkgs) == 0 { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } + return pkgs +} + +// AllPackagesInFS is like allPackages but is passed a pattern +// beginning ./ or ../, meaning it should scan the tree rooted +// at the given directory. There are ... in the pattern too. +func AllPackagesInFS(pattern string) []string { + pkgs := MatchPackagesInFS(pattern) + if len(pkgs) == 0 { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } + return pkgs +} + +// MatchPackages returns a list of package paths matching pattern +// (see go help packages for pattern syntax). +func MatchPackages(pattern string) []string { + match := func(string) bool { return true } + treeCanMatch := func(string) bool { return true } + if !IsMetaPackage(pattern) { + match = MatchPattern(pattern) + treeCanMatch = TreeCanMatchPattern(pattern) + } + + have := map[string]bool{ + "builtin": true, // ignore pseudo-package that exists only for documentation + } + if !cfg.BuildContext.CgoEnabled { + have["runtime/cgo"] = true // ignore during walk + } + var pkgs []string + + for _, src := range cfg.BuildContext.SrcDirs() { + if (pattern == "std" || pattern == "cmd") && src != cfg.GOROOTsrc { + continue + } + src = filepath.Clean(src) + string(filepath.Separator) + root := src + if pattern == "cmd" { + root += "cmd" + string(filepath.Separator) + } + filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { + if err != nil || path == src { + return nil + } + + want := true + // Avoid .foo, _foo, and testdata directory trees. + _, elem := filepath.Split(path) + if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { + want = false + } + + name := filepath.ToSlash(path[len(src):]) + if pattern == "std" && (!IsStandardImportPath(name) || name == "cmd") { + // The name "std" is only the standard library. + // If the name is cmd, it's the root of the command tree. + want = false + } + if !treeCanMatch(name) { + want = false + } + + if !fi.IsDir() { + if fi.Mode()&os.ModeSymlink != 0 && want { + if target, err := os.Stat(path); err == nil && target.IsDir() { + fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) + } + } + return nil + } + if !want { + return filepath.SkipDir + } + + if have[name] { + return nil + } + have[name] = true + if !match(name) { + return nil + } + pkg, err := cfg.BuildContext.ImportDir(path, 0) + if err != nil { + if _, noGo := err.(*build.NoGoError); noGo { + return nil + } + } + + // If we are expanding "cmd", skip main + // packages under cmd/vendor. At least as of + // March, 2017, there is one there for the + // vendored pprof tool. + if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" { + return nil + } + + pkgs = append(pkgs, name) + return nil + }) + } + return pkgs +} + +var modRoot string + +func SetModRoot(dir string) { + modRoot = dir +} + +// MatchPackagesInFS returns a list of package paths matching pattern, +// which must begin with ./ or ../ +// (see go help packages for pattern syntax). +func MatchPackagesInFS(pattern string) []string { + // Find directory to begin the scan. + // Could be smarter but this one optimization + // is enough for now, since ... is usually at the + // end of a path. + i := strings.Index(pattern, "...") + dir, _ := path.Split(pattern[:i]) + + // pattern begins with ./ or ../. + // path.Clean will discard the ./ but not the ../. + // We need to preserve the ./ for pattern matching + // and in the returned import paths. + prefix := "" + if strings.HasPrefix(pattern, "./") { + prefix = "./" + } + match := MatchPattern(pattern) + + if modRoot != "" { + abs, err := filepath.Abs(dir) + if err != nil { + base.Fatalf("go: %v", err) + } + if !hasFilepathPrefix(abs, modRoot) { + base.Fatalf("go: pattern %s refers to dir %s, outside module root %s", pattern, abs, modRoot) + return nil + } + } + + var pkgs []string + filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() { + return nil + } + if path == dir { + // filepath.Walk starts at dir and recurses. For the recursive case, + // the path is the result of filepath.Join, which calls filepath.Clean. + // The initial case is not Cleaned, though, so we do this explicitly. + // + // This converts a path like "./io/" to "io". Without this step, running + // "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io + // package, because prepending the prefix "./" to the unclean path would + // result in "././io", and match("././io") returns false. + path = filepath.Clean(path) + } + + // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". + _, elem := filepath.Split(path) + dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." + if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { + return filepath.SkipDir + } + + name := prefix + filepath.ToSlash(path) + if !match(name) { + return nil + } + + // We keep the directory if we can import it, or if we can't import it + // due to invalid Go source files. This means that directories containing + // parse errors will be built (and fail) instead of being silently skipped + // as not matching the pattern. Go 1.5 and earlier skipped, but that + // behavior means people miss serious mistakes. + // See golang.org/issue/11407. + if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) { + if _, noGo := err.(*build.NoGoError); !noGo { + log.Print(err) + } + return nil + } + pkgs = append(pkgs, name) + return nil + }) + return pkgs +} + +// TreeCanMatchPattern(pattern)(name) reports whether +// name or children of name can possibly match pattern. +// Pattern is the same limited glob accepted by matchPattern. +func TreeCanMatchPattern(pattern string) func(name string) bool { + wildCard := false + if i := strings.Index(pattern, "..."); i >= 0 { + wildCard = true + pattern = pattern[:i] + } + return func(name string) bool { + return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || + wildCard && strings.HasPrefix(name, pattern) + } +} + +// MatchPattern(pattern)(name) reports whether +// name matches pattern. Pattern is a limited glob +// pattern in which '...' means 'any string' and there +// is no other special syntax. +// Unfortunately, there are two special cases. Quoting "go help packages": +// +// First, /... at the end of the pattern can match an empty string, +// so that net/... matches both net and packages in its subdirectories, like net/http. +// Second, any slash-separted pattern element containing a wildcard never +// participates in a match of the "vendor" element in the path of a vendored +// package, so that ./... does not match packages in subdirectories of +// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. +// Note, however, that a directory named vendor that itself contains code +// is not a vendored package: cmd/vendor would be a command named vendor, +// and the pattern cmd/... matches it. +func MatchPattern(pattern string) func(name string) bool { + // Convert pattern to regular expression. + // The strategy for the trailing /... is to nest it in an explicit ? expression. + // The strategy for the vendor exclusion is to change the unmatchable + // vendor strings to a disallowed code point (vendorChar) and to use + // "(anything but that codepoint)*" as the implementation of the ... wildcard. + // This is a bit complicated but the obvious alternative, + // namely a hand-written search like in most shell glob matchers, + // is too easy to make accidentally exponential. + // Using package regexp guarantees linear-time matching. + + const vendorChar = "\x00" + + if strings.Contains(pattern, vendorChar) { + return func(name string) bool { return false } + } + + re := regexp.QuoteMeta(pattern) + re = replaceVendor(re, vendorChar) + switch { + case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): + re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` + case re == vendorChar+`/\.\.\.`: + re = `(/vendor|/` + vendorChar + `/\.\.\.)` + case strings.HasSuffix(re, `/\.\.\.`): + re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` + } + re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1) + + reg := regexp.MustCompile(`^` + re + `$`) + + return func(name string) bool { + if strings.Contains(name, vendorChar) { + return false + } + return reg.MatchString(replaceVendor(name, vendorChar)) + } +} + +// replaceVendor returns the result of replacing +// non-trailing vendor path elements in x with repl. +func replaceVendor(x, repl string) string { + if !strings.Contains(x, "vendor") { + return x + } + elem := strings.Split(x, "/") + for i := 0; i < len(elem)-1; i++ { + if elem[i] == "vendor" { + elem[i] = repl + } + } + return strings.Join(elem, "/") +} + +// ImportPaths returns the import paths to use for the given command line. +func ImportPaths(args []string) []string { + args = CleanImportPaths(args) + var out []string + for _, a := range args { + if IsMetaPackage(a) { + out = append(out, AllPackages(a)...) + continue + } + if strings.Contains(a, "...") { + if build.IsLocalImport(a) { + out = append(out, AllPackagesInFS(a)...) + } else { + out = append(out, AllPackages(a)...) + } + continue + } + out = append(out, a) + } + return out +} + +// CleanImportPaths returns the import paths to use for the given +// command line, but it does no wildcard expansion. +func CleanImportPaths(args []string) []string { + if len(args) == 0 { + return []string{"."} + } + var out []string + for _, a := range args { + // Arguments are supposed to be import paths, but + // as a courtesy to Windows developers, rewrite \ to / + // in command-line arguments. Handles .\... and so on. + if filepath.Separator == '\\' { + a = strings.Replace(a, `\`, `/`, -1) + } + + // Put argument in canonical form, but preserve leading ./. + if strings.HasPrefix(a, "./") { + a = "./" + path.Clean(a) + if a == "./." { + a = "." + } + } else { + a = path.Clean(a) + } + out = append(out, a) + } + return out +} + +// ImportPathsNoDotExpansion returns the import paths to use for the given +// command line, but it does no ... expansion. +// TODO(vgo): Delete once old go get is gone. +func ImportPathsNoDotExpansion(args []string) []string { + args = CleanImportPaths(args) + var out []string + for _, a := range args { + if IsMetaPackage(a) { + out = append(out, AllPackages(a)...) + continue + } + out = append(out, a) + } + return out +} + +// IsMetaPackage checks if name is a reserved package name that expands to multiple packages. +func IsMetaPackage(name string) bool { + return name == "std" || name == "cmd" || name == "all" +} + +// hasPathPrefix reports whether the path s begins with the +// elements in prefix. +func hasPathPrefix(s, prefix string) bool { + switch { + default: + return false + case len(s) == len(prefix): + return s == prefix + case len(s) > len(prefix): + if prefix != "" && prefix[len(prefix)-1] == '/' { + return strings.HasPrefix(s, prefix) + } + return s[len(prefix)] == '/' && s[:len(prefix)] == prefix + } +} + +// hasFilepathPrefix reports whether the path s begins with the +// elements in prefix. +func hasFilepathPrefix(s, prefix string) bool { + switch { + default: + return false + case len(s) == len(prefix): + return s == prefix + case len(s) > len(prefix): + if prefix != "" && prefix[len(prefix)-1] == filepath.Separator { + return strings.HasPrefix(s, prefix) + } + return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix + } +} + +// IsStandardImportPath reports whether $GOROOT/src/path should be considered +// part of the standard distribution. For historical reasons we allow people to add +// their own code to $GOROOT instead of using $GOPATH, but we assume that +// code will start with a domain name (dot in the first element). +// +// Note that this function is meant to evaluate whether a directory found in GOROOT +// should be treated as part of the standard library. It should not be used to decide +// that a directory found in GOPATH should be rejected: directories in GOPATH +// need not have dots in the first element, and they just take their chances +// with future collisions in the standard library. +func IsStandardImportPath(path string) bool { + i := strings.Index(path, "/") + if i < 0 { + i = len(path) + } + elem := path[:i] + return !strings.Contains(elem, ".") +} diff --git a/src/cmd/go/internal/load/match_test.go b/src/cmd/go/internal/search/search_test.go similarity index 94% rename from src/cmd/go/internal/load/match_test.go rename to src/cmd/go/internal/search/search_test.go index b8d67dac74..0bef765fa4 100644 --- a/src/cmd/go/internal/load/match_test.go +++ b/src/cmd/go/internal/search/search_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package load +package search import ( "strings" @@ -65,8 +65,8 @@ var matchPatternTests = ` ` func TestMatchPattern(t *testing.T) { - testPatterns(t, "matchPattern", matchPatternTests, func(pattern, name string) bool { - return matchPattern(pattern)(name) + testPatterns(t, "MatchPattern", matchPatternTests, func(pattern, name string) bool { + return MatchPattern(pattern)(name) }) } @@ -106,8 +106,8 @@ var treeCanMatchPatternTests = ` ` func TestTreeCanMatchPattern(t *testing.T) { - testPatterns(t, "treeCanMatchPattern", treeCanMatchPatternTests, func(pattern, name string) bool { - return treeCanMatchPattern(pattern)(name) + testPatterns(t, "TreeCanMatchPattern", treeCanMatchPatternTests, func(pattern, name string) bool { + return TreeCanMatchPattern(pattern)(name) }) } diff --git a/src/cmd/go/internal/semver/semver.go b/src/cmd/go/internal/semver/semver.go new file mode 100644 index 0000000000..ecc5300c8c --- /dev/null +++ b/src/cmd/go/internal/semver/semver.go @@ -0,0 +1,351 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package semver implements comparison of semantic version strings. +// In this package, semantic version strings must begin with a leading "v", +// as in "v1.0.0". +// +// The general form of a semantic version string accepted by this package is +// +// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] +// +// where square brackets indicate optional parts of the syntax; +// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; +// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers +// using only alphanumeric characters and hyphens; and +// all-numeric PRERELEASE identifiers must not have leading zeros. +// +// This package follows Semantic Versioning 2.0.0 (see semver.org) +// with two exceptions. First, it requires the "v" prefix. Second, it recognizes +// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) +// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. +package semver + +// parsed returns the parsed form of a semantic version string. +type parsed struct { + major string + minor string + patch string + short string + prerelease string + build string + err string +} + +// IsValid reports whether v is a valid semantic version string. +func IsValid(v string) bool { + _, ok := parse(v) + return ok +} + +// Canonical returns the canonical formatting of the semantic version v. +// It fills in any missing .MINOR or .PATCH and discards build metadata. +// Two semantic versions compare equal only if their canonical formattings +// are identical strings. +// The canonical invalid semantic version is the empty string. +func Canonical(v string) string { + p, ok := parse(v) + if !ok { + return "" + } + if p.build != "" { + return v[:len(v)-len(p.build)] + } + if p.short != "" { + return v + p.short + } + return v +} + +// Major returns the major version prefix of the semantic version v. +// For example, Major("v2.1.0") == "v2". +// If v is an invalid semantic version string, Major returns the empty string. +func Major(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return v[:1+len(pv.major)] +} + +// Compare returns an integer comparing two versions according to +// according to semantic version precedence. +// The result will be 0 if v == w, -1 if v < w, or +1 if v > w. +// +// An invalid semantic version string is considered less than a valid one. +// All invalid semantic version strings compare equal to each other. +func Compare(v, w string) int { + pv, ok1 := parse(v) + pw, ok2 := parse(w) + if !ok1 && !ok2 { + return 0 + } + if !ok1 { + return -1 + } + if !ok2 { + return +1 + } + if c := compareInt(pv.major, pw.major); c != 0 { + return c + } + if c := compareInt(pv.minor, pw.minor); c != 0 { + return c + } + if c := compareInt(pv.patch, pw.patch); c != 0 { + return c + } + return comparePrerelease(pv.prerelease, pw.prerelease) +} + +// Max canonicalizes its arguments and then returns the version string +// that compares greater. +func Max(v, w string) string { + v = Canonical(v) + w = Canonical(w) + if Compare(v, w) > 0 { + return v + } + return w +} + +func parse(v string) (p parsed, ok bool) { + if v == "" || v[0] != 'v' { + p.err = "missing v prefix" + return + } + p.major, v, ok = parseInt(v[1:]) + if !ok { + p.err = "bad major version" + return + } + if v == "" { + p.minor = "0" + p.patch = "0" + p.short = ".0.0" + return + } + if v[0] != '.' { + p.err = "bad minor prefix" + ok = false + return + } + p.minor, v, ok = parseInt(v[1:]) + if !ok { + p.err = "bad minor version" + return + } + if v == "" { + p.patch = "0" + p.short = ".0" + return + } + if v[0] != '.' { + p.err = "bad patch prefix" + ok = false + return + } + p.patch, v, ok = parseInt(v[1:]) + if !ok { + p.err = "bad patch version" + return + } + if len(v) > 0 && v[0] == '-' { + p.prerelease, v, ok = parsePrerelease(v) + if !ok { + p.err = "bad prerelease" + return + } + } + if len(v) > 0 && v[0] == '+' { + p.build, v, ok = parseBuild(v) + if !ok { + p.err = "bad build" + return + } + } + if v != "" { + p.err = "junk on end" + ok = false + return + } + ok = true + return +} + +func parseInt(v string) (t, rest string, ok bool) { + if v == "" { + return + } + if v[0] < '0' || '9' < v[0] { + return + } + i := 1 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + if v[0] == '0' && i != 1 { + return + } + return v[:i], v[i:], true +} + +func parsePrerelease(v string) (t, rest string, ok bool) { + // "A pre-release version MAY be denoted by appending a hyphen and + // a series of dot separated identifiers immediately following the patch version. + // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. + // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." + if v == "" || v[0] != '-' { + return + } + i := 1 + start := 1 + for i < len(v) && v[i] != '+' { + if !isIdentChar(v[i]) && v[i] != '.' { + return + } + if v[i] == '.' { + if start == i || isBadNum(v[start:i]) { + return + } + start = i + 1 + } + i++ + } + if start == i || isBadNum(v[start:i]) { + return + } + return v[:i], v[i:], true +} + +func parseBuild(v string) (t, rest string, ok bool) { + if v == "" || v[0] != '+' { + return + } + i := 1 + start := 1 + for i < len(v) { + if !isIdentChar(v[i]) { + return + } + if v[i] == '.' { + if start == i { + return + } + start = i + 1 + } + i++ + } + if start == i { + return + } + return v[:i], v[i:], true +} + +func isIdentChar(c byte) bool { + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' +} + +func isBadNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) && i > 1 && v[0] == '0' +} + +func isNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) +} + +func compareInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} + +func comparePrerelease(x, y string) int { + // "When major, minor, and patch are equal, a pre-release version has + // lower precedence than a normal version. + // Example: 1.0.0-alpha < 1.0.0. + // Precedence for two pre-release versions with the same major, minor, + // and patch version MUST be determined by comparing each dot separated + // identifier from left to right until a difference is found as follows: + // identifiers consisting of only digits are compared numerically and + // identifiers with letters or hyphens are compared lexically in ASCII + // sort order. Numeric identifiers always have lower precedence than + // non-numeric identifiers. A larger set of pre-release fields has a + // higher precedence than a smaller set, if all of the preceding + // identifiers are equal. + // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < + // 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0." + if x == y { + return 0 + } + if x == "" { + return +1 + } + if y == "" { + return -1 + } + for x != "" && y != "" { + x = x[1:] // skip - or . + y = y[1:] // skip - or . + var dx, dy string + dx, x = nextIdent(x) + dy, y = nextIdent(y) + if dx != dy { + ix := isNum(dx) + iy := isNum(dy) + if ix != iy { + if ix { + return -1 + } else { + return +1 + } + } + if ix { + if len(dx) < len(dy) { + return -1 + } + if len(dx) > len(dy) { + return +1 + } + } + if dx < dy { + return -1 + } else { + return +1 + } + } + } + if x == "" { + return -1 + } else { + return +1 + } +} + +func nextIdent(x string) (dx, rest string) { + i := 0 + for i < len(x) && x[i] != '.' { + i++ + } + return x[:i], x[i:] +} diff --git a/src/cmd/go/internal/semver/semver_test.go b/src/cmd/go/internal/semver/semver_test.go new file mode 100644 index 0000000000..7a697f6800 --- /dev/null +++ b/src/cmd/go/internal/semver/semver_test.go @@ -0,0 +1,123 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package semver + +import ( + "strings" + "testing" +) + +var tests = []struct { + in string + out string +}{ + {"bad", ""}, + {"v1-pre", ""}, + {"v1+meta", ""}, + {"v1-pre+meta", ""}, + {"v1.2-pre", ""}, + {"v1.2+meta", ""}, + {"v1.2-pre+meta", ""}, + {"v1.0.0-alpha", "v1.0.0-alpha"}, + {"v1.0.0-alpha.1", "v1.0.0-alpha.1"}, + {"v1.0.0-alpha.beta", "v1.0.0-alpha.beta"}, + {"v1.0.0-beta", "v1.0.0-beta"}, + {"v1.0.0-beta.2", "v1.0.0-beta.2"}, + {"v1.0.0-beta.11", "v1.0.0-beta.11"}, + {"v1.0.0-rc.1", "v1.0.0-rc.1"}, + {"v1", "v1.0.0"}, + {"v1.0", "v1.0.0"}, + {"v1.0.0", "v1.0.0"}, + {"v1.2", "v1.2.0"}, + {"v1.2.0", "v1.2.0"}, + {"v1.2.3-456", "v1.2.3-456"}, + {"v1.2.3-456.789", "v1.2.3-456.789"}, + {"v1.2.3-456-789", "v1.2.3-456-789"}, + {"v1.2.3-456a", "v1.2.3-456a"}, + {"v1.2.3-pre", "v1.2.3-pre"}, + {"v1.2.3-pre+meta", "v1.2.3-pre"}, + {"v1.2.3-pre.1", "v1.2.3-pre.1"}, + {"v1.2.3-zzz", "v1.2.3-zzz"}, + {"v1.2.3", "v1.2.3"}, + {"v1.2.3+meta", "v1.2.3"}, +} + +func TestIsValid(t *testing.T) { + for _, tt := range tests { + ok := IsValid(tt.in) + if ok != (tt.out != "") { + t.Errorf("IsValid(%q) = %v, want %v", tt.in, ok, !ok) + } + } +} + +func TestCanonical(t *testing.T) { + for _, tt := range tests { + out := Canonical(tt.in) + if out != tt.out { + t.Errorf("Canonical(%q) = %q, want %q", tt.in, out, tt.out) + } + } +} + +func TestMajor(t *testing.T) { + for _, tt := range tests { + out := Major(tt.in) + want := "" + if i := strings.Index(tt.out, "."); i >= 0 { + want = tt.out[:i] + } + if out != want { + t.Errorf("Major(%q) = %q, want %q", tt.in, out, want) + } + } +} + +func TestCompare(t *testing.T) { + for i, ti := range tests { + for j, tj := range tests { + cmp := Compare(ti.in, tj.in) + var want int + if ti.out == tj.out { + want = 0 + } else if i < j { + want = -1 + } else { + want = +1 + } + if cmp != want { + t.Errorf("Compare(%q, %q) = %d, want %d", ti.in, tj.in, cmp, want) + } + } + } +} + +func TestMax(t *testing.T) { + for i, ti := range tests { + for j, tj := range tests { + max := Max(ti.in, tj.in) + want := Canonical(ti.in) + if i < j { + want = Canonical(tj.in) + } + if max != want { + t.Errorf("Max(%q, %q) = %q, want %q", ti.in, tj.in, max, want) + } + } + } +} + +var ( + v1 = "v1.0.0+metadata-dash" + v2 = "v1.0.0+metadata-dash1" +) + +func BenchmarkCompare(b *testing.B) { + for i := 0; i < b.N; i++ { + if Compare(v1, v2) != 0 { + b.Fatalf("bad compare") + } + } +} diff --git a/src/cmd/go/internal/vgo/build.go b/src/cmd/go/internal/vgo/build.go new file mode 100644 index 0000000000..ba103099aa --- /dev/null +++ b/src/cmd/go/internal/vgo/build.go @@ -0,0 +1,115 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vgo + +import ( + "bytes" + "cmd/go/internal/base" + "cmd/go/internal/cfg" + "cmd/go/internal/modinfo" + "cmd/go/internal/module" + "cmd/go/internal/search" + "encoding/hex" + "fmt" + "os" + "path/filepath" +) + +var ( + infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6") + infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2") +) + +func isStandardImportPath(path string) bool { + if search.IsStandardImportPath(path) { + if _, err := os.Stat(filepath.Join(cfg.GOROOT, "src", path)); err == nil { + return true + } + if _, err := os.Stat(filepath.Join(cfg.GOROOT, "src/vendor", path)); err == nil { + return true + } + } + return false +} + +func PackageModuleInfo(path string) *modinfo.ModulePublic { + var info modinfo.ModulePublic + if isStandardImportPath(path) || !Enabled() { + return nil + } + target := findModule(path, path) + info.Top = target.Path == buildList[0].Path + info.Path = target.Path + info.Version = target.Version + return &info +} + +func PackageBuildInfo(path string, deps []string) string { + if isStandardImportPath(path) || !Enabled() { + return "" + } + target := findModule(path, path) + mdeps := make(map[module.Version]bool) + for _, dep := range deps { + if !isStandardImportPath(dep) { + mdeps[findModule(path, dep)] = true + } + } + var mods []module.Version + delete(mdeps, target) + for mod := range mdeps { + mods = append(mods, mod) + } + sortModules(mods) + + var buf bytes.Buffer + fmt.Fprintf(&buf, "path\t%s\n", path) + tv := target.Version + if tv == "" { + tv = "(devel)" + } + fmt.Fprintf(&buf, "mod\t%s\t%s\t%s\n", target.Path, tv, findModHash(target)) + for _, mod := range mods { + mv := mod.Version + if mv == "" { + mv = "(devel)" + } + r := replaced(mod) + h := "" + if r == nil { + h = "\t" + findModHash(mod) + } + fmt.Fprintf(&buf, "dep\t%s\t%s%s\n", mod.Path, mod.Version, h) + if r := replaced(mod); r != nil { + fmt.Fprintf(&buf, "=>\t%s\t%s\t%s\n", r.New.Path, r.New.Version, findModHash(r.New)) + } + } + return buf.String() +} + +func findModule(target, path string) module.Version { + if path == "." { + return buildList[0] + } + for _, mod := range buildList { + if importPathInModule(path, mod.Path) { + return mod + } + } + base.Fatalf("build %v: cannot find module for path %v", target, path) + panic("unreachable") +} + +func ModInfoProg(info string) []byte { + return []byte(fmt.Sprintf(` + package main + import _ "unsafe" + //go:linkname __debug_modinfo__ runtime/debug.modinfo + var __debug_modinfo__ string + func init() { + __debug_modinfo__ = %q + } + `, string(infoStart)+info+string(infoEnd))) +} diff --git a/src/cmd/go/internal/vgo/fetch.go b/src/cmd/go/internal/vgo/fetch.go new file mode 100644 index 0000000000..c28353ccfa --- /dev/null +++ b/src/cmd/go/internal/vgo/fetch.go @@ -0,0 +1,232 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vgo + +import ( + "archive/zip" + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + + "cmd/go/internal/base" + "cmd/go/internal/dirhash" + "cmd/go/internal/modfetch" + "cmd/go/internal/module" + "cmd/go/internal/semver" +) + +// fetch returns the directory in the local download cache +// holding the root of mod's source tree. +// It downloads the module if needed. +func fetch(mod module.Version) (dir string, err error) { + if r := replaced(mod); r != nil { + if r.New.Version == "" { + dir = r.New.Path + if !filepath.IsAbs(dir) { + dir = filepath.Join(ModRoot, dir) + } + return dir, nil + } + mod = r.New + } + + modpath := mod.Path + "@" + mod.Version + dir = filepath.Join(srcV, modpath) + if files, _ := ioutil.ReadDir(dir); len(files) == 0 { + zipfile := filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".zip") + if _, err := os.Stat(zipfile); err == nil { + // Use it. + // This should only happen if the v/cache directory is preinitialized + // or if src/v/modpath was removed but not src/v/cache. + fmt.Fprintf(os.Stderr, "vgo: extracting %s %s\n", mod.Path, mod.Version) + } else { + if err := os.MkdirAll(filepath.Join(srcV, "cache", mod.Path, "@v"), 0777); err != nil { + return "", err + } + fmt.Fprintf(os.Stderr, "vgo: downloading %s %s\n", mod.Path, mod.Version) + if err := downloadZip(mod, zipfile); err != nil { + return "", err + } + } + if err := modfetch.Unzip(dir, zipfile, modpath, 0); err != nil { + fmt.Fprintf(os.Stderr, "-> %s\n", err) + return "", err + } + } + checkModHash(mod) + return dir, nil +} + +func downloadZip(mod module.Version, target string) error { + repo, err := modfetch.Lookup(mod.Path) + if err != nil { + return err + } + tmpfile, err := repo.Zip(mod.Version, os.TempDir()) + if err != nil { + return err + } + defer os.Remove(tmpfile) + + // Double-check zip file looks OK. + z, err := zip.OpenReader(tmpfile) + if err != nil { + z.Close() + return err + } + prefix := mod.Path + "@" + mod.Version + for _, f := range z.File { + if !strings.HasPrefix(f.Name, prefix) { + z.Close() + return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name) + } + } + z.Close() + + hash, err := dirhash.HashZip(tmpfile, dirhash.DefaultHash) + if err != nil { + return err + } + r, err := os.Open(tmpfile) + if err != nil { + return err + } + defer r.Close() + w, err := os.Create(target) + if err != nil { + return err + } + if _, err := io.Copy(w, r); err != nil { + w.Close() + return fmt.Errorf("copying: %v", err) + } + if err := w.Close(); err != nil { + return err + } + return ioutil.WriteFile(target+"hash", []byte(hash), 0666) +} + +var useModHash = false +var modHash map[module.Version][]string + +func initModHash() { + if modHash != nil { + return + } + modHash = make(map[module.Version][]string) + file := filepath.Join(ModRoot, "go.modverify") + data, err := ioutil.ReadFile(file) + if err != nil && os.IsNotExist(err) { + return + } + if err != nil { + base.Fatalf("vgo: %v", err) + } + useModHash = true + lineno := 0 + for len(data) > 0 { + var line []byte + lineno++ + i := bytes.IndexByte(data, '\n') + if i < 0 { + line, data = data, nil + } else { + line, data = data[:i], data[i+1:] + } + f := strings.Fields(string(line)) + if len(f) == 0 { + // blank line; skip it + continue + } + if len(f) != 3 { + base.Fatalf("vgo: malformed go.modverify:\n%s:%d: wrong number of fields %v", file, lineno, len(f)) + } + mod := module.Version{Path: f[0], Version: f[1]} + modHash[mod] = append(modHash[mod], f[2]) + } +} + +func checkModHash(mod module.Version) { + initModHash() + if !useModHash { + return + } + + data, err := ioutil.ReadFile(filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".ziphash")) + if err != nil { + base.Fatalf("vgo: verifying %s %s: %v", mod.Path, mod.Version, err) + } + h := strings.TrimSpace(string(data)) + if !strings.HasPrefix(h, "h1:") { + base.Fatalf("vgo: verifying %s %s: unexpected ziphash: %q", mod.Path, mod.Version, h) + } + + for _, vh := range modHash[mod] { + if h == vh { + return + } + if strings.HasPrefix(vh, "h1:") { + base.Fatalf("vgo: verifying %s %s: module hash mismatch\n\tdownloaded: %v\n\tgo.modverify: %v", mod.Path, mod.Version, h, vh) + } + } + if len(modHash[mod]) > 0 { + fmt.Fprintf(os.Stderr, "warning: verifying %s %s: unknown hashes in go.modverify: %v; adding %v", mod.Path, mod.Version, strings.Join(modHash[mod], ", "), h) + } + modHash[mod] = append(modHash[mod], h) +} + +func findModHash(mod module.Version) string { + data, err := ioutil.ReadFile(filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".ziphash")) + if err != nil { + return "" + } + return strings.TrimSpace(string(data)) +} + +func writeModHash() { + if !useModHash { + return + } + + var mods []module.Version + for m := range modHash { + mods = append(mods, m) + } + sortModules(mods) + var buf bytes.Buffer + for _, m := range mods { + list := modHash[m] + sort.Strings(list) + for _, h := range list { + fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) + } + } + + file := filepath.Join(ModRoot, "go.modverify") + data, _ := ioutil.ReadFile(filepath.Join(ModRoot, "go.modverify")) + if bytes.Equal(data, buf.Bytes()) { + return + } + + if err := ioutil.WriteFile(file, buf.Bytes(), 0666); err != nil { + base.Fatalf("vgo: writing go.modverify: %v", err) + } +} + +func sortModules(mods []module.Version) { + sort.Slice(mods, func(i, j int) bool { + mi := mods[i] + mj := mods[j] + if mi.Path != mj.Path { + return mi.Path < mj.Path + } + return semver.Compare(mi.Version, mj.Version) < 0 + }) +} diff --git a/src/cmd/go/internal/vgo/get.go b/src/cmd/go/internal/vgo/get.go new file mode 100644 index 0000000000..9fd8497f9d --- /dev/null +++ b/src/cmd/go/internal/vgo/get.go @@ -0,0 +1,152 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vgo + +import ( + "strings" + + "cmd/go/internal/base" + "cmd/go/internal/modfetch" + "cmd/go/internal/module" + "cmd/go/internal/mvs" + "cmd/go/internal/semver" +) + +var CmdGet = &base.Command{ + UsageLine: "get [build flags] [packages]", + Short: "download and install versioned modules and dependencies", + Long: ` +Get downloads the latest versions of modules containing the named packages, +along with the versions of the dependencies required by those modules +(not necessarily the latest ones). + +It then installs the named packages, like 'go install'. + +The -u flag causes get to download the latest version of dependencies as well. + +Each package being updated can be suffixed with @version to specify +the desired version. Specifying a version older than the one currently +in use causes a downgrade, which may in turn downgrade other +modules using that one, to keep everything consistent. + +TODO: Make this documentation better once the semantic dust settles. + `, +} + +var getU = CmdGet.Flag.Bool("u", false, "") + +func init() { + CmdGet.Run = runGet // break init loop +} + +func runGet(cmd *base.Command, args []string) { + if *getU && len(args) > 0 { + base.Fatalf("vgo get: -u not supported with argument list") + } + if !*getU && len(args) == 0 { + base.Fatalf("vgo get: need arguments or -u") + } + + if *getU { + isGetU = true + ImportPaths([]string{"."}) + return + } + + Init() + InitMod() + var upgrade []module.Version + var downgrade []module.Version + var newPkgs []string + for _, pkg := range args { + var path, vers string + /* OLD CODE + if n := strings.Count(pkg, "(") + strings.Count(pkg, ")"); n > 0 { + i := strings.Index(pkg, "(") + j := strings.Index(pkg, ")") + if n != 2 || i < 0 || j <= i+1 || j != len(pkg)-1 && pkg[j+1] != '/' { + base.Errorf("vgo get: invalid module version syntax: %s", pkg) + continue + } + path, vers = pkg[:i], pkg[i+1:j] + pkg = pkg[:i] + pkg[j+1:] + */ + if i := strings.Index(pkg, "@"); i >= 0 { + path, pkg, vers = pkg[:i], pkg[:i], pkg[i+1:] + if strings.Contains(vers, "@") { + base.Errorf("vgo get: invalid module version syntax: %s", pkg) + continue + } + } else { + path = pkg + vers = "latest" + } + if vers == "none" { + downgrade = append(downgrade, module.Version{Path: path, Version: ""}) + } else { + info, err := modfetch.Query(path, vers, allowed) + if err != nil { + base.Errorf("vgo get %v: %v", pkg, err) + continue + } + upgrade = append(upgrade, module.Version{Path: path, Version: info.Version}) + newPkgs = append(newPkgs, pkg) + } + } + args = newPkgs + + // Upgrade. + var err error + buildList, err = mvs.Upgrade(Target, newReqs(), upgrade...) + if err != nil { + base.Fatalf("vgo get: %v", err) + } + + importPaths([]string{"."}) + + // Downgrade anything that went too far. + version := make(map[string]string) + for _, mod := range buildList { + version[mod.Path] = mod.Version + } + for _, mod := range upgrade { + if semver.Compare(mod.Version, version[mod.Path]) < 0 { + downgrade = append(downgrade, mod) + } + } + + if len(downgrade) > 0 { + buildList, err = mvs.Downgrade(Target, newReqs(buildList[1:]...), downgrade...) + if err != nil { + base.Fatalf("vgo get: %v", err) + } + + // TODO: Check that everything we need to import is still available. + /* + local := v.matchPackages("all", v.Reqs[:1]) + for _, path := range local { + dir, err := v.importDir(path) + if err != nil { + return err // TODO + } + imports, testImports, err := imports.ScanDir(dir, v.Tags) + for _, path := range imports { + xxx + } + for _, path := range testImports { + xxx + } + } + */ + } + writeGoMod() + + if len(args) > 0 { + InstallHook(args) + } +} + +// Call into "go install". Set by internal/work, which imports us. +var InstallHook func([]string) diff --git a/src/cmd/go/internal/vgo/init.go b/src/cmd/go/internal/vgo/init.go new file mode 100644 index 0000000000..b307b6b1fe --- /dev/null +++ b/src/cmd/go/internal/vgo/init.go @@ -0,0 +1,411 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vgo + +import ( + "bytes" + "cmd/go/internal/base" + "cmd/go/internal/cfg" + "cmd/go/internal/modconv" + "cmd/go/internal/modfetch" + "cmd/go/internal/modfetch/codehost" + "cmd/go/internal/modfile" + "cmd/go/internal/module" + "cmd/go/internal/mvs" + "cmd/go/internal/search" + "cmd/go/internal/semver" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "regexp" + "strconv" + "strings" +) + +var ( + cwd string + enabled = MustBeVgo + MustBeVgo = mustBeVgo() + initialized bool + + ModRoot string + modFile *modfile.File + excluded map[module.Version]bool + Target module.Version + + gopath string + srcV string +) + +func BinDir() string { + if !Enabled() { + panic("vgo.Bin") + } + return filepath.Join(gopath, "bin") +} + +func init() { + flag.BoolVar(&MustBeVgo, "vgo", MustBeVgo, "require use of modules") +} + +// mustBeVgo reports whether we are invoked as vgo +// (as opposed to go). +// If so, we only support builds with go.mod files. +func mustBeVgo() bool { + name := os.Args[0] + name = name[strings.LastIndex(name, "/")+1:] + name = name[strings.LastIndex(name, `\`)+1:] + return strings.HasPrefix(name, "vgo") +} + +func Init() { + if initialized { + return + } + initialized = true + + // If this is testgo - the test binary during cmd/go tests - then + // do not let it look for a go.mod. Only use vgo support if the + // global -vgo flag has been passed on the command line. + if base := filepath.Base(os.Args[0]); (base == "testgo" || base == "testgo.exe") && !MustBeVgo { + return + } + + // Disable any prompting for passwords by Git. + // Only has an effect for 2.3.0 or later, but avoiding + // the prompt in earlier versions is just too hard. + // If user has explicitly set GIT_TERMINAL_PROMPT=1, keep + // prompting. + // See golang.org/issue/9341 and golang.org/issue/12706. + if os.Getenv("GIT_TERMINAL_PROMPT") == "" { + os.Setenv("GIT_TERMINAL_PROMPT", "0") + } + + // Disable any ssh connection pooling by Git. + // If a Git subprocess forks a child into the background to cache a new connection, + // that child keeps stdout/stderr open. After the Git subprocess exits, + // os /exec expects to be able to read from the stdout/stderr pipe + // until EOF to get all the data that the Git subprocess wrote before exiting. + // The EOF doesn't come until the child exits too, because the child + // is holding the write end of the pipe. + // This is unfortunate, but it has come up at least twice + // (see golang.org/issue/13453 and golang.org/issue/16104) + // and confuses users when it does. + // If the user has explicitly set GIT_SSH or GIT_SSH_COMMAND, + // assume they know what they are doing and don't step on it. + // But default to turning off ControlMaster. + if os.Getenv("GIT_SSH") == "" && os.Getenv("GIT_SSH_COMMAND") == "" { + os.Setenv("GIT_SSH_COMMAND", "ssh -o ControlMaster=no") + } + + var err error + cwd, err = os.Getwd() + if err != nil { + base.Fatalf("go: %v", err) + } + + root, _ := FindModuleRoot(cwd, "", MustBeVgo) + if root == "" { + // If invoked as vgo, insist on a mod file. + if MustBeVgo { + base.Fatalf("cannot determine module root; please create a go.mod file there") + } + return + } + enabled = true + ModRoot = root + search.SetModRoot(root) +} + +func Enabled() bool { + return false // COMPLETELY OFF FOR NOW + /* + if !initialized { + panic("vgo: Enabled called before Init") + } + return enabled + */ +} + +func InitMod() { + if Init(); !Enabled() || modFile != nil { + return + } + + list := filepath.SplitList(cfg.BuildContext.GOPATH) + if len(list) == 0 || list[0] == "" { + base.Fatalf("missing $GOPATH") + } + gopath = list[0] + if _, err := os.Stat(filepath.Join(gopath, "go.mod")); err == nil { + base.Fatalf("$GOPATH/go.mod exists but should not") + } + srcV = filepath.Join(list[0], "src/v") + codehost.WorkRoot = filepath.Join(srcV, "cache/vcswork") + + gomod := filepath.Join(ModRoot, "go.mod") + data, err := ioutil.ReadFile(gomod) + if err != nil { + legacyModInit() + return + } + + f, err := modfile.Parse(gomod, data, fixVersion) + if err != nil { + // Errors returned by modfile.Parse begin with file:line. + base.Fatalf("vgo: errors parsing go.mod:\n%s\n", err) + } + modFile = f + + if len(f.Syntax.Stmt) == 0 || f.Module == nil { + // Empty mod file. Must add module path. + path, err := FindModulePath(ModRoot) + if err != nil { + base.Fatalf("vgo: %v", err) + } + f.AddModuleStmt(path) + } + + if len(f.Syntax.Stmt) == 1 && f.Module != nil { + // Entire file is just a module statement. + // Populate require if possible. + legacyModInit() + } + + excluded = make(map[module.Version]bool) + for _, x := range f.Exclude { + excluded[x.Mod] = true + } + Target = f.Module.Mod + writeGoMod() +} + +func allowed(m module.Version) bool { + return !excluded[m] +} + +func legacyModInit() { + if modFile == nil { + path, err := FindModulePath(ModRoot) + if err != nil { + base.Fatalf("vgo: %v", err) + } + fmt.Fprintf(os.Stderr, "vgo: creating new go.mod: module %s\n", path) + modFile = new(modfile.File) + modFile.AddModuleStmt(path) + } + + Target = modFile.Module.Mod + for _, name := range altConfigs { + cfg := filepath.Join(ModRoot, name) + data, err := ioutil.ReadFile(cfg) + if err == nil { + convert := modconv.Converters[name] + if convert == nil { + return + } + fmt.Fprintf(os.Stderr, "vgo: copying requirements from %s\n", cfg) + cfg = filepath.ToSlash(cfg) + if err := modfetch.ConvertLegacyConfig(modFile, cfg, data); err != nil { + base.Fatalf("vgo: %v", err) + } + if len(modFile.Syntax.Stmt) == 1 { + // Add comment to prevent vgo from re-converting every time it runs. + modFile.AddComment("// vgo: no requirements found in " + name) + } + return + } + } +} + +var altConfigs = []string{ + "Gopkg.lock", + + "GLOCKFILE", + "Godeps/Godeps.json", + "dependencies.tsv", + "glide.lock", + "vendor.conf", + "vendor.yml", + "vendor/manifest", + "vendor/vendor.json", + + ".git/config", +} + +// Exported only for testing. +func FindModuleRoot(dir, limit string, legacyConfigOK bool) (root, file string) { + dir = filepath.Clean(dir) + dir1 := dir + limit = filepath.Clean(limit) + + // Look for enclosing go.mod. + for { + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + return dir, "go.mod" + } + if dir == limit { + break + } + d := filepath.Dir(dir) + if d == dir { + break + } + dir = d + } + + // Failing that, look for enclosing alternate version config. + if legacyConfigOK { + dir = dir1 + for { + for _, name := range altConfigs { + if _, err := os.Stat(filepath.Join(dir, name)); err == nil { + return dir, name + } + } + if dir == limit { + break + } + d := filepath.Dir(dir) + if d == dir { + break + } + dir = d + } + } + + return "", "" +} + +// Exported only for testing. +func FindModulePath(dir string) (string, error) { + for _, gpdir := range filepath.SplitList(cfg.BuildContext.GOPATH) { + src := filepath.Join(gpdir, "src") + string(filepath.Separator) + if strings.HasPrefix(dir, src) { + return filepath.ToSlash(dir[len(src):]), nil + } + } + + // Cast about for import comments, + // first in top-level directory, then in subdirectories. + list, _ := ioutil.ReadDir(dir) + for _, info := range list { + if info.Mode().IsRegular() && strings.HasSuffix(info.Name(), ".go") { + if com := findImportComment(filepath.Join(dir, info.Name())); com != "" { + return com, nil + } + } + } + for _, info1 := range list { + if info1.IsDir() { + files, _ := ioutil.ReadDir(filepath.Join(dir, info1.Name())) + for _, info2 := range files { + if info2.Mode().IsRegular() && strings.HasSuffix(info2.Name(), ".go") { + if com := findImportComment(filepath.Join(dir, info1.Name(), info2.Name())); com != "" { + return path.Dir(com), nil + } + } + } + } + } + + // Look for Godeps.json declaring import path. + data, _ := ioutil.ReadFile(filepath.Join(dir, "Godeps/Godeps.json")) + var cfg struct{ ImportPath string } + json.Unmarshal(data, &cfg) + if cfg.ImportPath != "" { + return cfg.ImportPath, nil + } + + // Look for vendor.json declaring import path. + data, _ = ioutil.ReadFile(filepath.Join(dir, "vendor/vendor.json")) + var cfg2 struct{ RootPath string } + json.Unmarshal(data, &cfg2) + if cfg2.RootPath != "" { + return cfg2.RootPath, nil + } + + // Look for .git/config with github origin as last resort. + data, _ = ioutil.ReadFile(filepath.Join(dir, ".git/config")) + if m := gitOriginRE.FindSubmatch(data); m != nil { + return "github.com/" + string(m[1]), nil + } + + return "", fmt.Errorf("cannot determine module path for source directory %s (outside GOPATH, no import comments)", dir) +} + +var ( + gitOriginRE = regexp.MustCompile(`(?m)^\[remote "origin"\]\r?\n\turl = (?:https://github.com/|git@github.com:|gh:)([^/]+/[^/]+?)(\.git)?\r?\n`) + importCommentRE = regexp.MustCompile(`(?m)^package[ \t]+[^ \t\r\n/]+[ \t]+//[ \t]+import[ \t]+(\"[^"]+\")[ \t]*\r?\n`) +) + +func findImportComment(file string) string { + data, err := ioutil.ReadFile(file) + if err != nil { + return "" + } + m := importCommentRE.FindSubmatch(data) + if m == nil { + return "" + } + path, err := strconv.Unquote(string(m[1])) + if err != nil { + return "" + } + return path +} + +func writeGoMod() { + writeModHash() + + if buildList != nil { + min, err := mvs.Req(Target, buildList, newReqs()) + if err != nil { + base.Fatalf("vgo: %v", err) + } + modFile.SetRequire(min) + } + + file := filepath.Join(ModRoot, "go.mod") + old, _ := ioutil.ReadFile(file) + new, err := modFile.Format() + if err != nil { + base.Fatalf("vgo: %v", err) + } + if bytes.Equal(old, new) { + return + } + if err := ioutil.WriteFile(file, new, 0666); err != nil { + base.Fatalf("vgo: %v", err) + } +} + +func fixVersion(path, vers string) (string, error) { + // Special case: remove the old -gopkgin- hack. + if strings.HasPrefix(path, "gopkg.in/") && strings.Contains(vers, "-gopkgin-") { + vers = vers[strings.Index(vers, "-gopkgin-")+len("-gopkgin-"):] + } + + // fixVersion is called speculatively on every + // module, version pair from every go.mod file. + // Avoid the query if it looks OK. + _, pathMajor, ok := module.SplitPathVersion(path) + if !ok { + return "", fmt.Errorf("malformed module path: %s", path) + } + if semver.IsValid(vers) && vers == semver.Canonical(vers) && module.MatchPathMajor(vers, pathMajor) { + return vers, nil + } + + info, err := modfetch.Query(path, vers, nil) + if err != nil { + return "", err + } + return info.Version, nil +} diff --git a/src/cmd/go/internal/vgo/list.go b/src/cmd/go/internal/vgo/list.go new file mode 100644 index 0000000000..c6656c292d --- /dev/null +++ b/src/cmd/go/internal/vgo/list.go @@ -0,0 +1,153 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vgo + +import ( + "bufio" + "fmt" + "io" + "os" + "regexp" + "unicode/utf8" + + "cmd/go/internal/base" + "cmd/go/internal/modfetch" + "cmd/go/internal/module" +) + +func ListT(pkgs []string) { + if Init(); !Enabled() { + base.Fatalf("go list: cannot use -t outside module") + } + InitMod() + + if len(pkgs) == 0 { + base.Fatalf("vgo list -t: need list of modules") + } + + for _, pkg := range pkgs { + repo, err := modfetch.Lookup(pkg) + if err != nil { + base.Errorf("vgo list -t: %v", err) + continue + } + path := repo.ModulePath() + fmt.Printf("%s\n", path) + tags, err := repo.Versions("") + if err != nil { + base.Errorf("vgo list -t: %v", err) + continue + } + for _, t := range tags { + if excluded[module.Version{Path: path, Version: t}] { + t += " # excluded" + } + fmt.Printf("\t%s\n", t) + } + } +} + +func ListM() { + if Init(); !Enabled() { + base.Fatalf("go list: cannot use -m outside module") + } + InitMod() + iterate(func(*loader) {}) + printListM(os.Stdout) +} + +func printListM(w io.Writer) { + var rows [][]string + rows = append(rows, []string{"MODULE", "VERSION"}) + for _, mod := range buildList { + v := mod.Version + if v == "" { + v = "-" + } + rows = append(rows, []string{mod.Path, v}) + if r := replaced(mod); r != nil { + rows = append(rows, []string{" => " + r.New.Path, r.New.Version}) + } + } + printTable(w, rows) +} + +func ListMU() { + if Init(); !Enabled() { + base.Fatalf("go list: cannot use -m outside module") + } + InitMod() + + quietLookup = true // do not chatter in v.Lookup + iterate(func(*loader) {}) + + var rows [][]string + rows = append(rows, []string{"MODULE", "VERSION", "LATEST"}) + for _, mod := range buildList { + var latest string + v := mod.Version + if v == "" { + v = "-" + latest = "-" + } else { + info, err := modfetch.Query(mod.Path, "latest", allowed) + if err != nil { + latest = "ERR: " + err.Error() + } else { + latest = info.Version + if !isPseudoVersion(latest) && !info.Time.IsZero() { + latest += info.Time.Local().Format(" (2006-01-02 15:04)") + } + } + if !isPseudoVersion(mod.Version) { + if info, err := modfetch.Query(mod.Path, mod.Version, nil); err == nil && !info.Time.IsZero() { + v += info.Time.Local().Format(" (2006-01-02 15:04)") + } + } + } + if latest == v { + latest = "-" + } + rows = append(rows, []string{mod.Path, v, latest}) + } + printTable(os.Stdout, rows) +} + +var pseudoVersionRE = regexp.MustCompile(`^v[0-9]+\.0\.0-[0-9]{14}-[A-Za-z0-9]+$`) + +func isPseudoVersion(v string) bool { + return pseudoVersionRE.MatchString(v) +} + +func printTable(w io.Writer, rows [][]string) { + var max []int + for _, row := range rows { + for i, c := range row { + n := utf8.RuneCountInString(c) + if i >= len(max) { + max = append(max, n) + } else if max[i] < n { + max[i] = n + } + } + } + + b := bufio.NewWriter(w) + for _, row := range rows { + for len(row) > 0 && row[len(row)-1] == "" { + row = row[:len(row)-1] + } + for i, c := range row { + b.WriteString(c) + if i+1 < len(row) { + for j := utf8.RuneCountInString(c); j < max[i]+2; j++ { + b.WriteRune(' ') + } + } + } + b.WriteRune('\n') + } + b.Flush() +} diff --git a/src/cmd/go/internal/vgo/load.go b/src/cmd/go/internal/vgo/load.go new file mode 100644 index 0000000000..3ebba06787 --- /dev/null +++ b/src/cmd/go/internal/vgo/load.go @@ -0,0 +1,575 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vgo + +import ( + "bytes" + "encoding/json" + "fmt" + "go/build" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + + "cmd/go/internal/base" + "cmd/go/internal/cfg" + "cmd/go/internal/imports" + "cmd/go/internal/modconv" + "cmd/go/internal/modfetch" + "cmd/go/internal/modfile" + "cmd/go/internal/module" + "cmd/go/internal/mvs" + "cmd/go/internal/search" + "cmd/go/internal/semver" +) + +type importLevel int + +const ( + levelNone importLevel = 0 + levelBuild importLevel = 1 + levelTest importLevel = 2 + levelTestRecursive importLevel = 3 +) + +var ( + buildList []module.Version + tags map[string]bool + importmap map[string]string + pkgdir map[string]string + pkgmod map[string]module.Version + isGetU bool +) + +func AddImports(gofiles []string) { + if Init(); !Enabled() { + return + } + InitMod() + + imports, testImports, err := imports.ScanFiles(gofiles, tags) + if err != nil { + base.Fatalf("vgo: %v", err) + } + + iterate(func(ld *loader) { + ld.importList(imports, levelBuild) + ld.importList(testImports, levelBuild) + }) + writeGoMod() +} + +func ImportPaths(args []string) []string { + if Init(); !Enabled() { + return search.ImportPaths(args) + } + InitMod() + + paths := importPaths(args) + writeGoMod() + return paths +} + +func importPaths(args []string) []string { + level := levelBuild + switch cfg.CmdName { + case "test", "vet": + level = levelTest + } + cleaned := search.CleanImportPaths(args) + iterate(func(ld *loader) { + args = expandImportPaths(cleaned) + for i, pkg := range args { + if pkg == "." || pkg == ".." || strings.HasPrefix(pkg, "./") || strings.HasPrefix(pkg, "../") { + dir := filepath.Join(cwd, pkg) + if dir == ModRoot { + pkg = Target.Path + } else if strings.HasPrefix(dir, ModRoot+string(filepath.Separator)) { + pkg = Target.Path + filepath.ToSlash(dir[len(ModRoot):]) + } else { + base.Errorf("vgo: package %s outside module root", pkg) + continue + } + args[i] = pkg + } + ld.importPkg(pkg, level) + } + }) + return args +} + +func Lookup(parentPath, path string) (dir, realPath string, err error) { + realPath = importmap[path] + if realPath == "" { + if isStandardImportPath(path) { + dir := filepath.Join(cfg.GOROOT, "src", path) + if _, err := os.Stat(dir); err == nil { + return dir, path, nil + } + } + return "", "", fmt.Errorf("no such package in module") + } + return pkgdir[realPath], realPath, nil +} + +func iterate(doImports func(*loader)) { + var err error + mvsOp := mvs.BuildList + if isGetU { + mvsOp = mvs.UpgradeAll + } + buildList, err = mvsOp(Target, newReqs()) + if err != nil { + base.Fatalf("vgo: %v", err) + } + + var ld *loader + for { + ld = newLoader() + doImports(ld) + if len(ld.missing) == 0 { + break + } + for _, m := range ld.missing { + findMissing(m) + } + base.ExitIfErrors() + buildList, err = mvsOp(Target, newReqs()) + if err != nil { + base.Fatalf("vgo: %v", err) + } + } + base.ExitIfErrors() + + importmap = ld.importmap + pkgdir = ld.pkgdir + pkgmod = ld.pkgmod +} + +type loader struct { + imported map[string]importLevel + importmap map[string]string + pkgdir map[string]string + pkgmod map[string]module.Version + tags map[string]bool + missing []missing + imports []string + stack []string +} + +type missing struct { + path string + stack string +} + +func newLoader() *loader { + ld := &loader{ + imported: make(map[string]importLevel), + importmap: make(map[string]string), + pkgdir: make(map[string]string), + pkgmod: make(map[string]module.Version), + tags: imports.Tags(), + } + ld.imported["C"] = 100 + return ld +} + +func (ld *loader) stackText() string { + var buf bytes.Buffer + for _, p := range ld.stack[:len(ld.stack)-1] { + fmt.Fprintf(&buf, "import %q ->\n\t", p) + } + fmt.Fprintf(&buf, "import %q", ld.stack[len(ld.stack)-1]) + return buf.String() +} + +func (ld *loader) importList(pkgs []string, level importLevel) { + for _, pkg := range pkgs { + ld.importPkg(pkg, level) + } +} + +func (ld *loader) importPkg(path string, level importLevel) { + if ld.imported[path] >= level { + return + } + + ld.stack = append(ld.stack, path) + defer func() { + ld.stack = ld.stack[:len(ld.stack)-1] + }() + + // Any rewritings go here. + realPath := path + + ld.imported[path] = level + ld.importmap[path] = realPath + if realPath != path && ld.imported[realPath] >= level { + // Already handled. + return + } + + dir := ld.importDir(realPath) + if dir == "" { + return + } + + ld.pkgdir[realPath] = dir + + imports, testImports, err := imports.ScanDir(dir, ld.tags) + if err != nil { + base.Errorf("vgo: %s [%s]: %v", ld.stackText(), dir, err) + return + } + nextLevel := level + if level == levelTest { + nextLevel = levelBuild + } + for _, pkg := range imports { + ld.importPkg(pkg, nextLevel) + } + if level >= levelTest { + for _, pkg := range testImports { + ld.importPkg(pkg, nextLevel) + } + } +} + +func (ld *loader) importDir(path string) string { + if importPathInModule(path, Target.Path) { + dir := ModRoot + if len(path) > len(Target.Path) { + dir = filepath.Join(dir, path[len(Target.Path)+1:]) + } + ld.pkgmod[path] = Target + return dir + } + + i := strings.Index(path, "/") + if i < 0 || !strings.Contains(path[:i], ".") { + if strings.HasPrefix(path, "golang_org/") { + return filepath.Join(cfg.GOROOT, "src/vendor", path) + } + dir := filepath.Join(cfg.GOROOT, "src", path) + if _, err := os.Stat(dir); err == nil { + return dir + } + } + + var mod1 module.Version + var dir1 string + for _, mod := range buildList { + if !importPathInModule(path, mod.Path) { + continue + } + dir, err := fetch(mod) + if err != nil { + base.Errorf("vgo: %s: %v", ld.stackText(), err) + return "" + } + if len(path) > len(mod.Path) { + dir = filepath.Join(dir, path[len(mod.Path)+1:]) + } + if dir1 != "" { + base.Errorf("vgo: %s: found in both %v %v and %v %v", ld.stackText(), + mod1.Path, mod1.Version, mod.Path, mod.Version) + return "" + } + dir1 = dir + mod1 = mod + } + if dir1 != "" { + ld.pkgmod[path] = mod1 + return dir1 + } + ld.missing = append(ld.missing, missing{path, ld.stackText()}) + return "" +} + +func replaced(mod module.Version) *modfile.Replace { + var found *modfile.Replace + for _, r := range modFile.Replace { + if r.Old == mod { + found = r // keep going + } + } + return found +} + +func importPathInModule(path, mpath string) bool { + return mpath == path || + len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath +} + +var found = make(map[string]bool) + +func findMissing(m missing) { + for _, mod := range buildList { + if importPathInModule(m.path, mod.Path) { + // Leave for ordinary build to complain about the missing import. + return + } + } + if build.IsLocalImport(m.path) { + base.Errorf("vgo: relative import is not supported: %s", m.path) + return + } + fmt.Fprintf(os.Stderr, "vgo: resolving import %q\n", m.path) + repo, info, err := modfetch.Import(m.path, allowed) + if err != nil { + base.Errorf("vgo: %s: %v", m.stack, err) + return + } + root := repo.ModulePath() + fmt.Fprintf(os.Stderr, "vgo: finding %s (latest)\n", root) + if found[root] { + base.Fatalf("internal error: findmissing loop on %s", root) + } + found[root] = true + fmt.Fprintf(os.Stderr, "vgo: adding %s %s\n", root, info.Version) + buildList = append(buildList, module.Version{Path: root, Version: info.Version}) + modFile.AddRequire(root, info.Version) +} + +type mvsReqs struct { + extra []module.Version +} + +func newReqs(extra ...module.Version) *mvsReqs { + r := &mvsReqs{ + extra: extra, + } + return r +} + +func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) { + list, err := r.required(mod) + if err != nil { + return nil, err + } + if *getU { + for i := range list { + list[i].Version = "none" + } + return list, nil + } + for i, mv := range list { + for excluded[mv] { + mv1, err := r.Next(mv) + if err != nil { + return nil, err + } + if mv1.Version == "" { + return nil, fmt.Errorf("%s(%s) depends on excluded %s(%s) with no newer version available", mod.Path, mod.Version, mv.Path, mv.Version) + } + mv = mv1 + } + list[i] = mv + } + return list, nil +} + +var vgoVersion = []byte(modconv.Prefix) + +func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) { + if mod == Target { + var list []module.Version + if buildList != nil { + list = append(list, buildList[1:]...) + return list, nil + } + for _, r := range modFile.Require { + list = append(list, r.Mod) + } + list = append(list, r.extra...) + return list, nil + } + + origPath := mod.Path + if repl := replaced(mod); repl != nil { + if repl.New.Version == "" { + // TODO: need to slip the new version into the tags list etc. + dir := repl.New.Path + if !filepath.IsAbs(dir) { + dir = filepath.Join(ModRoot, dir) + } + gomod := filepath.Join(dir, "go.mod") + data, err := ioutil.ReadFile(gomod) + if err != nil { + return nil, err + } + f, err := modfile.Parse(gomod, data, nil) + if err != nil { + return nil, err + } + var list []module.Version + for _, r := range f.Require { + list = append(list, r.Mod) + } + return list, nil + } + mod = repl.New + } + + if mod.Version == "none" { + return nil, nil + } + + if !semver.IsValid(mod.Version) { + // Disallow the broader queries supported by fetch.Lookup. + panic(fmt.Errorf("invalid semantic version %q for %s", mod.Version, mod.Path)) + // TODO return nil, fmt.Errorf("invalid semantic version %q", mod.Version) + } + + gomod := filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".mod") + infofile := filepath.Join(srcV, "cache", mod.Path, "@v", mod.Version+".info") + var f *modfile.File + if data, err := ioutil.ReadFile(gomod); err == nil { + // If go.mod has a //vgo comment at the start, + // it was auto-converted from a legacy lock file. + // The auto-conversion details may have bugs and + // may be fixed in newer versions of vgo. + // We ignore cached go.mod files if they do not match + // our own vgoVersion. + if !bytes.HasPrefix(data, vgoVersion[:len("//vgo")]) || bytes.HasPrefix(data, vgoVersion) { + f, err := modfile.Parse(gomod, data, nil) + if err != nil { + return nil, err + } + var list []module.Version + for _, r := range f.Require { + list = append(list, r.Mod) + } + return list, nil + } + f, err = modfile.Parse("go.mod", data, nil) + if err != nil { + return nil, fmt.Errorf("parsing downloaded go.mod: %v", err) + } + } else { + if !quietLookup { + fmt.Fprintf(os.Stderr, "vgo: finding %s %s\n", mod.Path, mod.Version) + } + repo, err := modfetch.Lookup(mod.Path) + if err != nil { + base.Errorf("vgo: %s: %v\n", mod.Path, err) + return nil, err + } + info, err := repo.Stat(mod.Version) + if err != nil { + base.Errorf("vgo: %s %s: %v\n", mod.Path, mod.Version, err) + return nil, err + } + data, err := repo.GoMod(info.Version) + if err != nil { + base.Errorf("vgo: %s %s: %v\n", mod.Path, mod.Version, err) + return nil, err + } + + f, err = modfile.Parse("go.mod", data, nil) + if err != nil { + return nil, fmt.Errorf("parsing downloaded go.mod: %v", err) + } + + dir := filepath.Dir(gomod) + if err := os.MkdirAll(dir, 0777); err != nil { + return nil, fmt.Errorf("caching go.mod: %v", err) + } + js, err := json.Marshal(info) + if err != nil { + return nil, fmt.Errorf("internal error: json failure: %v", err) + } + if err := ioutil.WriteFile(infofile, js, 0666); err != nil { + return nil, fmt.Errorf("caching info: %v", err) + } + if err := ioutil.WriteFile(gomod, data, 0666); err != nil { + return nil, fmt.Errorf("caching go.mod: %v", err) + } + } + if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path { + return nil, fmt.Errorf("downloaded %q and got module %q", mod.Path, mpath) + } + + var list []module.Version + for _, req := range f.Require { + list = append(list, req.Mod) + } + if false { + fmt.Fprintf(os.Stderr, "REQLIST %v:\n", mod) + for _, req := range list { + fmt.Fprintf(os.Stderr, "\t%v\n", req) + } + } + return list, nil +} + +var quietLookup bool + +func (*mvsReqs) Max(v1, v2 string) string { + if semver.Compare(v1, v2) == -1 { + return v2 + } + return v1 +} + +func (*mvsReqs) Latest(path string) (module.Version, error) { + // Note that query "latest" is not the same as + // using repo.Latest. + // The query only falls back to untagged versions + // if nothing is tagged. The Latest method + // only ever returns untagged versions, + // which is not what we want. + fmt.Fprintf(os.Stderr, "vgo: finding %s latest\n", path) + info, err := modfetch.Query(path, "latest", allowed) + if err != nil { + return module.Version{}, err + } + return module.Version{Path: path, Version: info.Version}, nil +} + +var versionCache = make(map[string][]string) + +func versions(path string) ([]string, error) { + list, ok := versionCache[path] + if !ok { + var err error + repo, err := modfetch.Lookup(path) + if err != nil { + return nil, err + } + list, err = repo.Versions("") + if err != nil { + return nil, err + } + versionCache[path] = list + } + return list, nil +} + +func (*mvsReqs) Previous(m module.Version) (module.Version, error) { + list, err := versions(m.Path) + if err != nil { + return module.Version{}, err + } + i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) >= 0 }) + if i > 0 { + return module.Version{Path: m.Path, Version: list[i-1]}, nil + } + return module.Version{Path: m.Path, Version: "none"}, nil +} + +func (*mvsReqs) Next(m module.Version) (module.Version, error) { + list, err := versions(m.Path) + if err != nil { + return module.Version{}, err + } + i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) > 0 }) + if i < len(list) { + return module.Version{Path: m.Path, Version: list[i]}, nil + } + return module.Version{Path: m.Path, Version: "none"}, nil +} diff --git a/src/cmd/go/internal/vgo/search.go b/src/cmd/go/internal/vgo/search.go new file mode 100644 index 0000000000..c3f7ab13bf --- /dev/null +++ b/src/cmd/go/internal/vgo/search.go @@ -0,0 +1,196 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vgo + +import ( + "fmt" + "go/build" + "os" + "path/filepath" + "sort" + "strings" + + "cmd/go/internal/base" + "cmd/go/internal/cfg" + "cmd/go/internal/imports" + "cmd/go/internal/module" + "cmd/go/internal/search" +) + +func expandImportPaths(args []string) []string { + var out []string + for _, a := range args { + // TODO(rsc): Move a == "ALL" test into search.IsMetaPackage + // once we officially lock in all the module work (tentatively, Go 1.12). + if search.IsMetaPackage(a) || a == "ALL" { + switch a { + default: + fmt.Fprintf(os.Stderr, "vgo: warning: %q matches no packages when using modules\n", a) + case "all", "ALL": + out = append(out, AllPackages(a)...) + } + continue + } + if strings.Contains(a, "...") { + if build.IsLocalImport(a) { + out = append(out, search.AllPackagesInFS(a)...) + } else { + out = append(out, AllPackages(a)...) + } + continue + } + out = append(out, a) + } + return out +} + +// AllPackages returns all the packages that can be found +// under the $GOPATH directories and $GOROOT matching pattern. +// The pattern is either "all" (all packages), "std" (standard packages), +// "cmd" (standard commands), or a path including "...". +func AllPackages(pattern string) []string { + pkgs := MatchPackages(pattern) + if len(pkgs) == 0 { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } + return pkgs +} + +// MatchPackages returns a list of package paths matching pattern +// (see go help packages for pattern syntax). +func MatchPackages(pattern string) []string { + if pattern == "std" || pattern == "cmd" { + return nil + } + if pattern == "all" { + return MatchAll() + } + if pattern == "ALL" { + return MatchALL() + } + + return matchPackages(pattern, buildList) +} + +func matchPackages(pattern string, buildList []module.Version) []string { + match := func(string) bool { return true } + treeCanMatch := func(string) bool { return true } + if !search.IsMetaPackage(pattern) && pattern != "ALL" { + match = search.MatchPattern(pattern) + treeCanMatch = search.TreeCanMatchPattern(pattern) + } + + have := map[string]bool{ + "builtin": true, // ignore pseudo-package that exists only for documentation + } + if !cfg.BuildContext.CgoEnabled { + have["runtime/cgo"] = true // ignore during walk + } + var pkgs []string + + for _, mod := range buildList { + if !treeCanMatch(mod.Path) { + continue + } + var root string + if mod.Version == "" { + root = ModRoot + } else { + var err error + root, err = fetch(mod) + if err != nil { + base.Errorf("vgo: %v", err) + continue + } + } + root = filepath.Clean(root) + + filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { + if err != nil { + return nil + } + + want := true + // Avoid .foo, _foo, and testdata directory trees. + _, elem := filepath.Split(path) + if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { + want = false + } + + name := mod.Path + filepath.ToSlash(path[len(root):]) + if !treeCanMatch(name) { + want = false + } + + if !fi.IsDir() { + if fi.Mode()&os.ModeSymlink != 0 && want { + if target, err := os.Stat(path); err == nil && target.IsDir() { + fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) + } + } + return nil + } + + if !want { + return filepath.SkipDir + } + if path != root { + if _, err := os.Stat(filepath.Join(path, "go.mod")); err == nil { + return filepath.SkipDir + } + } + + if !have[name] { + have[name] = true + if match(name) { + if _, _, err := imports.ScanDir(path, imports.Tags()); err != imports.ErrNoGo { + pkgs = append(pkgs, name) + } + } + } + + if elem == "vendor" { + return filepath.SkipDir + } + return nil + }) + } + return pkgs +} + +// MatchAll returns a list of the packages matching the pattern "all". +// We redefine "all" to mean start with the packages in the current module +// and then follow imports into other modules to add packages imported +// (directly or indirectly) as part of builds in this module. +// It does not include packages in other modules that are not needed +// by builds of this module. +func MatchAll() []string { + return matchAll(imports.Tags()) +} + +// MatchALL returns a list of the packages matching the pattern "ALL". +// The pattern "ALL" is like "all" but looks at all source files, +// even ones that would be ignored by current build tag settings. +// That's useful for identifying which packages to include in a vendor directory. +func MatchALL() []string { + return matchAll(map[string]bool{"*": true}) +} + +// matchAll is the common implementation of MatchAll and MatchALL, +// which differ only in the set of tags to apply to select files. +func matchAll(tags map[string]bool) []string { + local := matchPackages("all", buildList[:1]) + ld := newLoader() + ld.tags = tags + ld.importList(local, levelTestRecursive) + var all []string + for _, pkg := range ld.importmap { + if !isStandardImportPath(pkg) { + all = append(all, pkg) + } + } + sort.Strings(all) + return all +} diff --git a/src/cmd/go/internal/vgo/vendor.go b/src/cmd/go/internal/vgo/vendor.go new file mode 100644 index 0000000000..acba4afbbc --- /dev/null +++ b/src/cmd/go/internal/vgo/vendor.go @@ -0,0 +1,156 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vgo + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "cmd/go/internal/base" + "cmd/go/internal/module" +) + +var CmdVendor = &base.Command{ + UsageLine: "vendor [-v]", + Short: "vendor dependencies of current module", + Long: ` +Vendor resets the module's vendor directory to include all +packages needed to build and test all packages in the module +and their dependencies. + +The -v flag causes vendor to print to standard error the +module paths of the modules processed and the import paths +of the packages copied. + `, +} + +var vendorV = CmdVendor.Flag.Bool("v", false, "") + +func init() { + CmdVendor.Run = runVendor // break init cycle +} + +func runVendor(cmd *base.Command, args []string) { + if Init(); !Enabled() { + base.Fatalf("vgo vendor: cannot use -m outside module") + } + if len(args) != 0 { + base.Fatalf("vgo vendor: vendor takes no arguments") + } + InitMod() + pkgs := ImportPaths([]string{"ALL"}) + + vdir := filepath.Join(ModRoot, "vendor") + if err := os.RemoveAll(vdir); err != nil { + base.Fatalf("vgo vendor: %v", err) + } + + modpkgs := make(map[module.Version][]string) + for _, pkg := range pkgs { + m := pkgmod[pkg] + if m == Target { + continue + } + modpkgs[m] = append(modpkgs[m], pkg) + } + + var buf bytes.Buffer + for _, m := range buildList[1:] { + if pkgs := modpkgs[m]; len(pkgs) > 0 { + repl := "" + if r := replaced(m); r != nil { + repl = " => " + r.New.Path + if r.New.Version != "" { + repl += " " + r.New.Version + } + } + fmt.Fprintf(&buf, "# %s %s%s\n", m.Path, m.Version, repl) + if *vendorV { + fmt.Fprintf(os.Stderr, "# %s %s%s\n", m.Path, m.Version, repl) + } + for _, pkg := range pkgs { + fmt.Fprintf(&buf, "%s\n", pkg) + if *vendorV { + fmt.Fprintf(os.Stderr, "%s\n", pkg) + } + vendorPkg(vdir, pkg) + } + } + } + if err := ioutil.WriteFile(filepath.Join(vdir, "vgo.list"), buf.Bytes(), 0666); err != nil { + base.Fatalf("vgo vendor: %v", err) + } +} + +func vendorPkg(vdir, pkg string) { + realPath := importmap[pkg] + if realPath != pkg && importmap[realPath] != "" { + fmt.Fprintf(os.Stderr, "warning: %s imported as both %s and %s; making two copies.\n", realPath, realPath, pkg) + } + + dst := filepath.Join(vdir, pkg) + src := pkgdir[realPath] + if src == "" { + fmt.Fprintf(os.Stderr, "internal error: no pkg for %s -> %s\n", pkg, realPath) + } + copyDir(dst, src, false) +} + +func copyDir(dst, src string, recursive bool) { + files, err := ioutil.ReadDir(src) + if err != nil { + base.Fatalf("vgo vendor: %v", err) + } + if err := os.MkdirAll(dst, 0777); err != nil { + base.Fatalf("vgo vendor: %v", err) + } + for _, file := range files { + if file.IsDir() { + if recursive || file.Name() == "testdata" { + copyDir(filepath.Join(dst, file.Name()), filepath.Join(src, file.Name()), true) + } + continue + } + if !file.Mode().IsRegular() { + continue + } + r, err := os.Open(filepath.Join(src, file.Name())) + if err != nil { + base.Fatalf("vgo vendor: %v", err) + } + w, err := os.Create(filepath.Join(dst, file.Name())) + if err != nil { + base.Fatalf("vgo vendor: %v", err) + } + if _, err := io.Copy(w, r); err != nil { + base.Fatalf("vgo vendor: %v", err) + } + r.Close() + if err := w.Close(); err != nil { + base.Fatalf("vgo vendor: %v", err) + } + } +} + +// hasPathPrefix reports whether the path s begins with the +// elements in prefix. +func hasPathPrefix(s, prefix string) bool { + switch { + default: + return false + case len(s) == len(prefix): + return s == prefix + case len(s) > len(prefix): + if prefix != "" && prefix[len(prefix)-1] == '/' { + return strings.HasPrefix(s, prefix) + } + return s[len(prefix)] == '/' && s[:len(prefix)] == prefix + } +} diff --git a/src/cmd/go/internal/vgo/verify.go b/src/cmd/go/internal/vgo/verify.go new file mode 100644 index 0000000000..d21c9d7673 --- /dev/null +++ b/src/cmd/go/internal/vgo/verify.go @@ -0,0 +1,104 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vgo + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "cmd/go/internal/base" + "cmd/go/internal/dirhash" + "cmd/go/internal/module" +) + +var CmdVerify = &base.Command{ + UsageLine: "verify", + Run: runVerify, + Short: "verify downloaded modules against expected hashes", + Long: ` +Verify checks that the dependencies of the current module, +which are stored in a local downloaded source cache, +have not been modified since being downloaded. + +If all the modules are unmodified, verify prints + + all modules verified + +and exits successfully (status 0). Otherwise, verify reports +which modules have been changed and exits with a non-zero status. + `, +} + +func runVerify(cmd *base.Command, args []string) { + if Init(); !Enabled() { + base.Fatalf("vgo verify: cannot use outside module") + } + if len(args) != 0 { + // TODO: take arguments + base.Fatalf("vgo verify: verify takes no arguments") + } + + // Make go.mod consistent but don't load any packages. + InitMod() + iterate(func(*loader) {}) + writeGoMod() + + ok := true + for _, mod := range buildList[1:] { + ok = verifyMod(mod) && ok + } + if ok { + fmt.Printf("all modules verified\n") + } +} + +func verifyMod(mod module.Version) bool { + ok := true + zip := filepath.Join(srcV, "cache", mod.Path, "/@v/", mod.Version+".zip") + _, zipErr := os.Stat(zip) + dir := filepath.Join(srcV, mod.Path+"@"+mod.Version) + _, dirErr := os.Stat(dir) + data, err := ioutil.ReadFile(zip + "hash") + if err != nil { + if zipErr != nil && os.IsNotExist(zipErr) && dirErr != nil && os.IsNotExist(dirErr) { + // Nothing downloaded yet. Nothing to verify. + return true + } + base.Errorf("%s %s: missing ziphash: %v", mod.Path, mod.Version, err) + return false + } + h := string(bytes.TrimSpace(data)) + + if zipErr != nil && os.IsNotExist(zipErr) { + // ok + } else { + hZ, err := dirhash.HashZip(zip, dirhash.DefaultHash) + if err != nil { + base.Errorf("%s %s: %v", mod.Path, mod.Version, err) + return false + } else if hZ != h { + base.Errorf("%s %s: zip has been modified (%v)", mod.Path, mod.Version, zip) + ok = false + } + } + if dirErr != nil && os.IsNotExist(dirErr) { + // ok + } else { + hD, err := dirhash.HashDir(dir, mod.Path+"@"+mod.Version, dirhash.DefaultHash) + if err != nil { + + base.Errorf("%s %s: %v", mod.Path, mod.Version, err) + return false + } + if hD != h { + base.Errorf("%s %s: dir has been modified (%v)", mod.Path, mod.Version, dir) + ok = false + } + } + return ok +} diff --git a/src/cmd/go/internal/web2/web.go b/src/cmd/go/internal/web2/web.go new file mode 100644 index 0000000000..d11ee6bb2b --- /dev/null +++ b/src/cmd/go/internal/web2/web.go @@ -0,0 +1,297 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package web2 + +import ( + "bytes" + "cmd/go/internal/base" + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" + "runtime/debug" + "strings" + "sync" +) + +var TraceGET = false +var webstack = false + +func init() { + flag.BoolVar(&TraceGET, "webtrace", TraceGET, "trace GET requests") + flag.BoolVar(&webstack, "webstack", webstack, "print stack for GET requests") +} + +type netrcLine struct { + machine string + login string + password string +} + +var netrcOnce sync.Once +var netrc []netrcLine + +func parseNetrc(data string) []netrcLine { + var nrc []netrcLine + var l netrcLine + for _, line := range strings.Split(data, "\n") { + f := strings.Fields(line) + for i := 0; i < len(f)-1; i += 2 { + switch f[i] { + case "machine": + l.machine = f[i+1] + case "login": + l.login = f[i+1] + case "password": + l.password = f[i+1] + } + } + if l.machine != "" && l.login != "" && l.password != "" { + nrc = append(nrc, l) + l = netrcLine{} + } + } + return nrc +} + +func havePassword(machine string) bool { + netrcOnce.Do(readNetrc) + for _, line := range netrc { + if line.machine == machine { + return true + } + } + return false +} + +func netrcPath() string { + switch runtime.GOOS { + case "windows": + return filepath.Join(os.Getenv("USERPROFILE"), "_netrc") + case "plan9": + return filepath.Join(os.Getenv("home"), ".netrc") + default: + return filepath.Join(os.Getenv("HOME"), ".netrc") + } +} + +func readNetrc() { + data, err := ioutil.ReadFile(netrcPath()) + if err != nil { + return + } + netrc = parseNetrc(string(data)) +} + +type getState struct { + req *http.Request + resp *http.Response + body io.ReadCloser + non200ok bool +} + +type Option interface { + option(*getState) error +} + +func Non200OK() Option { + return optionFunc(func(g *getState) error { + g.non200ok = true + return nil + }) +} + +type optionFunc func(*getState) error + +func (f optionFunc) option(g *getState) error { + return f(g) +} + +func DecodeJSON(dst interface{}) Option { + return optionFunc(func(g *getState) error { + if g.resp != nil { + return json.NewDecoder(g.body).Decode(dst) + } + return nil + }) +} + +func ReadAllBody(body *[]byte) Option { + return optionFunc(func(g *getState) error { + if g.resp != nil { + var err error + *body, err = ioutil.ReadAll(g.body) + return err + } + return nil + }) +} + +func Body(body *io.ReadCloser) Option { + return optionFunc(func(g *getState) error { + if g.resp != nil { + *body = g.body + g.body = nil + } + return nil + }) +} + +func Header(hdr *http.Header) Option { + return optionFunc(func(g *getState) error { + if g.resp != nil { + *hdr = CopyHeader(g.resp.Header) + } + return nil + }) +} + +func CopyHeader(hdr http.Header) http.Header { + if hdr == nil { + return nil + } + h2 := make(http.Header) + for k, v := range hdr { + v2 := make([]string, len(v)) + copy(v2, v) + h2[k] = v2 + } + return h2 +} + +var cache struct { + mu sync.Mutex + byURL map[string]*cacheEntry +} + +type cacheEntry struct { + mu sync.Mutex + resp *http.Response + body []byte +} + +var httpDo = http.DefaultClient.Do + +func SetHTTPDoForTesting(do func(*http.Request) (*http.Response, error)) { + if do == nil { + do = http.DefaultClient.Do + } + httpDo = do +} + +func Get(url string, options ...Option) error { + if TraceGET || webstack { + println("GET", url) + if webstack { + println(string(debug.Stack())) + } + } + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + + netrcOnce.Do(readNetrc) + for _, l := range netrc { + if l.machine == req.URL.Host { + req.SetBasicAuth(l.login, l.password) + break + } + } + + g := &getState{req: req} + for _, o := range options { + if err := o.option(g); err != nil { + return err + } + } + + cache.mu.Lock() + e := cache.byURL[url] + if e == nil { + e = new(cacheEntry) + if !strings.HasPrefix(url, "file:") { + if cache.byURL == nil { + cache.byURL = make(map[string]*cacheEntry) + } + cache.byURL[url] = e + } + } + cache.mu.Unlock() + + e.mu.Lock() + if strings.HasPrefix(url, "file:") { + body, err := ioutil.ReadFile(req.URL.Path) + if err != nil { + e.mu.Unlock() + return err + } + e.body = body + e.resp = &http.Response{ + StatusCode: 200, + } + } else if e.resp == nil { + resp, err := httpDo(req) + if err != nil { + e.mu.Unlock() + return err + } + e.resp = resp + // TODO: Spool to temp file. + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + resp.Body = nil + if err != nil { + e.mu.Unlock() + return err + } + e.body = body + } + g.resp = e.resp + g.body = ioutil.NopCloser(bytes.NewReader(e.body)) + e.mu.Unlock() + + defer func() { + if g.body != nil { + g.body.Close() + } + }() + + if g.resp.StatusCode == 403 && req.URL.Host == "api.github.com" && !havePassword("api.github.com") { + base.Errorf("%s", githubMessage) + } + if !g.non200ok && g.resp.StatusCode != 200 { + return fmt.Errorf("unexpected status (%s): %v", url, g.resp.Status) + } + + for _, o := range options { + if err := o.option(g); err != nil { + return err + } + } + return err +} + +var githubMessage = `vgo: 403 response from api.github.com + +GitHub applies fairly small rate limits to unauthenticated users, and +you appear to be hitting them. To authenticate, please visit +https://github.com/settings/tokens and click "Generate New Token" to +create a Personal Access Token. The token only needs "public_repo" +scope, but you can add "repo" if you want to access private +repositories too. + +Add the token to your $HOME/.netrc (%USERPROFILE%\_netrc on Windows): + + machine api.github.com login YOU password TOKEN + +Sorry for the interruption. +` diff --git a/src/cmd/go/internal/web2/web_test.go b/src/cmd/go/internal/web2/web_test.go new file mode 100644 index 0000000000..c6f6b1eff4 --- /dev/null +++ b/src/cmd/go/internal/web2/web_test.go @@ -0,0 +1,35 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package web2 + +import ( + "reflect" + "testing" +) + +var testNetrc = ` +machine api.github.com + login user + password pwd + +machine incomlete.host + login justlogin + +machine test.host +login user2 +password pwd2 +` + +func TestReadNetrc(t *testing.T) { + lines := parseNetrc(testNetrc) + want := []netrcLine{ + {"api.github.com", "user", "pwd"}, + {"test.host", "user2", "pwd2"}, + } + + if !reflect.DeepEqual(lines, want) { + t.Errorf("parseNetrc:\nhave %q\nwant %q", lines, want) + } +} diff --git a/src/cmd/go/internal/webtest/test.go b/src/cmd/go/internal/webtest/test.go new file mode 100644 index 0000000000..94b20a33ff --- /dev/null +++ b/src/cmd/go/internal/webtest/test.go @@ -0,0 +1,314 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package webtest + +import ( + "bufio" + "bytes" + "encoding/hex" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "sort" + "strconv" + "strings" + "sync" + "unicode/utf8" + + web "cmd/go/internal/web2" +) + +var mode = flag.String("webtest", "replay", "set webtest `mode` - record, replay, bypass") + +func Hook() { + if *mode == "bypass" { + return + } + web.SetHTTPDoForTesting(Do) +} + +func Unhook() { + web.SetHTTPDoForTesting(nil) +} + +func Print() { + web.SetHTTPDoForTesting(DoPrint) +} + +var responses struct { + mu sync.Mutex + byURL map[string]*respEntry +} + +type respEntry struct { + status string + code int + hdr http.Header + body []byte +} + +func Serve(url string, status string, hdr http.Header, body []byte) { + if status == "" { + status = "200 OK" + } + code, err := strconv.Atoi(strings.Fields(status)[0]) + if err != nil { + panic("bad Serve status - " + status + " - " + err.Error()) + } + + responses.mu.Lock() + defer responses.mu.Unlock() + + if responses.byURL == nil { + responses.byURL = make(map[string]*respEntry) + } + responses.byURL[url] = &respEntry{status: status, code: code, hdr: web.CopyHeader(hdr), body: body} +} + +func Do(req *http.Request) (*http.Response, error) { + if req.Method != "GET" { + return nil, fmt.Errorf("bad method - must be GET") + } + + responses.mu.Lock() + e := responses.byURL[req.URL.String()] + responses.mu.Unlock() + + if e == nil { + if *mode == "record" { + loaded.mu.Lock() + if len(loaded.did) != 1 { + loaded.mu.Unlock() + return nil, fmt.Errorf("cannot use -webtest=record with multiple loaded response files") + } + var file string + for file = range loaded.did { + break + } + loaded.mu.Unlock() + return doSave(file, req) + } + e = &respEntry{code: 599, status: "599 unexpected request (no canned response)"} + } + resp := &http.Response{ + Status: e.status, + StatusCode: e.code, + Header: web.CopyHeader(e.hdr), + Body: ioutil.NopCloser(bytes.NewReader(e.body)), + } + return resp, nil +} + +func DoPrint(req *http.Request) (*http.Response, error) { + return doSave("", req) +} + +func doSave(file string, req *http.Request) (*http.Response, error) { + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + data, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, err + } + resp.Body = ioutil.NopCloser(bytes.NewReader(data)) + + var f *os.File + if file == "" { + f = os.Stderr + } else { + f, err = os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatal(err) + } + defer f.Close() + } + + fmt.Fprintf(f, "GET %s\n", req.URL.String()) + fmt.Fprintf(f, "%s\n", resp.Status) + var keys []string + for k := range resp.Header { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + if k == "Set-Cookie" { + continue + } + for _, v := range resp.Header[k] { + fmt.Fprintf(f, "%s: %s\n", k, v) + } + } + fmt.Fprintf(f, "\n") + if utf8.Valid(data) && !bytes.Contains(data, []byte("\nGET")) && !isHexDump(data) { + fmt.Fprintf(f, "%s\n\n", data) + } else { + fmt.Fprintf(f, "%s\n", hex.Dump(data)) + } + return resp, err +} + +var loaded struct { + mu sync.Mutex + did map[string]bool +} + +func LoadOnce(file string) { + loaded.mu.Lock() + if loaded.did[file] { + loaded.mu.Unlock() + return + } + if loaded.did == nil { + loaded.did = make(map[string]bool) + } + loaded.did[file] = true + loaded.mu.Unlock() + + f, err := os.Open(file) + if err != nil { + log.Fatal(err) + } + defer f.Close() + + b := bufio.NewReader(f) + var ungetLine string + nextLine := func() string { + if ungetLine != "" { + l := ungetLine + ungetLine = "" + return l + } + line, err := b.ReadString('\n') + if err != nil { + if err == io.EOF { + return "" + } + log.Fatalf("%s: unexpected read error: %v", file, err) + } + return line + } + + for { + line := nextLine() + if line == "" { // EOF + break + } + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "#") || line == "" { + continue + } + if !strings.HasPrefix(line, "GET ") { + log.Fatalf("%s: malformed GET line: %s", file, line) + } + url := line[len("GET "):] + status := nextLine() + if _, err := strconv.Atoi(strings.Fields(status)[0]); err != nil { + log.Fatalf("%s: malformed status line (after GET %s): %s", file, url, status) + } + hdr := make(http.Header) + for { + kv := strings.TrimSpace(nextLine()) + if kv == "" { + break + } + i := strings.Index(kv, ":") + if i < 0 { + log.Fatalf("%s: malformed header line (after GET %s): %s", file, url, kv) + } + k, v := kv[:i], strings.TrimSpace(kv[i+1:]) + hdr[k] = append(hdr[k], v) + } + + var body []byte + Body: + for n := 0; ; n++ { + line := nextLine() + if n == 0 && isHexDump([]byte(line)) { + ungetLine = line + b, err := parseHexDump(nextLine) + if err != nil { + log.Fatalf("%s: malformed hex dump (after GET %s): %v", file, url, err) + } + body = b + break + } + if line == "" { // EOF + for i := 0; i < 2; i++ { + if len(body) > 0 && body[len(body)-1] == '\n' { + body = body[:len(body)-1] + } + } + break + } + body = append(body, line...) + for line == "\n" { + line = nextLine() + if strings.HasPrefix(line, "GET ") { + ungetLine = line + body = body[:len(body)-1] + if len(body) > 0 { + body = body[:len(body)-1] + } + break Body + } + body = append(body, line...) + } + } + + Serve(url, status, hdr, body) + } +} + +func isHexDump(data []byte) bool { + return bytes.HasPrefix(data, []byte("00000000 ")) || bytes.HasPrefix(data, []byte("0000000 ")) +} + +// parseHexDump parses the hex dump in text, which should be the +// output of "hexdump -C" or Plan 9's "xd -b" or Go's hex.Dump +// and returns the original data used to produce the dump. +// It is meant to enable storing golden binary files as text, so that +// changes to the golden files can be seen during code reviews. +func parseHexDump(nextLine func() string) ([]byte, error) { + var out []byte + for { + line := nextLine() + if line == "" || line == "\n" { + break + } + if i := strings.Index(line, "|"); i >= 0 { // remove text dump + line = line[:i] + } + f := strings.Fields(line) + if len(f) > 1+16 { + return nil, fmt.Errorf("parsing hex dump: too many fields on line %q", line) + } + if len(f) == 0 || len(f) == 1 && f[0] == "*" { // all zeros block omitted + continue + } + addr64, err := strconv.ParseUint(f[0], 16, 0) + if err != nil { + return nil, fmt.Errorf("parsing hex dump: invalid address %q", f[0]) + } + addr := int(addr64) + if len(out) < addr { + out = append(out, make([]byte, addr-len(out))...) + } + for _, x := range f[1:] { + val, err := strconv.ParseUint(x, 16, 8) + if err != nil { + return nil, fmt.Errorf("parsing hexdump: invalid hex byte %q", x) + } + out = append(out, byte(val)) + } + } + return out, nil +} diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index 5cb0c2431f..35ff25027e 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -18,6 +18,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/load" + "cmd/go/internal/search" ) var CmdBuild = &base.Command{ @@ -376,7 +377,7 @@ func libname(args []string, pkgs []*load.Package) (string, error) { } var haveNonMeta bool for _, arg := range args { - if load.IsMetaPackage(arg) { + if search.IsMetaPackage(arg) { appendName(arg) } else { haveNonMeta = true diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 1013e1a11f..5c4dc88821 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -30,8 +30,15 @@ import ( "cmd/go/internal/cfg" "cmd/go/internal/load" "cmd/go/internal/str" + "cmd/go/internal/vgo" ) +func init() { + vgo.InstallHook = func(args []string) { + CmdInstall.Run(CmdInstall, args) + } +} + // actionList returns the list of actions in the dag rooted at root // as visited in a depth-first post-order traversal. func actionList(root *Action) []*Action { @@ -599,6 +606,13 @@ func (b *Builder) build(a *Action) (err error) { fmt.Fprintf(&icfg, "packagefile %s=%s\n", p1.ImportPath, a1.built) } + if p.Internal.BuildInfo != "" { + if err := b.writeFile(objdir+"_gomod_.go", vgo.ModInfoProg(p.Internal.BuildInfo)); err != nil { + return err + } + gofiles = append(gofiles, objdir+"_gomod_.go") + } + // Compile Go. objpkg := objdir + "_pkg_.a" ofile, out, err := BuildToolchain.gc(b, a, objpkg, icfg.Bytes(), len(sfiles) > 0, gofiles) diff --git a/src/cmd/go/testdata/badmod/go.mod b/src/cmd/go/testdata/badmod/go.mod new file mode 100644 index 0000000000..f7f6423870 --- /dev/null +++ b/src/cmd/go/testdata/badmod/go.mod @@ -0,0 +1 @@ +module m diff --git a/src/cmd/go/testdata/badmod/x.go b/src/cmd/go/testdata/badmod/x.go new file mode 100644 index 0000000000..579fb086ee --- /dev/null +++ b/src/cmd/go/testdata/badmod/x.go @@ -0,0 +1,4 @@ +package x + +import _ "appengine" +import _ "nonexistent.rsc.io" // domain does not exist diff --git a/src/cmd/go/testdata/vendormod/go.mod b/src/cmd/go/testdata/vendormod/go.mod new file mode 100644 index 0000000000..6f71634253 --- /dev/null +++ b/src/cmd/go/testdata/vendormod/go.mod @@ -0,0 +1,16 @@ +module m + +replace x v1.0.0 => ./x + +replace y v1.0.0 => ./y + +replace z v1.0.0 => ./z + +replace w v1.0.0 => ./w + +require ( + w v1.0.0 + x v1.0.0 + y v1.0.0 + z v1.0.0 +) diff --git a/src/cmd/go/testdata/vendormod/v1.go b/src/cmd/go/testdata/vendormod/v1.go new file mode 100644 index 0000000000..6ca04a5aac --- /dev/null +++ b/src/cmd/go/testdata/vendormod/v1.go @@ -0,0 +1,3 @@ +package m + +import _ "x" diff --git a/src/cmd/go/testdata/vendormod/v2.go b/src/cmd/go/testdata/vendormod/v2.go new file mode 100644 index 0000000000..8b089e4365 --- /dev/null +++ b/src/cmd/go/testdata/vendormod/v2.go @@ -0,0 +1,5 @@ +// +build abc + +package mMmMmMm + +import _ "y" diff --git a/src/cmd/go/testdata/vendormod/v3.go b/src/cmd/go/testdata/vendormod/v3.go new file mode 100644 index 0000000000..318b5f0303 --- /dev/null +++ b/src/cmd/go/testdata/vendormod/v3.go @@ -0,0 +1,5 @@ +// +build !abc + +package m + +import _ "z" diff --git a/src/cmd/go/testdata/vendormod/w/go.mod b/src/cmd/go/testdata/vendormod/w/go.mod new file mode 100644 index 0000000000..ce2a6c161c --- /dev/null +++ b/src/cmd/go/testdata/vendormod/w/go.mod @@ -0,0 +1 @@ +module w diff --git a/src/cmd/go/testdata/vendormod/w/w.go b/src/cmd/go/testdata/vendormod/w/w.go new file mode 100644 index 0000000000..a796c0b5f4 --- /dev/null +++ b/src/cmd/go/testdata/vendormod/w/w.go @@ -0,0 +1 @@ +package w diff --git a/src/cmd/go/testdata/vendormod/x/go.mod b/src/cmd/go/testdata/vendormod/x/go.mod new file mode 100644 index 0000000000..c1914353d5 --- /dev/null +++ b/src/cmd/go/testdata/vendormod/x/go.mod @@ -0,0 +1 @@ +module x diff --git a/src/cmd/go/testdata/vendormod/x/x.go b/src/cmd/go/testdata/vendormod/x/x.go new file mode 100644 index 0000000000..823aafd071 --- /dev/null +++ b/src/cmd/go/testdata/vendormod/x/x.go @@ -0,0 +1 @@ +package x diff --git a/src/cmd/go/testdata/vendormod/y/go.mod b/src/cmd/go/testdata/vendormod/y/go.mod new file mode 100644 index 0000000000..ac82a48598 --- /dev/null +++ b/src/cmd/go/testdata/vendormod/y/go.mod @@ -0,0 +1 @@ +module y diff --git a/src/cmd/go/testdata/vendormod/y/y.go b/src/cmd/go/testdata/vendormod/y/y.go new file mode 100644 index 0000000000..789ca715ec --- /dev/null +++ b/src/cmd/go/testdata/vendormod/y/y.go @@ -0,0 +1 @@ +package y diff --git a/src/cmd/go/testdata/vendormod/z/go.mod b/src/cmd/go/testdata/vendormod/z/go.mod new file mode 100644 index 0000000000..efc58fedd0 --- /dev/null +++ b/src/cmd/go/testdata/vendormod/z/go.mod @@ -0,0 +1 @@ +module z diff --git a/src/cmd/go/testdata/vendormod/z/z.go b/src/cmd/go/testdata/vendormod/z/z.go new file mode 100644 index 0000000000..46458cbddb --- /dev/null +++ b/src/cmd/go/testdata/vendormod/z/z.go @@ -0,0 +1 @@ +package z -- 2.50.0