Commit 52a08ddc authored by Jeff Grafton's avatar Jeff Grafton

Update to gazelle 0.15.0

parent 3778646d
...@@ -361,78 +361,83 @@ ...@@ -361,78 +361,83 @@
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/cmd/gazelle", "ImportPath": "github.com/bazelbuild/bazel-gazelle/cmd/gazelle",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/config", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/config",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/flag", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/flag",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/label", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/label",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/language", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/language",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/language/go", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/language/go",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/language/proto", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/language/proto",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/merger", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/merger",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/pathtools", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/pathtools",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/repos", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/repos",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/resolve", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/resolve",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/rule", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/rule",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/testtools",
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/version", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/version",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/walk", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/walk",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/wspace", "ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/wspace",
"Comment": "0.14.0", "Comment": "0.15.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1" "Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
}, },
{ {
"ImportPath": "github.com/bazelbuild/buildtools/build", "ImportPath": "github.com/bazelbuild/buildtools/build",
......
...@@ -60,6 +60,7 @@ filegroup( ...@@ -60,6 +60,7 @@ filegroup(
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/repos:all-srcs", "//vendor/github.com/bazelbuild/bazel-gazelle/internal/repos:all-srcs",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/resolve:all-srcs", "//vendor/github.com/bazelbuild/bazel-gazelle/internal/resolve:all-srcs",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/rule:all-srcs", "//vendor/github.com/bazelbuild/bazel-gazelle/internal/rule:all-srcs",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/testtools:all-srcs",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/version:all-srcs", "//vendor/github.com/bazelbuild/bazel-gazelle/internal/version:all-srcs",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/walk:all-srcs", "//vendor/github.com/bazelbuild/bazel-gazelle/internal/walk:all-srcs",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/wspace:all-srcs", "//vendor/github.com/bazelbuild/bazel-gazelle/internal/wspace:all-srcs",
......
...@@ -28,6 +28,7 @@ go_library( ...@@ -28,6 +28,7 @@ go_library(
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/rule:go_default_library", "//vendor/github.com/bazelbuild/bazel-gazelle/internal/rule:go_default_library",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/version:go_default_library", "//vendor/github.com/bazelbuild/bazel-gazelle/internal/version:go_default_library",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/walk:go_default_library", "//vendor/github.com/bazelbuild/bazel-gazelle/internal/walk:go_default_library",
"//vendor/github.com/pmezard/go-difflib/difflib:go_default_library",
], ],
) )
......
...@@ -16,37 +16,68 @@ limitations under the License. ...@@ -16,37 +16,68 @@ limitations under the License.
package main package main
import ( import (
"bytes" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/rule"
"github.com/pmezard/go-difflib/difflib"
) )
func diffFile(path string, newContents []byte) error { func diffFile(c *config.Config, f *rule.File) error {
oldContents, err := ioutil.ReadFile(path) rel, err := filepath.Rel(c.RepoRoot, f.Path)
if err != nil { if err != nil {
oldContents = nil return fmt.Errorf("error getting old path for file %q: %v", f.Path, err)
} }
if bytes.Equal(oldContents, newContents) { rel = filepath.ToSlash(rel)
return nil
date := "1970-01-01 00:00:00.000000000 +0000"
diff := difflib.UnifiedDiff{
Context: 3,
FromDate: date,
ToDate: date,
} }
f, err := ioutil.TempFile("", filepath.Base(path))
if err != nil { if oldContent, err := ioutil.ReadFile(f.Path); err != nil && !os.IsNotExist(err) {
return err return fmt.Errorf("error reading original file: %v", err)
} else if err != nil {
diff.FromFile = "/dev/null"
} else if err == nil {
diff.A = difflib.SplitLines(string(oldContent))
if c.ReadBuildFilesDir == "" {
path, err := filepath.Rel(c.RepoRoot, f.Path)
if err != nil {
return fmt.Errorf("error getting old path for file %q: %v", f.Path, err)
}
diff.FromFile = filepath.ToSlash(path)
} else {
diff.FromFile = f.Path
}
} }
f.Close()
defer os.Remove(f.Name()) newContent := f.Format()
if err := ioutil.WriteFile(f.Name(), newContents, 0666); err != nil { diff.B = difflib.SplitLines(string(newContent))
return err outPath := findOutputPath(c, f)
if c.WriteBuildFilesDir == "" {
path, err := filepath.Rel(c.RepoRoot, f.Path)
if err != nil {
return fmt.Errorf("error getting new path for file %q: %v", f.Path, err)
}
diff.ToFile = filepath.ToSlash(path)
} else {
diff.ToFile = outPath
}
uc := getUpdateConfig(c)
var out io.Writer = os.Stdout
if uc.patchPath != "" {
out = &uc.patchBuffer
} }
cmd := exec.Command("diff", "-u", "--new-file", path, f.Name()) if err := difflib.WriteUnifiedDiff(out, diff); err != nil {
cmd.Stdout = os.Stdout return fmt.Errorf("error diffing %s: %v", f.Path, err)
cmd.Stderr = os.Stderr
err = cmd.Run()
if _, ok := err.(*exec.ExitError); ok {
// diff returns non-zero when files are different. This is not an error.
return nil
} }
return err return nil
} }
...@@ -16,6 +16,7 @@ limitations under the License. ...@@ -16,6 +16,7 @@ limitations under the License.
package main package main
import ( import (
"bytes"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
...@@ -38,11 +39,16 @@ import ( ...@@ -38,11 +39,16 @@ import (
// update commands. This includes everything in config.Config, but it also // update commands. This includes everything in config.Config, but it also
// includes some additional fields that aren't relevant to other packages. // includes some additional fields that aren't relevant to other packages.
type updateConfig struct { type updateConfig struct {
emit emitFunc dirs []string
repos []repos.Repo emit emitFunc
repos []repos.Repo
useIndex bool
walkMode walk.Mode
patchPath string
patchBuffer bytes.Buffer
} }
type emitFunc func(path string, data []byte) error type emitFunc func(c *config.Config, f *rule.File) error
var modeFromName = map[string]emitFunc{ var modeFromName = map[string]emitFunc{
"print": printFile, "print": printFile,
...@@ -57,7 +63,8 @@ func getUpdateConfig(c *config.Config) *updateConfig { ...@@ -57,7 +63,8 @@ func getUpdateConfig(c *config.Config) *updateConfig {
} }
type updateConfigurer struct { type updateConfigurer struct {
mode string mode string
recursive bool
} }
func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) { func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
...@@ -67,33 +74,49 @@ func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *conf ...@@ -67,33 +74,49 @@ func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *conf
c.ShouldFix = cmd == "fix" c.ShouldFix = cmd == "fix"
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") 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")
fs.BoolVar(&uc.useIndex, "index", true, "when true, gazelle will build an index of libraries in the workspace for dependency resolution")
fs.BoolVar(&ucr.recursive, "r", true, "when true, gazelle will update subdirectories recursively")
fs.StringVar(&uc.patchPath, "patch", "", "when set with -mode=diff, gazelle will write to a file instead of stdout")
} }
func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
uc := getUpdateConfig(c) uc := getUpdateConfig(c)
var ok bool var ok bool
uc.emit, ok = modeFromName[ucr.mode] uc.emit, ok = modeFromName[ucr.mode]
if !ok { if !ok {
return fmt.Errorf("unrecognized emit mode: %q", ucr.mode) return fmt.Errorf("unrecognized emit mode: %q", ucr.mode)
} }
if uc.patchPath != "" && ucr.mode != "diff" {
return fmt.Errorf("-patch set but -mode is %s, not diff", ucr.mode)
}
c.Dirs = fs.Args() dirs := fs.Args()
if len(c.Dirs) == 0 { if len(dirs) == 0 {
c.Dirs = []string{"."} dirs = []string{"."}
} }
for i := range c.Dirs { uc.dirs = make([]string, len(dirs))
dir, err := filepath.Abs(c.Dirs[i]) for i := range dirs {
dir, err := filepath.Abs(dirs[i])
if err != nil { if err != nil {
return fmt.Errorf("%s: failed to find absolute path: %v", c.Dirs[i], err) return fmt.Errorf("%s: failed to find absolute path: %v", dirs[i], err)
} }
dir, err = filepath.EvalSymlinks(dir) dir, err = filepath.EvalSymlinks(dir)
if err != nil { if err != nil {
return fmt.Errorf("%s: failed to resolve symlinks: %v", c.Dirs[i], err) return fmt.Errorf("%s: failed to resolve symlinks: %v", dirs[i], err)
} }
if !isDescendingDir(dir, c.RepoRoot) { if !isDescendingDir(dir, c.RepoRoot) {
return fmt.Errorf("dir %q is not a subdirectory of repo root %q", dir, c.RepoRoot) return fmt.Errorf("dir %q is not a subdirectory of repo root %q", dir, c.RepoRoot)
} }
c.Dirs[i] = dir uc.dirs[i] = dir
}
if ucr.recursive {
uc.walkMode = walk.VisitAllUpdateSubdirsMode
} else if uc.useIndex {
uc.walkMode = walk.VisitAllUpdateDirsMode
} else {
uc.walkMode = walk.UpdateDirsMode
} }
return nil return nil
...@@ -134,8 +157,12 @@ var genericLoads = []rule.LoadInfo{ ...@@ -134,8 +157,12 @@ var genericLoads = []rule.LoadInfo{
} }
func runFixUpdate(cmd command, args []string) error { func runFixUpdate(cmd command, args []string) error {
cexts := make([]config.Configurer, 0, len(languages)+2) cexts := make([]config.Configurer, 0, len(languages)+3)
cexts = append(cexts, &config.CommonConfigurer{}, &updateConfigurer{}) cexts = append(cexts,
&config.CommonConfigurer{},
&updateConfigurer{},
&walk.Configurer{},
&resolve.Configurer{})
kindToResolver := make(map[string]resolve.Resolver) kindToResolver := make(map[string]resolve.Resolver)
kinds := make(map[string]rule.KindInfo) kinds := make(map[string]rule.KindInfo)
loads := genericLoads loads := genericLoads
...@@ -163,11 +190,12 @@ func runFixUpdate(cmd command, args []string) error { ...@@ -163,11 +190,12 @@ func runFixUpdate(cmd command, args []string) error {
// Visit all directories in the repository. // Visit all directories in the repository.
var visits []visitRecord var visits []visitRecord
walk.Walk(c, cexts, func(dir, rel string, c *config.Config, update bool, f *rule.File, subdirs, regularFiles, genFiles []string) { uc := getUpdateConfig(c)
walk.Walk(c, cexts, uc.dirs, uc.walkMode, func(dir, rel string, c *config.Config, update bool, f *rule.File, subdirs, regularFiles, genFiles []string) {
// If this file is ignored or if Gazelle was not asked to update this // If this file is ignored or if Gazelle was not asked to update this
// directory, just index the build file and move on. // directory, just index the build file and move on.
if !update { if !update {
if f != nil { if uc.useIndex && f != nil {
for _, r := range f.Rules { for _, r := range f.Rules {
ruleIndex.AddRule(c, r, f) ruleIndex.AddRule(c, r, f)
} }
...@@ -215,8 +243,6 @@ func runFixUpdate(cmd command, args []string) error { ...@@ -215,8 +243,6 @@ func runFixUpdate(cmd command, args []string) error {
} }
}) })
uc := getUpdateConfig(c)
// Finish building the index for dependency resolution. // Finish building the index for dependency resolution.
ruleIndex.Finish() ruleIndex.Finish()
...@@ -233,12 +259,16 @@ func runFixUpdate(cmd command, args []string) error { ...@@ -233,12 +259,16 @@ func runFixUpdate(cmd command, args []string) error {
// Emit merged files. // Emit merged files.
for _, v := range visits { for _, v := range visits {
merger.FixLoads(v.file, loads) merger.FixLoads(v.file, loads)
content := v.file.Format() if err := uc.emit(c, v.file); err != nil {
outputPath := findOutputPath(c, v.file)
if err := uc.emit(outputPath, content); err != nil {
log.Print(err) log.Print(err)
} }
} }
if uc.patchPath != "" {
if err := ioutil.WriteFile(uc.patchPath, uc.patchBuffer.Bytes(), 0666); err != nil {
return err
}
}
return nil return nil
} }
...@@ -338,7 +368,7 @@ func fixWorkspace(c *config.Config, workspace *rule.File, loads []rule.LoadInfo) ...@@ -338,7 +368,7 @@ func fixWorkspace(c *config.Config, workspace *rule.File, loads []rule.LoadInfo)
return nil return nil
} }
shouldFix := false shouldFix := false
for _, d := range c.Dirs { for _, d := range uc.dirs {
if d == c.RepoRoot { if d == c.RepoRoot {
shouldFix = true shouldFix = true
} }
...@@ -352,7 +382,7 @@ func fixWorkspace(c *config.Config, workspace *rule.File, loads []rule.LoadInfo) ...@@ -352,7 +382,7 @@ func fixWorkspace(c *config.Config, workspace *rule.File, loads []rule.LoadInfo)
if err := merger.CheckGazelleLoaded(workspace); err != nil { if err := merger.CheckGazelleLoaded(workspace); err != nil {
return err return err
} }
return uc.emit(workspace.Path, workspace.Format()) return uc.emit(c, workspace)
} }
func findWorkspaceName(f *rule.File) string { func findWorkspaceName(f *rule.File) string {
......
...@@ -19,11 +19,15 @@ import ( ...@@ -19,11 +19,15 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/rule"
) )
func fixFile(path string, data []byte) error { func fixFile(c *config.Config, f *rule.File) error {
if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { outPath := findOutputPath(c, f)
if err := os.MkdirAll(filepath.Dir(outPath), 0777); err != nil {
return err return err
} }
return ioutil.WriteFile(path, data, 0666) return ioutil.WriteFile(outPath, f.Format(), 0666)
} }
...@@ -56,7 +56,7 @@ func main() { ...@@ -56,7 +56,7 @@ func main() {
log.SetPrefix("gazelle: ") log.SetPrefix("gazelle: ")
log.SetFlags(0) // don't print timestamps log.SetFlags(0) // don't print timestamps
if err := run(os.Args[1:]); err != nil { if err := run(os.Args[1:]); err != nil && err != flag.ErrHelp {
log.Fatal(err) log.Fatal(err)
} }
} }
...@@ -112,7 +112,7 @@ For example: ...@@ -112,7 +112,7 @@ For example:
gazelle update -h gazelle update -h
Gazelle is under active delevopment, and its interface may change Gazelle is under active development, and its interface may change
without notice. without notice.
`) `)
......
...@@ -17,9 +17,13 @@ package main ...@@ -17,9 +17,13 @@ package main
import ( import (
"os" "os"
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/rule"
) )
func printFile(_ string, data []byte) error { func printFile(c *config.Config, f *rule.File) error {
_, err := os.Stdout.Write(data) content := f.Format()
_, err := os.Stdout.Write(content)
return err return err
} }
...@@ -36,10 +36,6 @@ import ( ...@@ -36,10 +36,6 @@ import (
// information is language-specific and is stored in Exts. This information // information is language-specific and is stored in Exts. This information
// is modified by extensions that implement Configurer. // is modified by extensions that implement Configurer.
type Config struct { type Config struct {
// Dirs is a list of absolute, canonical paths to directories where Gazelle
// should run.
Dirs []string
// RepoRoot is the absolute, canonical path to the root directory of the // RepoRoot is the absolute, canonical path to the root directory of the
// repository with all symlinks resolved. // repository with all symlinks resolved.
RepoRoot string RepoRoot string
......
...@@ -91,7 +91,14 @@ func (gl *goLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repos.Rem ...@@ -91,7 +91,14 @@ func (gl *goLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repos.Rem
log.Print(err) log.Print(err)
} }
if !deps.IsEmpty() { if !deps.IsEmpty() {
r.SetAttr("deps", deps) if r.Kind() == "go_proto_library" {
// protos may import the same library multiple times by different names,
// so we need to de-duplicate them. Protos are not platform-specific,
// so it's safe to just flatten them.
r.SetAttr("deps", deps.Flat())
} else {
r.SetAttr("deps", deps)
}
} }
} }
...@@ -115,6 +122,10 @@ func resolveGo(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache, r ...@@ -115,6 +122,10 @@ func resolveGo(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache, r
return label.NoLabel, skipImportError return label.NoLabel, skipImportError
} }
if l, ok := resolve.FindRuleWithOverride(c, resolve.ImportSpec{Lang: "go", Imp: imp}, "go"); ok {
return l, nil
}
if pc.Mode.ShouldUseKnownImports() { if pc.Mode.ShouldUseKnownImports() {
// These are commonly used libraries that depend on Well Known Types. // These are commonly used libraries that depend on Well Known Types.
// They depend on the generated versions of these protos to avoid conflicts. // They depend on the generated versions of these protos to avoid conflicts.
...@@ -129,6 +140,8 @@ func resolveGo(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache, r ...@@ -129,6 +140,8 @@ func resolveGo(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache, r
return label.New("com_github_golang_protobuf", "descriptor", "go_default_library_gen"), nil return label.New("com_github_golang_protobuf", "descriptor", "go_default_library_gen"), nil
case "github.com/golang/protobuf/ptypes": case "github.com/golang/protobuf/ptypes":
return label.New("com_github_golang_protobuf", "ptypes", "go_default_library_gen"), nil return label.New("com_github_golang_protobuf", "ptypes", "go_default_library_gen"), nil
case "google.golang.org/grpc":
return label.New("org_golang_google_grpc", "", "go_default_library"), nil
} }
if l, ok := knownGoProtoImports[imp]; ok { if l, ok := knownGoProtoImports[imp]; ok {
return l, nil return l, nil
...@@ -236,6 +249,10 @@ func resolveProto(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache ...@@ -236,6 +249,10 @@ func resolveProto(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache
return label.NoLabel, skipImportError return label.NoLabel, skipImportError
} }
if l, ok := resolve.FindRuleWithOverride(c, resolve.ImportSpec{Lang: "proto", Imp: imp}, "go"); ok {
return l, nil
}
if l, ok := knownProtoImports[imp]; ok && pc.Mode.ShouldUseKnownImports() { if l, ok := knownProtoImports[imp]; ok && pc.Mode.ShouldUseKnownImports() {
if l.Equal(from) { if l.Equal(from) {
return label.NoLabel, skipImportError return label.NoLabel, skipImportError
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"log" "log"
"path" "path"
"sort"
"strings" "strings"
"github.com/bazelbuild/bazel-gazelle/internal/config" "github.com/bazelbuild/bazel-gazelle/internal/config"
...@@ -44,7 +45,6 @@ func (_ *protoLang) Embeds(r *rule.Rule, from label.Label) []label.Label { ...@@ -44,7 +45,6 @@ func (_ *protoLang) Embeds(r *rule.Rule, from label.Label) []label.Label {
} }
func (_ *protoLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache, r *rule.Rule, from label.Label) { func (_ *protoLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache, r *rule.Rule, from label.Label) {
pc := GetProtoConfig(c)
importsRaw := r.PrivateAttr(config.GazelleImportsKey) importsRaw := r.PrivateAttr(config.GazelleImportsKey)
if importsRaw == nil { if importsRaw == nil {
// may not be set in tests. // may not be set in tests.
...@@ -52,19 +52,24 @@ func (_ *protoLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repos.R ...@@ -52,19 +52,24 @@ func (_ *protoLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repos.R
} }
imports := importsRaw.([]string) imports := importsRaw.([]string)
r.DelAttr("deps") r.DelAttr("deps")
deps := make([]string, 0, len(imports)) depSet := make(map[string]bool)
for _, imp := range imports { for _, imp := range imports {
l, err := resolveProto(pc, ix, r, imp, from) l, err := resolveProto(c, ix, r, imp, from)
if err == skipImportError { if err == skipImportError {
continue continue
} else if err != nil { } else if err != nil {
log.Print(err) log.Print(err)
} else { } else {
l = l.Rel(from.Repo, from.Pkg) l = l.Rel(from.Repo, from.Pkg)
deps = append(deps, l.String()) depSet[l.String()] = true
} }
} }
if len(deps) > 0 { if len(depSet) > 0 {
deps := make([]string, 0, len(depSet))
for dep := range depSet {
deps = append(deps, dep)
}
sort.Strings(deps)
r.SetAttr("deps", deps) r.SetAttr("deps", deps)
} }
} }
...@@ -74,11 +79,16 @@ var ( ...@@ -74,11 +79,16 @@ var (
notFoundError = errors.New("not found") notFoundError = errors.New("not found")
) )
func resolveProto(pc *ProtoConfig, ix *resolve.RuleIndex, r *rule.Rule, imp string, from label.Label) (label.Label, error) { func resolveProto(c *config.Config, ix *resolve.RuleIndex, r *rule.Rule, imp string, from label.Label) (label.Label, error) {
pc := GetProtoConfig(c)
if !strings.HasSuffix(imp, ".proto") { if !strings.HasSuffix(imp, ".proto") {
return label.NoLabel, fmt.Errorf("can't import non-proto: %q", imp) return label.NoLabel, fmt.Errorf("can't import non-proto: %q", imp)
} }
if l, ok := resolve.FindRuleWithOverride(c, resolve.ImportSpec{Imp: imp, Lang: "proto"}, "proto"); ok {
return l, nil
}
if l, ok := knownImports[imp]; ok && pc.Mode.ShouldUseKnownImports() { if l, ok := knownImports[imp]; ok && pc.Mode.ShouldUseKnownImports() {
if l.Equal(from) { if l.Equal(from) {
return label.NoLabel, skipImportError return label.NoLabel, skipImportError
......
...@@ -4,6 +4,7 @@ go_library( ...@@ -4,6 +4,7 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"dep.go", "dep.go",
"modules.go",
"remote.go", "remote.go",
"repo.go", "repo.go",
], ],
......
/* Copyright 2018 The Bazel Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package repos
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/bazelbuild/bazel-gazelle/internal/label"
)
type module struct {
Path, Version string
Main bool
}
var regexMixedVersioning = regexp.MustCompile(`^(.*?)-([0-9]{14})-([a-fA-F0-9]{12})$`)
func toRepoRule(mod module) Repo {
var tag, commit string
if gr := regexMixedVersioning.FindStringSubmatch(mod.Version); gr != nil {
commit = gr[3]
} else {
tag = strings.TrimSuffix(mod.Version, "+incompatible")
}
return Repo{
Name: label.ImportPathToBazelRepoName(mod.Path),
GoPrefix: mod.Path,
Commit: commit,
Tag: tag,
}
}
func importRepoRulesModules(filename string) (repos []Repo, err error) {
tempDir, err := copyGoModToTemp(filename)
if err != nil {
return nil, err
}
defer os.RemoveAll(tempDir)
data, err := goListModulesFn(tempDir)
if err != nil {
return nil, err
}
dec := json.NewDecoder(bytes.NewReader(data))
for dec.More() {
var mod module
if err := dec.Decode(&mod); err != nil {
return nil, err
}
if mod.Main {
continue
}
repos = append(repos, toRepoRule(mod))
}
return repos, nil
}
// goListModulesFn may be overridden by tests.
var goListModulesFn = goListModules
// goListModules invokes "go list" in a directory containing a go.mod file.
func goListModules(dir string) ([]byte, error) {
goTool := findGoTool()
cmd := exec.Command(goTool, "list", "-m", "-json", "all")
cmd.Stderr = os.Stderr
cmd.Dir = dir
data, err := cmd.Output()
return data, err
}
// copyGoModToTemp copies to given go.mod file to a temporary directory.
// go list tends to mutate go.mod files, but gazelle shouldn't do that.
func copyGoModToTemp(filename string) (tempDir string, err error) {
goModOrig, err := os.Open(filename)
if err != nil {
return "", err
}
defer goModOrig.Close()
tempDir, err = ioutil.TempDir("", "gazelle-temp-gomod")
if err != nil {
return "", err
}
goModCopy, err := os.Create(filepath.Join(tempDir, "go.mod"))
if err != nil {
os.Remove(tempDir)
return "", err
}
defer func() {
if cerr := goModCopy.Close(); err == nil && cerr != nil {
err = cerr
}
}()
_, err = io.Copy(goModCopy, goModOrig)
if err != nil {
os.RemoveAll(tempDir)
return "", err
}
return tempDir, err
}
// findGoTool attempts to locate the go executable. If GOROOT is set, we'll
// prefer the one in there; otherwise, we'll rely on PATH. If the wrapper
// script generated by the gazelle rule is invoked by Bazel, it will set
// GOROOT to the configured SDK. We don't want to rely on the host SDK in
// that situation.
func findGoTool() string {
path := "go" // rely on PATH by default
if goroot, ok := os.LookupEnv("GOROOT"); ok {
path = filepath.Join(goroot, "bin", "go")
}
if runtime.GOOS == "windows" {
path += ".exe"
}
return path
}
...@@ -61,10 +61,12 @@ type lockFileFormat int ...@@ -61,10 +61,12 @@ type lockFileFormat int
const ( const (
unknownFormat lockFileFormat = iota unknownFormat lockFileFormat = iota
depFormat depFormat
moduleFormat
) )
var lockFileParsers = map[lockFileFormat]func(string) ([]Repo, error){ var lockFileParsers = map[lockFileFormat]func(string) ([]Repo, error){
depFormat: importRepoRulesDep, depFormat: importRepoRulesDep,
moduleFormat: importRepoRulesModules,
} }
// ImportRepoRules reads the lock file of a vendoring tool and returns // ImportRepoRules reads the lock file of a vendoring tool and returns
...@@ -94,6 +96,8 @@ func getLockFileFormat(filename string) lockFileFormat { ...@@ -94,6 +96,8 @@ func getLockFileFormat(filename string) lockFileFormat {
switch filepath.Base(filename) { switch filepath.Base(filename) {
case "Gopkg.lock": case "Gopkg.lock":
return depFormat return depFormat
case "go.mod":
return moduleFormat
default: default:
return unknownFormat return unknownFormat
} }
...@@ -103,7 +107,12 @@ func getLockFileFormat(filename string) lockFileFormat { ...@@ -103,7 +107,12 @@ func getLockFileFormat(filename string) lockFileFormat {
// be written in a WORKSPACE file. // be written in a WORKSPACE file.
func GenerateRule(repo Repo) *rule.Rule { func GenerateRule(repo Repo) *rule.Rule {
r := rule.NewRule("go_repository", repo.Name) r := rule.NewRule("go_repository", repo.Name)
r.SetAttr("commit", repo.Commit) if repo.Commit != "" {
r.SetAttr("commit", repo.Commit)
}
if repo.Tag != "" {
r.SetAttr("tag", repo.Tag)
}
r.SetAttr("importpath", repo.GoPrefix) r.SetAttr("importpath", repo.GoPrefix)
if repo.Remote != "" { if repo.Remote != "" {
r.SetAttr("remote", repo.Remote) r.SetAttr("remote", repo.Remote)
......
...@@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") ...@@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = ["index.go"], srcs = [
"config.go",
"index.go",
],
importmap = "k8s.io/kubernetes/vendor/github.com/bazelbuild/bazel-gazelle/internal/resolve", importmap = "k8s.io/kubernetes/vendor/github.com/bazelbuild/bazel-gazelle/internal/resolve",
importpath = "github.com/bazelbuild/bazel-gazelle/internal/resolve", importpath = "github.com/bazelbuild/bazel-gazelle/internal/resolve",
visibility = ["//vendor/github.com/bazelbuild/bazel-gazelle:__subpackages__"], visibility = ["//vendor/github.com/bazelbuild/bazel-gazelle:__subpackages__"],
......
/* Copyright 2018 The Bazel Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resolve
import (
"flag"
"log"
"strings"
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/label"
"github.com/bazelbuild/bazel-gazelle/internal/rule"
)
// FindRuleWithOverride searches the current configuration for user-specified
// dependency resolution overrides. Overrides specified later (in configuration
// files in deeper directories, or closer to the end of the file) are
// returned first. If no override is found, label.NoLabel is returned.
func FindRuleWithOverride(c *config.Config, imp ImportSpec, lang string) (label.Label, bool) {
rc := getResolveConfig(c)
for i := len(rc.overrides) - 1; i >= 0; i-- {
o := rc.overrides[i]
if o.matches(imp, lang) {
return o.dep, true
}
}
return label.NoLabel, false
}
type overrideSpec struct {
imp ImportSpec
lang string
dep label.Label
}
func (o overrideSpec) matches(imp ImportSpec, lang string) bool {
return imp.Lang == o.imp.Lang &&
imp.Imp == o.imp.Imp &&
(o.lang == "" || o.lang == lang)
}
type resolveConfig struct {
overrides []overrideSpec
}
const resolveName = "_resolve"
func getResolveConfig(c *config.Config) *resolveConfig {
return c.Exts[resolveName].(*resolveConfig)
}
type Configurer struct{}
func (_ *Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
c.Exts[resolveName] = &resolveConfig{}
}
func (_ *Configurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil }
func (_ *Configurer) KnownDirectives() []string {
return []string{"resolve"}
}
func (_ *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
rc := getResolveConfig(c)
rcCopy := &resolveConfig{
overrides: rc.overrides[:],
}
if f != nil {
for _, d := range f.Directives {
if d.Key == "resolve" {
parts := strings.Fields(d.Value)
o := overrideSpec{}
var lbl string
if len(parts) == 3 {
o.imp.Lang = parts[0]
o.imp.Imp = parts[1]
lbl = parts[2]
} else if len(parts) == 4 {
o.imp.Lang = parts[0]
o.lang = parts[1]
o.imp.Imp = parts[2]
lbl = parts[3]
} else {
log.Printf("could not parse directive: %s\n\texpected gazelle:resolve source-language [import-language] import-string label", d.Value)
continue
}
var err error
o.dep, err = label.Parse(lbl)
if err != nil {
log.Printf("gazelle:resolve %s: %v", d.Value, err)
continue
}
o.dep = o.dep.Abs("", rel)
rcCopy.overrides = append(rcCopy.overrides, o)
}
}
}
c.Exts[resolveName] = rcCopy
}
...@@ -195,8 +195,6 @@ type FindResult struct { ...@@ -195,8 +195,6 @@ type FindResult struct {
// a matched rule. // a matched rule.
Label label.Label Label label.Label
Rule *rule.Rule
// Embeds is the transitive closure of labels for rules that the matched // Embeds is the transitive closure of labels for rules that the matched
// rule embeds. It may contains duplicates and does not include the label // rule embeds. It may contains duplicates and does not include the label
// for the rule itself. // for the rule itself.
...@@ -222,7 +220,6 @@ func (ix *RuleIndex) FindRulesByImport(imp ImportSpec, lang string) []FindResult ...@@ -222,7 +220,6 @@ func (ix *RuleIndex) FindRulesByImport(imp ImportSpec, lang string) []FindResult
} }
results = append(results, FindResult{ results = append(results, FindResult{
Label: m.label, Label: m.label,
Rule: m.rule,
Embeds: m.embeds, Embeds: m.embeds,
}) })
} }
......
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"config.go",
"files.go",
],
importmap = "k8s.io/kubernetes/vendor/github.com/bazelbuild/bazel-gazelle/internal/testtools",
importpath = "github.com/bazelbuild/bazel-gazelle/internal/testtools",
visibility = ["//vendor/github.com/bazelbuild/bazel-gazelle:__subpackages__"],
deps = [
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/config:go_default_library",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/language:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
/* Copyright 2018 The Bazel Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testtools
import (
"flag"
"testing"
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/language"
)
// NewTestConfig returns a Config used for tests in any language extension.
// cexts is a list of configuration extensions to use. langs is a list of
// language extensions to use (languages are also configuration extensions,
// but it may be convenient to keep them separate). args is a list of
// command line arguments to apply. NewTestConfig calls t.Fatal if any
// error is encountered while processing flags.
func NewTestConfig(t *testing.T, cexts []config.Configurer, langs []language.Language, args []string) *config.Config {
c := config.New()
fs := flag.NewFlagSet("test", flag.ContinueOnError)
for _, lang := range langs {
cexts = append(cexts, lang)
}
for _, cext := range cexts {
cext.RegisterFlags(fs, "update", c)
}
if err := fs.Parse(args); err != nil {
t.Fatal(err)
}
for _, cext := range cexts {
if err := cext.CheckFlags(fs, c); err != nil {
t.Fatal(err)
}
}
return c
}
/* Copyright 2018 The Bazel Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testtools
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
// FileSpec specifies the content of a test file.
type FileSpec struct {
// Path is a slash-separated path relative to the test directory. If Path
// ends with a slash, it indicates a directory should be created
// instead of a file.
Path string
// Symlink is a slash-separated path relative to the test directory. If set,
// it indicates a symbolic link should be created with this path instead of a
// file.
Symlink string
// Content is the content of the test file.
Content string
}
// CreateFiles creates a directory of test files. This is a more compact
// alternative to testdata directories. CreateFiles returns a canonical path
// to the directory and a function to call to clean up the directory
// after the test.
func CreateFiles(t *testing.T, files []FileSpec) (dir string, cleanup func()) {
dir, err := ioutil.TempDir(os.Getenv("TEST_TEMPDIR"), "gazelle_test")
if err != nil {
t.Fatal(err)
}
dir, err = filepath.EvalSymlinks(dir)
if err != nil {
t.Fatal(err)
}
for _, f := range files {
path := filepath.Join(dir, filepath.FromSlash(f.Path))
if strings.HasSuffix(f.Path, "/") {
if err := os.MkdirAll(path, 0700); err != nil {
os.RemoveAll(dir)
t.Fatal(err)
}
continue
}
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
os.RemoveAll(dir)
t.Fatal(err)
}
if f.Symlink != "" {
if err := os.Symlink(f.Symlink, path); err != nil {
t.Fatal(err)
}
continue
}
if err := ioutil.WriteFile(path, []byte(f.Content), 0600); err != nil {
os.RemoveAll(dir)
t.Fatal(err)
}
}
return dir, func() { os.RemoveAll(dir) }
}
// CheckFiles checks that files in "dir" exist and have the content specified
// in "files". Files not listed in "files" are not tested, so extra files
// are allowed.
func CheckFiles(t *testing.T, dir string, files []FileSpec) {
for _, f := range files {
path := filepath.Join(dir, f.Path)
if strings.HasSuffix(f.Path, "/") {
if st, err := os.Stat(path); err != nil {
t.Errorf("could not stat %s: %v", f.Path, err)
} else if !st.IsDir() {
t.Errorf("not a directory: %s", f.Path)
}
} else {
want := strings.TrimSpace(f.Content)
gotBytes, err := ioutil.ReadFile(filepath.Join(dir, f.Path))
if err != nil {
t.Errorf("could not read %s: %v", f.Path, err)
continue
}
got := strings.TrimSpace(string(gotBytes))
if got != want {
t.Errorf("%s: got:\n%s\nwant:\n %s", f.Path, gotBytes, f.Content)
}
}
}
}
...@@ -18,7 +18,6 @@ package walk ...@@ -18,7 +18,6 @@ package walk
import ( import (
"flag" "flag"
"path" "path"
"strings"
"github.com/bazelbuild/bazel-gazelle/internal/config" "github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/rule" "github.com/bazelbuild/bazel-gazelle/internal/rule"
...@@ -27,60 +26,55 @@ import ( ...@@ -27,60 +26,55 @@ import (
type walkConfig struct { type walkConfig struct {
excludes []string excludes []string
ignore bool ignore bool
follow []string
} }
const walkName = "_walk" const walkName = "_walk"
func getWalkConfig(c *config.Config) walkConfig { func getWalkConfig(c *config.Config) *walkConfig {
return c.Exts[walkName].(walkConfig) return c.Exts[walkName].(*walkConfig)
} }
func (wc *walkConfig) isExcluded(base string) bool { func (wc *walkConfig) isExcluded(rel, base string) bool {
f := path.Join(rel, base)
for _, x := range wc.excludes { for _, x := range wc.excludes {
if base == x { if f == x {
return true return true
} }
} }
return false return false
} }
type walkConfigurer struct{} type Configurer struct{}
func (_ *walkConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {} func (_ *Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
c.Exts[walkName] = &walkConfig{}
}
func (_ *walkConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil } func (_ *Configurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { return nil }
func (_ *walkConfigurer) KnownDirectives() []string { func (_ *Configurer) KnownDirectives() []string {
return []string{"exclude", "ignore"} return []string{"exclude", "follow", "ignore"}
} }
func (_ *walkConfigurer) Configure(c *config.Config, rel string, f *rule.File) { func (_ *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
var wc walkConfig wc := getWalkConfig(c)
if raw, ok := c.Exts[walkName]; ok { wcCopy := &walkConfig{}
wc = raw.(walkConfig) *wcCopy = *wc
wc.ignore = false wcCopy.ignore = false
if rel != "" {
prefix := path.Base(rel) + "/"
excludes := make([]string, 0, len(wc.excludes))
for _, x := range wc.excludes {
if strings.HasPrefix(x, prefix) {
excludes = append(excludes, x[len(prefix):])
}
}
wc.excludes = excludes
}
}
if f != nil { if f != nil {
for _, d := range f.Directives { for _, d := range f.Directives {
switch d.Key { switch d.Key {
case "exclude": case "exclude":
wc.excludes = append(wc.excludes, d.Value) wcCopy.excludes = append(wcCopy.excludes, path.Join(rel, d.Value))
case "follow":
wcCopy.follow = append(wcCopy.follow, path.Join(rel, d.Value))
case "ignore": case "ignore":
wc.ignore = true wcCopy.ignore = true
} }
} }
} }
c.Exts[walkName] = wc c.Exts[walkName] = wcCopy
} }
...@@ -28,6 +28,26 @@ import ( ...@@ -28,6 +28,26 @@ import (
"github.com/bazelbuild/bazel-gazelle/internal/rule" "github.com/bazelbuild/bazel-gazelle/internal/rule"
) )
// Mode determines which directories Walk visits and which directories
// should be updated.
type Mode int
const (
// In VisitAllUpdateSubdirsMode, Walk visits every directory in the
// repository. The directories given to Walk and their subdirectories are
// updated.
VisitAllUpdateSubdirsMode Mode = iota
// In VisitAllUpdateDirsMode, Walk visits every directory in the repository.
// Only the directories given to Walk are updated (not their subdirectories).
VisitAllUpdateDirsMode
// In UpdateDirsMode, Walk only visits and updates directories given to Walk.
// Build files in parent directories are read in order to produce a complete
// configuration, but the callback is not called for parent directories.
UpdateDirsMode
)
// WalkFunc is a callback called by Walk in each visited directory. // WalkFunc is a callback called by Walk in each visited directory.
// //
// dir is the absolute file system path to the directory being visited. // dir is the absolute file system path to the directory being visited.
...@@ -57,11 +77,10 @@ type WalkFunc func(dir, rel string, c *config.Config, update bool, f *rule.File, ...@@ -57,11 +77,10 @@ type WalkFunc func(dir, rel string, c *config.Config, update bool, f *rule.File,
// //
// Walk calls the Configure method on each configuration extension in cexts // Walk calls the Configure method on each configuration extension in cexts
// in each directory in pre-order, whether a build file is present in the // in each directory in pre-order, whether a build file is present in the
// directory or not. // directory or not. cexts must contain a walk.Configurer.
// //
// Walk calls the callback wf in post-order. // Walk calls the callback wf in post-order.
func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) { func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, wf WalkFunc) {
cexts = append(cexts, &walkConfigurer{})
knownDirectives := make(map[string]bool) knownDirectives := make(map[string]bool)
for _, cext := range cexts { for _, cext := range cexts {
for _, d := range cext.KnownDirectives() { for _, d := range cext.KnownDirectives() {
...@@ -69,17 +88,14 @@ func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) { ...@@ -69,17 +88,14 @@ func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) {
} }
} }
updateRels := buildUpdateRels(c.RepoRoot, c.Dirs) symlinks := symlinkResolver{visited: []string{c.RepoRoot}}
symlinks := symlinkResolver{root: c.RepoRoot, visited: []string{c.RepoRoot}}
updateRels := buildUpdateRelMap(c.RepoRoot, dirs)
var visit func(*config.Config, string, string, bool) var visit func(*config.Config, string, string, bool)
visit = func(c *config.Config, dir, rel string, isUpdateDir bool) { visit = func(c *config.Config, dir, rel string, updateParent bool) {
haveError := false haveError := false
if !isUpdateDir {
isUpdateDir = shouldUpdateDir(rel, updateRels)
}
// TODO: OPT: ReadDir stats all the files, which is slow. We just care about // TODO: OPT: ReadDir stats all the files, which is slow. We just care about
// names and modes, so we should use something like // names and modes, so we should use something like
// golang.org/x/tools/internal/fastwalk to speed this up. // golang.org/x/tools/internal/fastwalk to speed this up.
...@@ -102,10 +118,10 @@ func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) { ...@@ -102,10 +118,10 @@ func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) {
for _, fi := range files { for _, fi := range files {
base := fi.Name() base := fi.Name()
switch { switch {
case base == "" || base[0] == '.' || base[0] == '_' || wc.isExcluded(base): case base == "" || base[0] == '.' || base[0] == '_' || wc.isExcluded(rel, base):
continue continue
case fi.IsDir() || fi.Mode()&os.ModeSymlink != 0 && symlinks.follow(dir, base): case fi.IsDir() || fi.Mode()&os.ModeSymlink != 0 && symlinks.follow(c, dir, rel, base):
subdirs = append(subdirs, base) subdirs = append(subdirs, base)
default: default:
...@@ -113,45 +129,75 @@ func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) { ...@@ -113,45 +129,75 @@ func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) {
} }
} }
shouldUpdate := shouldUpdate(rel, mode, updateParent, updateRels)
for _, sub := range subdirs { for _, sub := range subdirs {
visit(c, filepath.Join(dir, sub), path.Join(rel, sub), isUpdateDir) if subRel := path.Join(rel, sub); shouldVisit(subRel, mode, updateRels) {
visit(c, filepath.Join(dir, sub), subRel, shouldUpdate)
}
} }
genFiles := findGenFiles(wc, f) update := !haveError && !wc.ignore && shouldUpdate
update := !haveError && isUpdateDir && !wc.ignore if shouldCall(rel, mode, updateRels) {
wf(dir, rel, c, update, f, subdirs, regularFiles, genFiles) genFiles := findGenFiles(wc, f)
wf(dir, rel, c, update, f, subdirs, regularFiles, genFiles)
}
} }
visit(c, c.RepoRoot, "", false) visit(c, c.RepoRoot, "", false)
} }
// buildUpdateRels builds a list of relative paths from the repository root // buildUpdateRelMap builds a table of prefixes, used to determine which
// directory (passed as an absolute file path) to directories that Gazelle // directories to update and visit.
// may update. The relative paths are slash-separated. "" represents the //
// root directory itself. // root and dirs must be absolute, canonical file paths. Each entry in dirs
func buildUpdateRels(root string, dirs []string) []string { // must be a subdirectory of root. The caller is responsible for checking this.
var updateRels []string //
// buildUpdateRelMap returns a map from slash-separated paths relative to the
// root directory ("" for the root itself) to a boolean indicating whether
// the directory should be updated.
func buildUpdateRelMap(root string, dirs []string) map[string]bool {
relMap := make(map[string]bool)
for _, dir := range dirs { for _, dir := range dirs {
rel, err := filepath.Rel(root, dir) rel, _ := filepath.Rel(root, dir)
if err != nil {
// This should have been verified when c was built.
log.Panicf("%s: not a subdirectory of repository root %q", dir, root)
}
rel = filepath.ToSlash(rel) rel = filepath.ToSlash(rel)
if rel == "." || rel == "/" { if rel == "." {
rel = "" rel = ""
} }
updateRels = append(updateRels, rel)
i := 0
for {
next := strings.IndexByte(rel[i:], '/') + i
if next-i < 0 {
relMap[rel] = true
break
}
prefix := rel[:next]
relMap[prefix] = relMap[prefix] // set to false if not present
i = next + 1
}
} }
return updateRels return relMap
} }
func shouldUpdateDir(rel string, updateRels []string) bool { // shouldCall returns true if Walk should call the callback in the
for _, r := range updateRels { // directory rel.
if pathtools.HasPrefix(rel, r) { func shouldCall(rel string, mode Mode, updateRels map[string]bool) bool {
return true return mode != UpdateDirsMode || updateRels[rel]
} }
// shouldUpdate returns true if Walk should pass true to the callback's update
// parameter in the directory rel. This indicates the build file should be
// updated.
func shouldUpdate(rel string, mode Mode, updateParent bool, updateRels map[string]bool) bool {
return mode == VisitAllUpdateSubdirsMode && updateParent || updateRels[rel]
}
// shouldVisit returns true if Walk should visit the subdirectory rel.
func shouldVisit(rel string, mode Mode, updateRels map[string]bool) bool {
if mode != UpdateDirsMode {
return true
} }
return false _, ok := updateRels[rel]
return ok
} }
func loadBuildFile(c *config.Config, pkg, dir string, files []os.FileInfo) (*rule.File, error) { func loadBuildFile(c *config.Config, pkg, dir string, files []os.FileInfo) (*rule.File, error) {
...@@ -189,7 +235,7 @@ func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *co ...@@ -189,7 +235,7 @@ func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *co
return c return c
} }
func findGenFiles(wc walkConfig, f *rule.File) []string { func findGenFiles(wc *walkConfig, f *rule.File) []string {
if f == nil { if f == nil {
return nil return nil
} }
...@@ -206,7 +252,7 @@ func findGenFiles(wc walkConfig, f *rule.File) []string { ...@@ -206,7 +252,7 @@ func findGenFiles(wc walkConfig, f *rule.File) []string {
var genFiles []string var genFiles []string
for _, s := range strs { for _, s := range strs {
if !wc.isExcluded(s) { if !wc.isExcluded(f.Pkg, s) {
genFiles = append(genFiles, s) genFiles = append(genFiles, s)
} }
} }
...@@ -214,17 +260,26 @@ func findGenFiles(wc walkConfig, f *rule.File) []string { ...@@ -214,17 +260,26 @@ func findGenFiles(wc walkConfig, f *rule.File) []string {
} }
type symlinkResolver struct { type symlinkResolver struct {
root string
visited []string visited []string
} }
// Decide if symlink dir/base should be followed. // Decide if symlink dir/base should be followed.
func (r *symlinkResolver) follow(dir, base string) bool { func (r *symlinkResolver) follow(c *config.Config, dir, rel, base string) bool {
if dir == r.root && strings.HasPrefix(base, "bazel-") { if dir == c.RepoRoot && strings.HasPrefix(base, "bazel-") {
// Links such as bazel-<workspace>, bazel-out, bazel-genfiles are created by // Links such as bazel-<workspace>, bazel-out, bazel-genfiles are created by
// Bazel to point to internal build directories. // Bazel to point to internal build directories.
return false return false
} }
// See if the user has explicitly directed us to follow the link.
wc := getWalkConfig(c)
linkRel := path.Join(rel, base)
for _, follow := range wc.follow {
if linkRel == follow {
return true
}
}
// See if the symlink points to a tree that has been already visited. // See if the symlink points to a tree that has been already visited.
fullpath := filepath.Join(dir, base) fullpath := filepath.Join(dir, base)
dest, err := filepath.EvalSymlinks(fullpath) dest, err := filepath.EvalSymlinks(fullpath)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment