| #!/usr/bin/env python3 | |
| # Future improvements: | |
| # - link bugs and code references | |
| # - handle multiple bug references per line (currently only fetches one) | |
| # - fetch more than the first ~900 entries from Buganizer | |
| # - check if CLI tool is available + authenticated | |
| import pathlib, re, subprocess | |
| outfile = "closed_bugs.csv" | |
| print("Searching code for bug references") | |
| androidx_root = pathlib.Path(__file__).parent.parent.resolve() | |
| grep_cmd = subprocess.run( | |
| ["egrep", | |
| # -I excludes binary files | |
| # -i case insensitive search | |
| # -n include line numbers | |
| # -r recursive | |
| "-Iinr", | |
| # regex for buganizer format ("b/123456789") | |
| "b\/[[:digit:]]{8,9}", | |
| # Files and directories to include and exclude | |
| "--exclude-dir=.idea", | |
| "--include=*.gradle", | |
| "--include=*.java", | |
| "--include=*.kt", | |
| "--include=*.xml", | |
| # Search all of the AndroidX repo checkout | |
| f"{androidx_root}" | |
| ], | |
| capture_output=True, | |
| text=True | |
| ) | |
| raw_output_lines = grep_cmd.stdout.split("\n") | |
| print("Cleaning up search results") | |
| bug_dict = {} # mapping of bug id to list of filename + line number | |
| for line in raw_output_lines: | |
| regex_result = re.search('b\/[0-9]{8,9}', line) | |
| if regex_result is not None: | |
| bug_id = regex_result.group(0).removeprefix("b/") | |
| file = line.split(":")[0].removeprefix(str(androidx_root)) | |
| linenum = line.split(":")[1] | |
| if bug_id in bug_dict: | |
| matching_files = bug_dict[bug_id] | |
| else: | |
| matching_files = set() | |
| matching_files.add(f"{file}:{linenum}") | |
| bug_dict[bug_id] = matching_files | |
| print(f"Found {len(bug_dict)} bugs") | |
| # Create bug id query string. | |
| # The CLI tool fails if there are too many bugs (>900?); only use the first 900. | |
| bug_ids = list(bug_dict.keys()) | |
| bug_ids.sort() | |
| joined_ids = "|".join(bug_ids[0:899]) | |
| # Query buganizer to determine which of the given bugs are closed. | |
| # Store the issue, reporter, and assignee of the matching [closed] bugs. | |
| print("Querying Buganizer to find how many of these bugs are resolved") | |
| bugged_cmd = subprocess.run( | |
| ["bugged", "search", f"id:({joined_ids})", "status:closed", "--columns=issue,reporter,assignee"], | |
| capture_output=True, | |
| text=True # capture output as String instead of byte sequence | |
| ) | |
| closed_bug_list = bugged_cmd.stdout.split("\n") | |
| # Remove header and trailing rows of Buganizer query result | |
| closed_bug_list.pop(0) | |
| closed_bug_list.pop() | |
| print(f"{len(closed_bug_list)} have been resolved") | |
| # Combine buganizer results with file search results and write to CSV | |
| csv_str = "bug_id,reporter,assignee,files\n" | |
| for line in closed_bug_list: | |
| elements = re.split(" +", line) | |
| bug_id = elements[0] | |
| reporter = elements[1] | |
| assignee = elements[2] | |
| matching_files = bug_dict[bug_id] | |
| line_str = f"b/{bug_id},{reporter},{assignee}," | |
| # The list of matching file(s) are enclosed in double quotes to preserve \n in the csv | |
| line_str += ("\"" + "\n".join(matching_files) + "\"") | |
| csv_str += line_str + "\n" | |
| print(csv_str, file=open(outfile, 'w')) | |
| print(f"Wrote results to {outfile}") |