Skip to content

Commit f5ae196

Browse files
authored
[proto] Allow multiple outputs from a proto compiler (#3650)
1 parent 07ec991 commit f5ae196

File tree

7 files changed

+383
-7
lines changed

7 files changed

+383
-7
lines changed

proto/compiler.bzl

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,18 @@ def go_proto_compile(go, compiler, protos, imports, importpath):
8787
continue
8888
proto_paths[path] = src
8989

90-
out = go.declare_file(
91-
go,
92-
path = importpath + "/" + src.basename[:-len(".proto")],
93-
ext = compiler.internal.suffix,
94-
)
95-
go_srcs.append(out)
90+
suffixes = compiler.internal.suffixes
91+
if not suffixes:
92+
suffixes = [compiler.internal.suffix]
93+
for suffix in suffixes:
94+
out = go.declare_file(
95+
go,
96+
path = importpath + "/" + src.basename[:-len(".proto")],
97+
ext = suffix,
98+
)
99+
go_srcs.append(out)
96100
if outpath == None:
97-
outpath = out.dirname[:-len(importpath)]
101+
outpath = go_srcs[0].dirname[:-len(importpath)]
98102

99103
transitive_descriptor_sets = depset(direct = [], transitive = desc_sets)
100104

@@ -174,6 +178,7 @@ def _go_proto_compiler_impl(ctx):
174178
internal = struct(
175179
options = ctx.attr.options,
176180
suffix = ctx.attr.suffix,
181+
suffixes = ctx.attr.suffixes,
177182
protoc = ctx.executable._protoc,
178183
go_protoc = ctx.executable._go_protoc,
179184
plugin = ctx.executable.plugin,
@@ -190,6 +195,7 @@ _go_proto_compiler = rule(
190195
"deps": attr.label_list(providers = [GoLibrary]),
191196
"options": attr.string_list(),
192197
"suffix": attr.string(default = ".pb.go"),
198+
"suffixes": attr.string_list(),
193199
"valid_archive": attr.bool(default = True),
194200
"import_path_option": attr.bool(default = False),
195201
"plugin": attr.label(

tests/core/go_proto_library/BUILD.bazel

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ proto_library(
3232
srcs = ["grpc.proto"],
3333
)
3434

35+
proto_library(
36+
name = "enum_proto",
37+
srcs = ["enum.proto"],
38+
deps = [
39+
"@com_google_protobuf//:descriptor_proto",
40+
],
41+
)
42+
3543
# embed_test
3644
go_proto_library(
3745
name = "embed_go_proto",
@@ -199,6 +207,27 @@ go_proto_library(
199207
protos = [":grpc_proto"],
200208
)
201209

210+
# compilers with multiple suffixes
211+
go_test(
212+
name = "compilers_multi_suffix_test",
213+
srcs = ["compiler_multi_suffix_test.go"],
214+
deps = [
215+
":compilers_multi_suffix",
216+
],
217+
)
218+
219+
go_proto_library(
220+
name = "compilers_multi_suffix",
221+
compilers = ["//tests/core/go_proto_library/compilers:dbenum_compiler"],
222+
importpath = "github.com/bazelbuild/rules_go/tests/core/go_proto_library/enum",
223+
protos = [":enum_proto"],
224+
deps = [
225+
"@com_github_gogo_protobuf//proto",
226+
"@com_github_gogo_protobuf//protoc-gen-gogo/descriptor",
227+
"@com_github_gogo_protobuf//types",
228+
],
229+
)
230+
202231
# adjusted_import_test
203232
# TODO(#1851): uncomment when Bazel 0.22.0 is the minimum version.
204233
# go_test(
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* Copyright 2019 The Bazel Authors. All rights reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
16+
package multi_suffix_compiler
17+
18+
import (
19+
"testing"
20+
21+
"github.com/bazelbuild/rules_go/tests/core/go_proto_library/enum"
22+
)
23+
24+
func use(interface{}) {}
25+
26+
func TestMultiSuffixCompiler(t *testing.T) {
27+
// This test expects the compiler to generate two outputs:
28+
// <proto>.pb.go and <proto>_dbenums.pb.go.
29+
// Assert <proto>_dbenums.pb.go contains String() that returns dbenum value.
30+
v := enum.Enum_BYTES
31+
expected := "bytes_type"
32+
if v.String() != expected {
33+
panic(v.String())
34+
}
35+
// Assert <proto>.pb.go contains String() that returns proto Enum key.
36+
v = enum.Enum_INT32
37+
expected = "INT32"
38+
if v.String() != expected {
39+
panic(v.String())
40+
}
41+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
2+
load(
3+
"//proto:compiler.bzl",
4+
"go_proto_compiler",
5+
)
6+
load(
7+
"//proto/wkt:well_known_types.bzl",
8+
"GOGO_WELL_KNOWN_TYPE_REMAPS",
9+
)
10+
11+
go_library(
12+
name = "protoc_gen_dbenum_lib",
13+
srcs = [
14+
"dbenums.go",
15+
"main.go",
16+
],
17+
importpath = "github.com/bazelbuild/rules_go/tests/core/go_proto_library/compilers",
18+
visibility = ["//visibility:private"],
19+
deps = [
20+
"@com_github_gogo_protobuf//proto",
21+
"@com_github_gogo_protobuf//protoc-gen-gogo/descriptor",
22+
"@com_github_gogo_protobuf//protoc-gen-gogo/generator",
23+
"@com_github_gogo_protobuf//protoc-gen-gogo/plugin",
24+
"@com_github_gogo_protobuf//vanity",
25+
"@com_github_gogo_protobuf//vanity/command",
26+
],
27+
)
28+
29+
go_binary(
30+
name = "protoc-gen-dbenum-compiler",
31+
embed = [":protoc_gen_dbenum_lib"],
32+
visibility = ["//visibility:private"],
33+
)
34+
35+
go_proto_compiler(
36+
name = "dbenum_compiler",
37+
options = GOGO_WELL_KNOWN_TYPE_REMAPS,
38+
plugin = "//tests/core/go_proto_library/compilers:protoc-gen-dbenum-compiler",
39+
suffixes = [
40+
"_dbenum.pb.go",
41+
".pb.go",
42+
],
43+
visibility = ["//visibility:public"],
44+
)
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"text/template"
7+
8+
"github.com/gogo/protobuf/proto"
9+
"github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
10+
pb "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
11+
"github.com/gogo/protobuf/protoc-gen-gogo/generator"
12+
)
13+
14+
func init() {
15+
generator.RegisterPlugin(NewGenerator())
16+
}
17+
18+
type Generator struct {
19+
*generator.Generator
20+
generator.PluginImports
21+
write bool
22+
}
23+
24+
func NewGenerator() *Generator {
25+
return &Generator{}
26+
}
27+
28+
func (g *Generator) Name() string {
29+
return "dbenum"
30+
}
31+
32+
func (g *Generator) Init(gen *generator.Generator) {
33+
g.Generator = gen
34+
}
35+
36+
func (g *Generator) GenerateImports(file *generator.FileDescriptor) {
37+
}
38+
39+
func (g *Generator) Generate(file *generator.FileDescriptor) {
40+
for _, enum := range file.Enums() {
41+
g.enumHelper(enum)
42+
}
43+
g.writeTrailer(file.Enums())
44+
}
45+
46+
func (g *Generator) Write() bool {
47+
return g.write
48+
}
49+
50+
const initTmpl = `
51+
`
52+
53+
func (g *Generator) writeTrailer(enums []*generator.EnumDescriptor) {
54+
type desc struct {
55+
PackageName string
56+
TypeName string
57+
LowerCaseTypeName string
58+
}
59+
if !g.write {
60+
return
61+
}
62+
tmpl := template.Must(template.New("db_enum_trailer").Parse(initTmpl))
63+
g.P("func init() {")
64+
for _, e := range enums {
65+
if !HasDBEnum(e.Value) {
66+
continue
67+
}
68+
pkg := e.File().GetPackage()
69+
if pkg != "" {
70+
pkg += "."
71+
}
72+
tp := generator.CamelCaseSlice(e.TypeName())
73+
var buf bytes.Buffer
74+
tmpl.Execute(&buf, desc{
75+
PackageName: pkg + tp,
76+
TypeName: tp,
77+
LowerCaseTypeName: strings.ToLower(tp),
78+
})
79+
g.P(buf.String())
80+
}
81+
g.P("}")
82+
}
83+
84+
func (g *Generator) enumHelper(enum *generator.EnumDescriptor) {
85+
type anEnum struct {
86+
PBName string
87+
DBName string
88+
}
89+
type typeDesc struct {
90+
TypeName string
91+
TypeNamespace string
92+
LowerCaseTypeName string
93+
Found map[int32]bool
94+
Names []anEnum
95+
AllNames []anEnum
96+
}
97+
tp := generator.CamelCaseSlice(enum.TypeName())
98+
namespace := tp
99+
enumTypeName := enum.TypeName()
100+
if len(enumTypeName) > 1 { // This is a nested enum.
101+
names := enumTypeName[:len(enumTypeName)-1]
102+
// See https://protobuf.dev/reference/go/go-generated/#enum
103+
namespace = generator.CamelCaseSlice(names)
104+
}
105+
t := typeDesc{
106+
TypeName: tp,
107+
TypeNamespace: namespace,
108+
LowerCaseTypeName: strings.ToLower(tp),
109+
Found: make(map[int32]bool),
110+
}
111+
for _, v := range enum.Value {
112+
enumValue := v.GetNumber()
113+
if validDbEnum, dbName := getDbEnum(v); validDbEnum {
114+
names := anEnum{PBName: v.GetName(), DBName: dbName}
115+
t.AllNames = append(t.AllNames, names)
116+
// Skip enums that are aliased where one value has already been processed.
117+
if t.Found[enumValue] {
118+
continue
119+
}
120+
t.Found[enumValue] = true
121+
t.Names = append(t.Names, names)
122+
} else {
123+
t.Found[enumValue] = true
124+
}
125+
}
126+
if len(t.AllNames) == 0 {
127+
return
128+
}
129+
g.write = true
130+
tmpl := template.Must(template.New("db_enum").Parse(tmpl))
131+
var buf bytes.Buffer
132+
tmpl.Execute(&buf, t)
133+
g.P(buf.String())
134+
}
135+
136+
var E_DbEnum = &proto.ExtensionDesc{
137+
ExtendedType: (*descriptor.EnumValueOptions)(nil),
138+
ExtensionType: (*string)(nil),
139+
Field: 5002,
140+
Name: "tests.core.go_proto_library.enum",
141+
Tag: "bytes,5002,opt,name=db_enum",
142+
}
143+
144+
func getDbEnum(value *pb.EnumValueDescriptorProto) (bool, string) {
145+
if value == nil || value.Options == nil {
146+
return false, ""
147+
}
148+
EDbEnum := E_DbEnum
149+
v, err := proto.GetExtension(value.Options, EDbEnum)
150+
if err != nil {
151+
return false, ""
152+
}
153+
strPtr := v.(*string)
154+
if strPtr == nil {
155+
return false, ""
156+
}
157+
return true, *strPtr
158+
}
159+
160+
// HasDBEnum returns if there is DBEnums extensions defined in given enums.
161+
func HasDBEnum(enums []*pb.EnumValueDescriptorProto) bool {
162+
for _, enum := range enums {
163+
if validDbEnum, _ := getDbEnum(enum); validDbEnum {
164+
return true
165+
}
166+
}
167+
return false
168+
}
169+
170+
const tmpl = `
171+
172+
var {{ .LowerCaseTypeName }}ToStringValue = ` +
173+
`map[{{ .TypeName }}]string { {{ range $names := .Names }}
174+
{{ $.TypeNamespace }}_{{ $names.PBName }}: ` +
175+
`"{{ $names.DBName }}",{{ end }}
176+
}
177+
178+
179+
// String implements the stringer interface and should produce the same output
180+
// that is inserted into the db.
181+
func (v {{ .TypeName }}) String() string {
182+
if val, ok := {{ .LowerCaseTypeName }}ToStringValue[v]; ok {
183+
return val
184+
} else if int(v) == 0 {
185+
return "null"
186+
} else {
187+
return proto.EnumName({{ .TypeName }}_name, int32(v))
188+
}
189+
}`

0 commit comments

Comments
 (0)