cmd/go/internal/vgo: add -getmode=local, -getmode=vendor build flag

The new build flag -getmode=mode changes the way that vgo
resolves references to modules outside the target module.

The default (no -getmode flag) is to download and cache as usual.

With -getmode=local, the local cache is still consulted for modules,
but no network lookups are permitted.

With -getmode==vendor, only the vendor directory is used,
bypassing both the network and the local cache.
In this mode most of the interesting vgo behavior is inaccessible:
there is no world beyond the current module and its vendor directory.

I'm not thrilled with the name -getmode but don't have a better
name yet. It will probably change before the release - this CL is
about implementing the behavior.

Fixes golang/go#25073.

Change-Id: Ic5273f9f2d0263b49540401c4f554190702df099
Reviewed-on: https://go-review.googlesource.com/118316
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/cfg/cfg.go b/vendor/cmd/go/internal/cfg/cfg.go
index 98afaa2..af86b96 100644
--- a/vendor/cmd/go/internal/cfg/cfg.go
+++ b/vendor/cmd/go/internal/cfg/cfg.go
@@ -22,6 +22,7 @@
 	BuildA                 bool   // -a flag
 	BuildBuildmode         string // -buildmode flag
 	BuildContext           = build.Default
+	BuildGetmode           string             // -getmode flag
 	BuildI                 bool               // -i flag
 	BuildLinkshared        bool               // -linkshared flag
 	BuildMSan              bool               // -msan flag
diff --git a/vendor/cmd/go/internal/modfetch/repo.go b/vendor/cmd/go/internal/modfetch/repo.go
index 6e21a41..b089981 100644
--- a/vendor/cmd/go/internal/modfetch/repo.go
+++ b/vendor/cmd/go/internal/modfetch/repo.go
@@ -6,11 +6,13 @@
 
 import (
 	"errors"
+	"fmt"
 	pathpkg "path"
 	"sort"
 	"strings"
 	"time"
 
+	"cmd/go/internal/cfg"
 	"cmd/go/internal/modfetch/bitbucket"
 	"cmd/go/internal/modfetch/codehost"
 	"cmd/go/internal/modfetch/github"
@@ -60,6 +62,9 @@
 
 // Lookup returns the module with the given module path.
 func Lookup(path string) (Repo, error) {
+	if cfg.BuildGetmode != "" {
+		return nil, fmt.Errorf("module lookup disabled by -getmode=%s", cfg.BuildGetmode)
+	}
 	if proxyURL != "" {
 		return lookupProxy(path)
 	}
diff --git a/vendor/cmd/go/internal/vgo/help.go b/vendor/cmd/go/internal/vgo/help.go
new file mode 100644
index 0000000..4a373e9
--- /dev/null
+++ b/vendor/cmd/go/internal/vgo/help.go
@@ -0,0 +1,17 @@
+// 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 "cmd/go/internal/base"
+
+var HelpModule = &base.Command{
+	UsageLine: "module",
+	Short:     "using versioned modules",
+	Long: `
+TODO: write whole thing
+
+TODO: mention -getmode=vendor, -getmode=local
+	`,
+}
diff --git a/vendor/cmd/go/internal/vgo/load.go b/vendor/cmd/go/internal/vgo/load.go
index 7b85cd0..5be03b4 100644
--- a/vendor/cmd/go/internal/vgo/load.go
+++ b/vendor/cmd/go/internal/vgo/load.go
@@ -260,6 +260,13 @@
 		}
 	}
 
+	if cfg.BuildGetmode == "vendor" {
+		// Using -getmode=vendor, everything the module needs
+		// (beyond the current module and standard library)
+		// must be in the module's vendor directory.
+		return filepath.Join(ModRoot, "vendor", path)
+	}
+
 	var mod1 module.Version
 	var dir1 string
 	for _, mod := range buildList {
diff --git a/vendor/cmd/go/internal/work/build.go b/vendor/cmd/go/internal/work/build.go
index c6aca11..c637d76 100644
--- a/vendor/cmd/go/internal/work/build.go
+++ b/vendor/cmd/go/internal/work/build.go
@@ -88,6 +88,8 @@
 		arguments to pass on each gccgo compiler/linker invocation.
 	-gcflags '[pattern=]arg list'
 		arguments to pass on each go tool compile invocation.
+	-getmode mode
+		module download mode to use. See 'go help module' for more.
 	-installsuffix suffix
 		a suffix to use in the name of the package installation directory,
 		in order to keep output separate from default builds.
@@ -220,6 +222,7 @@
 	cmd.Flag.StringVar(&cfg.BuildBuildmode, "buildmode", "default", "")
 	cmd.Flag.Var(&load.BuildGcflags, "gcflags", "")
 	cmd.Flag.Var(&load.BuildGccgoflags, "gccgoflags", "")
+	cmd.Flag.StringVar(&cfg.BuildGetmode, "getmode", "", "")
 	cmd.Flag.StringVar(&cfg.BuildContext.InstallSuffix, "installsuffix", "", "")
 	cmd.Flag.Var(&load.BuildLdflags, "ldflags", "")
 	cmd.Flag.BoolVar(&cfg.BuildLinkshared, "linkshared", false, "")
diff --git a/vendor/cmd/go/internal/work/init.go b/vendor/cmd/go/internal/work/init.go
index 1081e51..41aecad 100644
--- a/vendor/cmd/go/internal/work/init.go
+++ b/vendor/cmd/go/internal/work/init.go
@@ -9,6 +9,7 @@
 import (
 	"cmd/go/internal/base"
 	"cmd/go/internal/cfg"
+	"cmd/go/internal/vgo"
 	"flag"
 	"fmt"
 	"os"
@@ -224,4 +225,16 @@
 			cfg.BuildContext.InstallSuffix += codegenArg[1:]
 		}
 	}
+
+	switch cfg.BuildGetmode {
+	case "":
+		// ok
+	case "local", "vendor":
+		// ok but check for vgo
+		if !vgo.Enabled() {
+			base.Fatalf("build flag -getmode=%s only valid when using modules", cfg.BuildGetmode)
+		}
+	default:
+		base.Fatalf("-getmode=%s not supported (can be '', 'local', or 'vendor')", cfg.BuildGetmode)
+	}
 }
diff --git a/vendor/cmd/go/main.go b/vendor/cmd/go/main.go
index c5a6df4..0604990 100644
--- a/vendor/cmd/go/main.go
+++ b/vendor/cmd/go/main.go
@@ -64,6 +64,7 @@
 		help.HelpFileType,
 		help.HelpGopath,
 		help.HelpImportPath,
+		vgo.HelpModule,
 		help.HelpPackages,
 		test.HelpTestflag,
 		test.HelpTestfunc,
@@ -123,8 +124,9 @@
 		os.Exit(2)
 	}
 
+	vgo.Init()
 	if !vgo.MustBeVgo {
-		if vgo.Init(); vgo.Enabled() {
+		if vgo.Enabled() {
 			// Didn't do this above, so do it now.
 			*get.CmdGet = *vgo.CmdGet
 		}
diff --git a/vendor/cmd/go/vgo_test.go b/vendor/cmd/go/vgo_test.go
index b743f40..0f304d9 100644
--- a/vendor/cmd/go/vgo_test.go
+++ b/vendor/cmd/go/vgo_test.go
@@ -227,11 +227,22 @@
 
 	wd, _ := os.Getwd()
 	tg.cd(filepath.Join(wd, "testdata/vendormod"))
+	defer tg.must(os.RemoveAll(filepath.Join(wd, "testdata/vendormod/vendor")))
+
 	tg.run("-vgo", "list", "-m")
 	tg.grepStdout(`^x`, "expected to see module x")
 	tg.grepStdout(`=> ./x`, "expected to see replacement for module x")
 	tg.grepStdout(`^w`, "expected to see module w")
 
+	tg.must(os.RemoveAll(filepath.Join(wd, "testdata/vendormod/vendor")))
+	if !testing.Short() {
+		tg.run("-vgo", "build")
+		tg.runFail("-vgo", "build", "-getmode=vendor")
+	}
+
+	tg.run("-vgo", "list", "-f={{.Dir}}", "x")
+	tg.grepStdout(`vendormod[/\\]x$`, "expected x in vendormod/x")
+
 	tg.run("-vgo", "vendor", "-v")
 	tg.grepStderr(`^# x v1.0.0 => ./x`, "expected to see module x with replacement")
 	tg.grepStderr(`^x`, "expected to see package x")
@@ -241,7 +252,27 @@
 	tg.grepStderr(`^z`, "expected to see package z")
 	tg.grepStderrNot(`w`, "expected NOT to see unused module w")
 
-	tg.must(os.RemoveAll(filepath.Join(wd, "testdata/vendormod/vendor")))
+	tg.run("-vgo", "list", "-f={{.Dir}}", "x")
+	tg.grepStdout(`vendormod[/\\]x$`, "expected x in vendormod/x")
+
+	tg.run("-vgo", "list", "-getmode=vendor", "-f={{.Dir}}", "x")
+	tg.grepStdout(`vendormod[/\\]vendor[/\\]x$`, "expected x in vendormod/vendor/x in -get=vendor mode")
+
+	tg.run("-vgo", "list", "-f={{.Dir}}", "w")
+	tg.grepStdout(`vendormod[/\\]w$`, "expected w in vendormod/w")
+	tg.runFail("-vgo", "list", "-getmode=vendor", "-f={{.Dir}}", "w")
+	tg.grepStderr(`vendormod[/\\]vendor[/\\]w`, "want error about vendormod/vendor/w not existing")
+
+	tg.run("-vgo", "list", "-getmode=local", "-f={{.Dir}}", "w")
+	tg.grepStdout(`vendormod[/\\]w`, "expected w in vendormod/w")
+
+	tg.runFail("-vgo", "list", "-getmode=local", "-f={{.Dir}}", "newpkg")
+	tg.grepStderr(`module lookup disabled by -getmode=local`, "expected -getmode=local to avoid network")
+
+	if !testing.Short() {
+		tg.run("-vgo", "build")
+		tg.run("-vgo", "build", "-getmode=vendor")
+	}
 }
 
 func TestFillGoMod(t *testing.T) {