Skip to content

Commit

Permalink
cmd/go: check for source files in relative paths before attempting to…
Browse files Browse the repository at this point in the history
… determine the package path

This is a more minimial fix for the immediate symptom of 32917 and
30590, but does not improve 'list -e' behavior or error
messages resulting from other package loading issues.

Fixes #32917
Fixes #30590

Change-Id: I6088d14d864410159ebf228d9392d186322fd2a5
Reviewed-on: https://go-review.googlesource.com/c/go/+/185417
Run-TryBot: Bryan C. Mills <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Jay Conrod <[email protected]>
  • Loading branch information
Bryan C. Mills committed Jul 15, 2019
1 parent 4b36588 commit b9edee3
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 17 deletions.
66 changes: 54 additions & 12 deletions src/cmd/go/internal/modload/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,31 @@ func ImportPathsQuiet(patterns []string, tags map[string]bool) []*search.Match {
dir = filepath.Clean(dir)
}

// golang.org/issue/32917: We should resolve a relative path to a
// package path only if the relative path actually contains the code
// for that package.
if !dirContainsPackage(dir) {
// If we're outside of a module, ensure that the failure mode
// indicates that.
ModRoot()

// If the directory is local but does not exist, don't return it
// while loader is iterating, since this might trigger a fetch.
// After loader is done iterating, we still need to return the
// path, so that "go list -e" produces valid output.
if !iterating {
// We don't have a valid path to resolve to, so report the
// unresolved path.
m.Pkgs = append(m.Pkgs, pkg)
}
continue
}

// Note: The checks for @ here are just to avoid misinterpreting
// the module cache directories (formerly GOPATH/src/mod/[email protected]/bar).
// It's not strictly necessary but helpful to keep the checks.
if modRoot != "" && dir == modRoot {
pkg = Target.Path
pkg = targetPrefix
} else if modRoot != "" && strings.HasPrefix(dir, modRoot+string(filepath.Separator)) && !strings.Contains(dir[len(modRoot):], "@") {
suffix := filepath.ToSlash(dir[len(modRoot):])
if strings.HasPrefix(suffix, "/vendor/") {
Expand All @@ -121,7 +141,13 @@ func ImportPathsQuiet(patterns []string, tags map[string]bool) []*search.Match {
continue
}
} else {
pkg = Target.Path + suffix
modPkg := targetPrefix + suffix
if _, ok := dirInModule(modPkg, targetPrefix, modRoot, true); ok {
pkg = modPkg
} else if !iterating {
ModRoot()
base.Errorf("go: directory %s is outside main module", base.ShortPath(dir))
}
}
} else if sub := search.InDir(dir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") {
pkg = filepath.ToSlash(sub)
Expand All @@ -134,16 +160,6 @@ func ImportPathsQuiet(patterns []string, tags map[string]bool) []*search.Match {
base.Errorf("go: directory %s outside available modules", base.ShortPath(dir))
}
}
info, err := os.Stat(dir)
if err != nil || !info.IsDir() {
// If the directory is local but does not exist, don't return it
// while loader is iterating, since this would trigger a fetch.
// After loader is done iterating, we still need to return the
// path, so that "go list -e" produces valid output.
if iterating {
continue
}
}
m.Pkgs = append(m.Pkgs, pkg)
}

Expand Down Expand Up @@ -247,6 +263,32 @@ func pathInModuleCache(dir string) string {
return ""
}

var dirContainsPackageCache sync.Map // absolute dir → bool

func dirContainsPackage(dir string) bool {
isPkg, ok := dirContainsPackageCache.Load(dir)
if !ok {
_, err := cfg.BuildContext.ImportDir(dir, 0)
if err == nil {
isPkg = true
} else {
if fi, statErr := os.Stat(dir); statErr != nil || !fi.IsDir() {
// A non-directory or inaccessible directory is not a Go package.
isPkg = false
} else if _, noGo := err.(*build.NoGoError); noGo {
// A directory containing no Go source files is not a Go package.
isPkg = false
} else {
// An error other than *build.NoGoError indicates that the package exists
// but has some other problem (such as a syntax error).
isPkg = true
}
}
isPkg, _ = dirContainsPackageCache.LoadOrStore(dir, isPkg)
}
return isPkg.(bool)
}

