Experimental: read and write build files in alternate directories (#286)
* The flags -experimental_{read,write}_build_files_dir may now be used
to read and write build files to alternate directories, which may be
outside of the repository root.
* When a build file is read from an alternate directory, the build
file in the source directory is ignored.
* When a build file is written to an alternate directory, any existing
build file in that directory is replaced. The build file in the
source directory is not updated.
diff --git a/cmd/gazelle/diff.go b/cmd/gazelle/diff.go
index a5cd6d5..4e74fea 100644
--- a/cmd/gazelle/diff.go
+++ b/cmd/gazelle/diff.go
@@ -20,21 +20,18 @@
"io/ioutil"
"os"
"os/exec"
-
- "github.com/bazelbuild/bazel-gazelle/internal/config"
- bzl "github.com/bazelbuild/buildtools/build"
+ "path/filepath"
)
-func diffFile(c *config.Config, file *bzl.File, path string) error {
- oldContents, err := ioutil.ReadFile(file.Path)
+func diffFile(path string, newContents []byte) error {
+ oldContents, err := ioutil.ReadFile(path)
if err != nil {
oldContents = nil
}
- newContents := bzl.Format(file)
if bytes.Equal(oldContents, newContents) {
return nil
}
- f, err := ioutil.TempFile("", c.DefaultBuildFileName())
+ f, err := ioutil.TempFile("", filepath.Base(path))
if err != nil {
return err
}
diff --git a/cmd/gazelle/fix-update.go b/cmd/gazelle/fix-update.go
index 60d15e2..a44994c 100644
--- a/cmd/gazelle/fix-update.go
+++ b/cmd/gazelle/fix-update.go
@@ -18,6 +18,7 @@
import (
"flag"
"fmt"
+ "io/ioutil"
"log"
"os"
"path/filepath"
@@ -31,19 +32,17 @@
"github.com/bazelbuild/bazel-gazelle/internal/resolve"
"github.com/bazelbuild/bazel-gazelle/internal/rule"
"github.com/bazelbuild/bazel-gazelle/internal/walk"
- bzl "github.com/bazelbuild/buildtools/build"
)
// updateConfig holds configuration information needed to run the fix and
// 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
- outDir, outSuffix string
- repos []repos.Repo
+ emit emitFunc
+ repos []repos.Repo
}
-type emitFunc func(*config.Config, *bzl.File, string) error
+type emitFunc func(path string, data []byte) error
var modeFromName = map[string]emitFunc{
"print": printFile,
@@ -68,8 +67,6 @@
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(&uc.outDir, "experimental_out_dir", "", "write build files to an alternate directory tree")
- fs.StringVar(&uc.outSuffix, "experimental_out_suffix", "", "extra suffix appended to build file names. Only used if -experimental_out_dir is also set.")
}
func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
@@ -198,7 +195,7 @@
// Insert or merge rules into the build file.
if f == nil {
- f = rule.EmptyFile(filepath.Join(dir, c.DefaultBuildFileName()))
+ f = rule.EmptyFile(filepath.Join(dir, c.DefaultBuildFileName()), rel)
for _, r := range gen {
r.Insert(f)
}
@@ -236,15 +233,9 @@
// Emit merged files.
for _, v := range visits {
merger.FixLoads(v.file, loads)
- v.file.Sync()
- bzl.Rewrite(v.file.File, nil) // have buildifier 'format' our rules.
-
- path := v.file.Path
- if uc.outDir != "" {
- stem := filepath.Base(v.file.Path) + uc.outSuffix
- path = filepath.Join(uc.outDir, v.pkgRel, stem)
- }
- if err := uc.emit(c, v.file.File, path); err != nil {
+ content := v.file.Format()
+ outputPath := findOutputPath(c, v.file)
+ if err := uc.emit(outputPath, content); err != nil {
log.Print(err)
}
}
@@ -269,7 +260,7 @@
if err := fs.Parse(args); err != nil {
if err == flag.ErrHelp {
fixUpdateUsage(fs)
- os.Exit(0)
+ return nil, err
}
// flag already prints the error; don't print it again.
log.Fatal("Try -help for more information.")
@@ -283,7 +274,7 @@
uc := getUpdateConfig(c)
workspacePath := filepath.Join(c.RepoRoot, "WORKSPACE")
- if workspace, err := rule.LoadFile(workspacePath); err != nil {
+ if workspace, err := rule.LoadFile(workspacePath, ""); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
@@ -361,8 +352,7 @@
if err := merger.CheckGazelleLoaded(workspace); err != nil {
return err
}
- workspace.Sync()
- return uc.emit(c, workspace.File, workspace.Path)
+ return uc.emit(workspace.Path, workspace.Format())
}
func findWorkspaceName(f *rule.File) string {
@@ -384,3 +374,25 @@
}
return !strings.HasPrefix(rel, "..")
}
+
+func findOutputPath(c *config.Config, f *rule.File) string {
+ if c.ReadBuildFilesDir == "" && c.WriteBuildFilesDir == "" {
+ return f.Path
+ }
+ baseDir := c.WriteBuildFilesDir
+ if c.WriteBuildFilesDir == "" {
+ baseDir = c.RepoRoot
+ }
+ outputDir := filepath.Join(baseDir, filepath.FromSlash(f.Pkg))
+ defaultOutputPath := filepath.Join(outputDir, c.DefaultBuildFileName())
+ files, err := ioutil.ReadDir(outputDir)
+ if err != nil {
+ // Ignore error. Directory probably doesn't exist.
+ return defaultOutputPath
+ }
+ outputPath := rule.MatchBuildFileName(outputDir, c.ValidBuildFileNames, files)
+ if outputPath == "" {
+ return defaultOutputPath
+ }
+ return outputPath
+}
diff --git a/cmd/gazelle/fix.go b/cmd/gazelle/fix.go
index af25cef..7a1c388 100644
--- a/cmd/gazelle/fix.go
+++ b/cmd/gazelle/fix.go
@@ -19,17 +19,11 @@
"io/ioutil"
"os"
"path/filepath"
-
- "github.com/bazelbuild/bazel-gazelle/internal/config"
- bzl "github.com/bazelbuild/buildtools/build"
)
-func fixFile(c *config.Config, file *bzl.File, path string) error {
+func fixFile(path string, data []byte) error {
if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
return err
}
- if err := ioutil.WriteFile(path, bzl.Format(file), 0666); err != nil {
- return err
- }
- return nil
+ return ioutil.WriteFile(path, data, 0666)
}
diff --git a/cmd/gazelle/fix_test.go b/cmd/gazelle/fix_test.go
index b26e62a..c4fe25e 100644
--- a/cmd/gazelle/fix_test.go
+++ b/cmd/gazelle/fix_test.go
@@ -20,9 +20,9 @@
"io/ioutil"
"os"
"path/filepath"
+ "strings"
"testing"
- "github.com/bazelbuild/bazel-gazelle/internal/config"
bzl "github.com/bazelbuild/buildtools/build"
)
@@ -63,9 +63,7 @@
},
},
}
- c := &config.Config{}
-
- if err := fixFile(c, stubFile, stubFile.Path); err != nil {
+ if err := fixFile(stubFile.Path, bzl.Format(stubFile)); err != nil {
t.Errorf("fixFile(%#v) failed with %v; want success", stubFile, err)
return
}
@@ -134,3 +132,157 @@
t.Errorf("BUILD.bazel should not exist")
}
}
+
+func TestReadWriteDir(t *testing.T) {
+ buildInFile := fileSpec{
+ path: "in/BUILD.in",
+ content: `
+go_binary(
+ name = "hello",
+ pure = "on",
+)
+`,
+ }
+ buildSrcFile := fileSpec{
+ path: "src/BUILD.bazel",
+ content: `# src build file`,
+ }
+ oldFiles := []fileSpec{
+ buildInFile,
+ buildSrcFile,
+ {
+ path: "src/hello.go",
+ content: `
+package main
+
+func main() {}
+`,
+ }, {
+ path: "out/BUILD",
+ content: `this should get replaced`,
+ },
+ }
+
+ for _, tc := range []struct {
+ desc string
+ args []string
+ want []fileSpec
+ }{
+ {
+ desc: "read",
+ args: []string{
+ "-repo_root={{dir}}/src",
+ "-experimental_read_build_files_dir={{dir}}/in",
+ "-build_file_name=BUILD.bazel,BUILD,BUILD.in",
+ "-go_prefix=example.com/repo",
+ "{{dir}}/src",
+ },
+ want: []fileSpec{
+ buildInFile,
+ {
+ path: "src/BUILD.bazel",
+ content: `
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_binary(
+ name = "hello",
+ embed = [":go_default_library"],
+ pure = "on",
+ visibility = ["//visibility:public"],
+)
+
+go_library(
+ name = "go_default_library",
+ srcs = ["hello.go"],
+ importpath = "example.com/repo",
+ visibility = ["//visibility:private"],
+)
+`,
+ },
+ },
+ }, {
+ desc: "write",
+ args: []string{
+ "-repo_root={{dir}}/src",
+ "-experimental_write_build_files_dir={{dir}}/out",
+ "-build_file_name=BUILD.bazel,BUILD,BUILD.in",
+ "-go_prefix=example.com/repo",
+ "{{dir}}/src",
+ },
+ want: []fileSpec{
+ buildInFile,
+ buildSrcFile,
+ {
+ path: "out/BUILD",
+ content: `
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+# src build file
+
+go_library(
+ name = "go_default_library",
+ srcs = ["hello.go"],
+ importpath = "example.com/repo",
+ visibility = ["//visibility:private"],
+)
+
+go_binary(
+ name = "repo",
+ embed = [":go_default_library"],
+ visibility = ["//visibility:public"],
+)
+`,
+ },
+ },
+ }, {
+ desc: "read_and_write",
+ args: []string{
+ "-repo_root={{dir}}/src",
+ "-experimental_read_build_files_dir={{dir}}/in",
+ "-experimental_write_build_files_dir={{dir}}/out",
+ "-build_file_name=BUILD.bazel,BUILD,BUILD.in",
+ "-go_prefix=example.com/repo",
+ "{{dir}}/src",
+ },
+ want: []fileSpec{
+ buildInFile,
+ {
+ path: "out/BUILD",
+ content: `
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_binary(
+ name = "hello",
+ embed = [":go_default_library"],
+ pure = "on",
+ visibility = ["//visibility:public"],
+)
+
+go_library(
+ name = "go_default_library",
+ srcs = ["hello.go"],
+ importpath = "example.com/repo",
+ visibility = ["//visibility:private"],
+)
+`,
+ },
+ },
+ },
+ } {
+ t.Run(tc.desc, func(t *testing.T) {
+ dir, err := createFiles(oldFiles)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+ replacer := strings.NewReplacer("{{dir}}", dir, "/", string(os.PathSeparator))
+ for i := range tc.args {
+ tc.args[i] = replacer.Replace(tc.args[i])
+ }
+ if err := run(tc.args); err != nil {
+ t.Error(err)
+ }
+ checkFiles(t, dir, tc.want)
+ })
+ }
+}
diff --git a/cmd/gazelle/gazelle.go b/cmd/gazelle/gazelle.go
index 2de75e4..fd44442 100644
--- a/cmd/gazelle/gazelle.go
+++ b/cmd/gazelle/gazelle.go
@@ -18,6 +18,7 @@
package main
import (
+ "flag"
"fmt"
"log"
"os"
@@ -76,7 +77,7 @@
case fixCmd, updateCmd:
return runFixUpdate(cmd, args)
case helpCmd:
- help()
+ return help()
case updateReposCmd:
return updateRepos(args)
default:
@@ -85,7 +86,7 @@
return nil
}
-func help() {
+func help() error {
fmt.Fprint(os.Stderr, `usage: gazelle <command> [args...]
Gazelle is a BUILD file generator for Go projects. It can create new BUILD files
@@ -115,4 +116,5 @@
without notice.
`)
+ return flag.ErrHelp
}
diff --git a/cmd/gazelle/integration_test.go b/cmd/gazelle/integration_test.go
index ae4b005..ae01e8f 100644
--- a/cmd/gazelle/integration_test.go
+++ b/cmd/gazelle/integration_test.go
@@ -21,6 +21,7 @@
import (
"bytes"
+ "flag"
"io/ioutil"
"log"
"os"
@@ -83,19 +84,15 @@
t.Errorf("not a directory: %s", f.path)
}
} else {
- want := f.content
- if len(want) > 0 && want[0] == '\n' {
- // Strip leading newline, added for readability.
- want = want[1:]
- }
+ 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 := string(gotBytes)
+ got := strings.TrimSpace(string(gotBytes))
if got != want {
- t.Errorf("%s: got %s ; want %s", f.path, got, f.content)
+ t.Errorf("%s: got:\n%s\nwant:\n %s", f.path, gotBytes, f.content)
}
}
}
@@ -124,8 +121,10 @@
{"update-repos", "-h"},
} {
t.Run(args[0], func(t *testing.T) {
- if err := runGazelle(".", args); err != nil {
- t.Error(err)
+ if err := runGazelle(".", args); err == nil {
+ t.Errorf("%s: got success, want flag.ErrHelp", args[0])
+ } else if err != flag.ErrHelp {
+ t.Errorf("%s: got %v, want flag.ErrHelp", args[0], err)
}
})
}
diff --git a/cmd/gazelle/print.go b/cmd/gazelle/print.go
index 63ddb77..e7dfe13 100644
--- a/cmd/gazelle/print.go
+++ b/cmd/gazelle/print.go
@@ -17,12 +17,9 @@
import (
"os"
-
- "github.com/bazelbuild/bazel-gazelle/internal/config"
- bzl "github.com/bazelbuild/buildtools/build"
)
-func printFile(c *config.Config, f *bzl.File, _ string) error {
- _, err := os.Stdout.Write(bzl.Format(f))
+func printFile(_ string, data []byte) error {
+ _, err := os.Stdout.Write(data)
return err
}
diff --git a/cmd/gazelle/update-repos.go b/cmd/gazelle/update-repos.go
index 8c13002..2f38374 100644
--- a/cmd/gazelle/update-repos.go
+++ b/cmd/gazelle/update-repos.go
@@ -93,7 +93,7 @@
uc := getUpdateReposConfig(c)
workspacePath := filepath.Join(c.RepoRoot, "WORKSPACE")
- f, err := rule.LoadFile(workspacePath)
+ f, err := rule.LoadFile(workspacePath, "")
if err != nil {
return fmt.Errorf("error loading %q: %v", workspacePath, err)
}
@@ -106,7 +106,7 @@
if err := merger.CheckGazelleLoaded(f); err != nil {
return err
}
- if err := f.Save(); err != nil {
+ if err := f.Save(f.Path); err != nil {
return fmt.Errorf("error writing %q: %v", f.Path, err)
}
return nil
@@ -124,7 +124,7 @@
if err := fs.Parse(args); err != nil {
if err == flag.ErrHelp {
updateReposUsage(fs)
- os.Exit(0)
+ return nil, err
}
// flag already prints the error; don't print it again.
return nil, errors.New("Try -help for more information")
diff --git a/internal/config/config.go b/internal/config/config.go
index 3b275d3..d959d78 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -47,6 +47,14 @@
// RepoName is the name of the repository.
RepoName string
+ // ReadBuildFilesDir is the absolute path to a directory where
+ // build files should be read from instead of RepoRoot.
+ ReadBuildFilesDir string
+
+ // WriteBuildFilesDir is the absolute path to a directory where
+ // build files should be written to instead of RepoRoot.
+ WriteBuildFilesDir string
+
// ValidBuildFileNames is a list of base names that are considered valid
// build files. Some repositories may have files named "BUILD" that are not
// used by Bazel and should be ignored. Must contain at least one string.
@@ -134,12 +142,14 @@
// CommonConfigurer handles language-agnostic command-line flags and directives,
// i.e., those that apply to Config itself and not to Config.Exts.
type CommonConfigurer struct {
- repoRoot, buildFileNames string
+ repoRoot, buildFileNames, readBuildFilesDir, writeBuildFilesDir string
}
func (cc *CommonConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *Config) {
fs.StringVar(&cc.repoRoot, "repo_root", "", "path to a directory which corresponds to go_prefix, otherwise gazelle searches for it.")
fs.StringVar(&cc.buildFileNames, "build_file_name", strings.Join(DefaultValidBuildFileNames, ","), "comma-separated list of valid build file names.\nThe first element of the list is the name of output build files to generate.")
+ fs.StringVar(&cc.readBuildFilesDir, "experimental_read_build_files_dir", "", "path to a directory where build files should be read from (instead of -repo_root)")
+ fs.StringVar(&cc.writeBuildFilesDir, "experimental_write_build_files_dir", "", "path to a directory where build files should be written to (instead of -repo_root)")
}
func (cc *CommonConfigurer) CheckFlags(fs *flag.FlagSet, c *Config) error {
@@ -159,6 +169,19 @@
return fmt.Errorf("%s: failed to resolve symlinks: %v", cc.repoRoot, err)
}
c.ValidBuildFileNames = strings.Split(cc.buildFileNames, ",")
+ if cc.readBuildFilesDir != "" {
+ c.ReadBuildFilesDir, err = filepath.Abs(cc.readBuildFilesDir)
+ if err != nil {
+ return fmt.Errorf("%s: failed to find absolute path of -read_build_files_dir: %v", cc.readBuildFilesDir, err)
+ }
+ }
+ if cc.writeBuildFilesDir != "" {
+ c.WriteBuildFilesDir, err = filepath.Abs(cc.writeBuildFilesDir)
+ if err != nil {
+ return fmt.Errorf("%s: failed to find absolute path of -write_build_files_dir: %v", cc.writeBuildFilesDir, err)
+ }
+ }
+
return nil
}
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index 41baef0..b532f91 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -66,7 +66,7 @@
c := New()
cc := &CommonConfigurer{}
buildData := []byte(`# gazelle:build_file_name x,y`)
- f, err := rule.LoadData("test", buildData)
+ f, err := rule.LoadData("test", "", buildData)
if err != nil {
t.Fatal(err)
}
diff --git a/internal/language/go/config_test.go b/internal/language/go/config_test.go
index e7d9c1b..0ebbfc0 100644
--- a/internal/language/go/config_test.go
+++ b/internal/language/go/config_test.go
@@ -69,7 +69,7 @@
# gazelle:importmap_prefix x
# gazelle:prefix y
`)
- f, err := rule.LoadData(filepath.FromSlash("test/BUILD.bazel"), content)
+ f, err := rule.LoadData(filepath.FromSlash("test/BUILD.bazel"), "test", content)
if err != nil {
t.Fatal(err)
}
@@ -188,7 +188,7 @@
var f *rule.File
if tc.content != "" {
var err error
- f, err = rule.LoadData(path.Join(tc.rel, "BUILD.bazel"), []byte(tc.content))
+ f, err = rule.LoadData(path.Join(tc.rel, "BUILD.bazel"), tc.rel, []byte(tc.content))
if err != nil {
t.Fatal(err)
}
@@ -243,7 +243,7 @@
},
} {
t.Run(tc.desc, func(t *testing.T) {
- f, err := rule.LoadData("BUILD.bazel", []byte(tc.content))
+ f, err := rule.LoadData("BUILD.bazel", "", []byte(tc.content))
if err != nil {
t.Fatal(err)
}
diff --git a/internal/language/go/fix_test.go b/internal/language/go/fix_test.go
index b34b78a..c6b0b6d 100644
--- a/internal/language/go/fix_test.go
+++ b/internal/language/go/fix_test.go
@@ -632,7 +632,7 @@
}
func testFix(t *testing.T, tc fixTestCase, fix func(*rule.File)) {
- f, err := rule.LoadData("old", []byte(tc.old))
+ f, err := rule.LoadData("old", "", []byte(tc.old))
if err != nil {
t.Fatalf("%s: parse error: %v", tc.desc, err)
}
diff --git a/internal/language/go/generate_test.go b/internal/language/go/generate_test.go
index 9685e17..5e57ddb 100644
--- a/internal/language/go/generate_test.go
+++ b/internal/language/go/generate_test.go
@@ -64,7 +64,7 @@
// there's no test.
return
}
- f := rule.EmptyFile("test")
+ f := rule.EmptyFile("test", "")
for _, r := range gen {
r.Insert(f)
}
@@ -93,7 +93,7 @@
if len(gen) > 0 {
t.Errorf("got %d generated rules; want 0", len(gen))
}
- f := rule.EmptyFile("test")
+ f := rule.EmptyFile("test", "")
for _, r := range empty {
r.Insert(f)
}
@@ -138,7 +138,7 @@
srcs = ["dead.proto"],
)
`)
- old, err := rule.LoadData("BUILD.bazel", oldContent)
+ old, err := rule.LoadData("BUILD.bazel", "", oldContent)
if err != nil {
t.Fatal(err)
}
@@ -147,7 +147,7 @@
es, _ := lang.GenerateRules(c, "./foo", "foo", old, nil, nil, nil, empty, nil)
empty = append(empty, es...)
}
- f := rule.EmptyFile("test")
+ f := rule.EmptyFile("test", "")
for _, r := range empty {
r.Insert(f)
}
diff --git a/internal/language/go/resolve_test.go b/internal/language/go/resolve_test.go
index f31484f..e720905 100644
--- a/internal/language/go/resolve_test.go
+++ b/internal/language/go/resolve_test.go
@@ -739,7 +739,7 @@
for _, bf := range tc.index {
buildPath := filepath.Join(filepath.FromSlash(bf.rel), "BUILD.bazel")
- f, err := rule.LoadData(buildPath, []byte(bf.content))
+ f, err := rule.LoadData(buildPath, bf.rel, []byte(bf.content))
if err != nil {
t.Fatal(err)
}
@@ -748,7 +748,7 @@
}
}
buildPath := filepath.Join(filepath.FromSlash(tc.old.rel), "BUILD.bazel")
- f, err := rule.LoadData(buildPath, []byte(tc.old.content))
+ f, err := rule.LoadData(buildPath, tc.old.rel, []byte(tc.old.content))
if err != nil {
t.Fatal(err)
}
@@ -814,7 +814,7 @@
],
)
`)
- f, err := rule.LoadData("BUILD.bazel", oldContent)
+ f, err := rule.LoadData("BUILD.bazel", "", oldContent)
if err != nil {
t.Fatal(err)
}
diff --git a/internal/language/proto/generate_test.go b/internal/language/proto/generate_test.go
index 7a7ceb4..183d732 100644
--- a/internal/language/proto/generate_test.go
+++ b/internal/language/proto/generate_test.go
@@ -50,7 +50,7 @@
if len(empty) > 0 {
t.Errorf("got %d empty rules; want 0", len(empty))
}
- f := rule.EmptyFile("test")
+ f := rule.EmptyFile("test", "")
for _, r := range gen {
r.Insert(f)
}
@@ -94,7 +94,7 @@
srcs = COMPLICATED_SRCS,
)
`)
- old, err := rule.LoadData("BUILD.bazel", oldContent)
+ old, err := rule.LoadData("BUILD.bazel", "", oldContent)
if err != nil {
t.Fatal(err)
}
@@ -103,7 +103,7 @@
if len(gen) > 0 {
t.Errorf("got %d generated rules; want 0", len(gen))
}
- f := rule.EmptyFile("test")
+ f := rule.EmptyFile("test", "")
for _, r := range empty {
r.Insert(f)
}
diff --git a/internal/language/proto/resolve.go b/internal/language/proto/resolve.go
index 0267277..0d25ad3 100644
--- a/internal/language/proto/resolve.go
+++ b/internal/language/proto/resolve.go
@@ -30,7 +30,7 @@
)
func (_ *protoLang) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec {
- rel := f.Rel(c.RepoRoot)
+ rel := f.Pkg
srcs := r.AttrStrings("srcs")
imports := make([]resolve.ImportSpec, len(srcs))
for i, src := range srcs {
diff --git a/internal/language/proto/resolve_test.go b/internal/language/proto/resolve_test.go
index aee061f..490ca04 100644
--- a/internal/language/proto/resolve_test.go
+++ b/internal/language/proto/resolve_test.go
@@ -179,7 +179,7 @@
ix := resolve.NewRuleIndex(map[string]resolve.Resolver{"proto_library": lang})
rc := (*repos.RemoteCache)(nil)
for _, bf := range tc.index {
- f, err := rule.LoadData(filepath.Join(bf.rel, "BUILD.bazel"), []byte(bf.content))
+ f, err := rule.LoadData(filepath.Join(bf.rel, "BUILD.bazel"), bf.rel, []byte(bf.content))
if err != nil {
t.Fatal(err)
}
@@ -187,7 +187,7 @@
ix.AddRule(c, r, f)
}
}
- f, err := rule.LoadData("test/BUILD.bazel", []byte(tc.old))
+ f, err := rule.LoadData("test/BUILD.bazel", "test", []byte(tc.old))
if err != nil {
t.Fatal(err)
}
diff --git a/internal/merger/merger.go b/internal/merger/merger.go
index e695ea6..16cc227 100644
--- a/internal/merger/merger.go
+++ b/internal/merger/merger.go
@@ -56,6 +56,9 @@
// Merge empty rules into the file and delete any rules which become empty.
for _, emptyRule := range emptyRules {
if oldRule, _ := match(oldFile.Rules, emptyRule, kinds[emptyRule.Kind()]); oldRule != nil {
+ if oldRule.ShouldKeep() {
+ continue
+ }
rule.MergeRules(emptyRule, oldRule, getMergeAttrs(emptyRule), oldFile.Path)
if oldRule.IsEmpty(kinds[oldRule.Kind()]) {
oldRule.Delete()
diff --git a/internal/merger/merger_test.go b/internal/merger/merger_test.go
index 84de20a..20f8f74 100644
--- a/internal/merger/merger_test.go
+++ b/internal/merger/merger_test.go
@@ -870,15 +870,15 @@
func TestMergeFile(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
- genFile, err := rule.LoadData("current", []byte(tc.current))
+ genFile, err := rule.LoadData("current", "", []byte(tc.current))
if err != nil {
t.Fatalf("%s: %v", tc.desc, err)
}
- f, err := rule.LoadData("previous", []byte(tc.previous))
+ f, err := rule.LoadData("previous", "", []byte(tc.previous))
if err != nil {
t.Fatalf("%s: %v", tc.desc, err)
}
- emptyFile, err := rule.LoadData("empty", []byte(tc.empty))
+ emptyFile, err := rule.LoadData("empty", "", []byte(tc.empty))
if err != nil {
t.Fatalf("%s: %v", tc.desc, err)
}
@@ -970,11 +970,11 @@
},
} {
t.Run(tc.desc, func(t *testing.T) {
- genFile, err := rule.LoadData("gen", []byte(tc.gen))
+ genFile, err := rule.LoadData("gen", "", []byte(tc.gen))
if err != nil {
t.Fatal(err)
}
- oldFile, err := rule.LoadData("old", []byte(tc.old))
+ oldFile, err := rule.LoadData("old", "", []byte(tc.old))
if err != nil {
t.Fatal(err)
}
@@ -987,7 +987,7 @@
} else if tc.wantError {
t.Error("unexpected success")
} else if got == nil && tc.wantIndex >= 0 {
- t.Error("got nil; want index %d", tc.wantIndex)
+ t.Errorf("got nil; want index %d", tc.wantIndex)
} else if got != nil && got.Index() != tc.wantIndex {
t.Fatalf("got index %d ; want %d", got.Index(), tc.wantIndex)
}
diff --git a/internal/repos/import_test.go b/internal/repos/import_test.go
index d16876f..21afa12 100644
--- a/internal/repos/import_test.go
+++ b/internal/repos/import_test.go
@@ -76,7 +76,7 @@
if err != nil {
t.Fatal(err)
}
- f := rule.EmptyFile("test")
+ f := rule.EmptyFile("test", "")
for _, r := range rules {
r.Insert(f)
}
diff --git a/internal/repos/repo_test.go b/internal/repos/repo_test.go
index 7646f88..8474277 100644
--- a/internal/repos/repo_test.go
+++ b/internal/repos/repo_test.go
@@ -33,7 +33,7 @@
Commit: "123456",
}
r := GenerateRule(repo)
- f := rule.EmptyFile("test")
+ f := rule.EmptyFile("test", "")
r.Insert(f)
got := strings.TrimSpace(string(f.Format()))
want := `go_repository(
@@ -110,7 +110,7 @@
},
} {
t.Run(tc.desc, func(t *testing.T) {
- workspace, err := rule.LoadData("WORKSPACE", []byte(tc.workspace))
+ workspace, err := rule.LoadData("WORKSPACE", "", []byte(tc.workspace))
if err != nil {
t.Fatal(err)
}
diff --git a/internal/resolve/index.go b/internal/resolve/index.go
index 5414766..211eba6 100644
--- a/internal/resolve/index.go
+++ b/internal/resolve/index.go
@@ -119,10 +119,9 @@
return
}
- rel := f.Rel(c.RepoRoot)
record := &ruleRecord{
rule: r,
- label: label.New(c.RepoName, rel, r.Name()),
+ label: label.New(c.RepoName, f.Pkg, r.Name()),
importedAs: imps,
}
if _, ok := ix.labelMap[record.label]; ok {
diff --git a/internal/rule/merge.go b/internal/rule/merge.go
index 3a246f7..0bc30c7 100644
--- a/internal/rule/merge.go
+++ b/internal/rule/merge.go
@@ -42,7 +42,7 @@
// a "# keep" comment will be dropped. If the attribute is empty afterward,
// it will be deleted.
func MergeRules(src, dst *Rule, mergeable map[string]bool, filename string) {
- if ShouldKeep(dst.call) {
+ if dst.ShouldKeep() {
return
}
@@ -270,7 +270,7 @@
// fails because the expression is not understood, an error is returned,
// and neither rule is modified.
func SquashRules(src, dst *Rule, filename string) error {
- if ShouldKeep(dst.call) {
+ if dst.ShouldKeep() {
return nil
}
diff --git a/internal/rule/rule.go b/internal/rule/rule.go
index d5a6601..8424359 100644
--- a/internal/rule/rule.go
+++ b/internal/rule/rule.go
@@ -27,7 +27,7 @@
import (
"io/ioutil"
- "log"
+ "os"
"path/filepath"
"sort"
"strings"
@@ -46,6 +46,9 @@
// may modify this, but editing is not complete until Sync() is called.
File *bzl.File
+ // Pkg is the Bazel package this build file defines.
+ Pkg string
+
// Path is the file system path to the build file (same as File.Path).
Path string
@@ -63,10 +66,11 @@
}
// EmptyFile creates a File wrapped around an empty syntax tree.
-func EmptyFile(path string) *File {
+func EmptyFile(path, pkg string) *File {
return &File{
File: &bzl.File{Path: path},
Path: path,
+ Pkg: pkg,
}
}
@@ -76,30 +80,31 @@
//
// This function returns I/O and parse errors without modification. It's safe
// to use os.IsNotExist and similar predicates.
-func LoadFile(path string) (*File, error) {
+func LoadFile(path, pkg string) (*File, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
- return LoadData(path, data)
+ return LoadData(path, pkg, data)
}
// LoadData parses a build file from a byte slice and scans it for rules and
// load statements. The syntax tree within the returned File will be modified
// by editing methods.
-func LoadData(path string, data []byte) (*File, error) {
+func LoadData(path, pkg string, data []byte) (*File, error) {
ast, err := bzl.Parse(path, data)
if err != nil {
return nil, err
}
- return ScanAST(ast), nil
+ return ScanAST(pkg, ast), nil
}
// ScanAST creates a File wrapped around the given syntax tree. This tree
// will be modified by editing methods.
-func ScanAST(bzlFile *bzl.File) *File {
+func ScanAST(pkg string, bzlFile *bzl.File) *File {
f := &File{
File: bzlFile,
+ Pkg: pkg,
Path: bzlFile.Path,
}
for i, stmt := range f.File.Stmt {
@@ -125,18 +130,19 @@
return f
}
-// Rel returns the slash-separated relative path from the given absolute path to
-// the directory containing this file. If the file is in the root directory, Rel
-// returns "". This string may be used as a Bazel package name.
-func (f *File) Rel(root string) string {
- rel, err := filepath.Rel(root, filepath.Dir(f.Path))
- if err != nil {
- log.Panicf("%s is not a parent of %s", root, f.Path)
+// MatchBuildFileName looks for a file in files that has a name from names.
+// If there is at least one matching file, a path will be returned by joining
+// dir and the first matching name. If there are no matching files, the
+// empty string is returned.
+func MatchBuildFileName(dir string, names []string, files []os.FileInfo) string {
+ for _, name := range names {
+ for _, fi := range files {
+ if fi.Name() == name && !fi.IsDir() {
+ return filepath.Join(dir, name)
+ }
+ }
}
- if rel == "." {
- rel = ""
- }
- return filepath.ToSlash(rel)
+ return ""
}
// Sync writes all changes back to the wrapped syntax tree. This should be
@@ -215,12 +221,11 @@
return bzl.Format(f.File)
}
-// Save writes the build file to disk at the same path it was loaded from.
-// This method calls Sync internally.
-func (f *File) Save() error {
+// Save writes the build file to disk. This method calls Sync internally.
+func (f *File) Save(path string) error {
f.Sync()
data := bzl.Format(f.File)
- return ioutil.WriteFile(f.Path, data, 0666)
+ return ioutil.WriteFile(path, data, 0666)
}
type stmt struct {
diff --git a/internal/rule/rule_test.go b/internal/rule/rule_test.go
index 78e259e..9f9d901 100644
--- a/internal/rule/rule_test.go
+++ b/internal/rule/rule_test.go
@@ -37,7 +37,7 @@
y_library(name = "bar")
`)
- f, err := LoadData("old", old)
+ f, err := LoadData("old", "", old)
if err != nil {
t.Fatal(err)
}
@@ -84,7 +84,7 @@
x_library(name = "bar")
`)
- f, err := LoadData("old", old)
+ f, err := LoadData("old", "", old)
if err != nil {
t.Fatal(err)
}
@@ -103,7 +103,7 @@
}
func TestSymbolsReturnsKeys(t *testing.T) {
- f, err := LoadData("load", []byte(`load("a.bzl", "y", z = "a")`))
+ f, err := LoadData("load", "", []byte(`load("a.bzl", "y", z = "a")`))
if err != nil {
t.Fatal(err)
}
@@ -158,7 +158,7 @@
},
} {
t.Run(tc.desc, func(t *testing.T) {
- f, err := LoadData(tc.desc, []byte(tc.src))
+ f, err := LoadData(tc.desc, "", []byte(tc.src))
if err != nil {
t.Fatal(err)
}
diff --git a/internal/walk/walk.go b/internal/walk/walk.go
index 32688a0..896a793 100644
--- a/internal/walk/walk.go
+++ b/internal/walk/walk.go
@@ -16,7 +16,6 @@
package walk
import (
- "fmt"
"io/ioutil"
"log"
"os"
@@ -90,7 +89,7 @@
return
}
- f, err := loadBuildFile(dir, files, c.ValidBuildFileNames)
+ f, err := loadBuildFile(c, rel, dir, files)
if err != nil {
log.Print(err)
haveError = true
@@ -155,25 +154,22 @@
return false
}
-func loadBuildFile(dir string, files []os.FileInfo, buildFileNames []string) (*rule.File, error) {
- var f *rule.File
- for _, base := range buildFileNames {
- for _, fi := range files {
- if fi.Name() != base || fi.IsDir() {
- continue
- }
- if f != nil {
- return f, fmt.Errorf("in directory %s, multiple Bazel files are present: %s, %s", dir, filepath.Base(f.Path), base)
- }
- var err error
- f, err = rule.LoadFile(filepath.Join(dir, base))
- if err != nil {
- return nil, err
- }
- return f, nil
+func loadBuildFile(c *config.Config, pkg, dir string, files []os.FileInfo) (*rule.File, error) {
+ var err error
+ readDir := dir
+ readFiles := files
+ if c.ReadBuildFilesDir != "" {
+ readDir = filepath.Join(c.ReadBuildFilesDir, filepath.FromSlash(pkg))
+ readFiles, err = ioutil.ReadDir(readDir)
+ if err != nil {
+ return nil, err
}
}
- return f, nil
+ path := rule.MatchBuildFileName(readDir, c.ValidBuildFileNames, readFiles)
+ if path == "" {
+ return nil, nil
+ }
+ return rule.LoadFile(path, pkg)
}
func configure(cexts []config.Configurer, knownDirectives map[string]bool, c *config.Config, rel string, f *rule.File) *config.Config {