Skip to content

Commit 98fc35a

Browse files
committed
chore(librarian): add support for proto-only library
1 parent 2f59cd6 commit 98fc35a

File tree

4 files changed

+278
-29
lines changed

4 files changed

+278
-29
lines changed

.generator/cli.py

Lines changed: 125 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,13 @@ def _run_post_processor(output: str, library_id: str):
300300
python_mono_repo.owlbot_main(path_to_library)
301301
else:
302302
raise SYNTHTOOL_IMPORT_ERROR # pragma: NO COVER
303+
304+
# If there is no noxfile, run `isort`` and `black` on the output.
305+
# This is required for proto-only libraries which are not GAPIC.
306+
if not Path(f"{output}/{path_to_library}/noxfile.py").exists():
307+
subprocess.run(["isort", output])
308+
subprocess.run(["black", output])
309+
303310
logger.info("Python post-processor ran successfully.")
304311

305312

@@ -541,16 +548,19 @@ def _read_bazel_build_py_rule(api_path: str, source: str) -> Dict:
541548
source (str): Path to the directory containing API protos.
542549
543550
Returns:
544-
Dict: A dictionary containing the parsed attributes of the `_py_gapic` rule.
551+
Dict: A dictionary containing the parsed attributes of the `_py_gapic` rule, if found.
545552
"""
546553
build_file_path = f"{source}/{api_path}/BUILD.bazel"
547554
content = _read_text_file(build_file_path)
548555

549556
result = parse_googleapis_content.parse_content(content)
550557
py_gapic_entries = [key for key in result.keys() if key.endswith("_py_gapic")]
551558

552-
# Assuming only one _py_gapic rule per BUILD file for a given language
553-
return result[py_gapic_entries[0]]
559+
# Assuming at most one _py_gapic rule per BUILD file for a given language
560+
if len(py_gapic_entries) > 0:
561+
return result[py_gapic_entries[0]]
562+
else:
563+
return {}
554564

555565

556566
def _get_api_generator_options(
@@ -598,6 +608,26 @@ def _get_api_generator_options(
598608
return generator_options
599609

600610

611+
def _construct_protoc_command(api_path: str, tmp_dir: str) -> str:
612+
"""
613+
Constructs the full protoc command string.
614+
615+
Args:
616+
api_path (str): The relative path to the API directory.
617+
tmp_dir (str): The temporary directory for protoc output.
618+
619+
Returns:
620+
str: The complete protoc command string suitable for shell execution.
621+
"""
622+
command_parts = [
623+
f"protoc {api_path}/*.proto",
624+
f"--python_out={tmp_dir}",
625+
f"--pyi_out={tmp_dir}",
626+
]
627+
628+
return " ".join(command_parts)
629+
630+
601631
def _determine_generator_command(
602632
api_path: str, tmp_dir: str, generator_options: List[str]
603633
) -> str:
@@ -626,7 +656,7 @@ def _determine_generator_command(
626656
return " ".join(command_parts)
627657

628658

629-
def _run_generator_command(generator_command: str, source: str):
659+
def _run_protoc_command(generator_command: str, source: str):
630660
"""
631661
Executes the protoc generation command using subprocess.
632662
@@ -645,6 +675,74 @@ def _run_generator_command(generator_command: str, source: str):
645675
)
646676

647677

