mirror of
https://github.com/golang/go
synced 2024-11-23 04:20:03 -07:00
cmd/go/internal/toolchain: apply the -modcacherw flag when downloading a module to determine what toolchain it needs
Fixes #64282. Change-Id: I3f211c599ee70cb58254d0bc07eeb3c135124e58 Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-windows-amd64-longtest Reviewed-on: https://go-review.googlesource.com/c/go/+/555436 Auto-Submit: Bryan Mills <bcmills@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
653abefddf
commit
cc38c68ae0
@ -8,6 +8,7 @@ package toolchain
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io/fs"
|
||||
@ -24,6 +25,7 @@ import (
|
||||
"cmd/go/internal/modfetch"
|
||||
"cmd/go/internal/modload"
|
||||
"cmd/go/internal/run"
|
||||
"cmd/go/internal/work"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
)
|
||||
@ -486,74 +488,132 @@ func goInstallVersion() bool {
|
||||
// Note: We assume there are no flags between 'go' and 'install' or 'run'.
|
||||
// During testing there are some debugging flags that are accepted
|
||||
// in that position, but in production go binaries there are not.
|
||||
if len(os.Args) < 3 || (os.Args[1] != "install" && os.Args[1] != "run") {
|
||||
if len(os.Args) < 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for pkg@version.
|
||||
var arg string
|
||||
var cmdFlags *flag.FlagSet
|
||||
switch os.Args[1] {
|
||||
default:
|
||||
// Command doesn't support a pkg@version as the main module.
|
||||
return false
|
||||
case "install":
|
||||
// We would like to let 'go install -newflag pkg@version' work even
|
||||
// across a toolchain switch. To make that work, assume the pkg@version
|
||||
// is the last argument and skip the flag parsing.
|
||||
arg = os.Args[len(os.Args)-1]
|
||||
cmdFlags = &work.CmdInstall.Flag
|
||||
case "run":
|
||||
// For run, the pkg@version can be anywhere on the command line,
|
||||
// because it is preceded by run flags and followed by arguments to the
|
||||
// program being run. To handle that precisely, we have to interpret the
|
||||
// flags a little bit, to know whether each flag takes an optional argument.
|
||||
// We can still allow unknown flags as long as they have an explicit =value.
|
||||
args := os.Args[2:]
|
||||
for i := 0; i < len(args); i++ {
|
||||
a := args[i]
|
||||
if !strings.HasPrefix(a, "-") {
|
||||
arg = a
|
||||
break
|
||||
}
|
||||
if a == "-" {
|
||||
// non-flag but also non-pkg@version
|
||||
cmdFlags = &run.CmdRun.Flag
|
||||
}
|
||||
|
||||
// The modcachrw flag is unique, in that it affects how we fetch the
|
||||
// requested module to even figure out what toolchain it needs.
|
||||
// We need to actually set it before we check the toolchain version.
|
||||
// (See https://go.dev/issue/64282.)
|
||||
modcacherwFlag := cmdFlags.Lookup("modcacherw")
|
||||
if modcacherwFlag == nil {
|
||||
base.Fatalf("internal error: modcacherw flag not registered for command")
|
||||
}
|
||||
modcacherwVal, ok := modcacherwFlag.Value.(interface {
|
||||
IsBoolFlag() bool
|
||||
flag.Value
|
||||
})
|
||||
if !ok || !modcacherwVal.IsBoolFlag() {
|
||||
base.Fatalf("internal error: modcacherw is not a boolean flag")
|
||||
}
|
||||
|
||||
// Make a best effort to parse the command's args to find the pkg@version
|
||||
// argument and the -modcacherw flag.
|
||||
var (
|
||||
pkgArg string
|
||||
modcacherwSeen bool
|
||||
)
|
||||
for args := os.Args[2:]; len(args) > 0; {
|
||||
a := args[0]
|
||||
args = args[1:]
|
||||
if a == "--" {
|
||||
if len(args) == 0 {
|
||||
return false
|
||||
}
|
||||
if a == "--" {
|
||||
if i+1 >= len(args) {
|
||||
return false
|
||||
pkgArg = args[0]
|
||||
break
|
||||
}
|
||||
|
||||
a, ok := strings.CutPrefix(a, "-")
|
||||
if !ok {
|
||||
// Not a flag argument. Must be a package.
|
||||
pkgArg = a
|
||||
break
|
||||
}
|
||||
a = strings.TrimPrefix(a, "-") // Treat --flag as -flag.
|
||||
|
||||
name, val, hasEq := strings.Cut(a, "=")
|
||||
|
||||
if name == "modcacherw" {
|
||||
if !hasEq {
|
||||
val = "true"
|
||||
}
|
||||
if err := modcacherwVal.Set(val); err != nil {
|
||||
return false
|
||||
}
|
||||
modcacherwSeen = true
|
||||
continue
|
||||
}
|
||||
|
||||
if hasEq {
|
||||
// Already has a value; don't bother parsing it.
|
||||
continue
|
||||
}
|
||||
|
||||
f := run.CmdRun.Flag.Lookup(a)
|
||||
if f == nil {
|
||||
// We don't know whether this flag is a boolean.
|
||||
if os.Args[1] == "run" {
|
||||
// We don't know where to find the pkg@version argument.
|
||||
// For run, the pkg@version can be anywhere on the command line,
|
||||
// because it is preceded by run flags and followed by arguments to the
|
||||
// program being run. Since we don't know whether this flag takes
|
||||
// an argument, we can't reliably identify the end of the run flags.
|
||||
// Just give up and let the user clarify using the "=" form..
|
||||
return false
|
||||
}
|
||||
|
||||
// We would like to let 'go install -newflag pkg@version' work even
|
||||
// across a toolchain switch. To make that work, assume by default that
|
||||
// the pkg@version is the last argument and skip the remaining args unless
|
||||
// we spot a plausible "-modcacherw" flag.
|
||||
for len(args) > 0 {
|
||||
a := args[0]
|
||||
name, _, _ := strings.Cut(a, "=")
|
||||
if name == "-modcacherw" || name == "--modcacherw" {
|
||||
break
|
||||
}
|
||||
arg = args[i+1]
|
||||
break
|
||||
if len(args) == 1 && !strings.HasPrefix(a, "-") {
|
||||
pkgArg = a
|
||||
}
|
||||
args = args[1:]
|
||||
}
|
||||
a = strings.TrimPrefix(a, "-")
|
||||
a = strings.TrimPrefix(a, "-")
|
||||
if strings.HasPrefix(a, "-") {
|
||||
// non-flag but also non-pkg@version
|
||||
return false
|
||||
}
|
||||
if strings.Contains(a, "=") {
|
||||
// already has value
|
||||
continue
|
||||
}
|
||||
f := run.CmdRun.Flag.Lookup(a)
|
||||
if f == nil {
|
||||
// Unknown flag. Give up. The command is going to fail in flag parsing.
|
||||
return false
|
||||
}
|
||||
if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); ok && bf.IsBoolFlag() {
|
||||
// Does not take value.
|
||||
continue
|
||||
}
|
||||
i++ // Does take a value; skip it.
|
||||
continue
|
||||
}
|
||||
|
||||
if bf, ok := f.Value.(interface{ IsBoolFlag() bool }); !ok || !bf.IsBoolFlag() {
|
||||
// The next arg is the value for this flag. Skip it.
|
||||
args = args[1:]
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !strings.Contains(arg, "@") || build.IsLocalImport(arg) || filepath.IsAbs(arg) {
|
||||
|
||||
if !strings.Contains(pkgArg, "@") || build.IsLocalImport(pkgArg) || filepath.IsAbs(pkgArg) {
|
||||
return false
|
||||
}
|
||||
path, version, _ := strings.Cut(arg, "@")
|
||||
path, version, _ := strings.Cut(pkgArg, "@")
|
||||
if path == "" || version == "" || gover.IsToolchain(path) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !modcacherwSeen && base.InGOFLAGS("-modcacherw") {
|
||||
fs := flag.NewFlagSet("goInstallVersion", flag.ExitOnError)
|
||||
fs.Var(modcacherwVal, "modcacherw", modcacherwFlag.Usage)
|
||||
base.SetFromGOFLAGS(fs)
|
||||
}
|
||||
|
||||
// It would be correct to simply return true here, bypassing use
|
||||
// of the current go.mod or go.work, and let "go run" or "go install"
|
||||
// do the rest, including a toolchain switch.
|
||||
|
45
src/cmd/go/testdata/script/install_modcacherw_issue64282.txt
vendored
Normal file
45
src/cmd/go/testdata/script/install_modcacherw_issue64282.txt
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
# Regression test for https://go.dev/issue/64282.
|
||||
#
|
||||
# 'go install' and 'go run' with pkg@version arguments should make
|
||||
# a best effort to parse flags relevant to downloading modules
|
||||
# (currently only -modcacherw) before actually downloading the module
|
||||
# to identify which toolchain version to use.
|
||||
#
|
||||
# However, the best-effort flag parsing should not interfere with
|
||||
# actual flag parsing if we don't switch toolchains. In particular,
|
||||
# unrecognized flags should still be diagnosed after the module for
|
||||
# the requested package has been downloaded and checked for toolchain
|
||||
# upgrades.
|
||||
|
||||
|
||||
! go install -cake=delicious -modcacherw example.com/printversion@v0.1.0
|
||||
stderr '^flag provided but not defined: -cake$'
|
||||
# Because the -modcacherw flag was set, we should be able to modify the contents
|
||||
# of a directory within the module cache.
|
||||
cp $WORK/extraneous.txt $GOPATH/pkg/mod/example.com/printversion@v0.1.0/extraneous_file.go
|
||||
go clean -modcache
|
||||
|
||||
|
||||
! go install -unknownflag -tags -modcacherw example.com/printversion@v0.1.0
|
||||
stderr '^flag provided but not defined: -unknownflag$'
|
||||
cp $WORK/extraneous.txt $GOPATH/pkg/mod/example.com/printversion@v0.1.0/extraneous_file.go
|
||||
go clean -modcache
|
||||
|
||||
|
||||
# Also try it with a 'go install' that succeeds.
|
||||
# (But skip in short mode, because linking a binary is expensive.)
|
||||
[!short] go install -modcacherw example.com/printversion@v0.1.0
|
||||
[!short] cp $WORK/extraneous.txt $GOPATH/pkg/mod/example.com/printversion@v0.1.0/extraneous_file.go
|
||||
[!short] go clean -modcache
|
||||
|
||||
|
||||
# The flag should also be applied if given in GOFLAGS
|
||||
# instead of on the command line.
|
||||
env GOFLAGS=-modcacherw
|
||||
! go install -cake=delicious example.com/printversion@v0.1.0
|
||||
stderr '^flag provided but not defined: -cake$'
|
||||
cp $WORK/extraneous.txt $GOPATH/pkg/mod/example.com/printversion@v0.1.0/extraneous_file.go
|
||||
|
||||
|
||||
-- $WORK/extraneous.txt --
|
||||
This is not a Go source file.
|
Loading…
Reference in New Issue
Block a user