blob: a9bee0af4e0d9b6906ee145b4f0a116008d9c936 [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modload
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"cmd/go/internal/cfg"
"cmd/go/internal/imports"
"cmd/go/internal/search"
"golang.org/x/mod/module"
)
type stdFilter int8
const (
omitStd = stdFilter(iota)
includeStd
)
// matchPackages is like m.MatchPackages, but uses a local variable (rather than
// a global) for tags, can include or exclude packages in the standard library,
// and is restricted to the given list of modules.
func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) {
m.Pkgs = []string{}
isMatch := func(string) bool { return true }
treeCanMatch := func(string) bool { return true }
if !m.IsMeta() {
isMatch = search.MatchPattern(m.Pattern())
treeCanMatch = search.TreeCanMatchPattern(m.Pattern())
}
have := map[string]bool{
"builtin": true, // ignore pseudo-package that exists only for documentation
}
if !cfg.BuildContext.CgoEnabled {
have["runtime/cgo"] = true // ignore during walk
}
type pruning int8
const (
pruneVendor = pruning(1 << iota)
pruneGoMod
)
walkPkgs := func(root, importPathRoot string, prune pruning) {
root = filepath.Clean(root)
err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if err != nil {
m.AddError(err)
return nil
}
want := true
elem := ""
// Don't use GOROOT/src but do walk down into it.
if path == root {
if importPathRoot == "" {
return nil
}
} else {
// Avoid .foo, _foo, and testdata subdirectory trees.
_, elem = filepath.Split(path)
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
want = false
}
}
name := importPathRoot + filepath.ToSlash(path[len(root):])
if importPathRoot == "" {
name = name[1:] // cut leading slash
}
if !treeCanMatch(name) {
want = false
}
if !fi.IsDir() {
if fi.Mode()&os.ModeSymlink != 0 && want {
if target, err := os.Stat(path); err == nil && target.IsDir() {
fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
}
}
return nil
}
if !want {
return filepath.SkipDir
}
// Stop at module boundaries.
if (prune&pruneGoMod != 0) && path != root {
if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
return filepath.SkipDir
}
}
if !have[name] {
have[name] = true
if isMatch(name) {
if _, _, err := scanDir(path, tags); err != imports.ErrNoGo {
m.Pkgs = append(m.Pkgs, name)
}
}
}
if elem == "vendor" && (prune&pruneVendor != 0) {
return filepath.SkipDir
}
return nil
})
if err != nil {
m.AddError(err)
}
}
if filter == includeStd {
walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
if treeCanMatch("cmd") {
walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
}
}
if cfg.BuildMod == "vendor" {
if HasModRoot() {
walkPkgs(ModRoot(), targetPrefix, pruneGoMod|pruneVendor)
walkPkgs(filepath.Join(ModRoot(), "vendor"), "", pruneVendor)
}
return
}
for _, mod := range modules {
if !treeCanMatch(mod.Path) {
continue
}
var (
root, modPrefix string
isLocal bool
)
if mod == Target {
if !HasModRoot() {
continue // If there is no main module, we can't search in it.
}
root = ModRoot()
modPrefix = targetPrefix
isLocal = true
} else {
var err error
root, isLocal, err = fetch(ctx, mod)
if err != nil {
m.AddError(err)
continue
}
modPrefix = mod.Path
}
prune := pruneVendor
if isLocal {
prune |= pruneGoMod
}
walkPkgs(root, modPrefix, prune)
}
return
}