1 package main
2 3 /*
4 5 This file holds a direct copy of the import path matching code of
6 https://github.com/golang/go/blob/master/src/cmd/go/main.go. It can be
7 replaced when https://golang.org/issue/8768 is resolved.
8 9 It has been updated to follow upstream changes in a few ways.
10 11 */
12 13 import (
14 "fmt"
15 "go/build"
16 "log"
17 "os"
18 "path"
19 "path/filepath"
20 "regexp"
21 "runtime"
22 "strings"
23 )
24 25 var (
26 buildContext = build.Default
27 goroot = filepath.Clean(runtime.GOROOT())
28 gorootSrc = filepath.Join(goroot, "src")
29 )
30 31 // importPathsNoDotExpansion returns the import paths to use for the given
32 // command line, but it does no ... expansion.
33 func importPathsNoDotExpansion(args []string) []string {
34 if len(args) == 0 {
35 return []string{"."}
36 }
37 var out []string
38 for _, a := range args {
39 // Arguments are supposed to be import paths, but
40 // as a courtesy to Windows developers, rewrite \ to /
41 // in command-line arguments. Handles .\... and so on.
42 if filepath.Separator == '\\' {
43 a = strings.Replace(a, `\`, `/`, -1)
44 }
45 46 // Put argument in canonical form, but preserve leading ./.
47 if strings.HasPrefix(a, "./") {
48 a = "./" + path.Clean(a)
49 if a == "./." {
50 a = "."
51 }
52 } else {
53 a = path.Clean(a)
54 }
55 if a == "all" || a == "std" {
56 out = append(out, allPackages(a)...)
57 continue
58 }
59 out = append(out, a)
60 }
61 return out
62 }
63 64 // importPaths returns the import paths to use for the given command line.
65 func importPaths(args []string) []string {
66 args = importPathsNoDotExpansion(args)
67 var out []string
68 for _, a := range args {
69 if strings.Contains(a, "...") {
70 if build.IsLocalImport(a) {
71 out = append(out, allPackagesInFS(a)...)
72 } else {
73 out = append(out, allPackages(a)...)
74 }
75 continue
76 }
77 out = append(out, a)
78 }
79 return out
80 }
81 82 // matchPattern(pattern)(name) reports whether
83 // name matches pattern. Pattern is a limited glob
84 // pattern in which '...' means 'any string' and there
85 // is no other special syntax.
86 func matchPattern(pattern string) func(name string) bool {
87 re := regexp.QuoteMeta(pattern)
88 re = strings.Replace(re, `\.\.\.`, `.*`, -1)
89 // Special case: foo/... matches foo too.
90 if strings.HasSuffix(re, `/.*`) {
91 re = re[:len(re)-len(`/.*`)] + `(/.*)?`
92 }
93 reg := regexp.MustCompile(`^` + re + `$`)
94 return func(name string) bool {
95 return reg.MatchString(name)
96 }
97 }
98 99 // hasPathPrefix reports whether the path s begins with the
100 // elements in prefix.
101 func hasPathPrefix(s, prefix string) bool {
102 switch {
103 default:
104 return false
105 case len(s) == len(prefix):
106 return s == prefix
107 case len(s) > len(prefix):
108 if prefix != "" && prefix[len(prefix)-1] == '/' {
109 return strings.HasPrefix(s, prefix)
110 }
111 return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
112 }
113 }
114 115 // treeCanMatchPattern(pattern)(name) reports whether
116 // name or children of name can possibly match pattern.
117 // Pattern is the same limited glob accepted by matchPattern.
118 func treeCanMatchPattern(pattern string) func(name string) bool {
119 wildCard := false
120 if i := strings.Index(pattern, "..."); i >= 0 {
121 wildCard = true
122 pattern = pattern[:i]
123 }
124 return func(name string) bool {
125 return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
126 wildCard && strings.HasPrefix(name, pattern)
127 }
128 }
129 130 // allPackages returns all the packages that can be found
131 // under the $GOPATH directories and $GOROOT matching pattern.
132 // The pattern is either "all" (all packages), "std" (standard packages)
133 // or a path including "...".
134 func allPackages(pattern string) []string {
135 pkgs := matchPackages(pattern)
136 if len(pkgs) == 0 {
137 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
138 }
139 return pkgs
140 }
141 142 func matchPackages(pattern string) []string {
143 match := func(string) bool { return true }
144 treeCanMatch := func(string) bool { return true }
145 if pattern != "all" && pattern != "std" {
146 match = matchPattern(pattern)
147 treeCanMatch = treeCanMatchPattern(pattern)
148 }
149 150 have := map[string]bool{
151 "builtin": true, // ignore pseudo-package that exists only for documentation
152 }
153 if !buildContext.CgoEnabled {
154 have["runtime/cgo"] = true // ignore during walk
155 }
156 var pkgs []string
157 158 // Commands
159 cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
160 filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
161 if err != nil || !fi.IsDir() || path == cmd {
162 return nil
163 }
164 name := path[len(cmd):]
165 if !treeCanMatch(name) {
166 return filepath.SkipDir
167 }
168 // Commands are all in cmd/, not in subdirectories.
169 if strings.Contains(name, string(filepath.Separator)) {
170 return filepath.SkipDir
171 }
172 173 // We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
174 name = "cmd/" + name
175 if have[name] {
176 return nil
177 }
178 have[name] = true
179 if !match(name) {
180 return nil
181 }
182 _, err = buildContext.ImportDir(path, 0)
183 if err != nil {
184 if _, noGo := err.(*build.NoGoError); !noGo {
185 log.Print(err)
186 }
187 return nil
188 }
189 pkgs = append(pkgs, name)
190 return nil
191 })
192 193 for _, src := range buildContext.SrcDirs() {
194 if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
195 continue
196 }
197 src = filepath.Clean(src) + string(filepath.Separator)
198 root := src
199 if pattern == "cmd" {
200 root += "cmd" + string(filepath.Separator)
201 }
202 filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
203 if err != nil || !fi.IsDir() || path == src {
204 return nil
205 }
206 207 // Avoid .foo, _foo, and testdata directory trees.
208 _, elem := filepath.Split(path)
209 if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
210 return filepath.SkipDir
211 }
212 213 name := filepath.ToSlash(path[len(src):])
214 if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") {
215 // The name "std" is only the standard library.
216 // If the name is cmd, it's the root of the command tree.
217 return filepath.SkipDir
218 }
219 if !treeCanMatch(name) {
220 return filepath.SkipDir
221 }
222 if have[name] {
223 return nil
224 }
225 have[name] = true
226 if !match(name) {
227 return nil
228 }
229 _, err = buildContext.ImportDir(path, 0)
230 if err != nil {
231 if _, noGo := err.(*build.NoGoError); noGo {
232 return nil
233 }
234 }
235 pkgs = append(pkgs, name)
236 return nil
237 })
238 }
239 return pkgs
240 }
241 242 // allPackagesInFS is like allPackages but is passed a pattern
243 // beginning ./ or ../, meaning it should scan the tree rooted
244 // at the given directory. There are ... in the pattern too.
245 func allPackagesInFS(pattern string) []string {
246 pkgs := matchPackagesInFS(pattern)
247 if len(pkgs) == 0 {
248 fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
249 }
250 return pkgs
251 }
252 253 func matchPackagesInFS(pattern string) []string {
254 // Find directory to begin the scan.
255 // Could be smarter but this one optimization
256 // is enough for now, since ... is usually at the
257 // end of a path.
258 i := strings.Index(pattern, "...")
259 dir, _ := path.Split(pattern[:i])
260 261 // pattern begins with ./ or ../.
262 // path.Clean will discard the ./ but not the ../.
263 // We need to preserve the ./ for pattern matching
264 // and in the returned import paths.
265 prefix := ""
266 if strings.HasPrefix(pattern, "./") {
267 prefix = "./"
268 }
269 match := matchPattern(pattern)
270 271 var pkgs []string
272 filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
273 if err != nil || !fi.IsDir() {
274 return nil
275 }
276 if path == dir {
277 // filepath.Walk starts at dir and recurses. For the recursive case,
278 // the path is the result of filepath.Join, which calls filepath.Clean.
279 // The initial case is not Cleaned, though, so we do this explicitly.
280 //
281 // This converts a path like "./io/" to "io". Without this step, running
282 // "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io
283 // package, because prepending the prefix "./" to the unclean path would
284 // result in "././io", and match("././io") returns false.
285 path = filepath.Clean(path)
286 }
287 288 // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
289 _, elem := filepath.Split(path)
290 dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
291 if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
292 return filepath.SkipDir
293 }
294 295 name := prefix + filepath.ToSlash(path)
296 if !match(name) {
297 return nil
298 }
299 if _, err = build.ImportDir(path, 0); err != nil {
300 if _, noGo := err.(*build.NoGoError); !noGo {
301 log.Print(err)
302 }
303 return nil
304 }
305 pkgs = append(pkgs, name)
306 return nil
307 })
308 return pkgs
309 }
310