diff --git a/script/analyze_component_buses.py b/script/analyze_component_buses.py index 457b82981e..3153a5b1dd 100644 --- a/script/analyze_component_buses.py +++ b/script/analyze_component_buses.py @@ -51,6 +51,32 @@ VALID_BUS_CONFIGS = { } +def uses_local_file_references(component_dir: Path) -> bool: + """Check if a component uses local file references via $component_dir. + + Components that reference local files cannot be grouped because each needs + a unique component_dir path pointing to their specific directory. + + Args: + component_dir: Path to the component's test directory + + Returns: + True if the component uses $component_dir for local file references + """ + common_yaml = component_dir / "common.yaml" + if not common_yaml.exists(): + return False + + try: + content = common_yaml.read_text() + except Exception: + return False + + # Pattern to match $component_dir or ${component_dir} references + # These indicate local file usage that prevents grouping + return bool(re.search(r"\$\{?component_dir\}?", content)) + + def extract_common_buses(yaml_file: Path) -> set[str]: """Extract which common bus configs are included in a YAML test file. @@ -110,23 +136,28 @@ def analyze_component(component_dir: Path) -> dict[str, list[str]]: return platform_buses -def analyze_all_components(tests_dir: Path = None) -> dict[str, dict[str, list[str]]]: +def analyze_all_components( + tests_dir: Path = None, +) -> tuple[dict[str, dict[str, list[str]]], set[str]]: """Analyze all component test directories. Args: tests_dir: Path to tests/components directory (defaults to auto-detect) Returns: - Dictionary mapping component name to platform->buses mapping + Tuple of: + - Dictionary mapping component name to platform->buses mapping + - Set of component names that use local files (cannot be grouped) """ if tests_dir is None: tests_dir = Path("tests/components") if not tests_dir.exists(): print(f"Error: {tests_dir} does not exist", file=sys.stderr) - return {} + return {}, set() components = {} + non_groupable = set() for component_dir in sorted(tests_dir.iterdir()): if not component_dir.is_dir(): @@ -138,7 +169,11 @@ def analyze_all_components(tests_dir: Path = None) -> dict[str, dict[str, list[s if platform_buses: components[component_name] = platform_buses - return components + # Check if component uses local file references + if uses_local_file_references(component_dir): + non_groupable.add(component_name) + + return components, non_groupable def create_grouping_signature( @@ -226,14 +261,17 @@ def main() -> None: if args.components: # Analyze only specified components components = {} + non_groupable = set() for comp in args.components: comp_dir = tests_dir / comp platform_buses = analyze_component(comp_dir) if platform_buses: components[comp] = platform_buses + if uses_local_file_references(comp_dir): + non_groupable.add(comp) else: # Analyze all components - components = analyze_all_components(tests_dir) + components, non_groupable = analyze_all_components(tests_dir) # Output results if args.group and args.platform: @@ -256,12 +294,19 @@ def main() -> None: else: # Human-readable output for component, platform_buses in sorted(components.items()): - print(f"{component}:") + non_groupable_marker = ( + " [NON-GROUPABLE]" if component in non_groupable else "" + ) + print(f"{component}{non_groupable_marker}:") for platform, buses in sorted(platform_buses.items()): bus_str = ", ".join(buses) print(f" {platform}: {bus_str}") print() print(f"Total components analyzed: {len(components)}") + if non_groupable: + print(f"Non-groupable components (use local files): {len(non_groupable)}") + for comp in sorted(non_groupable): + print(f" - {comp}") if __name__ == "__main__": diff --git a/script/test_build_components.py b/script/test_build_components.py index 1bbf923d44..9279e02022 100755 --- a/script/test_build_components.py +++ b/script/test_build_components.py @@ -331,13 +331,17 @@ def test_components( print("\n" + "=" * 80) print("Analyzing components for intelligent grouping...") print("=" * 80) - component_buses = analyze_all_components(tests_dir) + component_buses, non_groupable = analyze_all_components(tests_dir) # Group by (platform, bus_signature) for component, platforms in component_buses.items(): if component not in all_tests: continue + # Skip components that use local file references + if component in non_groupable: + continue + for platform, buses in platforms.items(): # Skip if platform doesn't match filter if platform_filter and not platform.startswith(platform_filter): @@ -352,6 +356,18 @@ def test_components( print("\nGrouping Plan:") print("-" * 80) + # Show excluded components + if non_groupable: + excluded_in_tests = [c for c in non_groupable if c in all_tests] + if excluded_in_tests: + print( + f"\n⚠ {len(excluded_in_tests)} components excluded from grouping (use local file references):" + ) + for comp in sorted(excluded_in_tests[:5]): + print(f" - {comp}") + if len(excluded_in_tests) > 5: + print(f" ... and {len(excluded_in_tests) - 5} more") + groups_to_test = [] individual_tests = []