Skip to content

Commit d65e1d4

Browse files
mkruskal-googlestapelberg
authored andcommitted
compiler/protogen: properly filter option dependencies in go-protobuf plugin.
Because protoc sends option dependencies to its plugins, this results in the go plugin enforcing the import path flag requirements on them. This is unnecessary though, since a true option dependency will have no generated code importing it anyway. This is especially problematic in (but not unique to) Bazel, since the aspect doesn't trigger on the option_deps attributes. Change-Id: I3b8cec00e462f84ef02ba1ab6bdb4dbe843703d5 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/713342 Reviewed-by: Michael Stapelberg <stapelberg@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 55497d0 commit d65e1d4

File tree

7 files changed

+411
-3
lines changed

7 files changed

+411
-3
lines changed

cmd/protoc-gen-go/descriptor_test/descriptor_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,13 @@ import (
88
"testing"
99

1010
testopenpb "google.golang.org/protobuf/internal/testprotos/test"
11+
testnopackagepb "google.golang.org/protobuf/internal/testprotos/test/test_nopackage"
12+
testoptionpb "google.golang.org/protobuf/internal/testprotos/test/test_option"
1113
testhybridpb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_hybrid"
1214
testopaquepb "google.golang.org/protobuf/internal/testprotos/testeditions/testeditions_opaque"
15+
"google.golang.org/protobuf/proto"
16+
"google.golang.org/protobuf/types/descriptorpb"
17+
"google.golang.org/protobuf/types/gofeaturespb"
1318
)
1419

1520
func TestFileModeEnum(t *testing.T) {
@@ -41,3 +46,17 @@ func TestFileModeMessage(t *testing.T) {
4146
t.Errorf("Hybrid proto did have deprecated method EnumDescriptor")
4247
}
4348
}
49+
50+
func TestImportOption(t *testing.T) {
51+
m := &testoptionpb.OptionImportMessage{}
52+
mdesc := m.ProtoReflect().Descriptor()
53+
54+
fileopts := mdesc.ParentFile().Options().(*descriptorpb.FileOptions)
55+
if proto.GetExtension(fileopts.Features, gofeaturespb.E_Go).(*gofeaturespb.GoFeatures).GetApiLevel() != gofeaturespb.GoFeatures_API_OPAQUE {
56+
t.Errorf("OptionImportMessage parent file options features does not have API_OPAQUE")
57+
}
58+
59+
if proto.GetExtension(fileopts, testnopackagepb.E_NoPackageOption).(*testnopackagepb.NoPackageOption).GetName() != "no package option" {
60+
t.Errorf("OptionImportMessage parent file options does not have no_package_option")
61+
}
62+
}

compiler/protogen/protogen.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,12 @@ func (opts Options) New(req *pluginpb.CodeGeneratorRequest) (*Plugin, error) {
267267
return nil, fmt.Errorf("cannot use module= with paths=source_relative")
268268
}
269269

