Skip to content

Commit 45f8632

Browse files
authored
Allow protoc_path to be a slice so user can specify extra args (#3098)
With this, the user can specify, for example `--experimental_editions`. When proxying to `protoc`, we can synthesize support for all editions since `protoc` will actually handle the validation and fail if it can't handle an edition.
1 parent 0b7c7fa commit 45f8632

File tree

10 files changed

+72
-44
lines changed

10 files changed

+72
-44
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
- Fix issue where `buf generate` would succeed on missing insertion points and
66
panic on empty insertion point files.
7+
- Update `buf generate` to allow the use of Editions syntax when doing local code
8+
generation by proxying to a `protoc` binary (for languages where code gen is
9+
implemented inside of `protoc` instead of in a plugin: Java, C++, Python, etc).
10+
- Allow use of an array of strings for the `protoc_path` property of for `buf.gen.yaml`,
11+
where the first array element is the actual path and other array elements are extra
12+
arguments that are passed to `protoc` each time it is invoked.
713

814
## [v1.33.0] - 2024-06-13
915

private/buf/bufgen/generator.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ func (g *generator) execLocalPlugin(
308308
pluginConfig.Name(),
309309
requests,
310310
bufprotopluginexec.GenerateWithPluginPath(pluginConfig.Path()...),
311-
bufprotopluginexec.GenerateWithProtocPath(pluginConfig.ProtocPath()),
311+
bufprotopluginexec.GenerateWithProtocPath(pluginConfig.ProtocPath()...),
312312
)
313313
if err != nil {
314314
return nil, fmt.Errorf("plugin %s: %v", pluginConfig.Name(), err)

private/buf/bufprotopluginexec/bufprotopluginexec.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func GenerateWithPluginPath(pluginPath ...string) GenerateOption {
9898

9999
// GenerateWithProtocPath returns a new GenerateOption that uses the given protoc
100100
// path to the plugin.
101-
func GenerateWithProtocPath(protocPath string) GenerateOption {
101+
func GenerateWithProtocPath(protocPath ...string) GenerateOption {
102102
return func(generateOptions *generateOptions) {
103103
generateOptions.protocPath = protocPath
104104
}
@@ -139,14 +139,15 @@ func NewHandler(
139139
// Initialize builtin protoc plugin handler. We always look for protoc-gen-X first,
140140
// but if not, check the builtins.
141141
if _, ok := bufconfig.ProtocProxyPluginNames[pluginName]; ok {
142-
if handlerOptions.protocPath == "" {
143-
handlerOptions.protocPath = "protoc"
142+
if len(handlerOptions.protocPath) == 0 {
143+
handlerOptions.protocPath = []string{"protoc"}
144144
}
145-
if protocPath, err := unsafeLookPath(handlerOptions.protocPath); err != nil {
145+
protocPath, protocExtraArgs := handlerOptions.protocPath[0], handlerOptions.protocPath[1:]
146+
protocPath, err := unsafeLookPath(protocPath)
147+
if err != nil {
146148
return nil, err
147-
} else {
148-
return newProtocProxyHandler(storageosProvider, runner, tracer, protocPath, pluginName), nil
149149
}
150+
return newProtocProxyHandler(storageosProvider, runner, tracer, protocPath, protocExtraArgs, pluginName), nil
150151
}
151152
return nil, fmt.Errorf(
152153
"could not find protoc plugin for name %s - please make sure protoc-gen-%s is installed and present on your $PATH",
@@ -162,7 +163,7 @@ type HandlerOption func(*handlerOptions)
162163
//
163164
// The default is to do exec.LookPath on "protoc".
164165
// protocPath is expected to be unnormalized.
165-
func HandlerWithProtocPath(protocPath string) HandlerOption {
166+
func HandlerWithProtocPath(protocPath ...string) HandlerOption {
166167
return func(handlerOptions *handlerOptions) {
167168
handlerOptions.protocPath = protocPath
168169
}
@@ -190,8 +191,8 @@ func NewBinaryHandler(runner command.Runner, tracer tracing.Tracer, pluginPath s
190191
}
191192

192193
type handlerOptions struct {
193-
protocPath string
194194
pluginPath []string
195+
protocPath []string
195196
}
196197

197198
func newHandlerOptions() *handlerOptions {

private/buf/bufprotopluginexec/generator.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (g *generator) Generate(
6060
}
6161
handlerOptions := []HandlerOption{
6262
HandlerWithPluginPath(generateOptions.pluginPath...),
63-
HandlerWithProtocPath(generateOptions.protocPath),
63+
HandlerWithProtocPath(generateOptions.protocPath...),
6464
}
6565
handler, err := NewHandler(
6666
g.storageosProvider,
@@ -84,7 +84,7 @@ func (g *generator) Generate(
8484

8585
type generateOptions struct {
8686
pluginPath []string
87-
protocPath string
87+
protocPath []string
8888
}
8989

9090
func newGenerateOptions() *generateOptions {

private/buf/bufprotopluginexec/protoc_proxy_handler.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/bufbuild/buf/private/pkg/command"
2828
"github.com/bufbuild/buf/private/pkg/ioext"
2929
"github.com/bufbuild/buf/private/pkg/protoencoding"
30+
"github.com/bufbuild/buf/private/pkg/slicesext"
3031
"github.com/bufbuild/buf/private/pkg/storage"
3132
"github.com/bufbuild/buf/private/pkg/storage/storageos"
3233
"github.com/bufbuild/buf/private/pkg/tmp"
@@ -43,6 +44,7 @@ type protocProxyHandler struct {
4344
runner command.Runner
4445
tracer tracing.Tracer
4546
protocPath string
47+
protocExtraArgs []string
4648
pluginName string
4749
}
4850

@@ -51,13 +53,15 @@ func newProtocProxyHandler(
5153
runner command.Runner,
5254
tracer tracing.Tracer,
5355
protocPath string,
56+
protocExtraArgs []string,
5457
pluginName string,
5558
) *protocProxyHandler {
5659
return &protocProxyHandler{
5760
storageosProvider: storageosProvider,
5861
runner: runner,
5962
tracer: tracer,
6063
protocPath: protocPath,
64+
protocExtraArgs: protocExtraArgs,
6165
pluginName: pluginName,
6266
}
6367
}
@@ -128,10 +132,10 @@ func (h *protocProxyHandler) Handle(
128132
defer func() {
129133
retErr = multierr.Append(retErr, tmpDir.Close())
130134
}()
131-
args := []string{
135+
args := slicesext.Concat(h.protocExtraArgs, []string{
132136
fmt.Sprintf("--descriptor_set_in=%s", descriptorFilePath),
133137
fmt.Sprintf("--%s_out=%s", h.pluginName, tmpDir.AbsPath()),
134-
}
138+
})
135139
if getSetExperimentalAllowProto3OptionalFlag(protocVersion) {
136140
args = append(
137141
args,
@@ -167,6 +171,12 @@ func (h *protocProxyHandler) Handle(
167171
if getFeatureProto3OptionalSupported(protocVersion) {
168172
responseWriter.SetFeatureProto3Optional()
169173
}
174+
// We always claim support for all Editions in the response because the invocation to
175+
// "protoc" will fail if it can't handle the input editions. That way, we don't have to
176+
// track which protoc versions support which editions and synthesize this information.
177+
// And that also lets us support users passing "--experimental_editions" to protoc.
178+
responseWriter.SetFeatureSupportsEditions(descriptorpb.Edition_EDITION_PROTO2, descriptorpb.Edition_EDITION_MAX)
179+
170180
// no need for symlinks here, and don't want to support
171181
readWriteBucket, err := h.storageosProvider.NewReadWriteBucket(tmpDir.AbsPath())
172182
if err != nil {
@@ -195,7 +205,7 @@ func (h *protocProxyHandler) getProtocVersion(
195205
if err := h.runner.Run(
196206
ctx,
197207
h.protocPath,
198-
command.RunWithArgs("--version"),
208+
command.RunWithArgs(slicesext.Concat(h.protocExtraArgs, []string{"--version"})...),
199209
command.RunWithEnviron(pluginEnv.Environ),
200210
command.RunWithStdout(stdoutBuffer),
201211
); err != nil {

private/buf/cmd/buf/command/alpha/protoc/protoc.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ Additional flags:
7373
BindFlags: flagsBuilder.Bind,
7474
NormalizeFlag: flagsBuilder.Normalize,
7575
Version: fmt.Sprintf(
76-
"%v.%v.%v-buf",
77-
bufprotopluginexec.DefaultVersion.GetMajor(),
76+
"%v.%v-buf",
77+
// DefaultVersion has an extra major version that corresponds to
78+
// backwards-compatibility level of C++ runtime. The actual version
79+
// of the compiler is just the minor and patch versions.
7880
bufprotopluginexec.DefaultVersion.GetMinor(),
7981
bufprotopluginexec.DefaultVersion.GetPatch(),
8082
),

private/bufpkg/bufconfig/buf_gen_yaml_file.go

+10-9
Original file line numberDiff line numberDiff line change
@@ -316,9 +316,9 @@ type externalGeneratePluginConfigV1 struct {
316316
// Opt can be one string or multiple strings.
317317
Opt interface{} `json:"opt,omitempty" yaml:"opt,omitempty"`
318318
// Path can be one string or multiple strings.
319-
Path interface{} `json:"path,omitempty" yaml:"path,omitempty"`
320-
ProtocPath string `json:"protoc_path,omitempty" yaml:"protoc_path,omitempty"`
321-
Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"`
319+
Path any `json:"path,omitempty" yaml:"path,omitempty"`
320+
ProtocPath any `json:"protoc_path,omitempty" yaml:"protoc_path,omitempty"`
321+
Strategy string `json:"strategy,omitempty" yaml:"strategy,omitempty"`
322322
}
323323

324324
// externalGenerateManagedConfigV1 represents the managed mode config in a v1 buf.gen.yaml file.
@@ -496,17 +496,18 @@ type externalGeneratePluginConfigV2 struct {
496496
// Local is the local path (either relative or absolute) to a binary or other runnable program which
497497
// implements the protoc plugin interface. This can be one string (the program) or multiple (remaining
498498
// strings are arguments to the program).
499-
Local interface{} `json:"local,omitempty" yaml:"local,omitempty"`
499+
Local any `json:"local,omitempty" yaml:"local,omitempty"`
500500
// ProtocBuiltin is the protoc built-in plugin name, in the form of 'java' instead of 'protoc-gen-java'.
501501
ProtocBuiltin *string `json:"protoc_builtin,omitempty" yaml:"protoc_builtin,omitempty"`
502-
// ProtocPath is only valid with ProtocBuiltin
503-
ProtocPath *string `json:"protoc_path,omitempty" yaml:"protoc_path,omitempty"`
502+
// ProtocPath is only valid with ProtocBuiltin. This can be one string (the path to protoc) or multiple
503+
// (remaining strings are extra args to pass to protoc).
504+
ProtocPath any `json:"protoc_path,omitempty" yaml:"protoc_path,omitempty"`
504505
// Out is required.
505506
Out string `json:"out,omitempty" yaml:"out,omitempty"`
506507
// Opt can be one string or multiple strings.
507-
Opt interface{} `json:"opt,omitempty" yaml:"opt,omitempty"`
508-
IncludeImports bool `json:"include_imports,omitempty" yaml:"include_imports,omitempty"`
509-
IncludeWKT bool `json:"include_wkt,omitempty" yaml:"include_wkt,omitempty"`
508+
Opt any `json:"opt,omitempty" yaml:"opt,omitempty"`
509+
IncludeImports bool `json:"include_imports,omitempty" yaml:"include_imports,omitempty"`
510+
IncludeWKT bool `json:"include_wkt,omitempty" yaml:"include_wkt,omitempty"`
510511
// Strategy is only valid with ProtoBuiltin and Local.
511512
Strategy *string `json:"strategy,omitempty" yaml:"strategy,omitempty"`
512513
}

private/bufpkg/bufconfig/generate_config_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ func TestParseConfigFromExternalV1(t *testing.T) {
206206
pluginConfigType: PluginConfigTypeProtocBuiltin,
207207
name: "cpp",
208208
out: "cpp/out",
209-
protocPath: "path/to/protoc",
209+
protocPath: []string{"path/to/protoc"},
210210
},
211211
},
212212
},
@@ -219,7 +219,7 @@ func TestParseConfigFromExternalV1(t *testing.T) {
219219
{
220220
Plugin: "cpp",
221221
Out: "cpp/out",
222-
ProtocPath: "path/to/protoc",
222+
ProtocPath: []any{"path/to/protoc", "--experimental_editions"},
223223
},
224224
},
225225
},
@@ -230,7 +230,7 @@ func TestParseConfigFromExternalV1(t *testing.T) {
230230
pluginConfigType: PluginConfigTypeProtocBuiltin,
231231
name: "cpp",
232232
out: "cpp/out",
233-
protocPath: "path/to/protoc",
233+
protocPath: []string{"path/to/protoc", "--experimental_editions"},
234234
},
235235
},
236236
},

private/bufpkg/bufconfig/generate_plugin_config.go

+23-15
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ type GeneratePluginConfig interface {
100100
//
101101
// This is not empty only when the plugin is local.
102102
Path() []string
103-
// ProtocPath returns a path to protoc.
103+
// ProtocPath returns a path to protoc, including any extra arguments.
104104
//
105-
// This is not empty only when the plugin is protoc-builtin
106-
ProtocPath() string
105+
// This is not empty only when the plugin is protoc-builtin.
106+
ProtocPath() []string
107107
// RemoteHost returns the remote host of the remote plugin.
108108
//
109109
// This is not empty only when the plugin is remote.
@@ -184,7 +184,7 @@ func NewProtocBuiltinPluginConfig(
184184
includeImports bool,
185185
includeWKT bool,
186186
strategy *GenerateStrategy,
187-
protocPath string,
187+
protocPath []string,
188188
) (GeneratePluginConfig, error) {
189189
return newProtocBuiltinPluginConfig(
190190
name,
@@ -229,7 +229,7 @@ type pluginConfig struct {
229229
includeWKT bool
230230
strategy *GenerateStrategy
231231
path []string
232-
protocPath string
232+
protocPath []string
233233
remoteHost string
234234
revision int
235235
}
@@ -307,14 +307,18 @@ func newPluginConfigFromExternalV1(
307307
if err != nil {
308308
return nil, err
309309
}
310+
protocPath, err := encoding.InterfaceSliceOrStringToStringSlice(externalConfig.ProtocPath)
311+
if err != nil {
312+
return nil, err
313+
}
310314
if externalConfig.Plugin != "" && bufremotepluginref.IsPluginReferenceOrIdentity(pluginIdentifier) {
311315
if externalConfig.Path != nil {
312316
return nil, fmt.Errorf("cannot specify path for remote plugin %s", externalConfig.Plugin)
313317
}
314318
if externalConfig.Strategy != "" {
315319
return nil, fmt.Errorf("cannot specify strategy for remote plugin %s", externalConfig.Plugin)
316320
}
317-
if externalConfig.ProtocPath != "" {
321+
if externalConfig.ProtocPath != nil {
318322
return nil, fmt.Errorf("cannot specify protoc_path for remote plugin %s", externalConfig.Plugin)
319323
}
320324
return newRemotePluginConfig(
@@ -339,15 +343,15 @@ func newPluginConfigFromExternalV1(
339343
path,
340344
)
341345
}
342-
if externalConfig.ProtocPath != "" {
346+
if externalConfig.ProtocPath != nil {
343347
return newProtocBuiltinPluginConfig(
344348
pluginIdentifier,
345349
externalConfig.Out,
346350
opt,
347351
false,
348352
false,
349353
strategy,
350-
externalConfig.ProtocPath,
354+
protocPath,
351355
)
352356
}
353357
// It could be either local or protoc built-in. We defer to the plugin executor
@@ -438,9 +442,9 @@ func newPluginConfigFromExternalV2(
438442
path,
439443
)
440444
case externalConfig.ProtocBuiltin != nil:
441-
var protocPath string
442-
if externalConfig.ProtocPath != nil {
443-
protocPath = *externalConfig.ProtocPath
445+
protocPath, err := encoding.InterfaceSliceOrStringToStringSlice(externalConfig.ProtocPath)
446+
if err != nil {
447+
return nil, err
444448
}
445449
if externalConfig.Revision != nil {
446450
return nil, fmt.Errorf("cannot specify revision for protoc built-in plugin %s", *externalConfig.ProtocBuiltin)
@@ -545,7 +549,7 @@ func newProtocBuiltinPluginConfig(
545549
includeImports bool,
546550
includeWKT bool,
547551
strategy *GenerateStrategy,
548-
protocPath string,
552+
protocPath []string,
549553
) (*pluginConfig, error) {
550554
if includeWKT && !includeImports {
551555
return nil, errors.New("cannot include well-known types without including imports")
@@ -597,7 +601,7 @@ func (p *pluginConfig) Path() []string {
597601
return p.path
598602
}
599603

600-
func (p *pluginConfig) ProtocPath() string {
604+
func (p *pluginConfig) ProtocPath() []string {
601605
return p.protocPath
602606
}
603607

@@ -653,8 +657,12 @@ func newExternalGeneratePluginConfigV2FromPluginConfig(
653657
}
654658
case PluginConfigTypeProtocBuiltin:
655659
externalPluginConfigV2.ProtocBuiltin = toPointer(generatePluginConfig.Name())
656-
if protocPath := generatePluginConfig.ProtocPath(); protocPath != "" {
657-
externalPluginConfigV2.ProtocPath = &protocPath
660+
if protocPath := generatePluginConfig.ProtocPath(); len(protocPath) > 0 {
661+
if len(protocPath) == 1 {
662+
externalPluginConfigV2.ProtocPath = protocPath[0]
663+
} else {
664+
externalPluginConfigV2.ProtocPath = protocPath
665+
}
658666
}
659667
case PluginConfigTypeLocalOrProtocBuiltin:
660668
binaryName := "protoc-gen-" + generatePluginConfig.Name()

private/pkg/encoding/encoding.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func InterfaceSliceOrStringToStringSlice(in interface{}) ([]string, error) {
183183
switch t := in.(type) {
184184
case string:
185185
return []string{t}, nil
186-
case []interface{}:
186+
case []any:
187187
if len(t) == 0 {
188188
return nil, nil
189189
}

0 commit comments

Comments
 (0)