1
0
mirror of https://github.com/golang/go synced 2024-11-26 18:06:55 -07:00

cmd/go: implement 'go install pkg@version'

With this change, 'go install' will install executables in module mode
without using or modifying the module in the current directory, if
there is one.

For #40276

Change-Id: I922e71719b3a4e0c779ce7a30429355fc29930bf
Reviewed-on: https://go-review.googlesource.com/c/go/+/254365
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
Jay Conrod 2020-09-02 13:14:36 -04:00
parent ea33523877
commit e306363612
11 changed files with 605 additions and 8 deletions

View File

@ -43,6 +43,18 @@ Do not send CLs removing the interior tags from such phrases.
<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

View File

@ -715,6 +715,33 @@
// 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.

View File

@ -35,7 +35,6 @@ import (
)
var (
mustUseModules = false
initialized bool
modRoot string
@ -55,9 +54,33 @@ var (
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,
@ -92,15 +115,19 @@ func Init() {
// 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
}
@ -135,6 +162,10 @@ func Init() {
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 == "" {
@ -154,6 +185,9 @@ func Init() {
// 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") {
@ -219,10 +253,12 @@ func init() {
// 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
}
@ -263,7 +299,7 @@ func WillBeEnabled() bool {
// (usually through MustModRoot).
func Enabled() bool {
Init()
return modRoot != "" || mustUseModules
return modRoot != "" || cfg.ModulesEnabled
}
// ModRoot returns the root of the main module.

View File

@ -9,8 +9,10 @@ import (
"errors"
"fmt"
"go/build"
"internal/goroot"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
@ -18,8 +20,13 @@ import (
"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{
@ -440,6 +447,33 @@ variable, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH
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.
@ -510,6 +544,12 @@ func libname(args []string, pkgs []*load.Package) (string, error) {
}
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))
}
@ -634,6 +674,158 @@ func InstallPackages(ctx context.Context, patterns []string, pkgs []*load.Packag
}
}
// 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

View File

@ -0,0 +1,28 @@
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

View File

@ -0,0 +1,28 @@
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

View File

@ -0,0 +1,28 @@
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

View File

@ -0,0 +1,27 @@
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

View File

@ -0,0 +1,30 @@
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

View File

@ -0,0 +1,187 @@
# '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.

View File

@ -177,9 +177,11 @@ go doc fmt
! 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