cmd/go/internal/modload: work better with -getmode=vendor

If we have -getmode=vendor, we can still make a list
of all the modules using the vendor/modules.txt file.
And we can make sure that builds work.

Fixes golang/go#26126.

Change-Id: I1a68cbfed71227b8dad2803fcdf8291129681b72
Reviewed-on: https://go-review.googlesource.com/122479
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/modload/build.go b/vendor/cmd/go/internal/modload/build.go
index b6b3280..d139996 100644
--- a/vendor/cmd/go/internal/modload/build.go
+++ b/vendor/cmd/go/internal/modload/build.go
@@ -101,6 +101,11 @@
 		Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path],
 	}
 
+	if cfg.BuildGetmode == "vendor" {
+		info.Dir = filepath.Join(ModRoot, "vendor", m.Path)
+		return info
+	}
+
 	// complete fills in the extra fields in m.
 	complete := func(m *modinfo.ModulePublic) {
 		if m.Version != "" {
diff --git a/vendor/cmd/go/internal/modload/load.go b/vendor/cmd/go/internal/modload/load.go
index 703d576..6d9f83e 100644
--- a/vendor/cmd/go/internal/modload/load.go
+++ b/vendor/cmd/go/internal/modload/load.go
@@ -819,6 +819,24 @@
 	return c.list, c.err
 }
 
+var vendorOnce sync.Once
+var vendorList []module.Version
+
+// readVendorList reads the list of vendored modules from vendor/modules.txt.
+func readVendorList() {
+	var list []module.Version
+	data, _ := ioutil.ReadFile(filepath.Join(ModRoot, "vendor/modules.txt"))
+	for _, line := range strings.Split(string(data), "\n") {
+		if strings.HasPrefix(line, "# ") {
+			f := strings.Fields(line)
+			if len(f) == 3 && semver.IsValid(f[2]) {
+				list = append(list, module.Version{Path: f[1], Version: f[2]})
+			}
+		}
+	}
+	vendorList = list
+}
+
 func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
 	if mod == Target {
 		var list []module.Version
@@ -826,6 +844,14 @@
 		return list, nil
 	}
 
+	if cfg.BuildGetmode == "vendor" {
+		// For every module other than the target,
+		// return the full list of modules from modules.txt.
+		// But only read vendor/modules.txt once.
+		vendorOnce.Do(readVendorList)
+		return vendorList, nil
+	}
+
 	origPath := mod.Path
 	if repl := Replacement(mod); repl.Path != "" {
 		if repl.Version == "" {
diff --git a/vendor/cmd/go/mod_test.go b/vendor/cmd/go/mod_test.go
index 01e07d0..d94556b 100644
--- a/vendor/cmd/go/mod_test.go
+++ b/vendor/cmd/go/mod_test.go
@@ -634,6 +634,21 @@
 	tg.grepStdout(`rsc.io/quote v1.2.1`, "expected quote v1.2.1") // -u=patch with no args applies to deps of main module
 	tg.grepStdout(`rsc.io/sampler v1.3.1`, "expected sampler line to stay")
 	tg.grepStdout(`golang.org/x/text v0.0.0-`, "expected x/text pseudo-version") // even though x/text v0.3.0 is tagged
+
+	tg.run("get", "-m", "rsc.io/quote@v1.5.1")
+	tg.run("mod", "-vendor")
+	tg.setenv("GOPATH", tg.path("empty"))
+	tg.setenv("GOPROXY", "file:///nonexist")
+
+	tg.run("list", "-getmode=vendor", "all")
+	tg.run("list", "-getmode=vendor", "-m", "-f={{.Path}} {{.Version}} {{.Dir}}", "all")
+	tg.grepStdout(`rsc.io/quote v1.5.1 .*vendor[\\/]rsc.io[\\/]quote`, "expected vendored rsc.io/quote")
+	tg.grepStdout(`golang.org/x/text v0.0.0.* .*vendor[\\/]golang.org[\\/]x[\\/]text`, "expected vendored golang.org/x/text")
+
+	tg.runFail("list", "-getmode=vendor", "-m", "rsc.io/quote@latest")
+	tg.grepStderr(`module lookup disabled by -getmode=vendor`, "expected disabled")
+	tg.runFail("get", "-getmode=vendor", "-u")
+	tg.grepStderr(`go get: disabled by -getmode=vendor`, "expected disabled")
 }
 
 func TestModBadDomain(t *testing.T) {
@@ -855,10 +870,12 @@
 	tg.must(os.MkdirAll(tg.path("x"), 0777))
 	tg.must(ioutil.WriteFile(tg.path("x/x.go"), []byte(`
 		package x
+		import _ "rsc.io/quote"
 	`), 0666))
 	tg.must(ioutil.WriteFile(tg.path("x/go.mod"), []byte(`
 		module x
-		require rsc.io/quote v1.2.0
+		require rsc.io/quote v1.5.1
+		replace rsc.io/sampler v1.3.0 => rsc.io/sampler v1.3.1
 	`), 0666))
 	tg.cd(tg.path("x"))
 
@@ -871,7 +888,7 @@
 	tg.grepStdoutNot(`quote@`, "should not have local copy of code")
 
 	tg.run("list", "-f={{.Dir}}", "rsc.io/quote") // downloads code to load package
-	tg.grepStdout(`mod[\\/]rsc.io[\\/]quote@v1.2.0`, "expected cached copy of code")
+	tg.grepStdout(`mod[\\/]rsc.io[\\/]quote@v1.5.1`, "expected cached copy of code")
 	dir := strings.TrimSpace(tg.getStdout())
 	info, err := os.Stat(dir)
 	if err != nil {
@@ -881,8 +898,9 @@
 		t.Fatalf("%s should be unwritable", dir)
 	}
 
-	tg.run("list", "-m", "-f={{.Dir}}", "rsc.io/quote") // now module list should find it too
-	tg.grepStdout(`mod[\\/]rsc.io[\\/]quote@v1.2.0`, "expected cached copy of code")
+	tg.run("list", "-m", "-f={{.Path}} {{.Version}} {{.Dir}}{{with .Replace}} => {{.Version}} {{.Dir}}{{end}}", "all")
+	tg.grepStdout(`mod[\\/]rsc.io[\\/]quote@v1.5.1`, "expected cached copy of code")
+	tg.grepStdout(`v1.3.0 .*mod[\\/]rsc.io[\\/]sampler@v1.3.1 => v1.3.1 .*@v1.3.1`, "expected v1.3.1 replacement")
 
 	// check that list std works; also check that rsc.io/quote/buggy is a listable package
 	tg.run("list", "std", "rsc.io/quote/buggy")