diff --git a/walk/config.go b/walk/config.go index bd59cb688..4c37668eb 100644 --- a/walk/config.go +++ b/walk/config.go @@ -16,9 +16,14 @@ limitations under the License. package walk import ( + "bufio" "flag" + "fmt" "log" + "os" "path" + "strings" + "sync" "github.com/bazelbuild/bazel-gazelle/config" "github.com/bazelbuild/bazel-gazelle/rule" @@ -35,6 +40,7 @@ type walkConfig struct { excludes []string ignore bool follow []string + loadOnce sync.Once } const walkName = "_walk" @@ -84,6 +90,12 @@ func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { *wcCopy = *wc wcCopy.ignore = false + wc.loadOnce.Do(func() { + if err := cr.loadBazelIgnore(c.RepoRoot, wcCopy); err != nil { + log.Printf("error loading .bazelignore: %v", err) + } + }) + if f != nil { for _, d := range f.Directives { switch d.Key { @@ -104,6 +116,35 @@ func (cr *Configurer) Configure(c *config.Config, rel string, f *rule.File) { c.Exts[walkName] = wcCopy } +func (c *Configurer) loadBazelIgnore(repoRoot string, wc *walkConfig) error { + ignorePath := path.Join(repoRoot, ".bazelignore") + file, err := os.Open(ignorePath) + if os.IsNotExist(err) { + return nil + } + if err != nil { + return fmt.Errorf(".bazelignore exists but couldn't be read: %v", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + ignore := strings.TrimSpace(scanner.Text()) + if ignore == "" || string(ignore[0]) == "#" { + continue + } + // Bazel ignore paths are always relative to repo root. + // Glob patterns are not supported. + if strings.ContainsAny(ignore, "*?[") { + log.Printf("the .bazelignore exclusion pattern must not be a glob %s", ignore) + continue + } + // Ensure we remove trailing slashes or the exclude matching won't work correctly + wc.excludes = append(wc.excludes, strings.TrimSuffix(ignore, "/")) + } + return nil +} + func checkPathMatchPattern(pattern string) error { _, err := doublestar.Match(pattern, "x") return err diff --git a/walk/walk_test.go b/walk/walk_test.go index 1cb38b083..0710fd420 100644 --- a/walk/walk_test.go +++ b/walk/walk_test.go @@ -192,13 +192,28 @@ func TestExcludeFiles(t *testing.T) { gen( name = "x", out = "gen", -) +)`, + }, + { + Path: ".bazelignore", + Content: ` +dir +dir2/a/b +dir3/ + +# Globs are not allowed in .bazelignore so this will not be ignored +foo/* + +# Random comment followed by a line +a.file `, }, {Path: ".dot"}, // not ignored {Path: "_blank"}, // not ignored {Path: "a/a.proto"}, // not ignored {Path: "a/b.gen.go"}, // not ignored + {Path: "dir2/a/c"}, // not ignored + {Path: "foo/a/c"}, // not ignored {Path: "a.gen.go"}, // ignored by '*.gen.go' {Path: "a.go"}, // ignored by 'a.go' @@ -210,6 +225,10 @@ gen( {Path: "c/x/y/b/foo/bar"}, // ignored by 'c/**/b' {Path: "ign/bad"}, // ignored by 'ign' {Path: "sub/b.go"}, // ignored by 'sub/b.go' + {Path: "dir/contents"}, // ignored by .bazelignore 'dir' + {Path: "dir2/a/b"}, // ignored by .bazelignore 'dir2/a/b' + {Path: "dir3/g/h"}, // ignored by .bazelignore 'dir3/' + {Path: "a.file"}, // ignored by .bazelignore 'a.file' }) defer cleanup() @@ -223,7 +242,7 @@ gen( files = append(files, path.Join(rel, f)) } }) - want := []string{"a/a.proto", "a/b.gen.go", ".dot", "BUILD.bazel", "_blank"} + want := []string{"a/a.proto", "a/b.gen.go", "dir2/a/c", "foo/a/c", ".bazelignore", ".dot", "BUILD.bazel", "_blank"} if diff := cmp.Diff(want, files); diff != "" { t.Errorf("Walk files (-want +got):\n%s", diff) }