Skip to content

Commit

Permalink
Gazelle: support for proto rules
Browse files Browse the repository at this point in the history
Gazelle now generates proto_library, go_proto_library, and
go_grpc_library rules. proto_library will contain .proto files in a
directory. go_{proto,grpc}_library are buildable libraries, linked
with the proto_library. A go_library rules embeds the
go_{proto,grpc}_library and includes an extra .go sources (but .pb.go
files are excluded).

The new proto rule generation is turned on by default in packages that
don't have existing proto rules. If there are existing rules, proto
rule generation will either follow previous behavior (if
go_proto_library.bzl is loaded) or will be disabled (if
go_proto_library is loaded from some other repo). The proto mode can
be selected explicitly with the -proto command line flag or with the
'# gazelle:proto' directive. Legacy rules can be migrated with
'gazelle fix'.

Limitations:

* Gazelle still only allows one package per directory. Gazelle infers
  package name and import path from .proto files. These must match
  other files in the directory.
* Import resolution is fairly crude and is just a guess, based on
  the import string.
* There's no way to import protos from other repositories, except for
  Well Known Types.

Fixes bazel-contrib#808
  • Loading branch information
Jay Conrod committed Oct 3, 2017
1 parent 51d6a4c commit 4c930b4
Show file tree
Hide file tree
Showing 35 changed files with 2,222 additions and 774 deletions.
37 changes: 37 additions & 0 deletions go/tools/gazelle/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ type Config struct {
// This is used to map imports to labels within the repository.
GoPrefix string

// ShouldFix determines whether Gazelle attempts to remove and replace
// usage of deprecated rules.
ShouldFix bool

// DepMode determines how imports outside of GoPrefix are resolved.
DepMode DependencyMode

Expand All @@ -54,6 +58,9 @@ type Config struct {

// StructureMode determines how build files are organized within a project.
StructureMode StructureMode

// ProtoMode determines how rules are generated for protos.
ProtoMode ProtoMode
}

var DefaultValidBuildFileNames = []string{"BUILD.bazel", "BUILD"}
Expand Down Expand Up @@ -166,3 +173,33 @@ const (
// new_http_archive.
FlatMode
)

// ProtoMode determines how proto rules are generated.
type ProtoMode int

const (
// In DefaultProtoMode, Gazelle generates proto_library and new
// go_proto_library or grpc_proto_library rules.
DefaultProtoMode ProtoMode = iota

// In DisableProtoMode, Gazelle will ignore .proto files. .pb.go files are
// treated as normal sources.
DisableProtoMode

// In LegacyProtoMode, Gazelle generates filegroups for .proto files if
// .pb.go files are present in the same directory.
LegacyProtoMode
)

func ProtoModeFromString(s string) (ProtoMode, error) {
switch s {
case "default":
return DefaultProtoMode, nil
case "disable":
return DisableProtoMode, nil
case "legacy":
return LegacyProtoMode, nil
default:
return 0, fmt.Errorf("unrecognized proto mode: %q", s)
}
}
10 changes: 10 additions & 0 deletions go/tools/gazelle/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,14 @@ const (
DefaultProtosName = "go_default_library_protos"
// DefaultCgoLibName is the name of the default cgo_library rule in a Go package directory.
DefaultCgoLibName = "cgo_default_library"

// WellKnownTypesProtoRepo is the repository containing proto_library rules
// for the Well Known Types.
WellKnownTypesProtoRepo = "com_google_protobuf"
// WellKnownTypesGoProtoRepo is the repository containing go_library rules
// for the Well Known Types.
WellKnownTypesGoProtoRepo = "com_github_golang_protobuf"
// WellKnownTypesGoPrefix is the import path for the Go repository containing
// pre-generated code for the Well Known Types.
WellKnownTypesGoPrefix = "github.com/golang/protobuf"
)
80 changes: 80 additions & 0 deletions go/tools/gazelle/config/directives.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var knownTopLevelDirectives = map[string]bool{
"build_tags": true,
"exclude": true,
"ignore": true,
"proto": true,
}

// TODO(jayconrod): annotation directives will apply to an individual rule.
Expand Down Expand Up @@ -107,10 +108,89 @@ func ApplyDirectives(c *Config, directives []Directive) *Config {
case "build_file_name":
modified.ValidBuildFileNames = strings.Split(d.Value, ",")
didModify = true
case "proto":
protoMode, err := ProtoModeFromString(d.Value)
if err != nil {
log.Print(err)
continue
}
modified.ProtoMode = protoMode
didModify = true
}
}
if !didModify {
return c
}
return &modified
}

