Skip to content

Commit 598de06

Browse files
authored
feat(librariangen): add protoc package (#3935)
This will be used for constructing the protoc command for generating Java GAPICs. Based on https://github.com/googleapis/google-cloud-go/tree/main/internal/librariangen/protoc with the following important changes: * The `Build` function in `protoc.go` is updated to construct the `protoc` command with the correct arguments for Java GAPIC generation. * The tests in `protoc_test.go` are updated to reflect the changes in `protoc.go`. * The `gapicImportPath` is removed from the test configuration, as it is not relevant for Java.' * The testdata is included with modifications for Java.
1 parent f32325e commit 598de06

File tree

9 files changed

+549
-0
lines changed

9 files changed

+549
-0
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2025 Google LLC
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+
package protoc
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"path/filepath"
21+
"strings"
22+
23+
)
24+
25+
// ConfigProvider is an interface that describes the configuration needed
26+
// by the Build function. This allows the protoc package to be decoupled
27+
// from the source of the configuration (e.g., Bazel files, JSON, etc.).
28+
type ConfigProvider interface {
29+
ServiceYAML() string
30+
GapicYAML() string
31+
GRPCServiceConfig() string
32+
Transport() string
33+
HasRESTNumericEnums() bool
34+
HasGAPIC() bool
35+
}
36+
37+
// Build constructs the full protoc command arguments for a given API.
38+
func Build(apiServiceDir string, config ConfigProvider, sourceDir, outputDir string) ([]string, error) {
39+
// Gather all .proto files in the API's source directory.
40+
entries, err := os.ReadDir(apiServiceDir)
41+
if err != nil {
42+
return nil, fmt.Errorf("librariangen: failed to read API source directory %s: %w", apiServiceDir, err)
43+
}
44+
45+
var protoFiles []string
46+
for _, entry := range entries {
47+
if !entry.IsDir() && filepath.Ext(entry.Name()) == ".proto" {
48+
protoFiles = append(protoFiles, filepath.Join(apiServiceDir, entry.Name()))
49+
}
50+
}
51+
52+
if len(protoFiles) == 0 {
53+
return nil, fmt.Errorf("librariangen: no .proto files found in %s", apiServiceDir)
54+
}
55+
56+
// Construct the protoc command arguments.
57+
var gapicOpts []string
58+
if config.HasGAPIC() {
59+
if config.ServiceYAML() != "" {
60+
gapicOpts = append(gapicOpts, fmt.Sprintf("api-service-config=%s", filepath.Join(apiServiceDir, config.ServiceYAML())))
61+
}
62+
if config.GapicYAML() != "" {
63+
gapicOpts = append(gapicOpts, fmt.Sprintf("gapic-config=%s", filepath.Join(apiServiceDir, config.GapicYAML())))
64+
}
65+
if config.GRPCServiceConfig() != "" {
66+
gapicOpts = append(gapicOpts, fmt.Sprintf("grpc-service-config=%s", filepath.Join(apiServiceDir, config.GRPCServiceConfig())))
67+
}
68+
if config.Transport() != "" {
69+
gapicOpts = append(gapicOpts, fmt.Sprintf("transport=%s", config.Transport()))
70+
}
71+
if config.HasRESTNumericEnums() {
72+
gapicOpts = append(gapicOpts, "rest-numeric-enums")
73+
}
74+
}
75+
76+
args := []string{
77+
"protoc",
78+
"--experimental_allow_proto3_optional",
79+
}
80+
81+
args = append(args, fmt.Sprintf("--java_out=%s", outputDir))
82+
if config.HasGAPIC() {
83+
args = append(args, fmt.Sprintf("--java_gapic_out=metadata:%s", filepath.Join(outputDir, "java_gapic.zip")))
84+
85+
if len(gapicOpts) > 0 {
86+
args = append(args, "--java_gapic_opt="+strings.Join(gapicOpts, ","))
87+
}
88+
}
89+
90+
args = append(args,
91+
// The -I flag specifies the import path for protoc. All protos
92+
// and their dependencies must be findable from this path.
93+
// The /source mount contains the complete googleapis repository.
94+
"-I="+sourceDir,
95+
)
96+
97+
args = append(args, protoFiles...)
98+
99+
return args, nil
100+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright 2025 Google LLC
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+
package protoc
16+
17+
import (
18+
"path/filepath"
19+
"strings"
20+
"testing"
21+
22+
"github.com/google/go-cmp/cmp"
23+
)
24+
25+
// mockConfigProvider is a mock implementation of the ConfigProvider interface for testing.
26+
type mockConfigProvider struct {
27+
serviceYAML string
28+
gapicYAML string
29+
grpcServiceConfig string
30+
transport string
31+
restNumericEnums bool
32+
hasGAPIC bool
33+
}
34+
35+
func (m *mockConfigProvider) ServiceYAML() string { return m.serviceYAML }
36+
func (m *mockConfigProvider) GapicYAML() string { return m.gapicYAML }
37+
func (m *mockConfigProvider) GRPCServiceConfig() string { return m.grpcServiceConfig }
38+
func (m *mockConfigProvider) Transport() string { return m.transport }
39+
func (m *mockConfigProvider) HasRESTNumericEnums() bool { return m.restNumericEnums }
40+
func (m *mockConfigProvider) HasGAPIC() bool { return m.hasGAPIC }
41+
42+
func TestBuild(t *testing.T) {
43+
// The testdata directory is a curated version of a valid protoc
44+
// import path that contains all the necessary proto definitions.
45+
sourceDir, err := filepath.Abs("../testdata/generate/source")
46+
if err != nil {
47+
t.Fatalf("failed to get absolute path for sourceDir: %v", err)
48+
}
49+
tests := []struct {
50+
name string
51+
apiPath string
52+
config mockConfigProvider
53+
want []string
54+
}{
55+
{
56+
name: "java_grpc_library rule",
57+
apiPath: "google/cloud/workflows/v1",
58+
config: mockConfigProvider{
59+
transport: "grpc",
60+
grpcServiceConfig: "workflows_grpc_service_config.json",
61+
gapicYAML: "workflows_gapic.yaml",
62+
serviceYAML: "workflows_v1.yaml",
63+
restNumericEnums: true,
64+
hasGAPIC: true,
65+
},
66+
want: []string{
67+
"protoc",
68+
"--experimental_allow_proto3_optional",
69+
"--java_out=/output",
70+
"--java_gapic_out=metadata:/output/java_gapic.zip",
71+
"--java_gapic_opt=" + strings.Join([]string{
72+
"api-service-config=" + filepath.Join(sourceDir, "google/cloud/workflows/v1/workflows_v1.yaml"),
73+
"gapic-config=" + filepath.Join(sourceDir, "google/cloud/workflows/v1/workflows_gapic.yaml"),
74+
"grpc-service-config=" + filepath.Join(sourceDir, "google/cloud/workflows/v1/workflows_grpc_service_config.json"),
75+
"transport=grpc",
76+
"rest-numeric-enums",
77+
}, ","),
78+
"-I=" + sourceDir,
79+
filepath.Join(sourceDir, "google/cloud/workflows/v1/workflows.proto"),
80+
},
81+
},
82+
{
83+
name: "java_proto_library rule with legacy gRPC",
84+
apiPath: "google/cloud/secretmanager/v1beta2",
85+
config: mockConfigProvider{
86+
transport: "grpc",
87+
grpcServiceConfig: "secretmanager_grpc_service_config.json",
88+
serviceYAML: "secretmanager_v1beta2.yaml",
89+
restNumericEnums: true,
90+
hasGAPIC: true,
91+
},
92+
want: []string{
93+
"protoc",
94+
"--experimental_allow_proto3_optional",
95+
"--java_out=/output",
96+
"--java_gapic_out=metadata:/output/java_gapic.zip",
97+
"--java_gapic_opt=" + strings.Join([]string{
98+
"api-service-config=" + filepath.Join(sourceDir, "google/cloud/secretmanager/v1beta2/secretmanager_v1beta2.yaml"),
99+
"grpc-service-config=" + filepath.Join(sourceDir, "google/cloud/secretmanager/v1beta2/secretmanager_grpc_service_config.json"),
100+
"transport=grpc",
101+
"rest-numeric-enums",
102+
}, ","),
103+
"-I=" + sourceDir,
104+
filepath.Join(sourceDir, "google/cloud/secretmanager/v1beta2/secretmanager.proto"),
105+
},
106+
},
107+
{
108+
// Note: we don't have a separate test directory with a proto-only library;
109+
// the config is used to say "don't generate GAPIC".
110+
name: "proto-only",
111+
apiPath: "google/cloud/secretmanager/v1beta2",
112+
config: mockConfigProvider{
113+
hasGAPIC: false,
114+
},
115+
want: []string{
116+
"protoc",
117+
"--experimental_allow_proto3_optional",
118+
"--java_out=/output",
119+
"-I=" + sourceDir,
120+
filepath.Join(sourceDir, "google/cloud/secretmanager/v1beta2/secretmanager.proto"),
121+
},
122+
},
123+
}
124+
125+
for _, tt := range tests {
126+
t.Run(tt.name, func(t *testing.T) {
127+
got, err := Build(filepath.Join(sourceDir, tt.apiPath), &tt.config, sourceDir, "/output")
128+
if err != nil {
129+
t.Fatalf("Build() failed: %v", err)
130+
}
131+
132+
if diff := cmp.Diff(tt.want, got); diff != "" {
133+
t.Errorf("Build() mismatch (-want +got):\n%s", diff)
134+
}
135+
})
136+
}
137+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2025 Google LLC
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+
syntax = "proto3";
16+
17+
package google.api;
18+
19+
import "google/protobuf/descriptor.proto";
20+
21+
option java_multiple_files = true;
22+
option java_outer_classname = "AnnotationsProto";
23+
option java_package = "com.google.api";
24+
25+
extend google.protobuf.MethodOptions {
26+
HttpRule http = 72295728;
27+
}
28+
29+
message HttpRule {
30+
string selector = 1;
31+
oneof pattern {
32+
string get = 2;
33+
string put = 3;
34+
string post = 4;
35+
string delete = 5;
36+
string patch = 6;
37+
CustomHttpPattern custom = 8;
38+
}
39+
string body = 7;
40+
repeated HttpRule additional_bindings = 11;
41+
}
42+
43+
message CustomHttpPattern {
44+
string kind = 1;
45+
string path = 2;
46+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
java_proto_library(
2+
name = "secretmanager_java_proto",
3+
deps = [":secretmanager_proto"],
4+
)
5+
6+
java_grpc_library(
7+
name = "secretmanager_java_grpc",
8+
srcs = [":secretmanager_proto"],
9+
deps = [":secretmanager_java_proto"],
10+
)
11+
12+
java_gapic_library(
13+
name = "secretmanager_java_gapic",
14+
srcs = [":secretmanager_proto_with_info"],
15+
gapic_yaml = None,
16+
grpc_service_config = "secretmanager_grpc_service_config.json",
17+
rest_numeric_enums = True,
18+
service_yaml = "secretmanager_v1beta2.yaml",
19+
test_deps = [
20+
"//google/cloud/location:location_java_grpc",
21+
"//google/iam/v1:iam_java_grpc",
22+
":secretmanager_java_grpc",
23+
],
24+
transport = "grpc+rest",
25+
deps = [
26+
":secretmanager_java_proto",
27+
"//google/api:api_java_proto",
28+
"//google/cloud/location:location_java_proto",
29+
"//google/iam/v1:iam_java_proto",
30+
],
31+
)
32+
33+
java_gapic_test(
34+
name = "secretmanager_java_gapic_test_suite",
35+
test_classes = [
36+
"com.google.cloud.secretmanager.v1beta2.SecretManagerServiceClientHttpJsonTest",
37+
"com.google.cloud.secretmanager.v1beta2.SecretManagerServiceClientTest",
38+
],
39+
runtime_deps = [":secretmanager_java_gapic_test"],
40+
)
41+
42+
# Open Source Packages
43+
java_gapic_assembly_gradle_pkg(
44+
name = "google-cloud-secretmanager-v1beta2-java",
45+
transport = "grpc+rest",
46+
deps = [
47+
":secretmanager_java_gapic",
48+
":secretmanager_java_grpc",
49+
":secretmanager_java_proto",
50+
":secretmanager_proto",
51+
],
52+
include_samples = True,
53+
)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2025 Google LLC
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+
syntax = "proto3";
16+
17+
package google.cloud.secretmanager.v1beta2;
18+
19+
import "google/api/annotations.proto";
20+
21+
option java_multiple_files = true;
22+
option java_outer_classname = "ServiceProto";
23+
option java_package = "com.google.cloud.secretmanager.v1beta2";
24+
25+
// A Secret is a secret value.
26+
message Secret {
27+
string name = 1;
28+
}
29+
30+
// Request for the `ListSecrets` method.
31+
message ListSecretsRequest {
32+
string parent = 1;
33+
}
34+
35+
// Response for the `ListSecrets` method.
36+
message ListSecretsResponse {
37+
repeated Secret Secrets = 1;
38+
}
39+
40+
// Service for managing secrets.
41+
service Secrets {
42+
// Lists Secrets in a given project and location.
43+
rpc ListSecrets(ListSecretsRequest) returns (ListSecretsResponse) {
44+
option (google.api.http) = {
45+
get: "/v1/{parent=projects/*/locations/*}/Secrets"
46+
};
47+
}
48+
}

0 commit comments

Comments
 (0)