// ImportFromFiles adds modules to the build list as needed
// to satisfy the imports in the named Go source files.
func ImportFromFiles(gofiles []string) {
Expand Down
36 changes: 36 additions & 0 deletions src/cmd/go/testdata/script/mod_dot.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
env GO111MODULE=on

# golang.org/issue/32917 and golang.org/issue/28459: 'go build' and 'go test'
# in an empty directory should refer to the path '.' and should not attempt
# to resolve an external module.
cd dir
! go get .
stderr 'go get \.: path .* is not a package in module rooted at .*[/\\]dir$'
! go list
! stderr 'cannot find module providing package'
stderr '^can.t load package: package \.: no Go files in '$WORK'[/\\]gopath[/\\]src[/\\]dir$'

cd subdir
! go list
! stderr 'cannot find module providing package'
stderr '^can.t load package: package \.: no Go files in '$WORK'[/\\]gopath[/\\]src[/\\]dir[/\\]subdir$'
cd ..

# golang.org/issue/30590: if a package is found in the filesystem
# but is not in the main module, the error message should not say
# "cannot find module providing package", and we shouldn't try
# to find a module providing the package.
! go list ./othermodule
! stderr 'cannot find module providing package'
stderr 'go: directory othermodule is outside main module'

-- dir/go.mod --
module example.com
go 1.13
-- dir/subdir/README --
There are no Go source files in this directory.
-- dir/othermodule/go.mod --
module example.com/othermodule
go 1.13
-- dir/othermodule/om.go --
package othermodule
4 changes: 2 additions & 2 deletions src/cmd/go/testdata/script/mod_fs_patterns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ stderr 'import lookup disabled'

! go build -mod=readonly ./nonexist
! stderr 'import lookup disabled'
stderr 'unknown import path "m/nonexist": cannot find package'
stderr '^can.t load package: package ./nonexist: cannot find package "." in:\n\t'$WORK'[/\\]gopath[/\\]src[/\\]x[/\\]nonexist$'

! go build -mod=readonly ./go.mod
! stderr 'import lookup disabled'
stderr 'unknown import path "m/go.mod": cannot find package'
stderr 'can.t load package: package ./go.mod: cannot find package'

-- x/go.mod --
module m
Expand Down
5 changes: 3 additions & 2 deletions src/cmd/go/testdata/script/mod_list_dir.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ stdout ^math$
go list -f '{{.ImportPath}}' .
stdout ^x$
! go list -f '{{.ImportPath}}' $GOPATH/pkg/mod/rsc.io/[email protected]
stderr 'unknown import path "rsc.io/quote": cannot find package'
stderr '^can.t load package: package '$WORK'[/\\]gopath/pkg/mod/rsc.io/[email protected]: can only use path@version syntax with .go get.'

go list -e -f '{{with .Error}}{{.}}{{end}}' $GOPATH/pkg/mod/rsc.io/[email protected]
stdout 'unknown import path "rsc.io/quote": cannot find package'
stdout '^package '$WORK'[/\\]gopath/pkg/mod/rsc.io/quote@v1.5.2: can only use path@version syntax with .go get.'
go mod download rsc.io/[email protected]
go list -f '{{.ImportPath}}' $GOPATH/pkg/mod/rsc.io/[email protected]
stdout '^rsc.io/quote$'
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/go/testdata/script/mod_list_replace_dir.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ env GO111MODULE=on
go mod download

! go list $GOPATH/pkg/mod/rsc.io/[email protected]
stderr 'outside available modules'
stderr 'can only use path@version syntax with .go get.'

go list $GOPATH/pkg/mod/rsc.io/[email protected]
stdout 'rsc.io/quote'
Expand Down

0 comments on commit b9edee3

Please sign in to comment.