blob: 799c48e50a8caca851b197c1ee584411bf2ad55c [file] [log] [blame]
Dan Willemsenc7413322018-08-27 23:21:26 -07001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package modload
6
7import (
Colin Cross1f805522021-05-14 11:10:59 -07008 "context"
Dan Willemsenc7413322018-08-27 23:21:26 -07009 "fmt"
Colin Cross1f805522021-05-14 11:10:59 -070010 "io/fs"
Dan Willemsenc7413322018-08-27 23:21:26 -070011 "os"
12 "path/filepath"
13 "strings"
14
Dan Willemsenc7413322018-08-27 23:21:26 -070015 "cmd/go/internal/cfg"
Colin Cross1f805522021-05-14 11:10:59 -070016 "cmd/go/internal/fsys"
Dan Willemsenc7413322018-08-27 23:21:26 -070017 "cmd/go/internal/imports"
Dan Willemsenc7413322018-08-27 23:21:26 -070018 "cmd/go/internal/search"
Patrice Arruda748609c2020-06-25 12:12:21 -070019
20 "golang.org/x/mod/module"
Dan Willemsenc7413322018-08-27 23:21:26 -070021)
22
Patrice Arruda748609c2020-06-25 12:12:21 -070023type stdFilter int8
24
25const (
26 omitStd = stdFilter(iota)
27 includeStd
28)
29
30// matchPackages is like m.MatchPackages, but uses a local variable (rather than
31// a global) for tags, can include or exclude packages in the standard library,
32// and is restricted to the given list of modules.
Colin Cross1f805522021-05-14 11:10:59 -070033func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, filter stdFilter, modules []module.Version) {
Patrice Arruda748609c2020-06-25 12:12:21 -070034 m.Pkgs = []string{}
35
36 isMatch := func(string) bool { return true }
Dan Willemsenc7413322018-08-27 23:21:26 -070037 treeCanMatch := func(string) bool { return true }
Patrice Arruda748609c2020-06-25 12:12:21 -070038 if !m.IsMeta() {
39 isMatch = search.MatchPattern(m.Pattern())
40 treeCanMatch = search.TreeCanMatchPattern(m.Pattern())
Dan Willemsenc7413322018-08-27 23:21:26 -070041 }
42
43 have := map[string]bool{
44 "builtin": true, // ignore pseudo-package that exists only for documentation
45 }
46 if !cfg.BuildContext.CgoEnabled {
47 have["runtime/cgo"] = true // ignore during walk
48 }
Dan Willemsenc7413322018-08-27 23:21:26 -070049
Colin Cross430342c2019-09-07 08:36:04 -070050 type pruning int8
51 const (
52 pruneVendor = pruning(1 << iota)
53 pruneGoMod
54 )
55
56 walkPkgs := func(root, importPathRoot string, prune pruning) {
Dan Willemsenc7413322018-08-27 23:21:26 -070057 root = filepath.Clean(root)
Colin Cross1f805522021-05-14 11:10:59 -070058 err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error {
Dan Willemsenc7413322018-08-27 23:21:26 -070059 if err != nil {
Patrice Arruda748609c2020-06-25 12:12:21 -070060 m.AddError(err)
Dan Willemsenc7413322018-08-27 23:21:26 -070061 return nil
62 }
63
Dan Willemsenc7413322018-08-27 23:21:26 -070064 want := true
Patrice Arruda748609c2020-06-25 12:12:21 -070065 elem := ""
66
67 // Don't use GOROOT/src but do walk down into it.
68 if path == root {
69 if importPathRoot == "" {
70 return nil
71 }
72 } else {
73 // Avoid .foo, _foo, and testdata subdirectory trees.
74 _, elem = filepath.Split(path)
75 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
76 want = false
77 }
Dan Willemsenc7413322018-08-27 23:21:26 -070078 }
79
80 name := importPathRoot + filepath.ToSlash(path[len(root):])
81 if importPathRoot == "" {
82 name = name[1:] // cut leading slash
83 }
84 if !treeCanMatch(name) {
85 want = false
86 }
87
88 if !fi.IsDir() {
Dan Willemsencc753b72021-08-31 13:25:42 -070089 if fi.Mode()&fs.ModeSymlink != 0 && want && strings.Contains(m.Pattern(), "...") {
Colin Cross1f805522021-05-14 11:10:59 -070090 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
Dan Willemsenc7413322018-08-27 23:21:26 -070091 fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
92 }
93 }
94 return nil
95 }
96
97 if !want {
98 return filepath.SkipDir
99 }
Colin Cross430342c2019-09-07 08:36:04 -0700100 // Stop at module boundaries.
101 if (prune&pruneGoMod != 0) && path != root {
102 if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
Dan Willemsenc7413322018-08-27 23:21:26 -0700103 return filepath.SkipDir
104 }
105 }
106
107 if !have[name] {
108 have[name] = true
Patrice Arruda748609c2020-06-25 12:12:21 -0700109 if isMatch(name) {
Dan Willemsenc7413322018-08-27 23:21:26 -0700110 if _, _, err := scanDir(path, tags); err != imports.ErrNoGo {
Patrice Arruda748609c2020-06-25 12:12:21 -0700111 m.Pkgs = append(m.Pkgs, name)
Dan Willemsenc7413322018-08-27 23:21:26 -0700112 }
113 }
114 }
115
Colin Cross430342c2019-09-07 08:36:04 -0700116 if elem == "vendor" && (prune&pruneVendor != 0) {
Dan Willemsenc7413322018-08-27 23:21:26 -0700117 return filepath.SkipDir
118 }
119 return nil
120 })
Patrice Arruda748609c2020-06-25 12:12:21 -0700121 if err != nil {
122 m.AddError(err)
123 }
Dan Willemsenc7413322018-08-27 23:21:26 -0700124 }
125
Patrice Arruda748609c2020-06-25 12:12:21 -0700126 if filter == includeStd {
Colin Cross430342c2019-09-07 08:36:04 -0700127 walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
128 if treeCanMatch("cmd") {
129 walkPkgs(filepath.Join(cfg.GOROOTsrc, "cmd"), "cmd", pruneGoMod)
130 }
131 }
132
133 if cfg.BuildMod == "vendor" {
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800134 mod := MainModules.mustGetSingleMainModule()
135 if modRoot := MainModules.ModRoot(mod); modRoot != "" {
136 walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
137 walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
Colin Cross430342c2019-09-07 08:36:04 -0700138 }
Patrice Arruda748609c2020-06-25 12:12:21 -0700139 return
Dan Willemsenc7413322018-08-27 23:21:26 -0700140 }
141
142 for _, mod := range modules {
143 if !treeCanMatch(mod.Path) {
144 continue
145 }
Colin Cross430342c2019-09-07 08:36:04 -0700146
147 var (
148 root, modPrefix string
149 isLocal bool
150 )
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800151 if MainModules.Contains(mod.Path) {
152 if MainModules.ModRoot(mod) == "" {
Colin Crossd9c6b802019-03-19 21:10:31 -0700153 continue // If there is no main module, we can't search in it.
154 }
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800155 root = MainModules.ModRoot(mod)
156 modPrefix = MainModules.PathPrefix(mod)
Colin Cross430342c2019-09-07 08:36:04 -0700157 isLocal = true
Dan Willemsenc7413322018-08-27 23:21:26 -0700158 } else {
159 var err error
Colin Cross1f805522021-05-14 11:10:59 -0700160 const needSum = true
161 root, isLocal, err = fetch(ctx, mod, needSum)
Dan Willemsenc7413322018-08-27 23:21:26 -0700162 if err != nil {
Patrice Arruda748609c2020-06-25 12:12:21 -0700163 m.AddError(err)
Dan Willemsenc7413322018-08-27 23:21:26 -0700164 continue
165 }
Colin Cross430342c2019-09-07 08:36:04 -0700166 modPrefix = mod.Path
Dan Willemsenc7413322018-08-27 23:21:26 -0700167 }
Colin Cross430342c2019-09-07 08:36:04 -0700168
169 prune := pruneVendor
170 if isLocal {
171 prune |= pruneGoMod
172 }
173 walkPkgs(root, modPrefix, prune)
Dan Willemsenc7413322018-08-27 23:21:26 -0700174 }
175
Patrice Arruda748609c2020-06-25 12:12:21 -0700176 return
Dan Willemsenc7413322018-08-27 23:21:26 -0700177}
Colin Cross1f805522021-05-14 11:10:59 -0700178
179// MatchInModule identifies the packages matching the given pattern within the
180// given module version, which does not need to be in the build list or module
181// requirement graph.
182//
183// If m is the zero module.Version, MatchInModule matches the pattern
184// against the standard library (std and cmd) in GOROOT/src.
185func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
186 match := search.NewMatch(pattern)
187 if m == (module.Version{}) {
188 matchPackages(ctx, match, tags, includeStd, nil)
189 }
190
Dan Willemsencc753b72021-08-31 13:25:42 -0700191 LoadModFile(ctx) // Sets Target, needed by fetch and matchPackages.
Colin Cross1f805522021-05-14 11:10:59 -0700192
193 if !match.IsLiteral() {
194 matchPackages(ctx, match, tags, omitStd, []module.Version{m})
195 return match
196 }
197
198 const needSum = true
199 root, isLocal, err := fetch(ctx, m, needSum)
200 if err != nil {
201 match.Errs = []error{err}
202 return match
203 }
204
205 dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
206 if err != nil {
207 match.Errs = []error{err}
208 return match
209 }
210 if haveGoFiles {
211 if _, _, err := scanDir(dir, tags); err != imports.ErrNoGo {
212 // ErrNoGo indicates that the directory is not actually a Go package,
213 // perhaps due to the tags in use. Any other non-nil error indicates a
214 // problem with one or more of the Go source files, but such an error does
215 // not stop the package from existing, so it has no impact on matching.
216 match.Pkgs = []string{pattern}
217 }
218 }
219 return match
220}