diff --git a/.generator/Dockerfile b/.generator/Dockerfile index b80af6f7103a..b1f49582c2c3 100644 --- a/.generator/Dockerfile +++ b/.generator/Dockerfile @@ -172,6 +172,10 @@ RUN git clone --depth 1 https://github.com/googleapis/synthtool.git /tmp/synthto bazel_env/bin/python3.9 -m pip install /tmp/synthtool nox && \ rm -rf /tmp/synthtool +# Install build which is used to get the metadata of package config files. +COPY .generator/requirements.in . +RUN python3.9 -m pip install -r requirements.in + # Copy the CLI script into the container. COPY .generator/cli.py . RUN chmod a+rx ./cli.py diff --git a/.generator/cli.py b/.generator/cli.py index 02563c3a5a57..17ba6602465c 100644 --- a/.generator/cli.py +++ b/.generator/cli.py @@ -27,6 +27,8 @@ from pathlib import Path from typing import Dict, List +import build.util + try: import synthtool from synthtool.languages import python_mono_repo @@ -546,12 +548,54 @@ def _verify_library_namespace(library_id: str, repo: str): ) +def _get_library_dist_name(library_id: str, repo: str) -> str: + """ + Gets the package name by programmatically building the metadata. + + Args: + library_id: id of the library. + repo: This directory will contain all directories that make up a + library, the .librarian folder, and any global file declared in + the config.yaml. + + Returns: + str: The library name string if found, otherwise None. + """ + library_path = f"{repo}/packages/{library_id}" + metadata = build.util.project_wheel_metadata(library_path) + return metadata.get("name") + + +def _verify_library_dist_name(library_id: str, repo: str): + """Verifies the library distribution name against its config files. + + This function ensures that: + 1. At least one of `setup.py` or `pyproject.toml` exists and is valid. + 2. Any existing config file's 'name' property matches the `library_id`. + + Args: + library_id: id of the library. + repo: This directory will contain all directories that make up a + library, the .librarian folder, and any global file declared in + the config.yaml. + + Raises: + ValueError: If a name in an existing config file does not match the `library_id`. + """ + dist_name = _get_library_dist_name(library_id, repo) + if dist_name != library_id: + raise ValueError( + f"The distribution name `{dist_name}` does not match the folder `{library_id}`." + ) + + def handle_build(librarian: str = LIBRARIAN_DIR, repo: str = REPO_DIR): """The main coordinator for validating client library generation.""" try: request_data = _read_json_file(f"{librarian}/{BUILD_REQUEST_FILE}") library_id = _get_library_id(request_data) _verify_library_namespace(library_id, repo) + _verify_library_dist_name(library_id, repo) _run_nox_sessions(library_id, repo) except Exception as e: raise ValueError("Build failed.") from e diff --git a/.generator/requirements-test.in b/.generator/requirements-test.in index 7b852b04129f..80bf22b7b8d6 100644 --- a/.generator/requirements-test.in +++ b/.generator/requirements-test.in @@ -17,3 +17,4 @@ pytest-cov pytest-mock gcp-synthtool @ git+https://github.com/googleapis/synthtool@5aa438a342707842d11fbbb302c6277fbf9e4655 starlark-pyo3>=2025.1 +build diff --git a/.generator/requirements.in b/.generator/requirements.in new file mode 100644 index 000000000000..378eac25d311 --- /dev/null +++ b/.generator/requirements.in @@ -0,0 +1 @@ +build diff --git a/.generator/test_cli.py b/.generator/test_cli.py index 7ebe90e93a5d..dcac0df5a0c3 100644 --- a/.generator/test_cli.py +++ b/.generator/test_cli.py @@ -37,6 +37,7 @@ _copy_files_needed_for_post_processing, _create_main_version_header, _determine_bazel_rule, + _get_library_dist_name, _determine_library_namespace, _get_library_id, _get_libraries_to_prepare_for_release, @@ -52,6 +53,7 @@ _update_changelog_for_library, _update_global_changelog, _update_version_for_library, + _verify_library_dist_name, _verify_library_namespace, _write_json_file, _write_text_file, @@ -525,7 +527,8 @@ def test_handle_build_success(caplog, mocker, mock_build_request_file): caplog.set_level(logging.INFO) mocker.patch("cli._run_nox_sessions") - mocker.patch("cli._verify_library_namespace", return_value=True) + mocker.patch("cli._verify_library_namespace") + mocker.patch("cli._verify_library_dist_name") handle_build() assert "'build' command executed." in caplog.text @@ -922,6 +925,26 @@ def test_determine_library_namespace_fails_not_subpath(): _determine_library_namespace(gapic_parent_path, pkg_root_path) +def test_get_library_dist_name_success(mocker): + mock_metadata = {"name": "my-lib", "version": "1.0.0"} + mocker.patch("build.util.project_wheel_metadata", return_value=mock_metadata) + assert _get_library_dist_name("my-lib", "repo") == "my-lib" + + +def test_verify_library_dist_name_setup_success(mocker): + """Tests success when a library distribution name in setup.py is valid.""" + mock_setup_file = mocker.patch("cli._get_library_dist_name", return_value="my-lib") + _verify_library_dist_name("my-lib", "repo") + mock_setup_file.assert_called_once_with("my-lib", "repo") + + +def test_verify_library_dist_name_fail(mocker): + """Tests failure when a library-id does not match the libary distribution name.""" + mocker.patch("cli._get_library_dist_name", return_value="invalid-lib") + with pytest.raises(ValueError): + _verify_library_dist_name("my-lib", "repo") + + def test_verify_library_namespace_success_valid(mocker, mock_path_class): """Tests success when a single valid namespace is found.""" # 1. Get the mock instance from the mock class's return_value