|  | #!/usr/bin/env python3 | 
|  |  | 
|  | # | 
|  | # Copyright (c) 2020 Project CHIP Authors | 
|  | # | 
|  | # 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 os | 
|  | import subprocess | 
|  | import sys | 
|  |  | 
|  | # Calculate OUTPUT_ROOT, equivalent to the Bash variable | 
|  | # This navigates two directories up from the script's location and then into 'out/coverage' | 
|  | script_dir = os.path.dirname(__file__) | 
|  | OUTPUT_ROOT = os.path.abspath(os.path.join(script_dir, '..', '..', 'out', 'coverage')) | 
|  |  | 
|  |  | 
|  | def parse_input_targets(query_output_lines, possible_rules_set, rules_set, new_targets_set): | 
|  | """ | 
|  | Parses the input from `ninja -t query` output. | 
|  | It extracts input targets and identifies associated rules, updating the provided sets. | 
|  |  | 
|  | Args: | 
|  | query_output_lines (list): A list of strings, where each string is a line from the ninja query output. | 
|  | possible_rules_set (set): A set of all rules considered 'possible' for cleaning. | 
|  | rules_set (set): A set to store rules that need to be cleaned (populated by this function). | 
|  | new_targets_set (set): A set to store new targets found that need further querying. | 
|  | """ | 
|  | in_input_block = False | 
|  | for line in query_output_lines: | 
|  |  | 
|  | trimmed_line = line.strip() | 
|  |  | 
|  | if trimmed_line.startswith("input: "): | 
|  | rule = trimmed_line[len("input: "):].strip() | 
|  | if rule == "phony": | 
|  | in_input_block = True | 
|  | elif rule in possible_rules_set: | 
|  | rules_set.add(rule)  # Add rule to the set | 
|  | elif not in_input_block: | 
|  | continue | 
|  | elif trimmed_line == "outputs:": | 
|  | in_input_block = False | 
|  | # Add new targets only if they haven't been queried already | 
|  | elif in_input_block: | 
|  | new_targets_set.add(trimmed_line)  # Add new target to the set | 
|  |  | 
|  |  | 
|  | def get_rules_to_clean(initial_targets): | 
|  | """ | 
|  | Determines the rules that should be cleaned based on the initial targets provided. | 
|  | This function simulates the logic of `ninja -t clean -r <rules>`. | 
|  |  | 
|  | Args: | 
|  | initial_targets (list): A list of target names (e.g., ['my_target', 'all']). | 
|  |  | 
|  | Returns: | 
|  | list: A list of rule names that should be cleaned. | 
|  | """ | 
|  | rules_to_clean = set()  # Stores rules identified for cleaning (now a set) | 
|  | queried_targets = set()  # Stores targets that have already been queried | 
|  | targets_to_query = set(initial_targets)  # Targets for the current iteration | 
|  |  | 
|  | # Get all the rules that execute targets from the toolchain.ninja file | 
|  | # Equivalent to: ninja -C out/coverage -f toolchain.ninja -t rules | grep "__rule" | 
|  | try: | 
|  | ninja_rules_process = subprocess.run( | 
|  | ['ninja', '-C', OUTPUT_ROOT, '-f', 'toolchain.ninja', '-t', 'rules'], | 
|  | capture_output=True, text=True, check=True | 
|  | ) | 
|  | # Filter for lines containing "__rule" | 
|  | possible_rules_set = {line.strip() for line in ninja_rules_process.stdout.splitlines() if "__rule" in line} | 
|  | except subprocess.CalledProcessError as e: | 
|  | print(f"Error getting possible rules from ninja: {e}", file=sys.stderr) | 
|  | print(f"Stdout: {e.stdout}", file=sys.stderr) | 
|  | print(f"Stderr: {e.stderr}", file=sys.stderr) | 
|  | sys.exit(1) | 
|  |  | 
|  | while targets_to_query: | 
|  |  | 
|  | if "all" in targets_to_query: | 
|  | return list(possible_rules_set) | 
|  |  | 
|  | new_targets = set()  # Stores targets discovered in the current query that need future querying | 
|  |  | 
|  | # Query the targets and parse their inputs | 
|  | query_cmd = ['ninja', '-C', OUTPUT_ROOT, '-t', 'query'] + list(targets_to_query) | 
|  | try: | 
|  | query_process = subprocess.run( | 
|  | query_cmd, | 
|  | capture_output=True, text=True, check=True | 
|  | ) | 
|  |  | 
|  | # Lines starting with a space and not containing '|' are relevant input targets | 
|  | filtered_query_lines = [ | 
|  | line for line in query_process.stdout.splitlines() | 
|  | if line.startswith(" ") and "|" not in line | 
|  | ] | 
|  |  | 
|  | parse_input_targets( | 
|  | filtered_query_lines, | 
|  | possible_rules_set, | 
|  | rules_to_clean, | 
|  | new_targets | 
|  | ) | 
|  |  | 
|  | except subprocess.CalledProcessError as e: | 
|  | print(f"Error querying ninja for targets: {e}", file=sys.stderr) | 
|  | print(f"Command: {' '.join(query_cmd)}", file=sys.stderr) | 
|  | print(f"Stdout: {e.stdout}", file=sys.stderr) | 
|  | print(f"Stderr: {e.stderr}", file=sys.stderr) | 
|  | sys.exit(1) | 
|  |  | 
|  | # Mark the current targets as queried | 
|  | queried_targets |= targets_to_query | 
|  |  | 
|  | # Update targets_to_query for the next iteration with the newly found targets | 
|  | targets_to_query = new_targets - queried_targets | 
|  |  | 
|  | # After all targets are processed, return the rules that were found | 
|  | return list(rules_to_clean) | 
|  |  | 
|  |  | 
|  | # Main script execution | 
|  | if __name__ == "__main__": | 
|  | # Get command-line arguments (targets to clean) | 
|  | targets_from_cli = sys.argv[1:] | 
|  |  | 
|  | if not targets_from_cli: | 
|  | print(f"Usage: {sys.argv[0]} <target1> [target2 ...]", file=sys.stderr) | 
|  | sys.exit(1) | 
|  |  | 
|  | print(f"Determining rules to clean for targets: {targets_from_cli}") | 
|  | rules_to_clear = get_rules_to_clean(targets_from_cli) | 
|  |  | 
|  | if rules_to_clear: | 
|  | try: | 
|  | # Construct the ninja clean command with the identified rules | 
|  | clean_cmd = ['ninja', '-C', OUTPUT_ROOT, '-f', 'toolchain.ninja', '-t', 'clean', '-r'] + rules_to_clear | 
|  |  | 
|  | subprocess.run(clean_cmd, check=True)  # `check=True` raises CalledProcessError on non-zero exit code | 
|  | print("Ninja clean command executed successfully.") | 
|  | except subprocess.CalledProcessError as e: | 
|  | print(f"Error executing ninja clean command: {e}", file=sys.stderr) | 
|  | print(f"Stdout: {e.stdout}", file=sys.stderr) | 
|  | print(f"Stderr: {e.stderr}", file=sys.stderr) | 
|  | sys.exit(e.returncode) | 
|  | else: | 
|  | print("No rules found to clean for the given targets.") |