1
15
16 package main
17
18 import (
19 "bytes"
20 "context"
21 "errors"
22 "flag"
23 "fmt"
24 "log"
25 "os"
26 "path/filepath"
27 "sort"
28 "strings"
29 "syscall"
30
31 "github.com/bazelbuild/bazel-gazelle/config"
32 gzflag "github.com/bazelbuild/bazel-gazelle/flag"
33 "github.com/bazelbuild/bazel-gazelle/internal/wspace"
34 "github.com/bazelbuild/bazel-gazelle/label"
35 "github.com/bazelbuild/bazel-gazelle/language"
36 "github.com/bazelbuild/bazel-gazelle/merger"
37 "github.com/bazelbuild/bazel-gazelle/repo"
38 "github.com/bazelbuild/bazel-gazelle/resolve"
39 "github.com/bazelbuild/bazel-gazelle/rule"
40 "github.com/bazelbuild/bazel-gazelle/walk"
41 )
42
43
44
45
46 type updateConfig struct {
47 dirs []string
48 emit emitFunc
49 repos []repo.Repo
50 workspaceFiles []*rule.File
51 walkMode walk.Mode
52 patchPath string
53 patchBuffer bytes.Buffer
54 print0 bool
55 profile profiler
56 }
57
58 type emitFunc func(c *config.Config, f *rule.File) error
59
60 var modeFromName = map[string]emitFunc{
61 "print": printFile,
62 "fix": fixFile,
63 "diff": diffFile,
64 }
65
66 const updateName = "_update"
67
68 func getUpdateConfig(c *config.Config) *updateConfig {
69 return c.Exts[updateName].(*updateConfig)
70 }
71
72 type updateConfigurer struct {
73 mode string
74 recursive bool
75 knownImports []string
76 repoConfigPath string
77 cpuProfile string
78 memProfile string
79 }
80
81 func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
82 uc := &updateConfig{}
83 c.Exts[updateName] = uc
84
85 c.ShouldFix = cmd == "fix"
86
87 fs.StringVar(&ucr.mode, "mode", "fix", "print: prints all of the updated BUILD files\n\tfix: rewrites all of the BUILD files in place\n\tdiff: computes the rewrite but then just does a diff")
88 fs.BoolVar(&ucr.recursive, "r", true, "when true, gazelle will update subdirectories recursively")
89 fs.StringVar(&uc.patchPath, "patch", "", "when set with -mode=diff, gazelle will write to a file instead of stdout")
90 fs.BoolVar(&uc.print0, "print0", false, "when set with -mode=fix, gazelle will print the names of rewritten files separated with \\0 (NULL)")
91 fs.StringVar(&ucr.cpuProfile, "cpuprofile", "", "write cpu profile to `file`")
92 fs.StringVar(&ucr.memProfile, "memprofile", "", "write memory profile to `file`")
93 fs.Var(&gzflag.MultiFlag{Values: &ucr.knownImports}, "known_import", "import path for which external resolution is skipped (can specify multiple times)")
94 fs.StringVar(&ucr.repoConfigPath, "repo_config", "", "file where Gazelle should load repository configuration. Defaults to WORKSPACE.")
95 }
96
97 func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
98 uc := getUpdateConfig(c)
99
100 var ok bool
101 uc.emit, ok = modeFromName[ucr.mode]
102 if !ok {
103 return fmt.Errorf("unrecognized emit mode: %q", ucr.mode)
104 }
105 if uc.patchPath != "" && ucr.mode != "diff" {
106 return fmt.Errorf("-patch set but -mode is %s, not diff", ucr.mode)
107 }
108 if uc.patchPath != "" && !filepath.IsAbs(uc.patchPath) {
109 uc.patchPath = filepath.Join(c.WorkDir, uc.patchPath)
110 }
111 p, err := newProfiler(ucr.cpuProfile, ucr.memProfile)
112 if err != nil {
113 return err
114 }
115 uc.profile = p
116
117 dirs := fs.Args()
118 if len(dirs) == 0 {
119 dirs = []string{"."}
120 }
121 uc.dirs = make([]string, len(dirs))
122 for i, arg := range dirs {
123 dir := arg
124 if !filepath.IsAbs(dir) {
125 dir = filepath.Join(c.WorkDir, dir)
126 }
127 dir, err := filepath.EvalSymlinks(dir)
128 if err != nil {
129 return fmt.Errorf("%s: failed to resolve symlinks: %v", arg, err)
130 }
131 if !isDescendingDir(dir, c.RepoRoot) {
132 return fmt.Errorf("%s: not a subdirectory of repo root %s", arg, c.RepoRoot)
133 }
134 uc.dirs[i] = dir
135 }
136
137 if ucr.recursive && c.IndexLibraries {
138 uc.walkMode = walk.VisitAllUpdateSubdirsMode
139 } else if c.IndexLibraries {
140 uc.walkMode = walk.VisitAllUpdateDirsMode
141 } else if ucr.recursive {
142 uc.walkMode = walk.UpdateSubdirsMode
143 } else {
144 uc.walkMode = walk.UpdateDirsMode
145 }
146
147
148
149
150
151 if ucr.repoConfigPath == "" {
152 ucr.repoConfigPath = wspace.FindWORKSPACEFile(c.RepoRoot)
153 }
154 repoConfigFile, err := rule.LoadWorkspaceFile(ucr.repoConfigPath, "")
155 if err != nil && !os.IsNotExist(err) && !isDirErr(err) {
156 return err
157 } else if err == nil {
158 c.Repos, _, err = repo.ListRepositories(repoConfigFile)
159 if err != nil {
160 return err
161 }
162 }
163 for _, imp := range ucr.knownImports {
164 uc.repos = append(uc.repos, repo.Repo{
165 Name: label.ImportPathToBazelRepoName(imp),
166 GoPrefix: imp,
167 })
168 }
169
170 for _, r := range c.Repos {
171 if r.Kind() == "go_repository" {
172 var name string
173 if apparentName := c.ModuleToApparentName(r.AttrString("module_name")); apparentName != "" {
174 name = apparentName
175 } else {
176 name = r.Name()
177 }
178 uc.repos = append(uc.repos, repo.Repo{
179 Name: name,
180 GoPrefix: r.AttrString("importpath"),
181 })
182 }
183 }
184
185
186
187 workspacePath := wspace.FindWORKSPACEFile(c.RepoRoot)
188 var workspace *rule.File
189 if ucr.repoConfigPath == workspacePath {
190 workspace = repoConfigFile
191 } else {
192 workspace, err = rule.LoadWorkspaceFile(workspacePath, "")
193 if err != nil && !os.IsNotExist(err) && !isDirErr(err) {
194 return err
195 }
196 }
197 if workspace != nil {
198 c.RepoName = findWorkspaceName(workspace)
199 _, repoFileMap, err := repo.ListRepositories(workspace)
200 if err != nil {
201 return err
202 }
203 seen := make(map[*rule.File]bool)
204 for _, f := range repoFileMap {
205 if !seen[f] {
206 uc.workspaceFiles = append(uc.workspaceFiles, f)
207 seen[f] = true
208 }
209 }
210 sort.Slice(uc.workspaceFiles, func(i, j int) bool {
211 return uc.workspaceFiles[i].Path < uc.workspaceFiles[j].Path
212 })
213 }
214
215 return nil
216 }
217
218 func (ucr *updateConfigurer) KnownDirectives() []string { return nil }
219
220 func (ucr *updateConfigurer) Configure(c *config.Config, rel string, f *rule.File) {}
221
222
223
224 type visitRecord struct {
225
226
227 pkgRel string
228
229
230 c *config.Config
231
232
233 rules []*rule.Rule
234
235
236 imports []interface{}
237
238
239 empty []*rule.Rule
240
241
242 file *rule.File
243
244
245 mappedKinds []config.MappedKind
246 mappedKindInfo map[string]rule.KindInfo
247 }
248
249 var genericLoads = []rule.LoadInfo{
250 {
251 Name: "@bazel_gazelle//:def.bzl",
252 Symbols: []string{"gazelle"},
253 },
254 }
255
256 func runFixUpdate(wd string, cmd command, args []string) (err error) {
257 cexts := make([]config.Configurer, 0, len(languages)+4)
258 cexts = append(cexts,
259 &config.CommonConfigurer{},
260 &updateConfigurer{},
261 &walk.Configurer{},
262 &resolve.Configurer{})
263
264 for _, lang := range languages {
265 cexts = append(cexts, lang)
266 }
267
268 c, err := newFixUpdateConfiguration(wd, cmd, args, cexts)
269 if err != nil {
270 return err
271 }
272
273 mrslv := newMetaResolver()
274 kinds := make(map[string]rule.KindInfo)
275 loads := genericLoads
276 exts := make([]interface{}, 0, len(languages))
277 for _, lang := range languages {
278 for kind, info := range lang.Kinds() {
279 mrslv.AddBuiltin(kind, lang)
280 kinds[kind] = info
281 }
282 if moduleAwareLang, ok := lang.(language.ModuleAwareLanguage); ok {
283 loads = append(loads, moduleAwareLang.ApparentLoads(c.ModuleToApparentName)...)
284 } else {
285 loads = append(loads, lang.Loads()...)
286 }
287 exts = append(exts, lang)
288 }
289 ruleIndex := resolve.NewRuleIndex(mrslv.Resolver, exts...)
290
291 if err := fixRepoFiles(c, loads); err != nil {
292 return err
293 }
294
295 ctx, cancel := context.WithCancel(context.Background())
296 defer cancel()
297 for _, lang := range languages {
298 if life, ok := lang.(language.LifecycleManager); ok {
299 life.Before(ctx)
300 }
301 }
302
303
304 var visits []visitRecord
305 uc := getUpdateConfig(c)
306 defer func() {
307 if err := uc.profile.stop(); err != nil {
308 log.Printf("stopping profiler: %v", err)
309 }
310 }()
311
312 var errorsFromWalk []error
313 walk.Walk(c, cexts, uc.dirs, uc.walkMode, func(dir, rel string, c *config.Config, update bool, f *rule.File, subdirs, regularFiles, genFiles []string) {
314
315
316 if !update {
317 if c.IndexLibraries && f != nil {
318 for _, repl := range c.KindMap {
319 mrslv.MappedKind(rel, repl)
320 }
321 for _, r := range f.Rules {
322 ruleIndex.AddRule(c, r, f)
323 }
324 }
325 return
326 }
327
328
329 if f != nil {
330 for _, l := range filterLanguages(c, languages) {
331 l.Fix(c, f)
332 }
333 }
334
335
336 var empty, gen []*rule.Rule
337 var imports []interface{}
338 for _, l := range filterLanguages(c, languages) {
339 res := l.GenerateRules(language.GenerateArgs{
340 Config: c,
341 Dir: dir,
342 Rel: rel,
343 File: f,
344 Subdirs: subdirs,
345 RegularFiles: regularFiles,
346 GenFiles: genFiles,
347 OtherEmpty: empty,
348 OtherGen: gen,
349 })
350 if len(res.Gen) != len(res.Imports) {
351 log.Panicf("%s: language %s generated %d rules but returned %d imports", rel, l.Name(), len(res.Gen), len(res.Imports))
352 }
353 empty = append(empty, res.Empty...)
354 gen = append(gen, res.Gen...)
355 imports = append(imports, res.Imports...)
356 }
357 if f == nil && len(gen) == 0 {
358 return
359 }
360
361
362 var (
363 mappedKinds []config.MappedKind
364 mappedKindInfo = make(map[string]rule.KindInfo)
365 )
366
367 var allRules []*rule.Rule
368 allRules = append(allRules, gen...)
369 if f != nil {
370 allRules = append(allRules, f.Rules...)
371 }
372 for _, r := range allRules {
373 repl, err := lookupMapKindReplacement(c.KindMap, r.Kind())
374 if err != nil {
375 errorsFromWalk = append(errorsFromWalk, fmt.Errorf("looking up mapped kind: %w", err))
376 continue
377 }
378 if repl != nil {
379 mappedKindInfo[repl.KindName] = kinds[r.Kind()]
380 mappedKinds = append(mappedKinds, *repl)
381 mrslv.MappedKind(rel, *repl)
382 r.SetKind(repl.KindName)
383 }
384 }
385 for _, r := range empty {
386 if repl, ok := c.KindMap[r.Kind()]; ok {
387 mappedKindInfo[repl.KindName] = kinds[r.Kind()]
388 mappedKinds = append(mappedKinds, repl)
389 mrslv.MappedKind(rel, repl)
390 r.SetKind(repl.KindName)
391 }
392 }
393
394
395 if f == nil {
396 f = rule.EmptyFile(filepath.Join(dir, c.DefaultBuildFileName()), rel)
397 for _, r := range gen {
398 r.Insert(f)
399 }
400 } else {
401 merger.MergeFile(f, empty, gen, merger.PreResolve,
402 unionKindInfoMaps(kinds, mappedKindInfo))
403 }
404 visits = append(visits, visitRecord{
405 pkgRel: rel,
406 c: c,
407 rules: gen,
408 imports: imports,
409 empty: empty,
410 file: f,
411 mappedKinds: mappedKinds,
412 mappedKindInfo: mappedKindInfo,
413 })
414
415
416 if c.IndexLibraries {
417 for _, r := range f.Rules {
418 ruleIndex.AddRule(c, r, f)
419 }
420 }
421 })
422
423 for _, lang := range languages {
424 if finishable, ok := lang.(language.FinishableLanguage); ok {
425 finishable.DoneGeneratingRules()
426 }
427 }
428
429 if len(errorsFromWalk) == 1 {
430 return errorsFromWalk[0]
431 }
432
433 if len(errorsFromWalk) > 1 {
434 var additionalErrors []string
435 for _, error := range errorsFromWalk[1:] {
436 additionalErrors = append(additionalErrors, error.Error())
437 }
438
439 return fmt.Errorf("encountered multiple errors: %w, %v", errorsFromWalk[0], strings.Join(additionalErrors, ", "))
440 }
441
442
443 ruleIndex.Finish()
444
445
446 rc, cleanupRc := repo.NewRemoteCache(uc.repos)
447 defer func() {
448 if cerr := cleanupRc(); err == nil && cerr != nil {
449 err = cerr
450 }
451 }()
452 if err := maybePopulateRemoteCacheFromGoMod(c, rc); err != nil {
453 log.Print(err)
454 }
455 for _, v := range visits {
456 for i, r := range v.rules {
457 from := label.New(c.RepoName, v.pkgRel, r.Name())
458 if rslv := mrslv.Resolver(r, v.pkgRel); rslv != nil {
459 rslv.Resolve(v.c, ruleIndex, rc, r, v.imports[i], from)
460 }
461 }
462 merger.MergeFile(v.file, v.empty, v.rules, merger.PostResolve,
463 unionKindInfoMaps(kinds, v.mappedKindInfo))
464 }
465 for _, lang := range languages {
466 if life, ok := lang.(language.LifecycleManager); ok {
467 life.AfterResolvingDeps(ctx)
468 }
469 }
470
471
472 var exit error
473 for _, v := range visits {
474 merger.FixLoads(v.file, applyKindMappings(v.mappedKinds, loads))
475 if err := uc.emit(v.c, v.file); err != nil {
476 if err == errExit {
477 exit = err
478 } else {
479 log.Print(err)
480 }
481 }
482 }
483 if uc.patchPath != "" {
484 if err := os.WriteFile(uc.patchPath, uc.patchBuffer.Bytes(), 0o666); err != nil {
485 return err
486 }
487 }
488
489 return exit
490 }
491
492
493
494
495
496 func lookupMapKindReplacement(kindMap map[string]config.MappedKind, kind string) (*config.MappedKind, error) {
497 var mapped *config.MappedKind
498 seenKinds := make(map[string]struct{})
499 seenKindPath := []string{kind}
500 for {
501 replacement, ok := kindMap[kind]
502 if !ok {
503 break
504 }
505
506 seenKindPath = append(seenKindPath, replacement.KindName)
507 if _, alreadySeen := seenKinds[replacement.KindName]; alreadySeen {
508 return nil, fmt.Errorf("found loop of map_kind replacements: %s", strings.Join(seenKindPath, " -> "))
509 }
510
511 seenKinds[replacement.KindName] = struct{}{}
512 mapped = &replacement
513 if kind == replacement.KindName {
514 break
515 }
516
517 kind = replacement.KindName
518 }
519
520 return mapped, nil
521 }
522
523 func newFixUpdateConfiguration(wd string, cmd command, args []string, cexts []config.Configurer) (*config.Config, error) {
524 c := config.New()
525 c.WorkDir = wd
526
527 fs := flag.NewFlagSet("gazelle", flag.ContinueOnError)
528
529
530 fs.Usage = func() {}
531
532 for _, cext := range cexts {
533 cext.RegisterFlags(fs, cmd.String(), c)
534 }
535
536 if err := fs.Parse(args); err != nil {
537 if err == flag.ErrHelp {
538 fixUpdateUsage(fs)
539 return nil, err
540 }
541
542 log.Fatal("Try -help for more information.")
543 }
544
545 for _, cext := range cexts {
546 if err := cext.CheckFlags(fs, c); err != nil {
547 return nil, err
548 }
549 }
550
551 return c, nil
552 }
553
554 func fixUpdateUsage(fs *flag.FlagSet) {
555 fmt.Fprint(os.Stderr, `usage: gazelle [fix|update] [flags...] [package-dirs...]
556
557 The update command creates new build files and update existing BUILD files
558 when needed.
559
560 The fix command also creates and updates build files, and in addition, it may
561 make potentially breaking updates to usage of rules. For example, it may
562 delete obsolete rules or rename existing rules.
563
564 There are several output modes which can be selected with the -mode flag. The
565 output mode determines what Gazelle does with updated BUILD files.
566
567 fix (default) - write updated BUILD files back to disk.
568 print - print updated BUILD files to stdout.
569 diff - diff updated BUILD files against existing files in unified format.
570
571 Gazelle accepts a list of paths to Go package directories to process (defaults
572 to the working directory if none are given). It recursively traverses
573 subdirectories. All directories must be under the directory specified by
574 -repo_root; if -repo_root is not given, this is the directory containing the
575 WORKSPACE file.
576
577 FLAGS:
578
579 `)
580 fs.PrintDefaults()
581 }
582
583 func fixRepoFiles(c *config.Config, loads []rule.LoadInfo) error {
584 uc := getUpdateConfig(c)
585 if !c.ShouldFix {
586 return nil
587 }
588 shouldFix := false
589 for _, d := range uc.dirs {
590 if d == c.RepoRoot {
591 shouldFix = true
592 }
593 }
594 if !shouldFix {
595 return nil
596 }
597
598 for _, f := range uc.workspaceFiles {
599 merger.FixLoads(f, loads)
600 workspaceFile := wspace.FindWORKSPACEFile(c.RepoRoot)
601
602 if f.Path == workspaceFile {
603 removeLegacyGoRepository(f)
604 if err := merger.CheckGazelleLoaded(f); err != nil {
605 return err
606 }
607 }
608 if err := uc.emit(c, f); err != nil {
609 return err
610 }
611 }
612 return nil
613 }
614
615
616
617
618 func removeLegacyGoRepository(f *rule.File) {
619 for _, l := range f.Loads {
620 if l.Name() == "@io_bazel_rules_go//go:def.bzl" {
621 l.Remove("go_repository")
622 if l.IsEmpty() {
623 l.Delete()
624 }
625 }
626 }
627 }
628
629 func findWorkspaceName(f *rule.File) string {
630 var name string
631 for _, r := range f.Rules {
632 if r.Kind() == "workspace" {
633 name = r.Name()
634 break
635 }
636 }
637
638
639
640 if name == "com_google_googleapis" {
641 return "go_googleapis"
642 }
643 return name
644 }
645
646 func isDescendingDir(dir, root string) bool {
647 rel, err := filepath.Rel(root, dir)
648 if err != nil {
649 return false
650 }
651 if rel == "." {
652 return true
653 }
654 return !strings.HasPrefix(rel, "..")
655 }
656
657 func findOutputPath(c *config.Config, f *rule.File) string {
658 if c.ReadBuildFilesDir == "" && c.WriteBuildFilesDir == "" {
659 return f.Path
660 }
661 baseDir := c.WriteBuildFilesDir
662 if c.WriteBuildFilesDir == "" {
663 baseDir = c.RepoRoot
664 }
665 outputDir := filepath.Join(baseDir, filepath.FromSlash(f.Pkg))
666 defaultOutputPath := filepath.Join(outputDir, c.DefaultBuildFileName())
667 ents, err := os.ReadDir(outputDir)
668 if err != nil {
669
670 return defaultOutputPath
671 }
672 outputPath := rule.MatchBuildFile(outputDir, c.ValidBuildFileNames, ents)
673 if outputPath == "" {
674 return defaultOutputPath
675 }
676 return outputPath
677 }
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694 func maybePopulateRemoteCacheFromGoMod(c *config.Config, rc *repo.RemoteCache) error {
695 haveGo := false
696 for name := range c.Exts {
697 if name == "go" {
698 haveGo = true
699 break
700 }
701 }
702 if !haveGo {
703 return nil
704 }
705
706 goModPath := filepath.Join(c.RepoRoot, "go.mod")
707 if _, err := os.Stat(goModPath); err != nil {
708 return nil
709 }
710
711 return rc.PopulateFromGoMod(goModPath)
712 }
713
714 func unionKindInfoMaps(a, b map[string]rule.KindInfo) map[string]rule.KindInfo {
715 if len(a) == 0 {
716 return b
717 }
718 if len(b) == 0 {
719 return a
720 }
721 result := make(map[string]rule.KindInfo, len(a)+len(b))
722 for _, m := range []map[string]rule.KindInfo{a, b} {
723 for k, v := range m {
724 result[k] = v
725 }
726 }
727 return result
728 }
729
730
731 func applyKindMappings(mappedKinds []config.MappedKind, loads []rule.LoadInfo) []rule.LoadInfo {
732 if len(mappedKinds) == 0 {
733 return loads
734 }
735
736
737 mappedLoads := make([]rule.LoadInfo, len(loads))
738 copy(mappedLoads, loads)
739 for _, mappedKind := range mappedKinds {
740 mappedLoads = appendOrMergeKindMapping(mappedLoads, mappedKind)
741 }
742 return mappedLoads
743 }
744
745
746 func appendOrMergeKindMapping(mappedLoads []rule.LoadInfo, mappedKind config.MappedKind) []rule.LoadInfo {
747
748 for i, load := range mappedLoads {
749 if load.Name == mappedKind.KindLoad {
750 mappedLoads[i].Symbols = append(load.Symbols, mappedKind.KindName)
751 return mappedLoads
752 }
753 }
754
755
756 return append(mappedLoads, rule.LoadInfo{
757 Name: mappedKind.KindLoad,
758 Symbols: []string{mappedKind.KindName},
759 })
760 }
761
762 func isDirErr(err error) bool {
763 if err == nil {
764 return false
765 }
766 var pe *os.PathError
767 if errors.As(err, &pe) {
768 return pe.Err == syscall.EISDIR
769 }
770 return false
771 }
772
View as plain text