diff options
author | Jay Conrod <jayconrod@google.com> | 2021-04-13 18:00:09 -0400 |
---|---|---|
committer | Jay Conrod <jayconrod@google.com> | 2021-04-16 16:11:09 +0000 |
commit | 04e1176fd288f1ceba987d8d2fd9040e45157b38 (patch) | |
tree | 10ec3e608b04147d8eb3939d8a7b230700f22d15 | |
parent | 639cb1b629e575487af78bb3f60af24a7df7a3f7 (diff) | |
download | go-04e1176fd288f1ceba987d8d2fd9040e45157b38.tar.gz go-04e1176fd288f1ceba987d8d2fd9040e45157b38.zip |
cmd/go: support 'go run cmd@version'
'go run' can now build a command at a specific version in module-aware
mode, ignoring the go.mod file in the current directory if there is one.
For #42088
Change-Id: I0bd9bcbe40c0442a268cd1cc315a8a2cbb5adeee
Reviewed-on: https://go-review.googlesource.com/c/go/+/310074
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
-rw-r--r-- | src/cmd/go/alldocs.go | 15 | ||||
-rw-r--r-- | src/cmd/go/internal/run/run.go | 74 | ||||
-rw-r--r-- | src/cmd/go/testdata/mod/example.com_cmd_v1.0.0.txt | 8 | ||||
-rw-r--r-- | src/cmd/go/testdata/script/mod_run_pkg_version.txt | 98 |
4 files changed, 184 insertions, 11 deletions
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 66e78bb1ac..999fcf7e53 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -1315,10 +1315,21 @@ // go run [build flags] [-exec xprog] package [arguments...] // // Run compiles and runs the named main Go package. -// Typically the package is specified as a list of .go source files from a single directory, -// but it may also be an import path, file system path, or pattern +// Typically the package is specified as a list of .go source files from a single +// directory, but it may also be an import path, file system path, or pattern // matching a single known package, as in 'go run .' or 'go run my/cmd'. // +// If the package argument has a version suffix (like @latest or @v1.0.0), +// "go run" builds the program 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 running programs without affecting the dependencies of the main module. +// +// If the package argument doesn't have a version suffix, "go run" 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 run" runs in the context of the main +// module. +// // By default, 'go run' runs the compiled binary directly: 'a.out arguments...'. // If the -exec flag is given, 'go run' invokes the binary using xprog: // 'xprog a.out arguments...'. diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go index f0137c20c1..914e5edc6f 100644 --- a/src/cmd/go/internal/run/run.go +++ b/src/cmd/go/internal/run/run.go @@ -8,13 +8,16 @@ package run import ( "context" "fmt" + "go/build" "os" "path" + "path/filepath" "strings" "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/load" + "cmd/go/internal/modload" "cmd/go/internal/str" "cmd/go/internal/work" ) @@ -24,10 +27,21 @@ var CmdRun = &base.Command{ Short: "compile and run Go program", Long: ` Run compiles and runs the named main Go package. -Typically the package is specified as a list of .go source files from a single directory, -but it may also be an import path, file system path, or pattern +Typically the package is specified as a list of .go source files from a single +directory, but it may also be an import path, file system path, or pattern matching a single known package, as in 'go run .' or 'go run my/cmd'. +If the package argument has a version suffix (like @latest or @v1.0.0), +"go run" builds the program 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 running programs without affecting the dependencies of the main module. + +If the package argument doesn't have a version suffix, "go run" 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 run" runs in the context of the main +module. + By default, 'go run' runs the compiled binary directly: 'a.out arguments...'. If the -exec flag is given, 'go run' invokes the binary using xprog: 'xprog a.out arguments...'. @@ -59,10 +73,21 @@ func printStderr(args ...interface{}) (int, error) { } func runRun(ctx context.Context, cmd *base.Command, args []string) { + if shouldUseOutsideModuleMode(args) { + // Set global module flags for 'go run cmd@version'. + // This must be done before modload.Init, but we need to call work.BuildInit + // before loading packages, since it affects package locations, e.g., + // for -race and -msan. + modload.ForceUseModules = true + modload.RootMode = modload.NoRoot + modload.AllowMissingModuleImports() + modload.Init() + } work.BuildInit() var b work.Builder b.Init() b.Print = printStderr + i := 0 for i < len(args) && strings.HasSuffix(args[i], ".go") { i++ @@ -79,16 +104,28 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) { } p = load.GoFilesPackage(ctx, load.PackageOpts{}, files) } else if len(args) > 0 && !strings.HasPrefix(args[0], "-") { - pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{}, args[:1]) + arg := args[0] + pkgOpts := load.PackageOpts{MainOnly: true} + var pkgs []*load.Package + if strings.Contains(arg, "@") && !build.IsLocalImport(arg) && !filepath.IsAbs(arg) { + var err error + pkgs, err = load.PackagesAndErrorsOutsideModule(ctx, pkgOpts, args[:1]) + if err != nil { + base.Fatalf("go run: %v", err) + } + } else { + pkgs = load.PackagesAndErrors(ctx, pkgOpts, args[:1]) + } + if len(pkgs) == 0 { - base.Fatalf("go run: no packages loaded from %s", args[0]) + base.Fatalf("go run: no packages loaded from %s", arg) } if len(pkgs) > 1 { var names []string for _, p := range pkgs { names = append(names, p.ImportPath) } - base.Fatalf("go run: pattern %s matches multiple packages:\n\t%s", args[0], strings.Join(names, "\n\t")) + base.Fatalf("go run: pattern %s matches multiple packages:\n\t%s", arg, strings.Join(names, "\n\t")) } p = pkgs[0] i++ @@ -96,11 +133,11 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) { base.Fatalf("go run: no go files listed") } cmdArgs := args[i:] - load.CheckPackageErrors([]*load.Package{p}) - if p.Name != "main" { base.Fatalf("go run: cannot run non-main package") } + load.CheckPackageErrors([]*load.Package{p}) + p.Internal.OmitDebug = true p.Target = "" // must build - not up to date if p.Internal.CmdlineFiles { @@ -123,11 +160,34 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) { } else { p.Internal.ExeName = path.Base(p.ImportPath) } + a1 := b.LinkAction(work.ModeBuild, work.ModeBuild, p) a := &work.Action{Mode: "go run", Func: buildRunProgram, Args: cmdArgs, Deps: []*work.Action{a1}} b.Do(ctx, a) } +// shouldUseOutsideModuleMode returns whether 'go run' will load packages in +// module-aware mode, ignoring the go.mod file in the current directory. It +// returns true if the first argument contains "@", does not begin with "-" +// (resembling a flag) or end with ".go" (a file). The argument must not be a +// local or absolute file path. +// +// These rules are slightly different than other commands. Whether or not +// 'go run' uses this mode, it interprets arguments ending with ".go" as files +// and uses arguments up to the last ".go" argument to comprise the package. +// If there are no ".go" arguments, only the first argument is interpreted +// as a package path, since there can be only one package. +func shouldUseOutsideModuleMode(args []string) bool { + // NOTE: "@" not allowed in import paths, but it is allowed in non-canonical + // versions. + return len(args) > 0 && + !strings.HasSuffix(args[0], ".go") && + !strings.HasPrefix(args[0], "-") && + strings.Contains(args[0], "@") && + !build.IsLocalImport(args[0]) && + !filepath.IsAbs(args[0]) +} + // buildRunProgram is the action for running a binary that has already // been compiled. We ignore exit status. func buildRunProgram(b *work.Builder, ctx context.Context, a *work.Action) error { diff --git a/src/cmd/go/testdata/mod/example.com_cmd_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_cmd_v1.0.0.txt index ee439384d2..c1981391a1 100644 --- a/src/cmd/go/testdata/mod/example.com_cmd_v1.0.0.txt +++ b/src/cmd/go/testdata/mod/example.com_cmd_v1.0.0.txt @@ -16,11 +16,15 @@ go 1.16 -- a/a.go -- package main -func main() {} +import "fmt" + +func main() { fmt.Println("a@v1.0.0") } -- b/b.go -- package main -func main() {} +import "fmt" + +func main() { fmt.Println("b@v1.0.0") } -- err/err.go -- package err diff --git a/src/cmd/go/testdata/script/mod_run_pkg_version.txt b/src/cmd/go/testdata/script/mod_run_pkg_version.txt new file mode 100644 index 0000000000..d96d3fc213 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_run_pkg_version.txt @@ -0,0 +1,98 @@ +# This test checks the behavior of 'go run' with a 'cmd@version' argument. +# Most of 'go run' is covered in other tests. +# mod_install_pkg_version covers most of the package loading functionality. +# This test focuses on 'go run' behavior specific to this mode. +[short] skip + +# 'go run pkg@version' works outside a module. +env GO111MODULE=auto +go run example.com/cmd/a@v1.0.0 +stdout '^a@v1.0.0$' + + +# 'go run pkg@version' reports an error if modules are disabled. +env GO111MODULE=off +! go run example.com/cmd/a@v1.0.0 +stderr '^go: modules disabled by GO111MODULE=off; see ''go help modules''$' +env GO111MODULE=on + + +# 'go run pkg@version' ignores go.mod in the current directory. +cd m +cp go.mod go.mod.orig +! go list -m all +stderr '^go list -m: example.com/cmd@v1.1.0-doesnotexist: missing go.sum entry; to add it:\n\tgo mod download example.com/cmd$' +go run example.com/cmd/a@v1.0.0 +stdout '^a@v1.0.0$' +cmp go.mod go.mod.orig +cd .. + + +# '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 run rsc.io/fortune@v1.0.0 +stderr '^go: found rsc.io/quote in rsc.io/quote v1.5.2$' +stderr '^Hello, world.$' + + +# 'go run pkg@version' should report errors if the module contains +# replace or exclude directives. +go mod download example.com/cmd@v1.0.0-replace +! go run example.com/cmd/a@v1.0.0-replace +cmp stderr replace-err + +go mod download example.com/cmd@v1.0.0-exclude +! go run example.com/cmd/a@v1.0.0-exclude +cmp stderr exclude-err + + +# 'go run dir@version' works like a normal 'go run' command if +# dir is a relative or absolute path. +go mod download rsc.io/fortune@v1.0.0 +! go run $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0 +stderr '^go: go\.mod file not found in current directory or any parent directory; see ''go help modules''$' +! go run ../pkg/mod/rsc.io/fortune@v1.0.0 +stderr '^go: go\.mod file not found in current directory or any parent directory; see ''go help modules''$' +mkdir tmp +cd tmp +go mod init tmp +go mod edit -require=rsc.io/fortune@v1.0.0 +! go run -mod=readonly $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0 +stderr '^go: rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$' +! go run -mod=readonly ../../pkg/mod/rsc.io/fortune@v1.0.0 +stderr '^go: rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$' +cd .. +rm tmp + + +# 'go run' does not interpret @version arguments after the first. +go run example.com/cmd/a@v1.0.0 example.com/doesnotexist@v1.0.0 +stdout '^a@v1.0.0$' + + +# 'go run pkg@version' succeeds when -mod=readonly is set explicitly. +# Verifies #43278. +go run -mod=readonly example.com/cmd/a@v1.0.0 +stdout '^a@v1.0.0$' + +-- 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 run: example.com/cmd/a@v1.0.0-replace (in 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 run: example.com/cmd/a@v1.0.0-exclude (in 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. |