mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
[dev.cmdgo] cmd/go: provide a more helpful missing required module error in workspaces
If the user is in a workspace, they might not be in the main module they need to run go get from to add a module that provides a missing dependency. Figure out what that module is from the import stack (there might be multiple but we pick according to the stack computed by the loader for errors) and tell the user to cd to that directory first in the message. Change-Id: I7c919eb61ea3dd122334ff1acd2d7e817cad4b25 Reviewed-on: https://go-review.googlesource.com/c/go/+/334940 Trust: Michael Matloob <matloob@golang.org> Run-TryBot: Michael Matloob <matloob@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Jay Conrod <jayconrod@google.com>
This commit is contained in:
parent
90830699ae
commit
47694b59eb
4 changed files with 37 additions and 12 deletions
|
|
@ -32,7 +32,7 @@ type ImportMissingError struct {
|
||||||
Module module.Version
|
Module module.Version
|
||||||
QueryErr error
|
QueryErr error
|
||||||
|
|
||||||
ImportingModule module.Version
|
ImportingMainModule module.Version
|
||||||
|
|
||||||
// isStd indicates whether we would expect to find the package in the standard
|
// isStd indicates whether we would expect to find the package in the standard
|
||||||
// library. This is normally true for all dotless import paths, but replace
|
// library. This is normally true for all dotless import paths, but replace
|
||||||
|
|
@ -73,6 +73,9 @@ func (e *ImportMissingError) Error() string {
|
||||||
if e.QueryErr != nil {
|
if e.QueryErr != nil {
|
||||||
return fmt.Sprintf("%s: %v", message, e.QueryErr)
|
return fmt.Sprintf("%s: %v", message, e.QueryErr)
|
||||||
}
|
}
|
||||||
|
if e.ImportingMainModule.Path != "" && e.ImportingMainModule != MainModules.ModContainingCWD() {
|
||||||
|
return fmt.Sprintf("%s; to add it:\n\tcd %s\n\tgo get %s", message, MainModules.ModRoot(e.ImportingMainModule), e.Path)
|
||||||
|
}
|
||||||
return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path)
|
return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ type MainModuleSet struct {
|
||||||
|
|
||||||
modFiles map[module.Version]*modfile.File
|
modFiles map[module.Version]*modfile.File
|
||||||
|
|
||||||
|
modContainingCWD module.Version
|
||||||
|
|
||||||
indexMu sync.Mutex
|
indexMu sync.Mutex
|
||||||
indices map[module.Version]*modFileIndex
|
indices map[module.Version]*modFileIndex
|
||||||
}
|
}
|
||||||
|
|
@ -184,6 +186,13 @@ func (mms *MainModuleSet) Len() int {
|
||||||
return len(mms.versions)
|
return len(mms.versions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ModContainingCWD returns the main module containing the working directory,
|
||||||
|
// or module.Version{} if none of the main modules contain the working
|
||||||
|
// directory.
|
||||||
|
func (mms *MainModuleSet) ModContainingCWD() module.Version {
|
||||||
|
return mms.modContainingCWD
|
||||||
|
}
|
||||||
|
|
||||||
var MainModules *MainModuleSet
|
var MainModules *MainModuleSet
|
||||||
|
|
||||||
type Root int
|
type Root int
|
||||||
|
|
@ -315,8 +324,7 @@ func Init() {
|
||||||
} else if inWorkspaceMode() {
|
} else if inWorkspaceMode() {
|
||||||
// We're in workspace mode.
|
// We're in workspace mode.
|
||||||
} else {
|
} else {
|
||||||
modRoots = findModuleRoots(base.Cwd())
|
if modRoot := findModuleRoot(base.Cwd()); modRoot == "" {
|
||||||
if modRoots == nil {
|
|
||||||
if cfg.ModFile != "" {
|
if cfg.ModFile != "" {
|
||||||
base.Fatalf("go: cannot find main module, but -modfile was set.\n\t-modfile cannot be used to set the module root directory.")
|
base.Fatalf("go: cannot find main module, but -modfile was set.\n\t-modfile cannot be used to set the module root directory.")
|
||||||
}
|
}
|
||||||
|
|
@ -328,17 +336,18 @@ func Init() {
|
||||||
// Stay in GOPATH mode.
|
// Stay in GOPATH mode.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if search.InDir(modRoots[0], os.TempDir()) == "." {
|
} else if search.InDir(modRoot, os.TempDir()) == "." {
|
||||||
// If you create /tmp/go.mod for experimenting,
|
// If you create /tmp/go.mod for experimenting,
|
||||||
// then any tests that create work directories under /tmp
|
// then any tests that create work directories under /tmp
|
||||||
// will find it and get modules when they're not expecting them.
|
// will find it and get modules when they're not expecting them.
|
||||||
// It's a bit of a peculiar thing to disallow but quite mysterious
|
// It's a bit of a peculiar thing to disallow but quite mysterious
|
||||||
// when it happens. See golang.org/issue/26708.
|
// when it happens. See golang.org/issue/26708.
|
||||||
modRoots = nil
|
|
||||||
fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir())
|
fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir())
|
||||||
if !mustUseModules {
|
if !mustUseModules {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
modRoots = []string{modRoot}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") {
|
if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") {
|
||||||
|
|
@ -424,12 +433,11 @@ func WillBeEnabled() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if modRoots := findModuleRoots(base.Cwd()); modRoots == nil {
|
if modRoot := findModuleRoot(base.Cwd()); modRoot == "" {
|
||||||
// GO111MODULE is 'auto', and we can't find a module root.
|
// GO111MODULE is 'auto', and we can't find a module root.
|
||||||
// Stay in GOPATH mode.
|
// Stay in GOPATH mode.
|
||||||
return false
|
return false
|
||||||
} else if search.InDir(modRoots[0], os.TempDir()) == "." {
|
} else if search.InDir(modRoot, os.TempDir()) == "." {
|
||||||
_ = TODOWorkspaces("modRoots[0] is not right here")
|
|
||||||
// If you create /tmp/go.mod for experimenting,
|
// If you create /tmp/go.mod for experimenting,
|
||||||
// then any tests that create work directories under /tmp
|
// then any tests that create work directories under /tmp
|
||||||
// will find it and get modules when they're not expecting them.
|
// will find it and get modules when they're not expecting them.
|
||||||
|
|
@ -856,6 +864,7 @@ func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile
|
||||||
panic("mainModulesCalled with module.Version with non empty Version field: " + fmt.Sprintf("%#v", m))
|
panic("mainModulesCalled with module.Version with non empty Version field: " + fmt.Sprintf("%#v", m))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
modRootContainingCWD := findModuleRoot(base.Cwd())
|
||||||
mainModules := &MainModuleSet{
|
mainModules := &MainModuleSet{
|
||||||
versions: ms[:len(ms):len(ms)],
|
versions: ms[:len(ms):len(ms)],
|
||||||
inGorootSrc: map[module.Version]bool{},
|
inGorootSrc: map[module.Version]bool{},
|
||||||
|
|
@ -870,6 +879,10 @@ func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile
|
||||||
mainModules.modFiles[m] = modFiles[i]
|
mainModules.modFiles[m] = modFiles[i]
|
||||||
mainModules.indices[m] = indices[i]
|
mainModules.indices[m] = indices[i]
|
||||||
|
|
||||||
|
if mainModules.modRoot[m] == modRootContainingCWD {
|
||||||
|
mainModules.modContainingCWD = m
|
||||||
|
}
|
||||||
|
|
||||||
if rel := search.InDir(rootDirs[i], cfg.GOROOTsrc); rel != "" {
|
if rel := search.InDir(rootDirs[i], cfg.GOROOTsrc); rel != "" {
|
||||||
mainModules.inGorootSrc[m] = true
|
mainModules.inGorootSrc[m] = true
|
||||||
if m.Path == "std" {
|
if m.Path == "std" {
|
||||||
|
|
@ -1108,7 +1121,7 @@ var altConfigs = []string{
|
||||||
".git/config",
|
".git/config",
|
||||||
}
|
}
|
||||||
|
|
||||||
func findModuleRoots(dir string) (roots []string) {
|
func findModuleRoot(dir string) (roots string) {
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
panic("dir not set")
|
panic("dir not set")
|
||||||
}
|
}
|
||||||
|
|
@ -1117,7 +1130,7 @@ func findModuleRoots(dir string) (roots []string) {
|
||||||
// Look for enclosing go.mod.
|
// Look for enclosing go.mod.
|
||||||
for {
|
for {
|
||||||
if fi, err := fsys.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
|
if fi, err := fsys.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
|
||||||
return []string{dir}
|
return dir
|
||||||
}
|
}
|
||||||
d := filepath.Dir(dir)
|
d := filepath.Dir(dir)
|
||||||
if d == dir {
|
if d == dir {
|
||||||
|
|
@ -1125,7 +1138,7 @@ func findModuleRoots(dir string) (roots []string) {
|
||||||
}
|
}
|
||||||
dir = d
|
dir = d
|
||||||
}
|
}
|
||||||
return nil
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func findWorkspaceFile(dir string) (root string) {
|
func findWorkspaceFile(dir string) (root string) {
|
||||||
|
|
|
||||||
|
|
@ -1364,6 +1364,15 @@ func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[mod
|
||||||
var err error
|
var err error
|
||||||
mod, err = queryImport(ctx, pkg.path, ld.requirements)
|
mod, err = queryImport(ctx, pkg.path, ld.requirements)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
var ime *ImportMissingError
|
||||||
|
if errors.As(err, &ime) {
|
||||||
|
for curstack := pkg.stack; curstack != nil; curstack = curstack.stack {
|
||||||
|
if MainModules.Contains(curstack.mod.Path) {
|
||||||
|
ime.ImportingMainModule = curstack.mod
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// pkg.err was already non-nil, so we can reasonably attribute the error
|
// pkg.err was already non-nil, so we can reasonably attribute the error
|
||||||
// for pkg to either the original error or the one returned by
|
// for pkg to either the original error or the one returned by
|
||||||
// queryImport. The existing error indicates only that we couldn't find
|
// queryImport. The existing error indicates only that we couldn't find
|
||||||
|
|
|
||||||
2
src/cmd/go/testdata/script/work.txt
vendored
2
src/cmd/go/testdata/script/work.txt
vendored
|
|
@ -2,7 +2,7 @@ go mod initwork ./a ./b
|
||||||
cmp go.work go.work.want
|
cmp go.work go.work.want
|
||||||
|
|
||||||
! go run example.com/b
|
! go run example.com/b
|
||||||
stderr 'a(\\|/)a.go:4:8: no required module provides package rsc.io/quote; to add it:\n\tgo get rsc.io/quote'
|
stderr 'a(\\|/)a.go:4:8: no required module provides package rsc.io/quote; to add it:\n\tcd '$WORK(\\|/)gopath(\\|/)src(\\|/)a'\n\tgo get rsc.io/quote'
|
||||||
cd a
|
cd a
|
||||||
go get rsc.io/quote
|
go get rsc.io/quote
|
||||||
go env GOMOD # go env GOMOD reports the module in a single module context
|
go env GOMOD # go env GOMOD reports the module in a single module context
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue