aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/go/internal/run/run.go
diff options
context:
space:
mode:
authorJay Conrod <jayconrod@google.com>2021-04-13 18:00:09 -0400
committerJay Conrod <jayconrod@google.com>2021-04-16 16:11:09 +0000
commit04e1176fd288f1ceba987d8d2fd9040e45157b38 (patch)
tree10ec3e608b04147d8eb3939d8a7b230700f22d15 /src/cmd/go/internal/run/run.go
parent639cb1b629e575487af78bb3f60af24a7df7a3f7 (diff)
downloadgo-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>
Diffstat (limited to 'src/cmd/go/internal/run/run.go')
-rw-r--r--src/cmd/go/internal/run/run.go74
1 files changed, 67 insertions, 7 deletions
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 {