1
15
16
17
18 package walk
19
20 import (
21 "io/fs"
22 "log"
23 "os"
24 "path"
25 "path/filepath"
26 "strings"
27
28 "github.com/bazelbuild/bazel-gazelle/config"
29 "github.com/bazelbuild/bazel-gazelle/rule"
30 )
31
32
33
34 type Mode int
35
36 const (
37
38
39
40 VisitAllUpdateSubdirsMode Mode = iota
41
42
43
44 VisitAllUpdateDirsMode
45
46
47
48
49 UpdateDirsMode
50
51
52
53
54
55 UpdateSubdirsMode
56 )
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 type WalkFunc func(dir, rel string, c *config.Config, update bool, f *rule.File, subdirs, regularFiles, genFiles []string)
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110 func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, wf WalkFunc) {
111 knownDirectives := make(map[string]bool)
112 for _, cext := range cexts {
113 for _, d := range cext.KnownDirectives() {
114 knownDirectives[d] = true
115 }
116 }
117
118 updateRels := buildUpdateRelMap(c.RepoRoot, dirs)
119
120 var visit func(*config.Config, string, string, bool)
121 visit = func(c *config.Config, dir, rel string, updateParent bool) {
122 haveError := false
123
124
125
126
127 ents, err := os.ReadDir(dir)
128 if err != nil {
129 log.Print(err)
130 return
131 }
132
133 f, err := loadBuildFile(c, rel, dir, ents)
134 if err != nil {
135 log.Print(err)
136 if c.Strict {
137
138
139 log.Fatal("Exit as strict mode is on")
140 }
141 haveError = true
142 }
143
144 c = configure(cexts, knownDirectives, c, rel, f)
145 wc := getWalkConfig(c)
146
147 if wc.isExcluded(rel, ".") {
148 return
149 }
150
151 var subdirs, regularFiles []string
152 for _, ent := range ents {
153 base := ent.Name()
154 ent := resolveFileInfo(wc, dir, rel, ent)
155 switch {
156 case ent == nil:
157 continue
158 case ent.IsDir():
159 subdirs = append(subdirs, base)
160 default:
161 regularFiles = append(regularFiles, base)
162 }
163 }
164
165 shouldUpdate := shouldUpdate(rel, mode, updateParent, updateRels)
166 for _, sub := range subdirs {
167 if subRel := path.Join(rel, sub); shouldVisit(subRel, mode, shouldUpdate, updateRels) {
168 visit(c, filepath.Join(dir, sub), subRel, shouldUpdate)
169 }
170 }
171
172 update := !haveError && !wc.ignore && shouldUpdate
173 if shouldCall(rel, mode, updateParent, updateRels) {
174 genFiles := findGenFiles(wc, f)
175 wf(dir, rel, c, update, f, subdirs, regularFiles, genFiles)
176 }
177 }
178 visit(c, c.RepoRoot, "", false)
179 }
180
181
182
183
184
185
186
187
188
189
190 func buildUpdateRelMap(root string, dirs []string) map[string]bool {
191 relMap := make(map[string]bool)
192 for _, dir := range dirs {
193 rel, _ := filepath.Rel(root, dir)
194 rel = filepath.ToSlash(rel)
195 if rel == "." {
196 rel = ""
197 }
198
199 i := 0
200 for {
201 next := strings.IndexByte(rel[i:], '/') + i
202 if next-i < 0 {
203 relMap[rel] = true
204 break
205 }
206 prefix := rel[:next]
207 if _, ok := relMap[prefix]; !ok {
208 relMap[prefix] = false
209 }
210 i = next + 1
211 }
212 }
213 return relMap
214 }
215
216
217
218 func shouldCall(rel string, mode Mode, updateParent bool, updateRels map[string]bool) bool {
219 switch mode {
220 case VisitAllUpdateSubdirsMode, VisitAllUpdateDirsMode:
221 return true
222 case UpdateSubdirsMode:
223 return updateParent || updateRels[rel]
224 default:
225 return updateRels[rel]
226 }
227 }
228
229
230
231
232 func shouldUpdate(rel string, mode Mode, updateParent bool, updateRels map[string]bool) bool {
233 if (mode == VisitAllUpdateSubdirsMode || mode == UpdateSubdirsMode) && updateParent {
234 return true
235 }
236 return updateRels[rel]
237 }
238
239
240 func shouldVisit(rel string, mode Mode, updateParent bool, updateRels map[string]bool) bool {
241 switch mode {
242 case VisitAllUpdateSubdirsMode, VisitAllUpdateDirsMode:
243 return true
244 case UpdateSubdirsMode:
245 _, ok := updateRels[rel]
246 return ok || updateParent
247 default:
248 _, ok := updateRels[rel]
249 return ok
250 }
251 }
252
253 func loadBuildFile(c *config.Config, pkg, dir string, ents []fs.DirEntry) (*rule.File, error) {
254 var err error
255 readDir := dir
256 readEnts := ents
257 if c.ReadBuildFilesDir != "" {
258 readDir = filepath.Join(c.ReadBuildFilesDir, filepath.FromSlash(pkg))
259 readEnts, err = os.ReadDir(readDir)
260 if err != nil {
261 return nil, err
262 }
263 }
264 path := rule.MatchBuildFile(readDir, c.ValidBuildFileNames, readEnts)
265 if path == "" {
266 return nil, nil
267 }
268 return rule.LoadFile(path, pkg)
269 }
270
271 func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *config.Config, rel string, f *rule.File) *config.Config {
272 if rel != "" {
273 c = c.Clone()
274 }
275 if f != nil {
276 for _, d := range f.Directives {
277 if !knownDirectives[d.Key] {
278 log.Printf("%s: unknown directive: gazelle:%s", f.Path, d.Key)
279 if c.Strict {
280
281
282 log.Fatal("Exit as strict mode is on")
283 }
284 }
285 }
286 }
287 for _, cext := range cexts {
288 cext.Configure(c, rel, f)
289 }
290 return c
291 }
292
293 func findGenFiles(wc *walkConfig, f *rule.File) []string {
294 if f == nil {
295 return nil
296 }
297 var strs []string
298 for _, r := range f.Rules {
299 for _, key := range []string{"out", "outs"} {
300 if s := r.AttrString(key); s != "" {
301 strs = append(strs, s)
302 } else if ss := r.AttrStrings(key); len(ss) > 0 {
303 strs = append(strs, ss...)
304 }
305 }
306 }
307
308 var genFiles []string
309 for _, s := range strs {
310 if !wc.isExcluded(f.Pkg, s) {
311 genFiles = append(genFiles, s)
312 }
313 }
314 return genFiles
315 }
316
317 func resolveFileInfo(wc *walkConfig, dir, rel string, ent fs.DirEntry) fs.DirEntry {
318 base := ent.Name()
319 if base == "" || wc.isExcluded(rel, base) {
320 return nil
321 }
322 if ent.Type()&os.ModeSymlink == 0 {
323
324 return ent
325 }
326 if !wc.shouldFollow(rel, ent.Name()) {
327
328 return nil
329 }
330 fi, err := os.Stat(path.Join(dir, base))
331 if err != nil {
332
333 return nil
334 }
335 return fs.FileInfoToDirEntry(fi)
336 }
337
View as plain text