mirror of
https://github.com/golang/go
synced 2024-11-22 23:50:03 -07:00
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>
This commit is contained in:
parent
639cb1b629
commit
04e1176fd2
@ -1315,10 +1315,21 @@
|
|||||||
// go run [build flags] [-exec xprog] package [arguments...]
|
// go run [build flags] [-exec xprog] package [arguments...]
|
||||||
//
|
//
|
||||||
// Run compiles and runs the named main Go package.
|
// 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,
|
// Typically the package is specified as a list of .go source files from a single
|
||||||
// but it may also be an import path, file system path, or pattern
|
// 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'.
|
// 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...'.
|
// 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:
|
// If the -exec flag is given, 'go run' invokes the binary using xprog:
|
||||||
// 'xprog a.out arguments...'.
|
// 'xprog a.out arguments...'.
|
||||||
|
@ -8,13 +8,16 @@ package run
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/build"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"cmd/go/internal/base"
|
"cmd/go/internal/base"
|
||||||
"cmd/go/internal/cfg"
|
"cmd/go/internal/cfg"
|
||||||
"cmd/go/internal/load"
|
"cmd/go/internal/load"
|
||||||
|
"cmd/go/internal/modload"
|
||||||
"cmd/go/internal/str"
|
"cmd/go/internal/str"
|
||||||
"cmd/go/internal/work"
|
"cmd/go/internal/work"
|
||||||
)
|
)
|
||||||
@ -24,10 +27,21 @@ var CmdRun = &base.Command{
|
|||||||
Short: "compile and run Go program",
|
Short: "compile and run Go program",
|
||||||
Long: `
|
Long: `
|
||||||
Run compiles and runs the named main Go package.
|
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,
|
Typically the package is specified as a list of .go source files from a single
|
||||||
but it may also be an import path, file system path, or pattern
|
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'.
|
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...'.
|
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:
|
If the -exec flag is given, 'go run' invokes the binary using xprog:
|
||||||
'xprog a.out arguments...'.
|
'xprog a.out arguments...'.
|
||||||
@ -59,10 +73,21 @@ func printStderr(args ...interface{}) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runRun(ctx context.Context, cmd *base.Command, args []string) {
|
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()
|
work.BuildInit()
|
||||||
var b work.Builder
|
var b work.Builder
|
||||||
b.Init()
|
b.Init()
|
||||||
b.Print = printStderr
|
b.Print = printStderr
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
for i < len(args) && strings.HasSuffix(args[i], ".go") {
|
for i < len(args) && strings.HasSuffix(args[i], ".go") {
|
||||||
i++
|
i++
|
||||||
@ -79,16 +104,28 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) {
|
|||||||
}
|
}
|
||||||
p = load.GoFilesPackage(ctx, load.PackageOpts{}, files)
|
p = load.GoFilesPackage(ctx, load.PackageOpts{}, files)
|
||||||
} else if len(args) > 0 && !strings.HasPrefix(args[0], "-") {
|
} 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 {
|
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 {
|
if len(pkgs) > 1 {
|
||||||
var names []string
|
var names []string
|
||||||
for _, p := range pkgs {
|
for _, p := range pkgs {
|
||||||
names = append(names, p.ImportPath)
|
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]
|
p = pkgs[0]
|
||||||
i++
|
i++
|
||||||
@ -96,11 +133,11 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) {
|
|||||||
base.Fatalf("go run: no go files listed")
|
base.Fatalf("go run: no go files listed")
|
||||||
}
|
}
|
||||||
cmdArgs := args[i:]
|
cmdArgs := args[i:]
|
||||||
load.CheckPackageErrors([]*load.Package{p})
|
|
||||||
|
|
||||||
if p.Name != "main" {
|
if p.Name != "main" {
|
||||||
base.Fatalf("go run: cannot run non-main package")
|
base.Fatalf("go run: cannot run non-main package")
|
||||||
}
|
}
|
||||||
|
load.CheckPackageErrors([]*load.Package{p})
|
||||||
|
|
||||||
p.Internal.OmitDebug = true
|
p.Internal.OmitDebug = true
|
||||||
p.Target = "" // must build - not up to date
|
p.Target = "" // must build - not up to date
|
||||||
if p.Internal.CmdlineFiles {
|
if p.Internal.CmdlineFiles {
|
||||||
@ -123,11 +160,34 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) {
|
|||||||
} else {
|
} else {
|
||||||
p.Internal.ExeName = path.Base(p.ImportPath)
|
p.Internal.ExeName = path.Base(p.ImportPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
a1 := b.LinkAction(work.ModeBuild, work.ModeBuild, p)
|
a1 := b.LinkAction(work.ModeBuild, work.ModeBuild, p)
|
||||||
a := &work.Action{Mode: "go run", Func: buildRunProgram, Args: cmdArgs, Deps: []*work.Action{a1}}
|
a := &work.Action{Mode: "go run", Func: buildRunProgram, Args: cmdArgs, Deps: []*work.Action{a1}}
|
||||||
b.Do(ctx, a)
|
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
|
// buildRunProgram is the action for running a binary that has already
|
||||||
// been compiled. We ignore exit status.
|
// been compiled. We ignore exit status.
|
||||||
func buildRunProgram(b *work.Builder, ctx context.Context, a *work.Action) error {
|
func buildRunProgram(b *work.Builder, ctx context.Context, a *work.Action) error {
|
||||||
|
@ -16,11 +16,15 @@ go 1.16
|
|||||||
-- a/a.go --
|
-- a/a.go --
|
||||||
package main
|
package main
|
||||||
|
|
||||||
func main() {}
|
import "fmt"
|
||||||
|
|
||||||
|
func main() { fmt.Println("a@v1.0.0") }
|
||||||
-- b/b.go --
|
-- b/b.go --
|
||||||
package main
|
package main
|
||||||
|
|
||||||
func main() {}
|
import "fmt"
|
||||||
|
|
||||||
|
func main() { fmt.Println("b@v1.0.0") }
|
||||||
-- err/err.go --
|
-- err/err.go --
|
||||||
package err
|
package err
|
||||||
|
|
||||||
|
98
src/cmd/go/testdata/script/mod_run_pkg_version.txt
vendored
Normal file
98
src/cmd/go/testdata/script/mod_run_pkg_version.txt
vendored
Normal file
@ -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.
|
Loading…
Reference in New Issue
Block a user