270+
// Instead of generating each gen.Request.ProtoFile,
271+
// generate gen.Request.FileToGenerate and its transitive dependencies.
272+
//
273+
// This effectively filters out 'import option' dependencies.
274+
files := gatherTransitiveDependencies(gen.Request)
275+
270276
// Figure out the import path and package name for each file.
271277
//
272278
// The rules here are complicated and have grown organically over time.
@@ -281,7 +287,7 @@ func (opts Options) New(req *pluginpb.CodeGeneratorRequest) (*Plugin, error) {
281287
//
282288
// Alternatively, build systems which want to exert full control over
283289
// import paths may specify M<filename>=<import_path> flags.
284-
for _, fdesc := range gen.Request.ProtoFile {
290+
for _, fdesc := range files {
285291
filename := fdesc.GetName()
286292
// The "M" command-line flags take precedence over
287293
// the "go_package" option in the .proto source file.
@@ -351,7 +357,7 @@ func (opts Options) New(req *pluginpb.CodeGeneratorRequest) (*Plugin, error) {
351357

352358
// The extracted types from the full import set
353359
typeRegistry := newExtensionRegistry()
354-
for _, fdesc := range gen.Request.ProtoFile {
360+
for _, fdesc := range files {
355361
filename := fdesc.GetName()
356362
if gen.FilesByPath[filename] != nil {
357363
return nil, fmt.Errorf("duplicate file name: %q", filename)
@@ -390,6 +396,50 @@ func (opts Options) New(req *pluginpb.CodeGeneratorRequest) (*Plugin, error) {
390396
return gen, nil
391397
}
392398

399+
type transitiveDependencies struct {
400+
files map[string]*descriptorpb.FileDescriptorProto
401+
deps map[string]bool
402+
sortedDeps []*descriptorpb.FileDescriptorProto
403+
}
404+
405+
func newTransitiveDependencies(req *pluginpb.CodeGeneratorRequest) *transitiveDependencies {
406+
files := make(map[string]*descriptorpb.FileDescriptorProto)
407+
for _, f := range req.GetProtoFile() {
408+
files[f.GetName()] = f
409+
}
410+
return &transitiveDependencies{
411+
files: files,
412+
deps: make(map[string]bool),
413+
}
414+
}
415+
416+
func (td *transitiveDependencies) add(name string) {
417+
if td.deps[name] {
418+
return
419+
}
420+
f := td.files[name]
421+
if f == nil {
422+
// This shouldn't happen, but will fail later if it does.
423+
return
424+
}
425+
td.deps[name] = true
426+
for _, dep := range f.GetDependency() {
427+
td.add(dep)
428+
}
429+
td.sortedDeps = append(td.sortedDeps, f)
430+
}
431+
432+
func gatherTransitiveDependencies(req *pluginpb.CodeGeneratorRequest) []*descriptorpb.FileDescriptorProto {
433+
if len(req.GetFileToGenerate()) == 0 {
434+
return req.GetProtoFile()
435+
}
436+
td := newTransitiveDependencies(req)
437+
for _, f := range req.GetFileToGenerate() {
438+
td.add(f)
439+
}
440+
return td.sortedDeps
441+
}
442+
393443
// InternalStripForEditionsDiff returns whether or not to strip non-functional
394444
// codegen for editions diff testing.
395445
//

internal/cmd/generate-protos/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,10 @@ func generateLocalProtos() {
345345
},
346346
annotate: map[string]bool{"cmd/protoc-gen-go/testdata/annotations/annotations.proto": true},
347347
}, {
348-
path: "internal/testprotos",
348+
path: "internal/testprotos",
349+
pkgPaths: map[string]string{
350+
"internal/testprotos/test/test_nopackage.proto": "google.golang.org/protobuf/internal/testprotos/test/test_nopackage",
351+
},
349352
exclude: map[string]bool{"internal/testprotos/irregular/irregular.proto": true},
350353
}, {
351354
path: "src/",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
edition = "2024";
6+
7+
package goproto.proto.test;
8+
9+
import "google/protobuf/descriptor.proto";
10+
11+
// Intentionally leave out go_package options to make this hostile as a
12+
// transitive dependency. It should still work well as an option dependency.
13+
14+
message NoPackageOption {
15+
string name = 1;
16+
}
17+
18+
extend google.protobuf.FileOptions {
19+
NoPackageOption no_package_option = 123456789;
20+
}

internal/testprotos/test/test_nopackage/test_nopackage.pb.go

Lines changed: 160 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
edition = "2024";
6+
7+
package goproto.proto.test;
8+
9+
import option "internal/testprotos/test/test_nopackage.proto";
10+
import option "google/protobuf/go_features.proto";
11+
12+
option go_package = "google.golang.org/protobuf/internal/testprotos/test/test_option";
13+
option features.(pb.go).api_level = API_OPAQUE;
14+
option (no_package_option).name = "no package option";
15+
16+
message OptionImportMessage {
17+
string name = 1;
18+
}

0 commit comments

Comments
 (0)