Skip to content

Commit 8929aab

Browse files
authored
chore: migrate off of bazelisk query command (#14214)
This PR migrates off of the `bazelisk query` command and instead uses a regex to read the bazel rule from the `BUILD.bazel` file directly.
1 parent a5ad4bc commit 8929aab

File tree

2 files changed

+55
-30
lines changed

2 files changed

+55
-30
lines changed

.generator/cli.py

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import json
1717
import logging
1818
import os
19+
import re
1920
import subprocess
2021
import sys
2122
import subprocess
@@ -66,37 +67,43 @@ def handle_configure():
6667

6768

6869
def _determine_bazel_rule(api_path: str, source: str) -> str:
69-
"""Executes a `bazelisk query` to find a Bazel rule.
70+
"""Finds a Bazel rule by parsing the BUILD.bazel file directly.
7071
7172
Args:
72-
api_path(str): The API path to query for.
73+
api_path (str): The API path, e.g., 'google/cloud/language/v1'.
7374
source(str): The path to the root of the Bazel workspace.
7475
7576
Returns:
76-
str: The discovered Bazel rule.
77+
str: The discovered Bazel rule, e.g., '//google/cloud/language/v1:language-v1-py'.
7778
7879
Raises:
79-
ValueError: If the subprocess call fails or returns an empty result.
80+
ValueError: If the file can't be processed or no matching rule is found.
8081
"""
81-
logger.info(f"Determining Bazel rule for api_path: '{api_path}'")
82+
logger.info(f"Determining Bazel rule for api_path: '{api_path}' by parsing file.")
8283
try:
83-
query = f'filter("-py$", kind("rule", //{api_path}/...:*))'
84-
command = ["bazelisk", "query", query]
85-
result = subprocess.run(
86-
command,
87-
cwd=source,
88-
capture_output=True,
89-
text=True,
90-
check=True,
91-
)
92-
bazel_rule = result.stdout.strip()
93-
if not bazel_rule:
94-
raise ValueError(f"Bazelisk query `{query}` returned an empty bazel rule.")
84+
build_file_path = os.path.join(source, api_path, "BUILD.bazel")
85+
86+
with open(build_file_path, "r") as f:
87+
content = f.read()
88+
89+
match = re.search(r'name\s*=\s*"([^"]+-py)"', content)
90+
91+
# This check is for a logical failure (no match), not a runtime exception.
92+
# It's good to keep it for clear error messaging.
93+
if not match:
94+
raise ValueError(
95+
f"No Bazel rule with a name ending in '-py' found in {build_file_path}"
96+
)
97+
98+
rule_name = match.group(1)
99+
bazel_rule = f"//{api_path}:{rule_name}"
95100

96101
logger.info(f"Found Bazel rule: {bazel_rule}")
97102
return bazel_rule
98103
except Exception as e:
99-
raise ValueError(f"Bazelisk query `{query}` failed") from e
104+
raise ValueError(
105+
f"Failed to determine Bazel rule for '{api_path}' by parsing."
106+
) from e
100107

101108

102109
def _get_library_id(request_data: Dict) -> str:
@@ -227,11 +234,11 @@ def handle_generate(
227234
Args:
228235
librarian(str): Path to the directory in the container which contains
229236
the librarian configuration.
230-
source(str): Path to the directory in the container which contains
237+
source(str): Path to the directory in the container which contains
231238
API protos.
232-
output(str): Path to the directory in the container where code
239+
output(str): Path to the directory in the container where code
233240
should be generated.
234-
input(str): The path path to the directory in the container
241+
input(str): The path path to the directory in the container
235242
which contains additional generator input.
236243
237244
Raises:

.generator/test_cli.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,8 @@ def test_determine_bazel_rule_success(mocker, caplog):
110110
Tests the happy path of _determine_bazel_rule.
111111
"""
112112
caplog.set_level(logging.INFO)
113-
mock_result = MagicMock(
114-
stdout="//google/cloud/language/v1:google-cloud-language-v1-py\n"
115-
)
116-
mocker.patch("cli.subprocess.run", return_value=mock_result)
113+
mock_content = 'name = "google-cloud-language-v1-py",\n'
114+
mocker.patch("cli.open", mock_open(read_data=mock_content))
117115

118116
rule = _determine_bazel_rule("google/cloud/language/v1", "source")
119117

@@ -131,15 +129,35 @@ def test_build_bazel_target_success(mocker, caplog):
131129
assert "Bazel build for mock/bazel:rule rule completed successfully" in caplog.text
132130

133131

132+
def test_build_bazel_target_fails_to_find_rule_match(mocker, caplog):
133+
"""
134+
Tests that ValueError is raised if the subprocess command fails.
135+
"""
136+
caplog.set_level(logging.ERROR)
137+
mock_content = '"google-cloud-language-v1-py",\n'
138+
mocker.patch("cli.open", mock_open(read_data=mock_content))
139+
140+
with pytest.raises(ValueError):
141+
_build_bazel_target("mock/bazel:rule", "source")
142+
143+
144+
def test_build_bazel_target_fails_to_determine_rule(caplog):
145+
"""
146+
Tests that ValueError is raised if the subprocess command fails.
147+
"""
148+
caplog.set_level(logging.ERROR)
149+
with pytest.raises(ValueError):
150+
_build_bazel_target("mock/bazel:rule", "source")
151+
152+
134153
def test_build_bazel_target_fails(mocker, caplog):
135154
"""
136155
Tests that ValueError is raised if the subprocess command fails.
137156
"""
138157
caplog.set_level(logging.ERROR)
139-
mocker.patch(
140-
"cli.subprocess.run",
141-
side_effect=subprocess.CalledProcessError(1, "cmd", stderr="Build failed"),
142-
)
158+
mock_content = '"google-cloud-language-v1-py",\n'
159+
mocker.patch("cli.open", mock_open(read_data=mock_content))
160+
143161
with pytest.raises(ValueError):
144162
_build_bazel_target("mock/bazel:rule", "source")
145163

@@ -174,7 +192,7 @@ def test_locate_and_extract_artifact_success(mocker, caplog):
174192
"google-cloud-language",
175193
"source",
176194
"output",
177-
"google/cloud/language/v1"
195+
"google/cloud/language/v1",
178196
)
179197
assert (
180198
"Found artifact at: /path/to/bazel-bin/google/cloud/language/v1/rule-py.tar.gz"

0 commit comments

Comments
 (0)