1
15
16 package walk
17
18 import (
19 "bufio"
20 "errors"
21 "flag"
22 "fmt"
23 "io/fs"
24 "log"
25 "os"
26 "path"
27 "strings"
28 "sync"
29
30 "github.com/bazelbuild/bazel-gazelle/config"
31 "github.com/bazelbuild/bazel-gazelle/rule"
32 "github.com/bmatcuk/doublestar/v4"
33
34 gzflag "github.com/bazelbuild/bazel-gazelle/flag"
35 )
36
37
38
39
40
41 type walkConfig struct {
42 excludes []string
43 ignore bool
44 follow []string
45 loadOnce *sync.Once
46 }
47
48 const walkName = "_walk"
49
50 func getWalkConfig(c *config.Config) *walkConfig {
51 return c.Exts[walkName].(*walkConfig)
52 }
53
54 func (wc *walkConfig) isExcluded(rel, base string) bool {
55 if base == ".git" {
56 return true
57 }
58 return matchAnyGlob(wc.excludes, path.Join(rel, base))
59 }
60
61 func (wc *walkConfig) shouldFollow(rel, base string) bool {
62 return matchAnyGlob(wc.follow, path.Join(rel, base))
63 }
64
65 type Configurer struct{}
66
67 func (*Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
68 wc := &walkConfig{loadOnce: &sync.Once{}}
69 c.Exts[walkName] = wc
70 fs.Var(&gzflag.MultiFlag{Values: &wc.excludes}, "exclude", "pattern that should be ignored (may be repeated)")
71 }
72
73 func (*Configurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil }
74
75 func (*Configurer) KnownDirectives() []string {
76 return []string{"exclude", "follow", "ignore"}
77 }
78
79 func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
80 wc := getWalkConfig(c)
81 wcCopy := &walkConfig{}
82 *wcCopy = *wc
83 wcCopy.ignore = false
84
85 wc.loadOnce.Do(func() {
86 if err := cr.loadBazelIgnore(c.RepoRoot, wcCopy); err != nil {
87 log.Printf("error loading .bazelignore: %v", err)
88 }
89 })
90
91 if f != nil {
92 for _, d := range f.Directives {
93 switch d.Key {
94 case "exclude":
95 if err := checkPathMatchPattern(path.Join(rel, d.Value)); err != nil {
96 log.Printf("the exclusion pattern is not valid %q: %s", path.Join(rel, d.Value), err)
97 continue
98 }
99 wcCopy.excludes = append(wcCopy.excludes, path.Join(rel, d.Value))
100 case "follow":
101 if err := checkPathMatchPattern(path.Join(rel, d.Value)); err != nil {
102 log.Printf("the follow pattern is not valid %q: %s", path.Join(rel, d.Value), err)
103 continue
104 }
105 wcCopy.follow = append(wcCopy.follow, path.Join(rel, d.Value))
106 case "ignore":
107 wcCopy.ignore = true
108 }
109 }
110 }
111
112 c.Exts[walkName] = wcCopy
113 }
114
115 func (c *Configurer) loadBazelIgnore(repoRoot string, wc *walkConfig) error {
116 ignorePath := path.Join(repoRoot, ".bazelignore")
117 file, err := os.Open(ignorePath)
118 if errors.Is(err, fs.ErrNotExist) {
119 return nil
120 }
121 if err != nil {
122 return fmt.Errorf(".bazelignore exists but couldn't be read: %v", err)
123 }
124 defer file.Close()
125
126 scanner := bufio.NewScanner(file)
127 for scanner.Scan() {
128 ignore := strings.TrimSpace(scanner.Text())
129 if ignore == "" || string(ignore[0]) == "#" {
130 continue
131 }
132
133
134 if strings.ContainsAny(ignore, "*?[") {
135 log.Printf("the .bazelignore exclusion pattern must not be a glob %s", ignore)
136 continue
137 }
138
139 wc.excludes = append(wc.excludes, strings.TrimSuffix(ignore, "/"))
140 }
141 return nil
142 }
143
144 func checkPathMatchPattern(pattern string) error {
145 _, err := doublestar.Match(pattern, "x")
146 return err
147 }
148
149 func matchAnyGlob(patterns []string, path string) bool {
150 for _, x := range patterns {
151 matched, err := doublestar.Match(x, path)
152 if err != nil {
153
154
155
156
157 log.Panicf("error during doublestar.Match. This should not happen, please file an issue https://github.com/bazelbuild/bazel-gazelle/issues/new: %s", err)
158 }
159 if matched {
160 return true
161 }
162 }
163 return false
164 }
165
View as plain text