Skip to content

Commit 29d45c4

Browse files
bufdevemcfarlane
andauthored
Add same_package option to protoc-gen-connect-go (#803)
This adds an option `package_suffix` to `protoc-gen-connect-go`. It allows to customize the output directory for the generated code. The sub-package of the package containing the base .pb.go files using the given suffix. An empty suffix denotes to generate into the same package as the base pb.go files. Default is "connect". --------- Signed-off-by: bufdev <[email protected]> Signed-off-by: Edward McFarlane <[email protected]> Co-authored-by: Edward McFarlane <[email protected]>
1 parent d55ebd8 commit 29d45c4

File tree

23 files changed

+1676
-77
lines changed

23 files changed

+1676
-77
lines changed

Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export PATH := $(BIN):$(PATH)
1111
export GOBIN := $(abspath $(BIN))
1212
COPYRIGHT_YEARS := 2021-2024
1313
LICENSE_IGNORE := --ignore /testdata/
14-
BUF_VERSION := 1.34.0
14+
BUF_VERSION := 1.47.2
1515

1616
.PHONY: help
1717
help: ## Describe useful make targets
@@ -81,6 +81,7 @@ generate: $(BIN)/buf $(BIN)/protoc-gen-go $(BIN)/protoc-gen-connect-go $(BIN)/li
8181
go mod tidy
8282
rm -rf internal/gen
8383
PATH="$(abspath $(BIN))" buf generate
84+
( cd cmd/protoc-gen-connect-go; ./generate.sh )
8485
license-header \
8586
--license-type apache \
8687
--copyright-holder "The Connect Authors" \

buf.gen.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ plugins:
1111
- local: protoc-gen-connect-go
1212
out: internal/gen
1313
opt: paths=source_relative
14+
clean: true

cmd/protoc-gen-connect-go/generate.sh

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/bash
2+
find testdata -maxdepth 1 -type d \( ! -name testdata \) -exec bash -c "cd '{}' && buf generate" \;

cmd/protoc-gen-connect-go/main.go

+80-42
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,50 @@
2323
//
2424
// With [buf], your buf.gen.yaml will look like this:
2525
//
26-
// version: v1
26+
// version: v2
2727
// plugins:
28-
// - name: go
28+
// - local: protoc-gen-go
2929
// out: gen
30-
// - name: connect-go
30+
// - local: protoc-gen-connect-go
3131
// out: gen
3232
//
3333
// This generates service definitions for the Protobuf types and services
3434
// defined by file.proto. If file.proto defines the foov1 Protobuf package, the
3535
// invocations above will write output to:
3636
//
3737
// gen/path/to/file.pb.go
38-
// gen/path/to/connectfoov1/file.connect.go
38+
// gen/path/to/foov1connect/file.connect.go
39+
//
40+
// The generated code is configurable with the same parameters as the protoc-gen-go
41+
// plugin, with the following additional parameters:
42+
//
43+
// - package_suffix: To generate into a sub-package of the package containing the
44+
// base .pb.go files using the given suffix. An empty suffix denotes to
45+
// generate into the same package as the base pb.go files. Default is "connect".
46+
//
47+
// For example, to generate into the same package as the base .pb.go files:
48+
//
49+
// version: v2
50+
// plugins:
51+
// - local: protoc-gen-go
52+
// out: gen
53+
// - local: protoc-gen-connect-go
54+
// out: gen
55+
// opts: package_suffix
56+
//
57+
// This will generate output to:
58+
//
59+
// gen/path/to/file.pb.go
60+
// gen/path/to/file.connect.go
3961
//
4062
// [buf]: https://buf.build
4163
package main
4264

4365
import (
4466
"bytes"
67+
"flag"
4568
"fmt"
69+
"go/token"
4670
"os"
4771
"path"
4872
"path/filepath"
@@ -64,7 +88,8 @@ const (
6488
connectPackage = protogen.GoImportPath("connectrpc.com/connect")
6589

6690
generatedFilenameExtension = ".connect.go"
67-
generatedPackageSuffix = "connect"
91+
defaultPackageSuffix = "connect"
92+
packageSuffixFlagName = "package_suffix"
6893

6994
usage = "See https://connectrpc.com/docs/go/getting-started to learn how to use this plugin.\n\nFlags:\n -h, --help\tPrint this help and exit.\n --version\tPrint the version and exit."
7095

@@ -89,46 +114,63 @@ func main() {
89114
fmt.Fprintln(os.Stderr, usage)
90115
os.Exit(1)
91116
}
92-
protogen.Options{}.Run(
117+
var flagSet flag.FlagSet
118+
packageSuffix := flagSet.String(
119+
packageSuffixFlagName,
120+
defaultPackageSuffix,
121+
"Generate files into a sub-package of the package containing the base .pb.go files using the given suffix. An empty suffix denotes to generate into the same package as the base pb.go files.",
122+
)
123+
protogen.Options{
124+
ParamFunc: flagSet.Set,
125+
}.Run(
93126
func(plugin *protogen.Plugin) error {
94127
plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) | uint64(pluginpb.CodeGeneratorResponse_FEATURE_SUPPORTS_EDITIONS)
95128
plugin.SupportedEditionsMinimum = descriptorpb.Edition_EDITION_PROTO2
96129
plugin.SupportedEditionsMaximum = descriptorpb.Edition_EDITION_2023
97130
for _, file := range plugin.Files {
98131
if file.Generate {
99-
generate(plugin, file)
132+
generate(plugin, file, *packageSuffix)
100133
}
101134
}
102135
return nil
103136
},
104137
)
105138
}
106139

107-
func generate(plugin *protogen.Plugin, file *protogen.File) {
140+
func generate(plugin *protogen.Plugin, file *protogen.File, packageSuffix string) {
108141
if len(file.Services) == 0 {
109142
return
110143
}
111-
file.GoPackageName += generatedPackageSuffix
112144

113-
generatedFilenamePrefixToSlash := filepath.ToSlash(file.GeneratedFilenamePrefix)
114-
file.GeneratedFilenamePrefix = path.Join(
115-
path.Dir(generatedFilenamePrefixToSlash),
116-
string(file.GoPackageName),
117-
path.Base(generatedFilenamePrefixToSlash),
118-
)
119-
generatedFile := plugin.NewGeneratedFile(
120-
file.GeneratedFilenamePrefix+generatedFilenameExtension,
121-
protogen.GoImportPath(path.Join(
145+
goImportPath := file.GoImportPath
146+
if packageSuffix != "" {
147+
if !token.IsIdentifier(packageSuffix) {
148+
plugin.Error(fmt.Errorf("package_suffix %q is not a valid Go identifier", packageSuffix))
149+
return
150+
}
151+
file.GoPackageName += protogen.GoPackageName(packageSuffix)
152+
generatedFilenamePrefixToSlash := filepath.ToSlash(file.GeneratedFilenamePrefix)
153+
file.GeneratedFilenamePrefix = path.Join(
154+
path.Dir(generatedFilenamePrefixToSlash),
155+
string(file.GoPackageName),
156+
path.Base(generatedFilenamePrefixToSlash),
157+
)
158+
goImportPath = protogen.GoImportPath(path.Join(
122159
string(file.GoImportPath),
123160
string(file.GoPackageName),
124-
)),
161+
))
162+
}
163+
generatedFile := plugin.NewGeneratedFile(
164+
file.GeneratedFilenamePrefix+generatedFilenameExtension,
165+
goImportPath,
125166
)
126-
generatedFile.Import(file.GoImportPath)
167+
if packageSuffix != "" {
168+
generatedFile.Import(file.GoImportPath)
169+
}
127170
generatePreamble(generatedFile, file)
128171
generateServiceNameConstants(generatedFile, file.Services)
129-
generateServiceNameVariables(generatedFile, file)
130172
for _, service := range file.Services {
131-
generateService(generatedFile, service)
173+
generateService(generatedFile, file, service)
132174
}
133175
}
134176

@@ -213,29 +255,22 @@ func generateServiceNameConstants(g *protogen.GeneratedFile, services []*protoge
213255
g.P()
214256
}
215257

216-
func generateServiceNameVariables(g *protogen.GeneratedFile, file *protogen.File) {
217-
wrapComments(g, "These variables are the protoreflect.Descriptor objects for the RPCs defined in this package.")
218-
g.P("var (")
219-
for _, service := range file.Services {
220-
serviceDescName := unexport(fmt.Sprintf("%sServiceDescriptor", service.Desc.Name()))
221-
g.P(serviceDescName, ` = `,
222-
g.QualifiedGoIdent(file.GoDescriptorIdent),
223-
`.Services().ByName("`, service.Desc.Name(), `")`)
224-
for _, method := range service.Methods {
225-
g.P(procedureVarMethodDescriptor(method), ` = `,
226-
serviceDescName,
227-
`.Methods().ByName("`, method.Desc.Name(), `")`)
228-
}
258+
func generateServiceMethodsVar(g *protogen.GeneratedFile, file *protogen.File, service *protogen.Service) {
259+
if len(service.Methods) == 0 {
260+
return
229261
}
230-
g.P(")")
262+
serviceMethodsName := unexport(fmt.Sprintf("%sMethods", service.Desc.Name()))
263+
g.P(serviceMethodsName, ` := `,
264+
g.QualifiedGoIdent(file.GoDescriptorIdent),
265+
`.Services().ByName("`, service.Desc.Name(), `").Methods()`)
231266
}
232267

233-
func generateService(g *protogen.GeneratedFile, service *protogen.Service) {
268+
func generateService(g *protogen.GeneratedFile, file *protogen.File, service *protogen.Service) {
234269
names := newNames(service)
235270
generateClientInterface(g, service, names)
236-
generateClientImplementation(g, service, names)
271+
generateClientImplementation(g, file, service, names)
237272
generateServerInterface(g, service, names)
238-
generateServerConstructor(g, service, names)
273+
generateServerConstructor(g, file, service, names)
239274
generateUnimplementedServerImplementation(g, service, names)
240275
}
241276

@@ -260,7 +295,7 @@ func generateClientInterface(g *protogen.GeneratedFile, service *protogen.Servic
260295
g.P()
261296
}
262297

263-
func generateClientImplementation(g *protogen.GeneratedFile, service *protogen.Service, names names) {
298+
func generateClientImplementation(g *protogen.GeneratedFile, file *protogen.File, service *protogen.Service, names names) {
264299
clientOption := connectPackage.Ident("ClientOption")
265300

266301
// Client constructor.
@@ -281,6 +316,7 @@ func generateClientImplementation(g *protogen.GeneratedFile, service *protogen.S
281316
if len(service.Methods) > 0 {
282317
g.P("baseURL = ", stringsPackage.Ident("TrimRight"), `(baseURL, "/")`)
283318
}
319+
generateServiceMethodsVar(g, file, service)
284320
g.P("return &", names.ClientImpl, "{")
285321
for _, method := range service.Methods {
286322
g.P(unexport(method.GoName), ": ",
@@ -396,7 +432,7 @@ func generateServerInterface(g *protogen.GeneratedFile, service *protogen.Servic
396432
g.P()
397433
}
398434

399-
func generateServerConstructor(g *protogen.GeneratedFile, service *protogen.Service, names names) {
435+
func generateServerConstructor(g *protogen.GeneratedFile, file *protogen.File, service *protogen.Service, names names) {
400436
wrapComments(g, names.ServerConstructor, " builds an HTTP handler from the service implementation.",
401437
" It returns the path on which to mount the handler and the handler itself.")
402438
g.P("//")
@@ -409,6 +445,7 @@ func generateServerConstructor(g *protogen.GeneratedFile, service *protogen.Serv
409445
handlerOption := connectPackage.Ident("HandlerOption")
410446
g.P("func ", names.ServerConstructor, "(svc ", names.Server, ", opts ...", handlerOption,
411447
") (string, ", httpPackage.Ident("Handler"), ") {")
448+
generateServiceMethodsVar(g, file, service)
412449
for _, method := range service.Methods {
413450
isStreamingServer := method.Desc.IsStreamingServer()
414451
isStreamingClient := method.Desc.IsStreamingClient()
@@ -522,7 +559,8 @@ func procedureHandlerName(m *protogen.Method) string {
522559
}
523560

524561
func procedureVarMethodDescriptor(m *protogen.Method) string {
525-
return unexport(fmt.Sprintf("%s%sMethodDescriptor", m.Parent.GoName, m.GoName))
562+
serviceMethodsName := unexport(fmt.Sprintf("%sMethods", m.Parent.GoName))
563+
return serviceMethodsName + `.ByName("` + string(m.Desc.Name()) + `")`
526564
}
527565

528566
func isDeprecatedService(service *protogen.Service) bool {

0 commit comments

Comments
 (0)