From 496ec9e7a22dd54e13604a5c9dc2ca1f86a72fba Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Thu, 12 Sep 2024 10:40:50 -0700 Subject: [PATCH] feat: support only updating existing BUILD files Close #1832 --- walk/config.go | 23 +++++++++++++++++++---- walk/walk.go | 44 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/walk/config.go b/walk/config.go index 4feceddea..fda907daf 100644 --- a/walk/config.go +++ b/walk/config.go @@ -33,14 +33,27 @@ import ( gzflag "github.com/bazelbuild/bazel-gazelle/flag" ) +// GenerationModeType represents one of the generation modes. +type GenerationModeType string + +// Generation modes +const ( + // Update: update and maintain existing BUILD files + GenerationModeUpdate GenerationModeType = "update" + + // Create: create new and update existing BUILD files + GenerationModeCreate GenerationModeType = "create" +) + // TODO(#472): store location information to validate each exclude. They // may be set in one directory and used in another. Excludes work on // declared generated files, so we can't just stat. type walkConfig struct { - excludes []string - ignore bool - follow []string + updateOnly bool + excludes []string + ignore bool + follow []string } const walkName = "_walk" @@ -70,7 +83,7 @@ func (*Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) func (*Configurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil } func (*Configurer) KnownDirectives() []string { - return []string{"exclude", "follow", "ignore"} + return []string{"generation_mode", "exclude", "follow", "ignore"} } func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { @@ -82,6 +95,8 @@ func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { if f != nil { for _, d := range f.Directives { switch d.Key { + case "generation_mode": + wcCopy.updateOnly = GenerationModeType(strings.TrimSpace(d.Value)) == GenerationModeUpdate case "exclude": if err := checkPathMatchPattern(path.Join(rel, d.Value)); err != nil { log.Printf("the exclusion pattern is not valid %q: %s", path.Join(rel, d.Value), err) diff --git a/walk/walk.go b/walk/walk.go index 48cc835a3..07ea7a220 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -132,7 +132,12 @@ func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, visit(c, cexts, knownDirectives, updateRels, trie, wf, "", false) } -func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[string]bool, updateRels *UpdateFilter, trie *pathTrie, wf WalkFunc, rel string, updateParent bool) { +// Read and traverse the 'rel' directory, calling the WalkFunc for each directory +// while respecting the `isBazelIgnored` and exclusion directives. +// +// If the given directory is not configured to produce a BUILD the collected files +// will be returned and the WalkFunc will not be called. +func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[string]bool, updateRels *UpdateFilter, trie *pathTrie, wf WalkFunc, rel string, updateParent bool) ([]string, bool) { haveError := false ents := make([]fs.DirEntry, 0, len(trie.children)) @@ -158,14 +163,22 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri haveError = true } - c = configure(cexts, knownDirectives, c, rel, f) wc := getWalkConfig(c) + collectionOnly := f == nil && wc.updateOnly + + // Configure the current directory if not only collecting files + if !collectionOnly { + c = configure(cexts, knownDirectives, c, rel, f) + wc = getWalkConfig(c) - if wc.isExcluded(rel) { - return + // If the config of this directory has excluded itself + if wc.isExcluded(rel) { + return nil, false + } } - var subdirs, regularFiles []string + // Divide the directory entries while filtering out ignored/excluded. + var dirs, regularFiles []string for _, ent := range ents { base := ent.Name() entRel := path.Join(rel, base) @@ -177,24 +190,39 @@ func visit(c *config.Config, cexts []config.Configurer, knownDirectives map[stri case ent == nil: continue case ent.IsDir(): - subdirs = append(subdirs, base) + dirs = append(dirs, base) default: regularFiles = append(regularFiles, base) } } + var subdirs []string + + // Visit subdirectories dividing them into `regularFiles` and `subdirs`` shouldUpdate := updateRels.shouldUpdate(rel, updateParent) - for _, sub := range subdirs { + for _, sub := range dirs { if subRel := path.Join(rel, sub); updateRels.shouldVisit(subRel, shouldUpdate) { - visit(c, cexts, knownDirectives, updateRels, trie.children[sub], wf, subRel, shouldUpdate) + subFiles, shouldMerge := visit(c, cexts, knownDirectives, updateRels, trie.children[sub], wf, subRel, shouldUpdate) + if shouldMerge { + for _, f := range subFiles { + regularFiles = append(regularFiles, path.Join(sub, f)) + } + } else { + subdirs = append(subdirs, sub) + } } } + if collectionOnly { + return regularFiles, true + } + update := !haveError && !wc.ignore && shouldUpdate if updateRels.shouldCall(rel, updateParent) { genFiles := findGenFiles(wc, f) wf(dir, rel, c, update, f, subdirs, regularFiles, genFiles) } + return nil, false } // An UpdateFilter tracks which directories need to be updated