blob: 7f32a12839d49ee389e6351d351dc68b2dc58bdf [file]
# Copyright (C) 2026 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import dataclasses
from pathlib import Path
from typing import Any, Callable, Optional
from .env import BuildContext
from .config import get_build_vars
from .build import build_targets
from interface.errors import ToolError
MODULE_INFO_JSON = "module-info.json"
@dataclasses.dataclass(frozen=True)
class ModuleInfo:
name: str
classes: list[str]
paths: list[str]
installed: list[str]
compatibility_suites: list[str]
test_config: list[str]
@classmethod
def from_dict(cls, name: str, data: dict[str, Any]) -> "ModuleInfo":
return cls(
name=name,
classes=data.get("class", []),
paths=data.get("path", []),
installed=data.get("installed", []),
compatibility_suites=data.get("compatibility_suites", []),
test_config=data.get("test_config", [])
)
# Global Caches
_MODULE_INFO_CACHE: dict[str, ModuleInfo] = {}
_MODULE_INFO_MTIME: float = 0.0
def _get_product_out(ctx: BuildContext) -> Path:
"""Resolves the PRODUCT_OUT directory."""
env = ctx.env
android_build_top = env.get("ANDROID_BUILD_TOP")
if not android_build_top:
raise ToolError("ANDROID_BUILD_TOP not found in environment.")
# Try getting it from build vars first (most accurate)
try:
vars_dict = get_build_vars(ctx, "PRODUCT_OUT")
out_dir = vars_dict.get("PRODUCT_OUT")
if out_dir:
path = Path(out_dir)
if not path.is_absolute():
path = Path(android_build_top) / path
return path
except Exception:
pass
# Fallback to env var or standard location
if env.get("ANDROID_PRODUCT_OUT"):
return Path(str(env.get("ANDROID_PRODUCT_OUT")))
# Last resort fallback
product = env.get("TARGET_PRODUCT")
if not product:
raise ToolError("TARGET_PRODUCT not found in environment.")
return Path(android_build_top) / "out/target/product" / product
def _load_json_db(ctx: BuildContext, force_refresh: bool = False, progress_callback: Optional[Callable[[float, Optional[float]], None]] = None) -> dict[str, ModuleInfo]:
"""
Loads module-info.json, rebuilding or reloading if necessary.
Returns the dictionary of ModuleInfo objects.
"""
global _MODULE_INFO_CACHE, _MODULE_INFO_MTIME
product_out = _get_product_out(ctx)
module_info_path = product_out / MODULE_INFO_JSON
# Step 1: Check if file exists. If not, we MUST build.
if not module_info_path.exists() or force_refresh:
build_targets(ctx, targets=["module-info"], clean=False, progress_callback=progress_callback)
if not module_info_path.exists():
raise ToolError(f"Build failed to generate {module_info_path}")
# Step 2: Check mtime to see if we need to reload from disk
current_mtime = module_info_path.stat().st_mtime
if _MODULE_INFO_CACHE and current_mtime == _MODULE_INFO_MTIME and not force_refresh:
return _MODULE_INFO_CACHE
# Step 3: Load from disk
with open(module_info_path, "r") as f:
raw_data = json.load(f)
# Step 4: Parse and Cache
new_cache = {}
for name, data in raw_data.items():
new_cache[name] = ModuleInfo.from_dict(name, data)
_MODULE_INFO_CACHE = new_cache
_MODULE_INFO_MTIME = current_mtime
return _MODULE_INFO_CACHE
def get_module_info(ctx: BuildContext, module_name: str, force_refresh: bool = False, progress_callback: Optional[Callable[[float, Optional[float]], None]] = None) -> ModuleInfo:
"""
Retrieves information for a specific module.
Use this to find source paths, installed paths, and class tags for a module.
Returns a ModuleInfo object containing the module metadata.
Args:
module_name: The name of the module to inspect (e.g. 'SystemUI').
force_refresh: If True, forces a rebuild of module-info.json.
"""
db = _load_json_db(ctx, force_refresh, progress_callback=progress_callback)
if module_name not in db:
raise ToolError(f"Module '{module_name}' not found in module-info.json")
return db[module_name]