// InferProtoMode sets Config.ProtoMode, based on the contents of f. If the
// proto mode is already set to something other than the default, or if the mode
// is set explicitly in directives, this function does nothing. If the legacy
// go_proto_library.bzl is loaded, or if this is the Well Known Types
// repository, legacy mode is used. If go_proto_library is loaded from another
// file or if this is the Well Known Types repository, protos are disabled.
func InferProtoMode(c *Config, f *bf.File, directives []Directive) *Config {
if c.ProtoMode != DefaultProtoMode {
return c
}
for _, d := range directives {
if d.Key == "proto" {
return c
}
}
if c.GoPrefix == WellKnownTypesGoPrefix && c.ProtoMode != DisableProtoMode {
// Use legacy mode in this repo. We don't need proto_library or
// go_proto_library, since we get that from @com_google_protobuf.
// Legacy rules still refer to .proto files in here, which need are
// exposed by filegroup. go_library rules from .pb.go files will be
// generated, which are depended upon by the new rules.
modified := *c
modified.ProtoMode = LegacyProtoMode
return &modified
}
if f == nil {
return c
}
mode := DefaultProtoMode
for _, stmt := range f.Stmt {
c, ok := stmt.(*bf.CallExpr)
if !ok {
continue
}
if x, ok := c.X.(*bf.LiteralExpr); !ok || x.Token != "load" || len(c.List) == 0 {
continue
}
name, ok := c.List[0].(*bf.StringExpr)
if !ok {
continue
}
if name.Value == "@io_bazel_rules_go//proto:def.bzl" {
break
}
if name.Value == "@io_bazel_rules_go//proto:go_proto_library.bzl" {
mode = LegacyProtoMode
break
}
for _, arg := range c.List[1:] {
if sym, ok := arg.(*bf.StringExpr); ok && sym.Value == "go_proto_library" {
mode = DisableProtoMode
break
}
kwarg, ok := arg.(*bf.BinaryExpr)
if !ok || kwarg.Op != "=" {
continue
}
if key, ok := kwarg.X.(*bf.LiteralExpr); ok && key.Token == "go_proto_library" {
mode = DisableProtoMode
break
}
}
}
if mode == DefaultProtoMode || c.ProtoMode == mode || c.ShouldFix && mode == LegacyProtoMode {
return c
}
modified := *c
modified.ProtoMode = mode
return &modified
}
62 changes: 62 additions & 0 deletions go/tools/gazelle/config/directives_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,65 @@ func TestApplyDirectives(t *testing.T) {
})
}
}

func TestInferProtoMode(t *testing.T) {
for _, tc := range []struct {
desc, content string
c Config
want ProtoMode
}{
{
desc: "default",
}, {
desc: "previous",
c: Config{ProtoMode: LegacyProtoMode},
want: LegacyProtoMode,
}, {
desc: "explicit",
content: `# gazelle:proto default
load("@io_bazel_rules_go//proto:go_proto_library.bzl", "go_proto_library")
`,
want: DefaultProtoMode,
}, {
desc: "legacy",
content: `load("@io_bazel_rules_go//proto:go_proto_library.bzl", "go_proto_library")`,
want: LegacyProtoMode,
}, {
desc: "disable",
content: `load("@com_example_repo//proto:go_proto_library.bzl", go_proto_library = "x")`,
want: DisableProtoMode,
}, {
desc: "fix legacy",
content: `load("@io_bazel_rules_go//proto:go_proto_library.bzl", "go_proto_library")`,
c: Config{ShouldFix: true},
}, {
desc: "fix disabled",
content: `load("@com_example_repo//proto:go_proto_library.bzl", go_proto_library = "x")`,
c: Config{ShouldFix: true},
want: DisableProtoMode,
}, {
desc: "well known types",
c: Config{GoPrefix: "github.com/golang/protobuf"},
want: LegacyProtoMode,
},
} {
t.Run(tc.desc, func(t *testing.T) {
var f *bf.File
var directives []Directive
if tc.content != "" {
var err error
f, err = bf.Parse("BUILD.bazel", []byte(tc.content))
if err != nil {
t.Fatalf("error parsing build file: %v", err)
}
directives = ParseDirectives(f)
}

got := InferProtoMode(&tc.c, f, directives)
if got.ProtoMode != tc.want {
t.Errorf("got proto mode %v ; want %v", got.ProtoMode, tc.want)
}
})
}
}
Loading

0 comments on commit 4c930b4

Please sign in to comment.