Skip to content

Commit 971fbd6

Browse files
committed
feat(librariangen): add bazel package
Based on https://github.com/googleapis/google-cloud-go/tree/main/internal/librariangen/bazel with adaptation for Java.
1 parent 4223a86 commit 971fbd6

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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 bazel
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
"log/slog"
21+
"os"
22+
"path/filepath"
23+
"regexp"
24+
"strconv"
25+
"strings"
26+
)
27+
28+
// Config holds configuration extracted from a googleapis BUILD.bazel file.
29+
type Config struct {
30+
grpcServiceConfig string
31+
restNumericEnums bool
32+
serviceYAML string
33+
transport string
34+
hasGAPIC bool
35+
}
36+
37+
// HasGAPIC indicates whether the GAPIC generator should be run.
38+
func (c *Config) HasGAPIC() bool { return c.hasGAPIC }
39+
40+
// ServiceYAML is the client config file in the API version directory in googleapis.
41+
func (c *Config) ServiceYAML() string { return c.serviceYAML }
42+
43+
// GRPCServiceConfig is name of the gRPC service config JSON file.
44+
func (c *Config) GRPCServiceConfig() string { return c.grpcServiceConfig }
45+
46+
// Transport is typically one of "grpc", "rest" or "grpc+rest".
47+
func (c *Config) Transport() string { return c.transport }
48+
49+
// HasRESTNumericEnums indicates whether the generated client should support numeric enums.
50+
func (c *Config) HasRESTNumericEnums() bool { return c.restNumericEnums }
51+
52+
// Validate ensures that the configuration is valid.
53+
func (c *Config) Validate() error {
54+
if c.hasGAPIC {
55+
if c.serviceYAML == "" {
56+
return errors.New("librariangen: serviceYAML is not set")
57+
}
58+
}
59+
return nil
60+
}
61+
62+
// Parse reads a BUILD.bazel file from the given directory and extracts the
63+
// relevant configuration from the java_gapic_library rule.
64+
func Parse(dir string) (*Config, error) {
65+
c := &Config{}
66+
fp := filepath.Join(dir, "BUILD.bazel")
67+
data, err := os.ReadFile(fp)
68+
if err != nil {
69+
return nil, fmt.Errorf("librariangen: failed to read BUILD.bazel file %s: %w", fp, err)
70+
}
71+
content := string(data)
72+
73+
re := regexp.MustCompile(`java_gapic_library\((?s:.)*?\)`)
74+
gapicLibraryBlock := re.FindString(content)
75+
if gapicLibraryBlock != "" {
76+
c.hasGAPIC = true
77+
c.grpcServiceConfig = findString(gapicLibraryBlock, "grpc_service_config")
78+
c.serviceYAML = strings.TrimPrefix(findString(gapicLibraryBlock, "service_yaml"), ":")
79+
c.transport = findString(gapicLibraryBlock, "transport")
80+
if c.restNumericEnums, err = findBool(gapicLibraryBlock, "rest_numeric_enums"); err != nil {
81+
return nil, fmt.Errorf("librariangen: failed to parse BUILD.bazel file %s: %w", fp, err)
82+
}
83+
}
84+
if err := c.Validate(); err != nil {
85+
return nil, fmt.Errorf("librariangen: invalid bazel config in %s: %w", dir, err)
86+
}
87+
slog.Debug("librariangen: bazel config loaded", "conf", fmt.Sprintf("%+v", c))
88+
return c, nil
89+
}
90+
91+
func findString(content, name string) string {
92+
re := regexp.MustCompile(fmt.Sprintf(`%s\s*=\s*"([^"]+)"`, name))
93+
if match := re.FindStringSubmatch(content); len(match) > 1 {
94+
return match[1]
95+
}
96+
slog.Debug("librariangen: failed to find string attr in BUILD.bazel", "name", name)
97+
return ""
98+
}
99+
100+
func findBool(content, name string) (bool, error) {
101+
re := regexp.MustCompile(fmt.Sprintf(`%s\s*=\s*(\w+)`, name))
102+
if match := re.FindStringSubmatch(content); len(match) > 1 {
103+
if b, err := strconv.ParseBool(match[1]); err == nil {
104+
return b, nil
105+
}
106+
return false, fmt.Errorf("librariangen: failed to parse bool attr in BUILD.bazel: %q, got: %q", name, match[1])
107+
}
108+
slog.Debug("librariangen: failed to find bool attr in BUILD.bazel", "name", name)
109+
return false, nil
110+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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 bazel
16+
17+
import (
18+
"os"
19+
"path/filepath"
20+
"testing"
21+
)
22+
23+
func TestParse(t *testing.T) {
24+
content := `
25+
java_grpc_library(
26+
name = "asset_java_grpc",
27+
srcs = [":asset_proto"],
28+
deps = [":asset_java_proto"],
29+
)
30+
31+
java_gapic_library(
32+
name = "asset_java_gapic",
33+
srcs = [":asset_proto_with_info"],
34+
grpc_service_config = "cloudasset_grpc_service_config.json",
35+
rest_numeric_enums = True,
36+
service_yaml = "cloudasset_v1.yaml",
37+
test_deps = [
38+
":asset_java_grpc",
39+
"//google/iam/v1:iam_java_grpc",
40+
],
41+
transport = "grpc+rest",
42+
deps = [
43+
":asset_java_proto",
44+
"//google/api:api_java_proto",
45+
"//google/iam/v1:iam_java_proto",
46+
],
47+
)
48+
`
49+
tmpDir := t.TempDir()
50+
buildPath := filepath.Join(tmpDir, "BUILD.bazel")
51+
if err := os.WriteFile(buildPath, []byte(content), 0644); err != nil {
52+
t.Fatalf("failed to write test file: %v", err)
53+
}
54+
55+
got, err := Parse(tmpDir)
56+
if err != nil {
57+
t.Fatalf("Parse() failed: %v", err)
58+
}
59+
60+
if !got.HasGAPIC() {
61+
t.Error("HasGAPIC() = false; want true")
62+
}
63+
if want := "cloudasset_v1.yaml"; got.ServiceYAML() != want {
64+
t.Errorf("ServiceYAML() = %q; want %q", got.ServiceYAML(), want)
65+
}
66+
if want := "cloudasset_grpc_service_config.json"; got.GRPCServiceConfig() != want {
67+
t.Errorf("GRPCServiceConfig() = %q; want %q", got.GRPCServiceConfig(), want)
68+
}
69+
if want := "grpc+rest"; got.Transport() != want {
70+
t.Errorf("Transport() = %q; want %q", got.Transport(), want)
71+
}
72+
if !got.HasRESTNumericEnums() {
73+
t.Error("HasRESTNumericEnums() = false; want true")
74+
}
75+
}
76+
77+
func TestParse_serviceConfigIsTarget(t *testing.T) {
78+
content := `
79+
java_grpc_library(
80+
name = "asset_java_grpc",
81+
srcs = [":asset_proto"],
82+
deps = [":asset_java_proto"],
83+
)
84+
85+
java_gapic_library(
86+
name = "asset_java_gapic",
87+
srcs = [":asset_proto_with_info"],
88+
grpc_service_config = "cloudasset_grpc_service_config.json",
89+
rest_numeric_enums = True,
90+
service_yaml = ":cloudasset_v1.yaml",
91+
test_deps = [
92+
":asset_java_grpc",
93+
"//google/iam/v1:iam_java_grpc",
94+
],
95+
transport = "grpc+rest",
96+
deps = [
97+
":asset_java_proto",
98+
"//google/api:api_java_proto",
99+
"//google/iam/v1:iam_java_proto",
100+
],
101+
)
102+
`
103+
tmpDir := t.TempDir()
104+
buildPath := filepath.Join(tmpDir, "BUILD.bazel")
105+
if err := os.WriteFile(buildPath, []byte(content), 0644); err != nil {
106+
t.Fatalf("failed to write test file: %v", err)
107+
}
108+
109+
got, err := Parse(tmpDir)
110+
if err != nil {
111+
t.Fatalf("Parse() failed: %v", err)
112+
}
113+
114+
if want := "cloudasset_v1.yaml"; got.ServiceYAML() != want {
115+
t.Errorf("ServiceYAML() = %q; want %q", got.ServiceYAML(), want)
116+
}
117+
}
118+
119+
func TestConfig_Validate(t *testing.T) {
120+
tests := []struct {
121+
name string
122+
cfg *Config
123+
wantErr bool
124+
}{
125+
{
126+
name: "valid GAPIC",
127+
cfg: &Config{
128+
hasGAPIC: true,
129+
serviceYAML: "b",
130+
grpcServiceConfig: "c",
131+
transport: "d",
132+
},
133+
wantErr: false,
134+
},
135+
{
136+
name: "valid non-GAPIC",
137+
cfg: &Config{},
138+
wantErr: false,
139+
},
140+
{
141+
name: "gRPC service config and transport are optional",
142+
cfg: &Config{hasGAPIC: true, serviceYAML: "b"},
143+
wantErr: false,
144+
},
145+
{
146+
name: "missing serviceYAML",
147+
cfg: &Config{hasGAPIC: true, grpcServiceConfig: "c", transport: "d"},
148+
wantErr: true,
149+
},
150+
}
151+
for _, tt := range tests {
152+
t.Run(tt.name, func(t *testing.T) {
153+
if err := tt.cfg.Validate(); (err != nil) != tt.wantErr {
154+
t.Errorf("Config.Validate() error = %v, wantErr %v", err, tt.wantErr)
155+
}
156+
})
157+
}
158+
}
159+
160+
func TestParse_noGapic(t *testing.T) {
161+
content := `
162+
java_grpc_library(
163+
name = "asset_java_grpc",
164+
srcs = [":asset_proto"],
165+
deps = [":asset_java_proto"],
166+
)
167+
`
168+
tmpDir := t.TempDir()
169+
buildPath := filepath.Join(tmpDir, "BUILD.bazel")
170+
if err := os.WriteFile(buildPath, []byte(content), 0644); err != nil {
171+
t.Fatalf("failed to write test file: %v", err)
172+
}
173+
174+
got, err := Parse(tmpDir)
175+
if err != nil {
176+
t.Fatalf("Parse() failed: %v", err)
177+
}
178+
179+
if got.HasGAPIC() {
180+
t.Error("HasGAPIC() = true; want false")
181+
}
182+
}

0 commit comments

Comments
 (0)