|
| 1 | +# Copyright 2026 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 | +# https://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 | +import importlib.util |
| 16 | +import os |
| 17 | +import shutil |
| 18 | +from hatchling.builders.hooks.plugin.interface import BuildHookInterface |
| 19 | + |
| 20 | + |
| 21 | +def load_constants(project_root): |
| 22 | + """Loads the shared constants module directly from its path in src/.""" |
| 23 | + constants_path = os.path.join( |
| 24 | + project_root, "src", "a2ui", "inference", "schema", "constants.py" |
| 25 | + ) |
| 26 | + if not os.path.exists(constants_path): |
| 27 | + raise RuntimeError(f"Could not find shared constants at {constants_path}") |
| 28 | + |
| 29 | + spec = importlib.util.spec_from_file_location("_constants_load", constants_path) |
| 30 | + if spec and spec.loader: |
| 31 | + module = importlib.util.module_from_spec(spec) |
| 32 | + spec.loader.exec_module(module) |
| 33 | + return module |
| 34 | + raise RuntimeError(f"Could not load shared constants from {constants_path}") |
| 35 | + |
| 36 | + |
| 37 | +class PackSpecsBuildHook(BuildHookInterface): |
| 38 | + |
| 39 | + def initialize(self, version, build_data): |
| 40 | + project_root = self.root |
| 41 | + |
| 42 | + # Load constants dynamically from src/a2ui/inference/schema/constants.py |
| 43 | + a2ui_constants = load_constants(project_root) |
| 44 | + |
| 45 | + spec_version_map = a2ui_constants.SPEC_VERSION_MAP |
| 46 | + a2ui_asset_package = a2ui_constants.A2UI_ASSET_PACKAGE |
| 47 | + specification_dir = a2ui_constants.SPECIFICATION_DIR |
| 48 | + |
| 49 | + # project root is in a2a_agents/python/a2ui_agent |
| 50 | + # Dynamically find repo root by looking for specification_dir |
| 51 | + repo_root = a2ui_constants.find_repo_root(project_root) |
| 52 | + if not repo_root: |
| 53 | + # Check for PKG-INFO which implies a packaged state (sdist). |
| 54 | + # If PKG-INFO is present, trust the bundled assets. |
| 55 | + if os.path.exists(os.path.join(project_root, "PKG-INFO")): |
| 56 | + print("Repository root not found, but PKG-INFO present (sdist). Skipping copy.") |
| 57 | + return |
| 58 | + |
| 59 | + raise RuntimeError( |
| 60 | + f"Could not find repository root (looked for '{specification_dir}'" |
| 61 | + " directory)." |
| 62 | + ) |
| 63 | + |
| 64 | + # Target directory: src/a2ui/assets |
| 65 | + target_base = os.path.join( |
| 66 | + project_root, "src", a2ui_asset_package.replace(".", os.sep) |
| 67 | + ) |
| 68 | + |
| 69 | + for ver, schema_map in spec_version_map.items(): |
| 70 | + target_dir = os.path.join(target_base, ver) |
| 71 | + os.makedirs(target_dir, exist_ok=True) |
| 72 | + |
| 73 | + for _schema_key, source_rel_path in schema_map.items(): |
| 74 | + source_path = os.path.join(repo_root, source_rel_path) |
| 75 | + |
| 76 | + if not os.path.exists(source_path): |
| 77 | + print( |
| 78 | + f"WARNING: Source schema file not found at {source_path}. Build" |
| 79 | + " might produce incomplete wheel if not running from monorepo" |
| 80 | + " root." |
| 81 | + ) |
| 82 | + continue |
| 83 | + |
| 84 | + filename = os.path.basename(source_path) |
| 85 | + dst_file = os.path.join(target_dir, filename) |
| 86 | + |
| 87 | + print(f"Copying {source_path} -> {dst_file}") |
| 88 | + shutil.copy2(source_path, dst_file) |
0 commit comments