678+
def _get_staging_child_directory(api_path: str) -> str:
679+
"""
680+
Determines the correct sub-path within 'owl-bot-staging' for the generated code.
681+
682+
For proto-only libraries, the structure is usually just the proto directory,
683+
e.g., 'thing-py/google/thing'.
684+
For GAPIC libraries, it's typically the version segment, e.g., 'v1'.
685+
686+
Args:
687+
api_path (str): The relative path to the API directory (e.g., 'google/cloud/language/v1').
688+
689+
Returns:
690+
str: The sub-directory name to use for staging.
691+
"""
692+
693+
version_candidate = api_path.split("/")[-1]
694+
if version_candidate.startswith("v"):
695+
return version_candidate
696+
else:
697+
# Fallback for non-'v' version segment
698+
return f"{os.path.basename(api_path)}-py/{api_path}"
699+
700+
701+
def _stage_proto_only_library(
702+
api_path: str, source_dir: str, tmp_dir: str, staging_dir: str
703+
) -> None:
704+
"""
705+
Handles staging for proto-only libraries (e.g., common protos).
706+
707+
This involves copying the generated python files and the original proto files.
708+
709+
Args:
710+
api_path (str): The relative path to the API directory.
711+
source_dir (str): Path to the directory containing API protos.
712+
tmp_dir (str): The temporary directory where protoc output was placed.
713+
staging_dir (str): The final destination for the staged code.
714+
"""
715+
# 1. Copy the generated Python files (e.g., *_pb2.py) from the protoc output
716+
# The generated Python files are placed under a directory corresponding to `api_path`
717+
# inside the temporary directory, since the protoc command ran with `api_path`
718+
# specified.
719+
shutil.copytree(f"{tmp_dir}/{api_path}", staging_dir, dirs_exist_ok=True)
720+
721+
# 2. Copy the original proto files to the staging directory
722+
# This is typically done for proto-only libraries so that the protos are included
723+
# in the distributed package.
724+
proto_glob_path = f"{source_dir}/{api_path}/*.proto"
725+
for proto_file in glob.glob(proto_glob_path):
726+
# The glob is expected to find the file inside the source_dir.
727+
# We copy only the filename to the target staging directory.
728+
shutil.copyfile(proto_file, f"{staging_dir}/{os.path.basename(proto_file)}")
729+
730+
731+
def _stage_gapic_library(tmp_dir: str, staging_dir: str) -> None:
732+
"""
733+
Handles staging for GAPIC client libraries.
734+
735+
This involves copying all contents from the temporary output directory.
736+
737+
Args:
738+
tmp_dir (str): The temporary directory where protoc/GAPIC generator output was placed.
739+
staging_dir (str): The final destination for the staged code.
740+
"""
741+
# For GAPIC, the generator output is flat in `tmp_dir` and includes all
742+
# necessary files like setup.py, client library, etc.
743+
shutil.copytree(tmp_dir, staging_dir)
744+
745+
648746
def _generate_api(
649747
api_path: str, library_id: str, source: str, output: str, gapic_version: str
650748
):
@@ -660,18 +758,32 @@ def _generate_api(
660758
in a format which follows PEP-440.
661759
"""
662760
py_gapic_config = _read_bazel_build_py_rule(api_path, source)
663-
generator_options = _get_api_generator_options(
664-
api_path, py_gapic_config, gapic_version=gapic_version
665-
)
761+
is_proto_only_library = len(py_gapic_config) == 0
666762

667763
with tempfile.TemporaryDirectory() as tmp_dir:
668-
generator_command = _determine_generator_command(
669-
api_path, tmp_dir, generator_options
764+
# 1. Determine the command for code generation
765+
if is_proto_only_library:
766+
command = _construct_protoc_command(api_path, tmp_dir)
767+
else:
768+
generator_options = _get_api_generator_options(
769+
api_path, py_gapic_config, gapic_version=gapic_version
770+
)
771+
command = _determine_generator_command(api_path, tmp_dir, generator_options)
772+
773+
# 2. Execute the code generation command
774+
_run_protoc_command(command, source)
775+
776+
# 3. Determine staging location
777+
staging_child_directory = _get_staging_child_directory(api_path)
778+
staging_dir = os.path.join(
779+
output, "owl-bot-staging", library_id, staging_child_directory
670780
)
671-
_run_generator_command(generator_command, source)
672-
api_version = api_path.split("/")[-1]
673-
staging_dir = os.path.join(output, "owl-bot-staging", library_id, api_version)
674-
shutil.copytree(tmp_dir, staging_dir)
781+
782+
# 4. Stage the generated code
783+
if is_proto_only_library:
784+
_stage_proto_only_library(api_path, source, tmp_dir, staging_dir)
785+
else:
786+
_stage_gapic_library(tmp_dir, staging_dir)
675787

676788

677789
def _run_nox_sessions(library_id: str, repo: str):

.generator/requirements-test.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ pytest-mock
1818
gcp-synthtool @ git+https://github.com/googleapis/synthtool@5aa438a342707842d11fbbb302c6277fbf9e4655
1919
starlark-pyo3>=2025.1
2020
build
21+
black==23.7.0
22+
isort==5.11.0

.generator/requirements.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ gapic-generator>=1.27.0
33
nox
44
starlark-pyo3>=2025.1
55
build
6+
black==23.7.0
7+
isort==5.11.0

0 commit comments

Comments
 (0)