Skip to content

Commit

Permalink
internal/repo: use declared repository names in external resolver (#85)
Browse files Browse the repository at this point in the history
Gazelle now reads go_repository rules from WORKSPACE when starting
up. When externalResolver encounters an import path that is covered by
one of the declared external repositories, it will emit a label using
that repository's name. This allows custom repository names.

Fixes #13
  • Loading branch information
jayconrod authored Jan 11, 2018
1 parent 9d0cfaa commit ac55d35
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 86 deletions.
31 changes: 29 additions & 2 deletions cmd/gazelle/fix-update.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/bazelbuild/bazel-gazelle/internal/label"
"github.com/bazelbuild/bazel-gazelle/internal/merger"
"github.com/bazelbuild/bazel-gazelle/internal/packages"
"github.com/bazelbuild/bazel-gazelle/internal/repos"
"github.com/bazelbuild/bazel-gazelle/internal/resolve"
"github.com/bazelbuild/bazel-gazelle/internal/rules"
"github.com/bazelbuild/bazel-gazelle/internal/wspace"
Expand All @@ -42,6 +43,7 @@ type updateConfig struct {
c *config.Config
emit emitFunc
outDir, outSuffix string
repos []repos.Repo
}

type emitFunc func(*config.Config, *bf.File, string) error
Expand Down Expand Up @@ -150,7 +152,7 @@ func runFixUpdate(cmd command, args []string) error {
ruleIndex.Finish()

// Resolve dependencies.
resolver := resolve.NewResolver(uc.c, l, ruleIndex)
resolver := resolve.NewResolver(uc.c, l, ruleIndex, uc.repos)
for i := range visits {
for j := range visits[i].rules {
visits[i].rules[j] = resolver.ResolveRule(visits[i].rules[j], visits[i].pkgRel)
Expand Down Expand Up @@ -282,7 +284,32 @@ func newFixUpdateConfiguration(cmd command, args []string) (*updateConfig, error
uc.outDir = *outDir
uc.outSuffix = *outSuffix

uc.c.KnownImports = append(uc.c.KnownImports, knownImports...)
workspacePath := filepath.Join(uc.c.RepoRoot, "WORKSPACE")
workspaceContent, err := ioutil.ReadFile(workspacePath)
if os.IsNotExist(err) {
workspaceContent = nil
} else if err != nil {
return nil, err
}
workspace, err := bf.Parse(workspacePath, workspaceContent)
if err != nil {
return nil, err
}
uc.repos = repos.ListRepositories(workspace)
repoPrefixes := make(map[string]bool)
for _, r := range uc.repos {
repoPrefixes[r.GoPrefix] = true
}
for _, imp := range knownImports {
if repoPrefixes[imp] {
continue
}
repo := repos.Repo{
Name: label.ImportPathToBazelRepoName(imp),
GoPrefix: imp,
}
uc.repos = append(uc.repos, repo)
}

return uc, nil
}
Expand Down
49 changes: 49 additions & 0 deletions cmd/gazelle/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,55 @@ go_library(
})
}

func TestCustomRepoNames(t *testing.T) {
files := []fileSpec{
{
path: "WORKSPACE",
content: `
go_repository(
name = "custom_repo",
importpath = "example.com/bar",
commit = "123456",
)
`,
}, {
path: "foo.go",
content: `
package foo
import _ "example.com/bar"
`,
},
}
dir, err := createFiles(files)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)

args := []string{"-go_prefix", "example.com/foo"}
if err := runGazelle(dir, args); err != nil {
t.Fatal(err)
}

checkFiles(t, dir, []fileSpec{
{
path: "BUILD.bazel",
content: `
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["foo.go"],
importpath = "example.com/foo",
visibility = ["//visibility:public"],
deps = ["@custom_repo//:go_default_library"],
)
`,
},
})
}

func TestImportReposFromDep(t *testing.T) {
files := []fileSpec{
{
Expand Down
3 changes: 0 additions & 3 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ type Config struct {
// DepMode determines how imports outside of GoPrefix are resolved.
DepMode DependencyMode

// KnownImports is a list of imports to add to the external resolver cache.
KnownImports []string

// ProtoMode determines how rules are generated for protos.
ProtoMode ProtoMode
}
Expand Down
14 changes: 7 additions & 7 deletions internal/repos/dep.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type depProject struct {
Source string `toml:"source"`
}

func importRepoRulesDep(filename string) ([]repo, error) {
func importRepoRulesDep(filename string) ([]Repo, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
Expand All @@ -42,13 +42,13 @@ func importRepoRulesDep(filename string) ([]repo, error) {
return nil, err
}

var repos []repo
var repos []Repo
for _, p := range file.Projects {
repos = append(repos, repo{
name: label.ImportPathToBazelRepoName(p.Name),
importPath: p.Name,
commit: p.Revision,
remote: p.Source,
repos = append(repos, Repo{
Name: label.ImportPathToBazelRepoName(p.Name),
GoPrefix: p.Name,
Commit: p.Revision,
Remote: p.Source,
})
}
return repos, nil
Expand Down
92 changes: 78 additions & 14 deletions internal/repos/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,31 @@ import (
bf "github.com/bazelbuild/buildtools/build"
)

type repo struct {
name string
importPath string
commit string
remote string
// Repo describes an external repository rule declared in a Bazel
// WORKSPACE file.
type Repo struct {
// Name is the value of the "name" attribute of the repository rule.
Name string

// GoPrefix is the portion of the Go import path for the root of this
// repository. Usually the same as Remote.
GoPrefix string

// Commit is the revision at which a repository is checked out (for example,
// a Git commit id).
Commit string

// Tag is the name of the version at which a repository is checked out.
Tag string

// Remote is the URL the repository can be cloned or checked out from.
Remote string
}

type byName []repo
type byName []Repo

func (s byName) Len() int { return len(s) }
func (s byName) Less(i, j int) bool { return s[i].name < s[j].name }
func (s byName) Less(i, j int) bool { return s[i].Name < s[j].Name }
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

type lockFileFormat int
Expand All @@ -46,7 +60,7 @@ const (
depFormat
)

var lockFileParsers = map[lockFileFormat]func(string) ([]repo, error){
var lockFileParsers = map[lockFileFormat]func(string) ([]Repo, error){
depFormat: importRepoRulesDep,
}

Expand Down Expand Up @@ -82,14 +96,14 @@ func getLockFileFormat(filename string) lockFileFormat {
}
}

func generateRepoRule(repo repo) bf.Expr {
func generateRepoRule(repo Repo) bf.Expr {
attrs := []rules.KeyValue{
{Key: "name", Value: repo.name},
{Key: "commit", Value: repo.commit},
{Key: "importpath", Value: repo.importPath},
{Key: "name", Value: repo.Name},
{Key: "commit", Value: repo.Commit},
{Key: "importpath", Value: repo.GoPrefix},
}
if repo.remote != "" {
attrs = append(attrs, rules.KeyValue{Key: "remote", Value: repo.remote})
if repo.Remote != "" {
attrs = append(attrs, rules.KeyValue{Key: "remote", Value: repo.Remote})
}
return rules.NewRule("go_repository", attrs)
}
Expand Down Expand Up @@ -120,3 +134,53 @@ func FindExternalRepo(repoRoot, name string) (string, error) {
}
return cleanPath, nil
}

// ListRepositories extracts metadata about repositories declared in a
// WORKSPACE file.
//
// The set of repositories returned is necessarily incomplete, since we don't
// evaluate the file, and repositories may be declared in macros in other files.
func ListRepositories(workspace *bf.File) []Repo {
var repos []Repo
for _, e := range workspace.Stmt {
call, ok := e.(*bf.CallExpr)
if !ok {
continue
}
r := bf.Rule{Call: call}
name := r.Name()
if name == "" {
continue
}
var repo Repo
switch r.Kind() {
case "go_repository":
// TODO(jayconrod): extract other fields needed by go_repository.
// Currently, we don't use the result of this function to produce new
// go_repository rules, so it doesn't matter.
goPrefix := r.AttrString("importpath")
revision := r.AttrString("commit")
remote := r.AttrString("remote")
if goPrefix == "" {
continue
}
repo = Repo{
Name: name,
GoPrefix: goPrefix,
Commit: revision,
Remote: remote,
}

// TODO(jayconrod): infer from {new_,}git_repository, {new_,}http_archive

default:
continue
}
repos = append(repos, repo)
}

// TODO(jayconrod): look for directives that describe repositories that
// aren't declared in the top-level of WORKSPACE (e.g., behind a macro).

return repos
}
47 changes: 43 additions & 4 deletions internal/repos/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"

bf "github.com/bazelbuild/buildtools/build"
)

func TestGenerateRepoRules(t *testing.T) {
repo := repo{
name: "org_golang_x_tools",
importPath: "golang.org/x/tools",
commit: "123456",
repo := Repo{
Name: "org_golang_x_tools",
GoPrefix: "golang.org/x/tools",
Commit: "123456",
}
got := bf.FormatString(generateRepoRule(repo))
want := `go_repository(
Expand Down Expand Up @@ -77,3 +78,41 @@ func TestFindExternalRepo(t *testing.T) {
t.Errorf("got %q ; want %q", got, externalPath)
}
}

func TestListRepositories(t *testing.T) {
for _, tc := range []struct {
desc, workspace string
want []Repo
}{
{
desc: "empty",
want: nil,
}, {
desc: "go_repository",
workspace: `
go_repository(
name = "custom_repo",
commit = "123456",
remote = "https://example.com/repo",
importpath = "example.com/repo",
)
`,
want: []Repo{{
Name: "custom_repo",
GoPrefix: "example.com/repo",
Remote: "https://example.com/repo",
Commit: "123456",
}},
},
} {
t.Run(tc.desc, func(t *testing.T) {
workspace, err := bf.Parse("WORKSPACE", []byte(tc.workspace))
if err != nil {
t.Fatal(err)
}
if got := ListRepositories(workspace); !reflect.DeepEqual(got, tc.want) {
t.Errorf("got %#v ; want %#v", got, tc.want)
}
})
}
}
5 changes: 3 additions & 2 deletions internal/resolve/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/bazelbuild/bazel-gazelle/internal/config"
"github.com/bazelbuild/bazel-gazelle/internal/label"
"github.com/bazelbuild/bazel-gazelle/internal/pathtools"
"github.com/bazelbuild/bazel-gazelle/internal/repos"
bf "github.com/bazelbuild/buildtools/build"
)

Expand All @@ -44,11 +45,11 @@ type nonlocalResolver interface {
resolve(imp string) (label.Label, error)
}

func NewResolver(c *config.Config, l *label.Labeler, ix *RuleIndex) *Resolver {
func NewResolver(c *config.Config, l *label.Labeler, ix *RuleIndex, repos []repos.Repo) *Resolver {
var e nonlocalResolver
switch c.DepMode {
case config.ExternalMode:
e = newExternalResolver(l, c.KnownImports)
e = newExternalResolver(l, repos)
case config.VendorMode:
e = newVendoredResolver(l)
}
Expand Down
Loading

0 comments on commit ac55d35

Please sign in to comment.