Skip to content

Commit 6a58759

Browse files
ohmayrparthea
andauthored
feat: determine bazel rule for an api path in generate (#14124)
Fixes googleapis/librarian#903 --------- Co-authored-by: Anthonios Partheniou <[email protected]>
1 parent 2ce745c commit 6a58759

File tree

3 files changed

+93
-10
lines changed

3 files changed

+93
-10
lines changed

.generator/Dockerfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ RUN apt-get update && \
2222
# For downloading secure files
2323
wget \
2424
ca-certificates \
25+
# For running bazelisk commands
26+
openjdk-17-jdk \
2527
# --- Critical libraries for a complete Python build ---
2628
libssl-dev \
2729
zlib1g-dev \
@@ -35,6 +37,7 @@ RUN apt-get update && \
3537

3638
# Set up environment variables for tool versions to make updates easier.
3739
ENV PYTHON_VERSION=3.11.5
40+
ENV BAZELISK_VERSION=v1.26.0
3841

3942
# Create a symbolic link for `python3` to point to our specific version.
4043
ENV PATH /usr/local/bin/python3.11:$PATH
@@ -50,7 +53,9 @@ RUN wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VER
5053

5154
# TODO(https://github.com/googleapis/librarian/issues/904): Install protoc for gencode.
5255

53-
# TODO(https://github.com/googleapis/librarian/issues/903): Install Bazelisk for building a client library.
56+
# Install Bazelisk
57+
RUN wget https://github.com/bazelbuild/bazelisk/releases/download/${BAZELISK_VERSION}/bazelisk-linux-amd64 -O /usr/local/bin/bazelisk && \
58+
chmod +x /usr/local/bin/bazelisk
5459

5560
# TODO(https://github.com/googleapis/librarian/issues/902): Create a dedicate non-root user and
5661
# switch to the non-root user to run subsequent commands.

.generator/cli.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
import logging
1818
import os
1919
import sys
20+
import subprocess
2021

2122
logger = logging.getLogger()
2223

2324
LIBRARIAN_DIR = "librarian"
2425
GENERATE_REQUEST_FILE = "generate-request.json"
26+
SOURCE_DIR = "source"
2527

2628

2729
def _read_json_file(path):
@@ -47,6 +49,39 @@ def handle_configure():
4749
logger.info("'configure' command executed.")
4850

4951

52+
def _determine_bazel_rule(api_path):
53+
"""Executes a `bazelisk query` to find a Bazel rule.
54+
55+
Args:
56+
api_path (str): The API path to query for.
57+
58+
Returns:
59+
str: The discovered Bazel rule.
60+
61+
Raises:
62+
Exception: If the subprocess call fails or returns an empty result.
63+
"""
64+
logger.info(f"Determining Bazel rule for api_path: '{api_path}'")
65+
try:
66+
query = f'filter("-py$", kind("rule", //{api_path}/...:*))'
67+
command = ["bazelisk", "query", query]
68+
result = subprocess.run(
69+
command,
70+
cwd=f"{SOURCE_DIR}/googleapis",
71+
capture_output=True,
72+
text=True,
73+
check=True,
74+
)
75+
bazel_rule = result.stdout.strip()
76+
if not bazel_rule:
77+
raise ValueError(f"Bazelisk query `{query}` returned an empty bazel rule.")
78+
79+
logger.info(f"Found Bazel rule: {bazel_rule}")
80+
return bazel_rule
81+
except Exception as e:
82+
raise ValueError(f"Bazelisk query `{query}` failed") from e
83+
84+
5085
def handle_generate():
5186
"""The main coordinator for the code generation process.
5287
@@ -61,12 +96,18 @@ def handle_generate():
6196
# Read a generate-request.json file
6297
try:
6398
request_data = _read_json_file(f"{LIBRARIAN_DIR}/{GENERATE_REQUEST_FILE}")
64-
except Exception as e:
65-
raise ValueError(
66-
f"failed to read {LIBRARIAN_DIR}/{GENERATE_REQUEST_FILE}"
67-
) from e
99+
library_id = request_data.get("id")
100+
if not library_id:
101+
raise ValueError("Request file is missing required 'id' field.")
102+
103+
for api in request_data.get("apis", []):
104+
api_path = api.get("path")
105+
if api_path:
106+
bazel_rule = _determine_bazel_rule(api_path)
68107

69-
logger.info(json.dumps(request_data, indent=2))
108+
logger.info(json.dumps(request_data, indent=2))
109+
except Exception as e:
110+
raise ValueError("Generation failed.") from e
70111

71112
# TODO(https://github.com/googleapis/librarian/issues/448): Implement generate command and update docstring.
72113
logger.info("'generate' command executed.")

.generator/test_cli.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
import pytest
1717
import json
1818
import logging
19+
import subprocess
1920

20-
from unittest.mock import mock_open
21+
from unittest.mock import mock_open, MagicMock
2122

2223
from cli import (
2324
_read_json_file,
25+
_determine_bazel_rule,
2426
handle_generate,
2527
handle_build,
2628
handle_configure,
@@ -60,16 +62,51 @@ def test_handle_configure_success(caplog, mock_generate_request_file):
6062
assert "'configure' command executed." in caplog.text
6163

6264

63-
def test_handle_generate_success(caplog, mock_generate_request_file):
65+
def test_determine_bazel_rule_success(mocker, caplog):
66+
"""
67+
Tests the happy path of _determine_bazel_rule.
68+
"""
69+
caplog.set_level(logging.INFO)
70+
mock_result = MagicMock(
71+
stdout="//google/cloud/language/v1:google-cloud-language-v1-py\n"
72+
)
73+
mocker.patch("cli.subprocess.run", return_value=mock_result)
74+
75+
rule = _determine_bazel_rule("google/cloud/language/v1")
76+
77+
assert rule == "//google/cloud/language/v1:google-cloud-language-v1-py"
78+
assert "Found Bazel rule" in caplog.text
79+
80+
81+
def test_determine_bazel_rule_command_fails(mocker, caplog):
82+
"""
83+
Tests that an exception is raised if the subprocess command fails.
84+
"""
85+
caplog.set_level(logging.INFO)
86+
mocker.patch(
87+
"cli.subprocess.run",
88+
side_effect=subprocess.CalledProcessError(1, "cmd", stderr="Bazel error"),
89+
)
90+
91+
with pytest.raises(ValueError):
92+
_determine_bazel_rule("google/cloud/language/v1")
93+
94+
assert "Found Bazel rule" not in caplog.text
95+
96+
97+
def test_handle_generate_success(caplog, mock_generate_request_file, mocker):
6498
"""
6599
Tests the successful execution path of handle_generate.
66100
"""
67101
caplog.set_level(logging.INFO)
68102

103+
mock_determine_rule = mocker.patch(
104+
"cli._determine_bazel_rule", return_value="mock-rule"
105+
)
106+
69107
handle_generate()
70108

71-
assert "google-cloud-language" in caplog.text
72-
assert "'generate' command executed." in caplog.text
109+
mock_determine_rule.assert_called_once_with("google/cloud/language/v1")
73110

74111

75112
def test_handle_generate_fail(caplog):

0 commit comments

Comments
 (0)