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

Update to gazelle 0.15.0

parent 3778646d
......@@ -361,78 +361,83 @@
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/cmd/gazelle",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/config",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/flag",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/label",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/language",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/language/go",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/language/proto",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/merger",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/pathtools",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/repos",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/resolve",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/rule",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/testtools",
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/version",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/walk",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/bazel-gazelle/internal/wspace",
"Comment": "0.14.0",
"Rev": "6a1b93cc9b1c7e55e7d05a6d324bcf9d87ea3ab1"
"Comment": "0.15.0",
"Rev": "c728ce9f663e2bff26361ba5978ec5c9e6816a3c"
},
{
"ImportPath": "github.com/bazelbuild/buildtools/build",
......
......@@ -60,6 +60,7 @@ filegroup(
"//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/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/walk:all-srcs",
"//vendor/github.com/bazelbuild/bazel-gazelle/internal/wspace:all-srcs",
......
......@@ -28,6 +28,7 @@ go_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/walk:go_default_library",
"//vendor/github.com/pmezard/go-difflib/difflib:go_default_library",
],
)
......
......@@ -16,37 +16,68 @@ limitations under the License.
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"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 {
oldContents, err := ioutil.ReadFile(path)
func diffFile(c *config.Config, f *rule.File) error {
rel, err := filepath.Rel(c.RepoRoot, f.Path)
if err != nil {
oldContents = nil
return fmt.Errorf("error getting old path for file %q: %v", f.Path, err)
}
if bytes.Equal(oldContents, newContents) {
return nil
rel = filepath.ToSlash(rel)
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 {
return err
if oldContent, err := ioutil.ReadFile(f.Path); err != nil && !os.IsNotExist(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())
if err := ioutil.WriteFile(f.Name(), newContents, 0666); err != nil {
return err
newContent := f.Format()
diff.B = difflib.SplitLines(string(newContent))
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())
cmd.Stdout = os.Stdout
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
if err := difflib.WriteUnifiedDiff(out, diff); err != nil {
return fmt.Errorf("error diffing %s: %v", f.Path, err)
}
return err
return nil
}
......@@ -16,6 +16,7 @@ limitations under the License.
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
......@@ -38,11 +39,16 @@ import (
// update commands. This includes everything in config.Config, but it also
// includes some additional fields that aren't relevant to other packages.
type updateConfig struct {
emit emitFunc
repos []repos.Repo
dirs []string
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{
"print": printFile,
......@@ -57,7 +63,8 @@ func getUpdateConfig(c *config.Config) *updateConfig {
}
type updateConfigurer struct {
mode string
mode string
recursive bool
}
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
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.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 {
uc := getUpdateConfig(c)
var ok bool
uc.emit, ok = modeFromName[ucr.mode]
if !ok {
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()
if len(c.Dirs) == 0 {
c.Dirs = []string{"."}
dirs := fs.Args()
if len(dirs) == 0 {
dirs = []string{"."}
}
for i := range c.Dirs {
dir, err := filepath.Abs(c.Dirs[i])
uc.dirs = make([]string, len(dirs))
for i := range dirs {
dir, err := filepath.Abs(dirs[i])
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)
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) {
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
......@@ -134,8 +157,12 @@ var genericLoads = []rule.LoadInfo{
}
func runFixUpdate(cmd command, args []string) error {
cexts := make([]config.Configurer, 0, len(languages)+2)
cexts = append(cexts, &config.CommonConfigurer{}, &updateConfigurer{})
cexts := make([]config.Configurer, 0, len(languages)+3)
cexts = append(cexts,
&config.CommonConfigurer{},
&updateConfigurer{},
&walk.Configurer{},
&resolve.Configurer{})
kindToResolver := make(map[string]resolve.Resolver)
kinds := make(map[string]rule.KindInfo)
loads := genericLoads
......@@ -163,11 +190,12 @@ func runFixUpdate(cmd command, args []string) error {
// Visit all directories in the repository.
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
// directory, just index the build file and move on.
if !update {
if f != nil {
if uc.useIndex && f != nil {
for _, r := range f.Rules {
ruleIndex.AddRule(c, r, f)
}
......@@ -215,8 +243,6 @@ func runFixUpdate(cmd command, args []string) error {
}
})
uc := getUpdateConfig(c)
// Finish building the index for dependency resolution.
ruleIndex.Finish()
......@@ -233,12 +259,16 @@ func runFixUpdate(cmd command, args []string) error {
// Emit merged files.
for _, v := range visits {
merger.FixLoads(v.file, loads)
content := v.file.Format()
outputPath := findOutputPath(c, v.file)
if err := uc.emit(outputPath, content); err != nil {
if err := uc.emit(c, v.file); err != nil {
log.Print(err)
}
}
if uc.patchPath != "" {
if err := ioutil.WriteFile(uc.patchPath, uc.patchBuffer.Bytes(), 0666); err != nil {
return err
}
}
return nil
}
......@@ -338,7 +368,7 @@ func fixWorkspace(c *config.Config, workspace *rule.File, loads []rule.LoadInfo)
return nil
}
shouldFix := false
for _, d := range c.Dirs {
for _, d := range uc.dirs {
if d == c.RepoRoot {
shouldFix = true
}
......@@ -352,7 +382,7 @@ func fixWorkspace(c *config.Config, workspace *rule.File, loads []rule.LoadInfo)
if err := merger.CheckGazelleLoaded(workspace); err != nil {
return err
}
return uc.emit(workspace.Path, workspace.Format())
return uc.emit(c, workspace)
}
func findWorkspaceName(f *rule.File) string {
......
......@@ -19,11 +19,15 @@ import (
"io/ioutil"
"os"
"path/filepath"
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/rule"
)
func fixFile(path string, data []byte) error {
if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
func fixFile(c *config.Config, f *rule.File) error {
outPath := findOutputPath(c, f)
if err := os.MkdirAll(filepath.Dir(outPath), 0777); err != nil {
return err
}
return ioutil.WriteFile(path, data, 0666)
return ioutil.WriteFile(outPath, f.Format(), 0666)
}
......@@ -56,7 +56,7 @@ func main() {
log.SetPrefix("gazelle: ")
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)
}
}
......@@ -112,7 +112,7 @@ For example:
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.
`)
......
......@@ -17,9 +17,13 @@ package main
import (
"os"
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/rule"
)
func printFile(_ string, data []byte) error {
_, err := os.Stdout.Write(data)
func printFile(c *config.Config, f *rule.File) error {
content := f.Format()
_, err := os.Stdout.Write(content)
return err
}
......@@ -36,10 +36,6 @@ import (
// information is language-specific and is stored in Exts. This information
// is modified by extensions that implement Configurer.
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
// repository with all symlinks resolved.
RepoRoot string
......
......@@ -91,7 +91,14 @@ func (gl *goLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repos.Rem
log.Print(err)
}
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
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() {
// These are commonly used libraries that depend on Well Known Types.
// 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
return label.New("com_github_golang_protobuf", "descriptor", "go_default_library_gen"), nil
case "github.com/golang/protobuf/ptypes":
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 {
return l, nil
......@@ -236,6 +249,10 @@ func resolveProto(c *config.Config, ix *resolve.RuleIndex, rc *repos.RemoteCache
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.Equal(from) {
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 (
"fmt"
"log"
"path"
"sort"
"strings"
"github.com/bazelbuild/bazel-gazelle/internal/config"
......@@ -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) {
pc := GetProtoConfig(c)
importsRaw := r.PrivateAttr(config.GazelleImportsKey)
if importsRaw == nil {
// may not be set in tests.
......@@ -52,19 +52,24 @@ func (_ *protoLang) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repos.R
}
imports := importsRaw.([]string)
r.DelAttr("deps")
deps := make([]string, 0, len(imports))
depSet := make(map[string]bool)
for _, imp := range imports {
l, err := resolveProto(pc, ix, r, imp, from)
l, err := resolveProto(c, ix, r, imp, from)
if err == skipImportError {
continue
} else if err != nil {
log.Print(err)
} else {
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)
}
}
......@@ -74,11 +79,16 @@ var (
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") {
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.Equal(from) {
return label.NoLabel, skipImportError
......
......@@ -4,6 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"dep.go",
"modules.go",
"remote.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
const (
unknownFormat lockFileFormat = iota
depFormat
moduleFormat
)
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
......@@ -94,6 +96,8 @@ func getLockFileFormat(filename string) lockFileFormat {
switch filepath.Base(filename) {
case "Gopkg.lock":
return depFormat
case "go.mod":
return moduleFormat
default:
return unknownFormat
}
......@@ -103,7 +107,12 @@ func getLockFileFormat(filename string) lockFileFormat {
// be written in a WORKSPACE file.
func GenerateRule(repo Repo) *rule.Rule {
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)
if repo.Remote != "" {
r.SetAttr("remote", repo.Remote)
......
......@@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_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",
importpath = "github.com/bazelbuild/bazel-gazelle/internal/resolve",
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 {
// a matched rule.
Label label.Label
Rule *rule.Rule
// Embeds is the transitive closure of labels for rules that the matched
// rule embeds. It may contains duplicates and does not include the label
// for the rule itself.
......@@ -222,7 +220,6 @@ func (ix *RuleIndex) FindRulesByImport(imp ImportSpec, lang string) []FindResult
}
results = append(results, FindResult{
Label: m.label,
Rule: m.rule,
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
import (
"flag"
"path"
"strings"
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/rule"
......@@ -27,60 +26,55 @@ import (
type walkConfig struct {
excludes []string
ignore bool
follow []string
}
const walkName = "_walk"
func getWalkConfig(c *config.Config) walkConfig {
return c.Exts[walkName].(walkConfig)
func getWalkConfig(c *config.Config) *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 {
if base == x {
if f == x {
return true
}
}
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 {
return []string{"exclude", "ignore"}
func (_ *Configurer) KnownDirectives() []string {
return []string{"exclude", "follow", "ignore"}
}
func (_ *walkConfigurer) Configure(c *config.Config, rel string, f *rule.File) {
var wc walkConfig
if raw, ok := c.Exts[walkName]; ok {
wc = raw.(walkConfig)
wc.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
}
}
func (_ *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
wc := getWalkConfig(c)
wcCopy := &walkConfig{}
*wcCopy = *wc
wcCopy.ignore = false
if f != nil {
for _, d := range f.Directives {
switch d.Key {
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":
wc.ignore = true
wcCopy.ignore = true
}
}
}
c.Exts[walkName] = wc
c.Exts[walkName] = wcCopy
}
......@@ -28,6 +28,26 @@ import (
"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.
//
// 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,
//
// 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
// directory or not.
// directory or not. cexts must contain a walk.Configurer.
//
// Walk calls the callback wf in post-order.
func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) {
cexts = append(cexts, &walkConfigurer{})
func Walk(c *config.Config, cexts []config.Configurer, dirs []string, mode Mode, wf WalkFunc) {
knownDirectives := make(map[string]bool)
for _, cext := range cexts {
for _, d := range cext.KnownDirectives() {
......@@ -69,17 +88,14 @@ func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) {
}
}
updateRels := buildUpdateRels(c.RepoRoot, c.Dirs)
symlinks := symlinkResolver{root: c.RepoRoot, visited: []string{c.RepoRoot}}
symlinks := symlinkResolver{visited: []string{c.RepoRoot}}
updateRels := buildUpdateRelMap(c.RepoRoot, dirs)
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
if !isUpdateDir {
isUpdateDir = shouldUpdateDir(rel, updateRels)
}
// TODO: OPT: ReadDir stats all the files, which is slow. We just care about
// names and modes, so we should use something like
// golang.org/x/tools/internal/fastwalk to speed this up.
......@@ -102,10 +118,10 @@ func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) {
for _, fi := range files {
base := fi.Name()
switch {
case base == "" || base[0] == '.' || base[0] == '_' || wc.isExcluded(base):
case base == "" || base[0] == '.' || base[0] == '_' || wc.isExcluded(rel, base):
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)
default:
......@@ -113,45 +129,75 @@ func Walk(c *config.Config, cexts []config.Configurer, wf WalkFunc) {
}
}
shouldUpdate := shouldUpdate(rel, mode, updateParent, updateRels)
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 && isUpdateDir && !wc.ignore
wf(dir, rel, c, update, f, subdirs, regularFiles, genFiles)
update := !haveError && !wc.ignore && shouldUpdate
if shouldCall(rel, mode, updateRels) {
genFiles := findGenFiles(wc, f)
wf(dir, rel, c, update, f, subdirs, regularFiles, genFiles)
}
}
visit(c, c.RepoRoot, "", false)
}
// buildUpdateRels builds a list of relative paths from the repository root
// directory (passed as an absolute file path) to directories that Gazelle
// may update. The relative paths are slash-separated. "" represents the
// root directory itself.
func buildUpdateRels(root string, dirs []string) []string {
var updateRels []string
// buildUpdateRelMap builds a table of prefixes, used to determine which
// directories to update and visit.
//
// root and dirs must be absolute, canonical file paths. Each entry in dirs
// must be a subdirectory of root. The caller is responsible for checking this.
//
// 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 {
rel, err := 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.Rel(root, dir)
rel = filepath.ToSlash(rel)
if rel == "." || rel == "/" {
if 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 {
for _, r := range updateRels {
if pathtools.HasPrefix(rel, r) {
return true
}
// shouldCall returns true if Walk should call the callback in the
// directory rel.
func shouldCall(rel string, mode Mode, updateRels map[string]bool) bool {
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) {
......@@ -189,7 +235,7 @@ func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *co
return c
}
func findGenFiles(wc walkConfig, f *rule.File) []string {
func findGenFiles(wc *walkConfig, f *rule.File) []string {
if f == nil {
return nil
}
......@@ -206,7 +252,7 @@ func findGenFiles(wc walkConfig, f *rule.File) []string {
var genFiles []string
for _, s := range strs {
if !wc.isExcluded(s) {
if !wc.isExcluded(f.Pkg, s) {
genFiles = append(genFiles, s)
}
}
......@@ -214,17 +260,26 @@ func findGenFiles(wc walkConfig, f *rule.File) []string {
}
type symlinkResolver struct {
root string
visited []string
}
// Decide if symlink dir/base should be followed.
func (r *symlinkResolver) follow(dir, base string) bool {
if dir == r.root && strings.HasPrefix(base, "bazel-") {
func (r *symlinkResolver) follow(c *config.Config, dir, rel, base string) bool {
if dir == c.RepoRoot && strings.HasPrefix(base, "bazel-") {
// Links such as bazel-<workspace>, bazel-out, bazel-genfiles are created by
// Bazel to point to internal build directories.
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.
fullpath := filepath.Join(dir, base)
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