<h3 id="go-command">Go command</h3>
+<p><!-- golang.org/issue/40276 -->
+ <code>go</code> <code>install</code> now accepts arguments with
+ version suffixes (for example, <code>go</code> <code>install</code>
+ <code>example.com/cmd@v1.0.0</code>). This causes <code>go</code>
+ <code>install</code> to build and install packages in module-aware mode,
+ ignoring the <code>go.mod</code> file in the current directory or any parent
+ directory, if there is one. This is useful for installing executables without
+ affecting the dependencies of the main module.<br>
+ TODO: write and link to section in golang.org/ref/mod<br>
+ TODO: write and link to blog post
+</p>
+
<p><!-- golang.org/issue/24031 -->
<code>retract</code> directives may now be used in a <code>go.mod</code> file
to indicate that certain published versions of the module should not be used
// environment variable is not set. Executables in $GOROOT
// are installed in $GOROOT/bin or $GOTOOLDIR instead of $GOBIN.
//
+// If the arguments have version suffixes (like @latest or @v1.0.0), "go install"
+// builds packages in module-aware mode, ignoring the go.mod file in the current
+// directory or any parent directory, if there is one. This is useful for
+// installing executables without affecting the dependencies of the main module.
+// To eliminate ambiguity about which module versions are used in the build, the
+// arguments must satisfy the following constraints:
+//
+// - Arguments must be package paths or package patterns (with "..." wildcards).
+// They must not be standard packages (like fmt), meta-patterns (std, cmd,
+// all), or relative or absolute file paths.
+// - All arguments must have the same version suffix. Different queries are not
+// allowed, even if they refer to the same version.
+// - All arguments must refer to packages in the same module at the same version.
+// - No module is considered the "main" module. If the module containing
+// packages named on the command line has a go.mod file, it must not contain
+// directives (replace and exclude) that would cause it to be interpreted
+// differently than if it were the main module. The module must not require
+// a higher version of itself.
+// - Package path arguments must refer to main packages. Pattern arguments
+// will only match main packages.
+//
+// If the arguments don't have version suffixes, "go install" may run in
+// module-aware mode or GOPATH mode, depending on the GO111MODULE environment
+// variable and the presence of a go.mod file. See 'go help modules' for details.
+// If module-aware mode is enabled, "go install" runs in the context of the main
+// module.
+//
// When module-aware mode is disabled, other packages are installed in the
// directory $GOPATH/pkg/$GOOS_$GOARCH. When module-aware mode is enabled,
// other packages are built and cached but not installed.
)
var (
- mustUseModules = false
- initialized bool
+ initialized bool
modRoot string
Target module.Version
CmdModInit bool // running 'go mod init'
CmdModModule string // module argument for 'go mod init'
+ // RootMode determines whether a module root is needed.
+ RootMode Root
+
+ // ForceUseModules may be set to force modules to be enabled when
+ // GO111MODULE=auto or to report an error when GO111MODULE=off.
+ ForceUseModules bool
+
allowMissingModuleImports bool
)
+type Root int
+
+const (
+ // AutoRoot is the default for most commands. modload.Init will look for
+ // a go.mod file in the current directory or any parent. If none is found,
+ // modules may be disabled (GO111MODULE=on) or commands may run in a
+ // limited module mode.
+ AutoRoot Root = iota
+
+ // NoRoot is used for commands that run in module mode and ignore any go.mod
+ // file the current directory or in parent directories.
+ NoRoot
+
+ // TODO(jayconrod): add NeedRoot for commands like 'go mod vendor' that
+ // don't make sense without a main module.
+)
+
// ModFile returns the parsed go.mod file.
//
// Note that after calling ImportPaths or LoadBuildList,
// Keep in sync with WillBeEnabled. We perform extra validation here, and
// there are lots of diagnostics and side effects, so we can't use
// WillBeEnabled directly.
+ var mustUseModules bool
env := cfg.Getenv("GO111MODULE")
switch env {
default:
base.Fatalf("go: unknown environment setting GO111MODULE=%s", env)
case "auto", "":
- mustUseModules = false
+ mustUseModules = ForceUseModules
case "on":
mustUseModules = true
case "off":
+ if ForceUseModules {
+ base.Fatalf("go: modules disabled by GO111MODULE=off; see 'go help modules'")
+ }
mustUseModules = false
return
}
if CmdModInit {
// Running 'go mod init': go.mod will be created in current directory.
modRoot = base.Cwd
+ } else if RootMode == NoRoot {
+ // TODO(jayconrod): report an error if -mod -modfile is explicitly set on
+ // the command line. Ignore those flags if they come from GOFLAGS.
+ modRoot = ""
} else {
modRoot = findModuleRoot(base.Cwd)
if modRoot == "" {
// when it happens. See golang.org/issue/26708.
modRoot = ""
fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir())
+ if !mustUseModules {
+ return
+ }
}
}
if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") {
// be called until the command is installed and flags are parsed. Instead of
// calling Init and Enabled, the main package can call this function.
func WillBeEnabled() bool {
- if modRoot != "" || mustUseModules {
+ if modRoot != "" || cfg.ModulesEnabled {
+ // Already enabled.
return true
}
if initialized {
+ // Initialized, not enabled.
return false
}
// (usually through MustModRoot).
func Enabled() bool {
Init()
- return modRoot != "" || mustUseModules
+ return modRoot != "" || cfg.ModulesEnabled
}
// ModRoot returns the root of the main module.
"errors"
"fmt"
"go/build"
+ "internal/goroot"
"os"
"os/exec"
+ "path"
"path/filepath"
"runtime"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
+ "cmd/go/internal/modfetch"
+ "cmd/go/internal/modload"
"cmd/go/internal/search"
"cmd/go/internal/trace"
+
+ "golang.org/x/mod/modfile"
+ "golang.org/x/mod/module"
)
var CmdBuild = &base.Command{
environment variable is not set. Executables in $GOROOT
are installed in $GOROOT/bin or $GOTOOLDIR instead of $GOBIN.
+If the arguments have version suffixes (like @latest or @v1.0.0), "go install"
+builds packages in module-aware mode, ignoring the go.mod file in the current
+directory or any parent directory, if there is one. This is useful for
+installing executables without affecting the dependencies of the main module.
+To eliminate ambiguity about which module versions are used in the build, the
+arguments must satisfy the following constraints:
+
+- Arguments must be package paths or package patterns (with "..." wildcards).
+ They must not be standard packages (like fmt), meta-patterns (std, cmd,
+ all), or relative or absolute file paths.
+- All arguments must have the same version suffix. Different queries are not
+ allowed, even if they refer to the same version.
+- All arguments must refer to packages in the same module at the same version.
+- No module is considered the "main" module. If the module containing
+ packages named on the command line has a go.mod file, it must not contain
+ directives (replace and exclude) that would cause it to be interpreted
+ differently than if it were the main module. The module must not require
+ a higher version of itself.
+- Package path arguments must refer to main packages. Pattern arguments
+ will only match main packages.
+
+If the arguments don't have version suffixes, "go install" may run in
+module-aware mode or GOPATH mode, depending on the GO111MODULE environment
+variable and the presence of a go.mod file. See 'go help modules' for details.
+If module-aware mode is enabled, "go install" runs in the context of the main
+module.
+
When module-aware mode is disabled, other packages are installed in the
directory $GOPATH/pkg/$GOOS_$GOARCH. When module-aware mode is enabled,
other packages are built and cached but not installed.
}
func runInstall(ctx context.Context, cmd *base.Command, args []string) {
+ for _, arg := range args {
+ if strings.Contains(arg, "@") && !build.IsLocalImport(arg) && !filepath.IsAbs(arg) {
+ installOutsideModule(ctx, args)
+ return
+ }
+ }
BuildInit()
InstallPackages(ctx, args, load.PackagesForBuild(ctx, args))
}
}
}
+// installOutsideModule implements 'go install pkg@version'. It builds and
+// installs one or more main packages in module mode while ignoring any go.mod
+// in the current directory or parent directories.
+//
+// See golang.org/issue/40276 for details and rationale.
+func installOutsideModule(ctx context.Context, args []string) {
+ modload.ForceUseModules = true
+ modload.RootMode = modload.NoRoot
+ modload.AllowMissingModuleImports()
+ modload.Init()
+
+ // Check that the arguments satisfy syntactic constraints.
+ var version string
+ for _, arg := range args {
+ if i := strings.Index(arg, "@"); i >= 0 {
+ version = arg[i+1:]
+ if version == "" {
+ base.Fatalf("go install %s: version must not be empty", arg)
+ }
+ break
+ }
+ }
+ patterns := make([]string, len(args))
+ for i, arg := range args {
+ if !strings.HasSuffix(arg, "@"+version) {
+ base.Errorf("go install %s: all arguments must have the same version (@%s)", arg, version)
+ continue
+ }
+ p := arg[:len(arg)-len(version)-1]
+ switch {
+ case build.IsLocalImport(p):
+ base.Errorf("go install %s: argument must be a package path, not a relative path", arg)
+ case filepath.IsAbs(p):
+ base.Errorf("go install %s: argument must be a package path, not an absolute path", arg)
+ case search.IsMetaPackage(p):
+ base.Errorf("go install %s: argument must be a package path, not a meta-package", arg)
+ case path.Clean(p) != p:
+ base.Errorf("go install %s: argument must be a clean package path", arg)
+ case !strings.Contains(p, "...") && search.IsStandardImportPath(p) && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, p):
+ base.Errorf("go install %s: argument must not be a package in the standard library", arg)
+ default:
+ patterns[i] = p
+ }
+ }
+ base.ExitIfErrors()
+ BuildInit()
+
+ // Query the module providing the first argument, load its go.mod file, and
+ // check that it doesn't contain directives that would cause it to be
+ // interpreted differently if it were the main module.
+ //
+ // If multiple modules match the first argument, accept the longest match
+ // (first result). It's possible this module won't provide packages named by
+ // later arguments, and other modules would. Let's not try to be too
+ // magical though.
+ allowed := modload.CheckAllowed
+ if modload.IsRevisionQuery(version) {
+ // Don't check for retractions if a specific revision is requested.
+ allowed = nil
+ }
+ qrs, err := modload.QueryPattern(ctx, patterns[0], version, allowed)
+ if err != nil {
+ base.Fatalf("go install %s: %v", args[0], err)
+ }
+ installMod := qrs[0].Mod
+ data, err := modfetch.GoMod(installMod.Path, installMod.Version)
+ if err != nil {
+ base.Fatalf("go install %s: %v", args[0], err)
+ }
+ f, err := modfile.Parse("go.mod", data, nil)
+ if err != nil {
+ base.Fatalf("go install %s: %s: %v", args[0], installMod, err)
+ }
+ directiveFmt := "go install %s: %s\n" +
+ "\tThe go.mod file for the module providing named packages contains one or\n" +
+ "\tmore %s directives. It must not contain directives that would cause\n" +
+ "\tit to be interpreted differently than if it were the main module."
+ if len(f.Replace) > 0 {
+ base.Fatalf(directiveFmt, args[0], installMod, "replace")
+ }
+ if len(f.Exclude) > 0 {
+ base.Fatalf(directiveFmt, args[0], installMod, "exclude")
+ }
+
+ // Initialize the build list using a dummy main module that requires the
+ // module providing the packages on the command line.
+ target := module.Version{Path: "go-install-target"}
+ modload.SetBuildList([]module.Version{target, installMod})
+
+ // Load packages for all arguments. Ignore non-main packages.
+ // Print a warning if an argument contains "..." and matches no main packages.
+ // PackagesForBuild already prints warnings for patterns that don't match any
+ // packages, so be careful not to double print.
+ matchers := make([]func(string) bool, len(patterns))
+ for i, p := range patterns {
+ if strings.Contains(p, "...") {
+ matchers[i] = search.MatchPattern(p)
+ }
+ }
+
+ // TODO(golang.org/issue/40276): don't report errors loading non-main packages
+ // matched by a pattern.
+ pkgs := load.PackagesForBuild(ctx, patterns)
+ mainPkgs := make([]*load.Package, 0, len(pkgs))
+ mainCount := make([]int, len(patterns))
+ nonMainCount := make([]int, len(patterns))
+ for _, pkg := range pkgs {
+ if pkg.Name == "main" {
+ mainPkgs = append(mainPkgs, pkg)
+ for i := range patterns {
+ if matchers[i] != nil && matchers[i](pkg.ImportPath) {
+ mainCount[i]++
+ }
+ }
+ } else {
+ for i := range patterns {
+ if matchers[i] == nil && patterns[i] == pkg.ImportPath {
+ base.Errorf("go install: package %s is not a main package", pkg.ImportPath)
+ } else if matchers[i] != nil && matchers[i](pkg.ImportPath) {
+ nonMainCount[i]++
+ }
+ }
+ }
+ }
+ base.ExitIfErrors()
+ for i, p := range patterns {
+ if matchers[i] != nil && mainCount[i] == 0 && nonMainCount[i] > 0 {
+ fmt.Fprintf(os.Stderr, "go: warning: %q matched no main packages\n", p)
+ }
+ }
+
+ // Check that named packages are all provided by the same module.
+ for _, mod := range modload.LoadedModules() {
+ if mod.Path == installMod.Path && mod.Version != installMod.Version {
+ base.Fatalf("go install: %s: module requires a higher version of itself (%s)", installMod, mod.Version)
+ }
+ }
+ for _, pkg := range mainPkgs {
+ if pkg.Module == nil {
+ // Packages in std, cmd, and their vendored dependencies
+ // don't have this field set.
+ base.Errorf("go install: package %s not provided by module %s", pkg.ImportPath, installMod)
+ } else if pkg.Module.Path != installMod.Path || pkg.Module.Version != installMod.Version {
+ base.Errorf("go install: package %s provided by module %s@%s\n\tAll packages must be provided by the same module (%s).", pkg.ImportPath, pkg.Module.Path, pkg.Module.Version, installMod)
+ }
+ }
+ base.ExitIfErrors()
+
+ // Build and install the packages.
+ InstallPackages(ctx, patterns, mainPkgs)
+}
+
// ExecCmd is the command to use to run user binaries.
// Normally it is empty, meaning run the binaries directly.
// If cross-compiling and running on a remote system or
--- /dev/null
+example.com/cmd contains main packages.
+
+-- .info --
+{"Version":"v1.0.0-exclude"}
+-- .mod --
+module example.com/cmd
+
+go 1.16
+
+exclude rsc.io/quote v1.5.2
+-- go.mod --
+module example.com/cmd
+
+go 1.16
+
+exclude rsc.io/quote v1.5.2
+-- a/a.go --
+package main
+
+func main() {}
+-- b/b.go --
+package main
+
+func main() {}
+-- err/err.go --
+package err
+
+var X = DoesNotCompile
--- /dev/null
+example.com/cmd contains main packages.
+
+-- .info --
+{"Version":"v1.0.0-newerself"}
+-- .mod --
+module example.com/cmd
+
+go 1.16
+
+require example.com/cmd v1.0.0
+-- go.mod --
+module example.com/cmd
+
+go 1.16
+
+require example.com/cmd v1.0.0
+-- a/a.go --
+package main
+
+func main() {}
+-- b/b.go --
+package main
+
+func main() {}
+-- err/err.go --
+package err
+
+var X = DoesNotCompile
--- /dev/null
+example.com/cmd contains main packages.
+
+-- .info --
+{"Version":"v1.0.0-replace"}
+-- .mod --
+module example.com/cmd
+
+go 1.16
+
+replace rsc.io/quote => rsc.io/quote v1.5.2
+-- go.mod --
+module example.com/cmd
+
+go 1.16
+
+replace rsc.io/quote => rsc.io/quote v1.5.2
+-- a/a.go --
+package main
+
+func main() {}
+-- b/b.go --
+package main
+
+func main() {}
+-- err/err.go --
+package err
+
+var X = DoesNotCompile
--- /dev/null
+example.com/cmd contains main packages.
+
+v1.0.0 is the latest non-retracted version. Other versions contain errors or
+detectable problems.
+
+-- .info --
+{"Version":"v1.0.0"}
+-- .mod --
+module example.com/cmd
+
+go 1.16
+-- go.mod --
+module example.com/cmd
+
+go 1.16
+-- a/a.go --
+package main
+
+func main() {}
+-- b/b.go --
+package main
+
+func main() {}
+-- err/err.go --
+package err
+
+var X = DoesNotCompile
--- /dev/null
+example.com/cmd contains main packages.
+
+-- .info --
+{"Version":"v1.9.0"}
+-- .mod --
+module example.com/cmd
+
+go 1.16
+
+// this is a bad version
+retract v1.9.0
+-- go.mod --
+module example.com/cmd
+
+go 1.16
+
+// this is a bad version
+retract v1.9.0
+-- a/a.go --
+package main
+
+func main() {}
+-- b/b.go --
+package main
+
+func main() {}
+-- err/err.go --
+package err
+
+var X = DoesNotCompile
--- /dev/null
+# 'go install pkg@version' works outside a module.
+env GO111MODULE=auto
+go install example.com/cmd/a@v1.0.0
+exists $GOPATH/bin/a$GOEXE
+rm $GOPATH/bin
+
+
+# 'go install pkg@version' reports an error if modules are disabled.
+env GO111MODULE=off
+! go install example.com/cmd/a@v1.0.0
+stderr '^go: modules disabled by GO111MODULE=off; see ''go help modules''$'
+env GO111MODULE=auto
+
+
+# 'go install pkg@version' ignores go.mod in current directory.
+cd m
+cp go.mod go.mod.orig
+! go list -m all
+stderr 'example.com/cmd@v1.1.0-doesnotexist:.*404 Not Found'
+go install example.com/cmd/a@latest
+cmp go.mod go.mod.orig
+exists $GOPATH/bin/a$GOEXE
+go version -m $GOPATH/bin/a$GOEXE
+stdout '^\tmod\texample.com/cmd\tv1.0.0\t' # "latest", not from go.mod
+rm $GOPATH/bin/a
+cd ..
+
+
+# Every test case requires linking, so we only cover the most important cases
+# when -short is set.
+[short] stop
+
+
+# 'go install pkg@version' works on a module that doesn't have a go.mod file
+# and with a module whose go.mod file has missing requirements.
+# With a proxy, the two cases are indistinguishable.
+go install rsc.io/fortune@v1.0.0
+stderr '^go: found rsc.io/quote in rsc.io/quote v1.5.2$'
+exists $GOPATH/bin/fortune$GOEXE
+! exists $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0/go.mod # no go.mod file
+go version -m $GOPATH/bin/fortune$GOEXE
+stdout '^\tdep\trsc.io/quote\tv1.5.2\t' # latest version of fortune's dependency
+rm $GOPATH/bin
+
+
+# 'go install dir@version' works like a normal 'go install' command if
+# dir is a relative or absolute path.
+env GO111MODULE=on
+go mod download rsc.io/fortune@v1.0.0
+! go install $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0
+stderr '^go: cannot find main module; see ''go help modules''$'
+! go install ../pkg/mod/rsc.io/fortune@v1.0.0
+stderr '^go: cannot find main module; see ''go help modules''$'
+mkdir tmp
+cd tmp
+go mod init tmp
+go mod edit -require=rsc.io/fortune@v1.0.0
+! go install -mod=readonly $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0
+stderr '^go: updates to go.sum needed, disabled by -mod=readonly$'
+! go install -mod=readonly ../../pkg/mod/rsc.io/fortune@v1.0.0
+stderr '^go: updates to go.sum needed, disabled by -mod=readonly$'
+go get -d rsc.io/fortune@v1.0.0
+go install -mod=readonly $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0
+exists $GOPATH/bin/fortune$GOEXE
+cd ..
+rm tmp
+rm $GOPATH/bin
+env GO111MODULE=auto
+
+# 'go install pkg@version' reports errors for meta packages, std packages,
+# and directories.
+! go install std@v1.0.0
+stderr '^go install std@v1.0.0: argument must be a package path, not a meta-package$'
+! go install fmt@v1.0.0
+stderr '^go install fmt@v1.0.0: argument must not be a package in the standard library$'
+! go install example.com//cmd/a@v1.0.0
+stderr '^go install example.com//cmd/a@v1.0.0: argument must be a clean package path$'
+! go install example.com/cmd/a@v1.0.0 ./x@v1.0.0
+stderr '^go install ./x@v1.0.0: argument must be a package path, not a relative path$'
+! go install example.com/cmd/a@v1.0.0 $GOPATH/src/x@v1.0.0
+stderr '^go install '$WORK'[/\\]gopath/src/x@v1.0.0: argument must be a package path, not an absolute path$'
+! go install example.com/cmd/a@v1.0.0 cmd/...@v1.0.0
+stderr '^go install: package cmd/go not provided by module example.com/cmd@v1.0.0$'
+
+# 'go install pkg@version' should accept multiple arguments but report an error
+# if the version suffixes are different, even if they refer to the same version.
+go install example.com/cmd/a@v1.0.0 example.com/cmd/b@v1.0.0
+exists $GOPATH/bin/a$GOEXE
+exists $GOPATH/bin/b$GOEXE
+rm $GOPATH/bin
+
+env GO111MODULE=on
+go list -m example.com/cmd@latest
+stdout '^example.com/cmd v1.0.0$'
+env GO111MODULE=auto
+
+! go install example.com/cmd/a@v1.0.0 example.com/cmd/b@latest
+stderr '^go install example.com/cmd/b@latest: all arguments must have the same version \(@v1.0.0\)$'
+
+
+# 'go install pkg@version' should report an error if the arguments are in
+# different modules.
+! go install example.com/cmd/a@v1.0.0 rsc.io/fortune@v1.0.0
+stderr '^go install: package rsc.io/fortune provided by module rsc.io/fortune@v1.0.0\n\tAll packages must be provided by the same module \(example.com/cmd@v1.0.0\).$'
+
+
+# 'go install pkg@version' should report an error if an argument is not
+# a main package.
+! go install example.com/cmd/a@v1.0.0 example.com/cmd/err@v1.0.0
+stderr '^go install: package example.com/cmd/err is not a main package$'
+
+# Wildcards should match only main packages. This module has a non-main package
+# with an error, so we'll know if that gets built.
+mkdir tmp
+cd tmp
+go mod init m
+go get -d example.com/cmd@v1.0.0
+! go build example.com/cmd/...
+stderr 'err[/\\]err.go:3:9: undefined: DoesNotCompile$'
+cd ..
+
+go install example.com/cmd/...@v1.0.0
+exists $GOPATH/bin/a$GOEXE
+exists $GOPATH/bin/b$GOEXE
+rm $GOPATH/bin
+
+# If a wildcard matches no packages, we should see a warning.
+! go install example.com/cmd/nomatch...@v1.0.0
+stderr '^go install example.com/cmd/nomatch\.\.\.@v1.0.0: module example.com/cmd@v1.0.0 found, but does not contain packages matching example.com/cmd/nomatch\.\.\.$'
+go install example.com/cmd/a@v1.0.0 example.com/cmd/nomatch...@v1.0.0
+stderr '^go: warning: "example.com/cmd/nomatch\.\.\." matched no packages$'
+
+# If a wildcard matches only non-main packges, we should see a different warning.
+go install example.com/cmd/err...@v1.0.0
+stderr '^go: warning: "example.com/cmd/err\.\.\." matched no main packages$'
+
+
+# 'go install pkg@version' should report errors if the module contains
+# replace or exclude directives.
+go mod download example.com/cmd@v1.0.0-replace
+! go install example.com/cmd/a@v1.0.0-replace
+cmp stderr replace-err
+
+go mod download example.com/cmd@v1.0.0-exclude
+! go install example.com/cmd/a@v1.0.0-exclude
+cmp stderr exclude-err
+
+# 'go install pkg@version' should report an error if the module requires a
+# higher version of itself.
+! go install example.com/cmd/a@v1.0.0-newerself
+stderr '^go install: example.com/cmd@v1.0.0-newerself: module requires a higher version of itself \(v1.0.0\)$'
+
+
+# 'go install pkg@version' will only match a retracted version if it's
+# explicitly requested.
+env GO111MODULE=on
+go list -m -versions example.com/cmd
+! stdout v1.9.0
+go list -m -versions -retracted example.com/cmd
+stdout v1.9.0
+go install example.com/cmd/a@latest
+go version -m $GOPATH/bin/a$GOEXE
+stdout '^\tmod\texample.com/cmd\tv1.0.0\t'
+go install example.com/cmd/a@v1.9.0
+go version -m $GOPATH/bin/a$GOEXE
+stdout '^\tmod\texample.com/cmd\tv1.9.0\t'
+
+-- m/go.mod --
+module m
+
+go 1.16
+
+require example.com/cmd v1.1.0-doesnotexist
+-- x/x.go --
+package main
+
+func main() {}
+-- replace-err --
+go install example.com/cmd/a@v1.0.0-replace: example.com/cmd@v1.0.0-replace
+ The go.mod file for the module providing named packages contains one or
+ more replace directives. It must not contain directives that would cause
+ it to be interpreted differently than if it were the main module.
+-- exclude-err --
+go install example.com/cmd/a@v1.0.0-exclude: example.com/cmd@v1.0.0-exclude
+ The go.mod file for the module providing named packages contains one or
+ more exclude directives. It must not contain directives that would cause
+ it to be interpreted differently than if it were the main module.
! go doc example.com/version
stderr 'doc: cannot find module providing package example.com/version: working directory is not part of a module'
-# 'go install' with a version should fail due to syntax.
-! go install example.com/printversion@v1.0.0
-stderr 'can only use path@version syntax with'
+# 'go install' with a version should succeed if all constraints are met.
+# See mod_install_pkg_version.
+rm $GOPATH/bin
+go install example.com/printversion@v0.1.0
+exists $GOPATH/bin/printversion$GOEXE
# 'go install' should fail if a package argument must be resolved to a module.
! go install example.com/printversion