diff --git a/script/analyze_component_buses.py b/script/analyze_component_buses.py index 5b4a94dac4..550d551843 100644 --- a/script/analyze_component_buses.py +++ b/script/analyze_component_buses.py @@ -245,7 +245,7 @@ def analyze_component(component_dir: Path) -> tuple[dict[str, list[str]], bool, def analyze_all_components( tests_dir: Path = None, -) -> tuple[dict[str, dict[str, list[str]]], set[str]]: +) -> tuple[dict[str, dict[str, list[str]]], set[str], set[str]]: """Analyze all component test directories. Args: @@ -254,17 +254,19 @@ def analyze_all_components( Returns: Tuple of: - Dictionary mapping component name to platform->buses mapping - - Set of component names that cannot be grouped (use local files, define buses directly, or are platform components) + - Set of component names that cannot be grouped + - Set of component names that define buses directly (need migration warning) """ 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 {}, set() + return {}, set(), set() components = {} non_groupable = set() + direct_bus_components = set() for component_dir in sorted(tests_dir.iterdir()): if not component_dir.is_dir(): @@ -296,8 +298,9 @@ def analyze_all_components( # These create unique bus IDs and cause conflicts when merged if has_direct_bus_config: non_groupable.add(component_name) + direct_bus_components.add(component_name) - return components, non_groupable + return components, non_groupable, direct_bus_components def create_grouping_signature( @@ -395,9 +398,12 @@ def main() -> None: # Analyze only specified components components = {} non_groupable = set() + direct_bus_components = set() for comp in args.components: comp_dir = tests_dir / comp - platform_buses, has_extend_remove = analyze_component(comp_dir) + platform_buses, has_extend_remove, has_direct_bus_config = ( + analyze_component(comp_dir) + ) if platform_buses: components[comp] = platform_buses if uses_local_file_references(comp_dir): @@ -406,9 +412,14 @@ def main() -> None: non_groupable.add(comp) if has_extend_remove: non_groupable.add(comp) + if has_direct_bus_config: + non_groupable.add(comp) + direct_bus_components.add(comp) else: # Analyze all components - components, non_groupable = analyze_all_components(tests_dir) + components, non_groupable, direct_bus_components = analyze_all_components( + tests_dir + ) # Output results if args.group and args.platform: diff --git a/script/test_build_components.py b/script/test_build_components.py index 2644d09938..fb22afa700 100755 --- a/script/test_build_components.py +++ b/script/test_build_components.py @@ -29,6 +29,8 @@ sys.path.insert(0, str(Path(__file__).parent.parent)) from script.analyze_component_buses import ( analyze_all_components, create_grouping_signature, + is_platform_component, + uses_local_file_references, ) from script.merge_component_configs import merge_component_configs @@ -331,19 +333,45 @@ def run_grouped_component_tests( print("\n" + "=" * 80) print("Analyzing components for intelligent grouping...") print("=" * 80) - component_buses, non_groupable = analyze_all_components(tests_dir) + component_buses, non_groupable, direct_bus_components = analyze_all_components( + tests_dir + ) + + # Track why components can't be grouped (for detailed output) + non_groupable_reasons = {} # 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 + # Skip components that use local file references or direct bus configs if component in non_groupable: + # Track the reason (using pre-calculated results to avoid expensive re-analysis) + if component not in non_groupable_reasons: + if component in direct_bus_components: + non_groupable_reasons[component] = ( + "Defines buses directly (not via packages) - NEEDS MIGRATION" + ) + elif uses_local_file_references(tests_dir / component): + non_groupable_reasons[component] = ( + "Uses local file references ($component_dir)" + ) + elif is_platform_component(tests_dir / component): + non_groupable_reasons[component] = ( + "Platform component (abstract base class)" + ) + else: + non_groupable_reasons[component] = ( + "Uses !extend or !remove directives" + ) continue # Skip components that must be tested in isolation if component in ISOLATED_COMPONENTS: + non_groupable_reasons[component] = ( + f"Known build issue: {ISOLATED_COMPONENTS[component]}" + ) continue for platform, buses in platforms.items(): @@ -376,17 +404,40 @@ def run_grouped_component_tests( reason = ISOLATED_COMPONENTS[comp] print(f" - {comp}: {reason}") - # Show excluded components - if non_groupable: - excluded_in_tests = [c for c in non_groupable if c in all_tests] + # Show excluded components with detailed reasons + if non_groupable_reasons: + excluded_in_tests = [c for c in non_groupable_reasons if c in all_tests] if excluded_in_tests: print( - f"\n⚠ {len(excluded_in_tests)} components excluded from grouping (use local file references):" + f"\n⚠ {len(excluded_in_tests)} components excluded from grouping (each needs individual build):" ) - 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") + # Group by reason to show summary + direct_bus = [ + c + for c in excluded_in_tests + if "NEEDS MIGRATION" in non_groupable_reasons.get(c, "") + ] + if direct_bus: + print( + f"\n ⚠⚠⚠ {len(direct_bus)} DEFINE BUSES DIRECTLY - NEED MIGRATION TO PACKAGES:" + ) + for comp in sorted(direct_bus): + print(f" - {comp}") + + other_reasons = [ + c + for c in excluded_in_tests + if "NEEDS MIGRATION" not in non_groupable_reasons.get(c, "") + ] + if other_reasons and len(other_reasons) <= 10: + print("\n Other non-groupable components:") + for comp in sorted(other_reasons): + reason = non_groupable_reasons[comp] + print(f" - {comp}: {reason}") + elif other_reasons: + print( + f"\n Other non-groupable components: {len(other_reasons)} components" + ) groups_to_test = [] individual_tests = set() # Use set to avoid duplicates