1*bb4ee6a4SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2022 The ChromiumOS Authors 3*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file. 5*bb4ee6a4SAndroid Build Coastguard Worker 6*bb4ee6a4SAndroid Build Coastguard Workerimport os 7*bb4ee6a4SAndroid Build Coastguard Workerimport subprocess 8*bb4ee6a4SAndroid Build Coastguard Workerimport sys 9*bb4ee6a4SAndroid Build Coastguard Workerimport traceback 10*bb4ee6a4SAndroid Build Coastguard Workerfrom concurrent.futures import ThreadPoolExecutor 11*bb4ee6a4SAndroid Build Coastguard Workerfrom dataclasses import dataclass 12*bb4ee6a4SAndroid Build Coastguard Workerfrom datetime import datetime, timedelta 13*bb4ee6a4SAndroid Build Coastguard Workerfrom fnmatch import fnmatch 14*bb4ee6a4SAndroid Build Coastguard Workerfrom pathlib import Path 15*bb4ee6a4SAndroid Build Coastguard Workerfrom time import sleep 16*bb4ee6a4SAndroid Build Coastguard Workerfrom typing import Callable, List, Sequence, NamedTuple, Optional, Union 17*bb4ee6a4SAndroid Build Coastguard Worker 18*bb4ee6a4SAndroid Build Coastguard Workerfrom impl.common import ( 19*bb4ee6a4SAndroid Build Coastguard Worker Command, 20*bb4ee6a4SAndroid Build Coastguard Worker ParallelCommands, 21*bb4ee6a4SAndroid Build Coastguard Worker all_tracked_files, 22*bb4ee6a4SAndroid Build Coastguard Worker cmd, 23*bb4ee6a4SAndroid Build Coastguard Worker console, 24*bb4ee6a4SAndroid Build Coastguard Worker rich, 25*bb4ee6a4SAndroid Build Coastguard Worker strip_ansi_escape_sequences, 26*bb4ee6a4SAndroid Build Coastguard Worker verbose, 27*bb4ee6a4SAndroid Build Coastguard Worker) 28*bb4ee6a4SAndroid Build Coastguard Worker 29*bb4ee6a4SAndroid Build Coastguard Workergit = cmd("git") 30*bb4ee6a4SAndroid Build Coastguard Worker 31*bb4ee6a4SAndroid Build Coastguard Worker 32*bb4ee6a4SAndroid Build Coastguard Worker@dataclass 33*bb4ee6a4SAndroid Build Coastguard Workerclass CheckContext(object): 34*bb4ee6a4SAndroid Build Coastguard Worker "Information passed to each check when it's called." 35*bb4ee6a4SAndroid Build Coastguard Worker 36*bb4ee6a4SAndroid Build Coastguard Worker # Whether or not --fix was set and checks should attempt to fix problems they encounter. 37*bb4ee6a4SAndroid Build Coastguard Worker fix: bool 38*bb4ee6a4SAndroid Build Coastguard Worker 39*bb4ee6a4SAndroid Build Coastguard Worker # All files that this check should cover (e.g. all python files on a python check). 40*bb4ee6a4SAndroid Build Coastguard Worker all_files: List[Path] 41*bb4ee6a4SAndroid Build Coastguard Worker 42*bb4ee6a4SAndroid Build Coastguard Worker # Those files of all_files that were modified locally. 43*bb4ee6a4SAndroid Build Coastguard Worker modified_files: List[Path] 44*bb4ee6a4SAndroid Build Coastguard Worker 45*bb4ee6a4SAndroid Build Coastguard Worker # Files that do not exist upstream and have been added locally. 46*bb4ee6a4SAndroid Build Coastguard Worker new_files: List[Path] 47*bb4ee6a4SAndroid Build Coastguard Worker 48*bb4ee6a4SAndroid Build Coastguard Worker 49*bb4ee6a4SAndroid Build Coastguard Workerclass Check(NamedTuple): 50*bb4ee6a4SAndroid Build Coastguard Worker "Metadata for each check, definining on which files it should run." 51*bb4ee6a4SAndroid Build Coastguard Worker 52*bb4ee6a4SAndroid Build Coastguard Worker # Function to call for this check 53*bb4ee6a4SAndroid Build Coastguard Worker check_function: Callable[[CheckContext], Union[Command, None, List[Command]]] 54*bb4ee6a4SAndroid Build Coastguard Worker 55*bb4ee6a4SAndroid Build Coastguard Worker custom_name: Optional[str] = None 56*bb4ee6a4SAndroid Build Coastguard Worker 57*bb4ee6a4SAndroid Build Coastguard Worker # List of globs that this check should be triggered on 58*bb4ee6a4SAndroid Build Coastguard Worker files: List[str] = [] 59*bb4ee6a4SAndroid Build Coastguard Worker 60*bb4ee6a4SAndroid Build Coastguard Worker python_tools: bool = False 61*bb4ee6a4SAndroid Build Coastguard Worker 62*bb4ee6a4SAndroid Build Coastguard Worker # List of globs to exclude from this check 63*bb4ee6a4SAndroid Build Coastguard Worker exclude: List[str] = [] 64*bb4ee6a4SAndroid Build Coastguard Worker 65*bb4ee6a4SAndroid Build Coastguard Worker # Whether or not this check can fix issues. 66*bb4ee6a4SAndroid Build Coastguard Worker can_fix: bool = False 67*bb4ee6a4SAndroid Build Coastguard Worker 68*bb4ee6a4SAndroid Build Coastguard Worker # Which groups this check belongs to. 69*bb4ee6a4SAndroid Build Coastguard Worker groups: List[str] = [] 70*bb4ee6a4SAndroid Build Coastguard Worker 71*bb4ee6a4SAndroid Build Coastguard Worker # Priority tasks usually take lonkger and are started first, and will show preliminary output. 72*bb4ee6a4SAndroid Build Coastguard Worker priority: bool = False 73*bb4ee6a4SAndroid Build Coastguard Worker 74*bb4ee6a4SAndroid Build Coastguard Worker @property 75*bb4ee6a4SAndroid Build Coastguard Worker def name(self): 76*bb4ee6a4SAndroid Build Coastguard Worker if self.custom_name: 77*bb4ee6a4SAndroid Build Coastguard Worker return self.custom_name 78*bb4ee6a4SAndroid Build Coastguard Worker name = self.check_function.__name__ 79*bb4ee6a4SAndroid Build Coastguard Worker if name.startswith("check_"): 80*bb4ee6a4SAndroid Build Coastguard Worker return name[len("check_") :] 81*bb4ee6a4SAndroid Build Coastguard Worker return name 82*bb4ee6a4SAndroid Build Coastguard Worker 83*bb4ee6a4SAndroid Build Coastguard Worker @property 84*bb4ee6a4SAndroid Build Coastguard Worker def doc(self): 85*bb4ee6a4SAndroid Build Coastguard Worker if self.check_function.__doc__: 86*bb4ee6a4SAndroid Build Coastguard Worker return self.check_function.__doc__.strip() 87*bb4ee6a4SAndroid Build Coastguard Worker else: 88*bb4ee6a4SAndroid Build Coastguard Worker return None 89*bb4ee6a4SAndroid Build Coastguard Worker 90*bb4ee6a4SAndroid Build Coastguard Worker 91*bb4ee6a4SAndroid Build Coastguard Workerclass Group(NamedTuple): 92*bb4ee6a4SAndroid Build Coastguard Worker "Metadata for a group of checks" 93*bb4ee6a4SAndroid Build Coastguard Worker 94*bb4ee6a4SAndroid Build Coastguard Worker name: str 95*bb4ee6a4SAndroid Build Coastguard Worker 96*bb4ee6a4SAndroid Build Coastguard Worker doc: str 97*bb4ee6a4SAndroid Build Coastguard Worker 98*bb4ee6a4SAndroid Build Coastguard Worker checks: List[str] 99*bb4ee6a4SAndroid Build Coastguard Worker 100*bb4ee6a4SAndroid Build Coastguard Worker 101*bb4ee6a4SAndroid Build Coastguard Workerdef list_file_diff(): 102*bb4ee6a4SAndroid Build Coastguard Worker """ 103*bb4ee6a4SAndroid Build Coastguard Worker Lists files there were modified compared to the upstream branch. 104*bb4ee6a4SAndroid Build Coastguard Worker 105*bb4ee6a4SAndroid Build Coastguard Worker Falls back to all files tracked by git if there is no upstream branch. 106*bb4ee6a4SAndroid Build Coastguard Worker """ 107*bb4ee6a4SAndroid Build Coastguard Worker upstream = git("rev-parse @{u}").stdout(check=False) 108*bb4ee6a4SAndroid Build Coastguard Worker if upstream: 109*bb4ee6a4SAndroid Build Coastguard Worker for line in git("diff --name-status", upstream).lines(): 110*bb4ee6a4SAndroid Build Coastguard Worker parts = line.split("\t", 1) 111*bb4ee6a4SAndroid Build Coastguard Worker file = Path(parts[1].strip()) 112*bb4ee6a4SAndroid Build Coastguard Worker if file.is_file(): 113*bb4ee6a4SAndroid Build Coastguard Worker yield (parts[0].strip(), file) 114*bb4ee6a4SAndroid Build Coastguard Worker else: 115*bb4ee6a4SAndroid Build Coastguard Worker print("WARNING: Not tracking a branch. Checking all files.") 116*bb4ee6a4SAndroid Build Coastguard Worker for file in all_tracked_files(): 117*bb4ee6a4SAndroid Build Coastguard Worker yield ("M", file) 118*bb4ee6a4SAndroid Build Coastguard Worker 119*bb4ee6a4SAndroid Build Coastguard Worker 120*bb4ee6a4SAndroid Build Coastguard Workerdef should_run_check_on_file(check: Check, file: Path): 121*bb4ee6a4SAndroid Build Coastguard Worker "Returns true if `file` should be run on `check`." 122*bb4ee6a4SAndroid Build Coastguard Worker 123*bb4ee6a4SAndroid Build Coastguard Worker # Skip third_party except vmm_vhost. 124*bb4ee6a4SAndroid Build Coastguard Worker if str(file).startswith("third_party") and not str(file).startswith("third_party/vmm_vhost"): 125*bb4ee6a4SAndroid Build Coastguard Worker return False 126*bb4ee6a4SAndroid Build Coastguard Worker 127*bb4ee6a4SAndroid Build Coastguard Worker # Skip excluded files 128*bb4ee6a4SAndroid Build Coastguard Worker for glob in check.exclude: 129*bb4ee6a4SAndroid Build Coastguard Worker if fnmatch(str(file), glob): 130*bb4ee6a4SAndroid Build Coastguard Worker return False 131*bb4ee6a4SAndroid Build Coastguard Worker 132*bb4ee6a4SAndroid Build Coastguard Worker # Match python tools (no file-extension, but with a python shebang line) 133*bb4ee6a4SAndroid Build Coastguard Worker if check.python_tools: 134*bb4ee6a4SAndroid Build Coastguard Worker if fnmatch(str(file), "tools/*") and file.suffix == "" and file.is_file(): 135*bb4ee6a4SAndroid Build Coastguard Worker if file.open(errors="ignore").read(32).startswith("#!/usr/bin/env python3"): 136*bb4ee6a4SAndroid Build Coastguard Worker return True 137*bb4ee6a4SAndroid Build Coastguard Worker 138*bb4ee6a4SAndroid Build Coastguard Worker # If no constraint is specified, match all files. 139*bb4ee6a4SAndroid Build Coastguard Worker if not check.files and not check.python_tools: 140*bb4ee6a4SAndroid Build Coastguard Worker return True 141*bb4ee6a4SAndroid Build Coastguard Worker 142*bb4ee6a4SAndroid Build Coastguard Worker # Otherwise, match only those specified by `files`. 143*bb4ee6a4SAndroid Build Coastguard Worker for glob in check.files: 144*bb4ee6a4SAndroid Build Coastguard Worker if fnmatch(str(file), glob): 145*bb4ee6a4SAndroid Build Coastguard Worker return True 146*bb4ee6a4SAndroid Build Coastguard Worker 147*bb4ee6a4SAndroid Build Coastguard Worker return False 148*bb4ee6a4SAndroid Build Coastguard Worker 149*bb4ee6a4SAndroid Build Coastguard Worker 150*bb4ee6a4SAndroid Build Coastguard Workerclass Task(object): 151*bb4ee6a4SAndroid Build Coastguard Worker """ 152*bb4ee6a4SAndroid Build Coastguard Worker Represents a task that needs to be executed to perform a `Check`. 153*bb4ee6a4SAndroid Build Coastguard Worker 154*bb4ee6a4SAndroid Build Coastguard Worker The task can be executed via `Task.execute`, which will update the state variables with 155*bb4ee6a4SAndroid Build Coastguard Worker status and progress information. 156*bb4ee6a4SAndroid Build Coastguard Worker 157*bb4ee6a4SAndroid Build Coastguard Worker This information can then be rendered from a separate thread via `Task.status_widget()` 158*bb4ee6a4SAndroid Build Coastguard Worker """ 159*bb4ee6a4SAndroid Build Coastguard Worker 160*bb4ee6a4SAndroid Build Coastguard Worker def __init__(self, title: str, commands: Sequence[Command], priority: bool): 161*bb4ee6a4SAndroid Build Coastguard Worker "Display title." 162*bb4ee6a4SAndroid Build Coastguard Worker self.title = title 163*bb4ee6a4SAndroid Build Coastguard Worker "Commands to execute." 164*bb4ee6a4SAndroid Build Coastguard Worker self.commands = commands 165*bb4ee6a4SAndroid Build Coastguard Worker "Task is a priority check." 166*bb4ee6a4SAndroid Build Coastguard Worker self.priority = priority 167*bb4ee6a4SAndroid Build Coastguard Worker "List of log lines (stdout+stderr) produced by the task." 168*bb4ee6a4SAndroid Build Coastguard Worker self.log_lines: List[str] = [] 169*bb4ee6a4SAndroid Build Coastguard Worker "Task was compleded, but may or not have been successful." 170*bb4ee6a4SAndroid Build Coastguard Worker self.done = False 171*bb4ee6a4SAndroid Build Coastguard Worker "True if the task completed successfully." 172*bb4ee6a4SAndroid Build Coastguard Worker self.success = False 173*bb4ee6a4SAndroid Build Coastguard Worker "Time the task was started." 174*bb4ee6a4SAndroid Build Coastguard Worker self.start_time = datetime.min 175*bb4ee6a4SAndroid Build Coastguard Worker "Duration the task took to execute. Only filled after completion." 176*bb4ee6a4SAndroid Build Coastguard Worker self.duration = timedelta.max 177*bb4ee6a4SAndroid Build Coastguard Worker "Spinner object for status_widget UI." 178*bb4ee6a4SAndroid Build Coastguard Worker self.spinner = rich.spinner.Spinner("point", title) 179*bb4ee6a4SAndroid Build Coastguard Worker 180*bb4ee6a4SAndroid Build Coastguard Worker def status_widget(self): 181*bb4ee6a4SAndroid Build Coastguard Worker "Returns a rich console object showing the currrent status of the task." 182*bb4ee6a4SAndroid Build Coastguard Worker duration = self.duration if self.done else datetime.now() - self.start_time 183*bb4ee6a4SAndroid Build Coastguard Worker title = f"[{duration.total_seconds():6.2f}s] [bold]{self.title}[/bold]" 184*bb4ee6a4SAndroid Build Coastguard Worker 185*bb4ee6a4SAndroid Build Coastguard Worker if self.done: 186*bb4ee6a4SAndroid Build Coastguard Worker status: str = "[green]OK [/green]" if self.success else "[red]ERR[/red]" 187*bb4ee6a4SAndroid Build Coastguard Worker title_widget = rich.text.Text.from_markup(f"{status} {title}") 188*bb4ee6a4SAndroid Build Coastguard Worker else: 189*bb4ee6a4SAndroid Build Coastguard Worker self.spinner.text = rich.text.Text.from_markup(title) 190*bb4ee6a4SAndroid Build Coastguard Worker title_widget = self.spinner 191*bb4ee6a4SAndroid Build Coastguard Worker 192*bb4ee6a4SAndroid Build Coastguard Worker if not self.priority: 193*bb4ee6a4SAndroid Build Coastguard Worker return title_widget 194*bb4ee6a4SAndroid Build Coastguard Worker 195*bb4ee6a4SAndroid Build Coastguard Worker last_lines = [ 196*bb4ee6a4SAndroid Build Coastguard Worker self.log_lines[-3] if len(self.log_lines) >= 3 else "", 197*bb4ee6a4SAndroid Build Coastguard Worker self.log_lines[-2] if len(self.log_lines) >= 2 else "", 198*bb4ee6a4SAndroid Build Coastguard Worker self.log_lines[-1] if len(self.log_lines) >= 1 else "", 199*bb4ee6a4SAndroid Build Coastguard Worker ] 200*bb4ee6a4SAndroid Build Coastguard Worker 201*bb4ee6a4SAndroid Build Coastguard Worker return rich.console.Group( 202*bb4ee6a4SAndroid Build Coastguard Worker *( 203*bb4ee6a4SAndroid Build Coastguard Worker # Print last log lines without it's original colors 204*bb4ee6a4SAndroid Build Coastguard Worker rich.text.Text( 205*bb4ee6a4SAndroid Build Coastguard Worker "│ " + strip_ansi_escape_sequences(log_line), 206*bb4ee6a4SAndroid Build Coastguard Worker style="light_slate_grey", 207*bb4ee6a4SAndroid Build Coastguard Worker overflow="ellipsis", 208*bb4ee6a4SAndroid Build Coastguard Worker no_wrap=True, 209*bb4ee6a4SAndroid Build Coastguard Worker ) 210*bb4ee6a4SAndroid Build Coastguard Worker for log_line in last_lines 211*bb4ee6a4SAndroid Build Coastguard Worker ), 212*bb4ee6a4SAndroid Build Coastguard Worker rich.text.Text("└ ", end="", style="light_slate_grey"), 213*bb4ee6a4SAndroid Build Coastguard Worker title_widget, 214*bb4ee6a4SAndroid Build Coastguard Worker rich.text.Text(), 215*bb4ee6a4SAndroid Build Coastguard Worker ) 216*bb4ee6a4SAndroid Build Coastguard Worker 217*bb4ee6a4SAndroid Build Coastguard Worker def execute(self): 218*bb4ee6a4SAndroid Build Coastguard Worker "Execute the task while updating the status variables." 219*bb4ee6a4SAndroid Build Coastguard Worker try: 220*bb4ee6a4SAndroid Build Coastguard Worker self.start_time = datetime.now() 221*bb4ee6a4SAndroid Build Coastguard Worker success = True 222*bb4ee6a4SAndroid Build Coastguard Worker if verbose(): 223*bb4ee6a4SAndroid Build Coastguard Worker for command in self.commands: 224*bb4ee6a4SAndroid Build Coastguard Worker self.log_lines.append(f"$ {command}") 225*bb4ee6a4SAndroid Build Coastguard Worker 226*bb4ee6a4SAndroid Build Coastguard Worker # Spawn all commands as separate processes 227*bb4ee6a4SAndroid Build Coastguard Worker processes = [ 228*bb4ee6a4SAndroid Build Coastguard Worker command.popen(stdout=subprocess.PIPE, stderr=subprocess.STDOUT, errors="replace") 229*bb4ee6a4SAndroid Build Coastguard Worker for command in self.commands 230*bb4ee6a4SAndroid Build Coastguard Worker ] 231*bb4ee6a4SAndroid Build Coastguard Worker 232*bb4ee6a4SAndroid Build Coastguard Worker # The stdout is collected before we wait for the processes to exit so that the UI is 233*bb4ee6a4SAndroid Build Coastguard Worker # at least real-time for the first process. Note that in this way, the output for 234*bb4ee6a4SAndroid Build Coastguard Worker # other processes other than the first process are not real-time. In addition, we 235*bb4ee6a4SAndroid Build Coastguard Worker # can't proactively kill other processes in the same task if any process fails. 236*bb4ee6a4SAndroid Build Coastguard Worker for process in processes: 237*bb4ee6a4SAndroid Build Coastguard Worker assert process.stdout 238*bb4ee6a4SAndroid Build Coastguard Worker for line in iter(process.stdout.readline, ""): 239*bb4ee6a4SAndroid Build Coastguard Worker self.log_lines.append(line.strip()) 240*bb4ee6a4SAndroid Build Coastguard Worker 241*bb4ee6a4SAndroid Build Coastguard Worker # Wait for all processes to finish and check return code 242*bb4ee6a4SAndroid Build Coastguard Worker for process in processes: 243*bb4ee6a4SAndroid Build Coastguard Worker if process.wait() != 0: 244*bb4ee6a4SAndroid Build Coastguard Worker success = False 245*bb4ee6a4SAndroid Build Coastguard Worker 246*bb4ee6a4SAndroid Build Coastguard Worker self.duration = datetime.now() - self.start_time 247*bb4ee6a4SAndroid Build Coastguard Worker self.success = success 248*bb4ee6a4SAndroid Build Coastguard Worker self.done = True 249*bb4ee6a4SAndroid Build Coastguard Worker except Exception: 250*bb4ee6a4SAndroid Build Coastguard Worker self.log_lines.append(traceback.format_exc()) 251*bb4ee6a4SAndroid Build Coastguard Worker 252*bb4ee6a4SAndroid Build Coastguard Worker 253*bb4ee6a4SAndroid Build Coastguard Workerdef print_logs(tasks: List[Task]): 254*bb4ee6a4SAndroid Build Coastguard Worker "Prints logs of all failed or unfinished tasks." 255*bb4ee6a4SAndroid Build Coastguard Worker for task in tasks: 256*bb4ee6a4SAndroid Build Coastguard Worker if not task.done: 257*bb4ee6a4SAndroid Build Coastguard Worker print() 258*bb4ee6a4SAndroid Build Coastguard Worker console.rule(f"{task.title} did not finish", style="yellow") 259*bb4ee6a4SAndroid Build Coastguard Worker for line in task.log_lines: 260*bb4ee6a4SAndroid Build Coastguard Worker print(line) 261*bb4ee6a4SAndroid Build Coastguard Worker if not task.log_lines: 262*bb4ee6a4SAndroid Build Coastguard Worker print(f"{task.title} did not output any logs") 263*bb4ee6a4SAndroid Build Coastguard Worker for task in tasks: 264*bb4ee6a4SAndroid Build Coastguard Worker if task.done and not task.success: 265*bb4ee6a4SAndroid Build Coastguard Worker console.rule(f"{task.title} failed", style="red") 266*bb4ee6a4SAndroid Build Coastguard Worker for line in task.log_lines: 267*bb4ee6a4SAndroid Build Coastguard Worker print(line) 268*bb4ee6a4SAndroid Build Coastguard Worker if not task.log_lines: 269*bb4ee6a4SAndroid Build Coastguard Worker print(f"{task.title} did not output any logs") 270*bb4ee6a4SAndroid Build Coastguard Worker 271*bb4ee6a4SAndroid Build Coastguard Worker 272*bb4ee6a4SAndroid Build Coastguard Workerdef print_summary(tasks: List[Task]): 273*bb4ee6a4SAndroid Build Coastguard Worker "Prints a summary of all task results." 274*bb4ee6a4SAndroid Build Coastguard Worker console.rule("Summary") 275*bb4ee6a4SAndroid Build Coastguard Worker tasks.sort(key=lambda t: t.duration) 276*bb4ee6a4SAndroid Build Coastguard Worker for task in tasks: 277*bb4ee6a4SAndroid Build Coastguard Worker title = f"[{task.duration.total_seconds():6.2f}s] [bold]{task.title}[/bold]" 278*bb4ee6a4SAndroid Build Coastguard Worker status: str = "[green]OK [/green]" if task.success else "[red]ERR[/red]" 279*bb4ee6a4SAndroid Build Coastguard Worker console.print(f"{status} {title}") 280*bb4ee6a4SAndroid Build Coastguard Worker 281*bb4ee6a4SAndroid Build Coastguard Worker 282*bb4ee6a4SAndroid Build Coastguard Workerdef execute_tasks_parallel(tasks: List[Task]): 283*bb4ee6a4SAndroid Build Coastguard Worker "Executes the list of tasks in parallel, while rendering live status updates." 284*bb4ee6a4SAndroid Build Coastguard Worker with ThreadPoolExecutor() as executor: 285*bb4ee6a4SAndroid Build Coastguard Worker try: 286*bb4ee6a4SAndroid Build Coastguard Worker # Since tasks are executed in subprocesses, we can use a thread pool to parallelize 287*bb4ee6a4SAndroid Build Coastguard Worker # despite the GIL. 288*bb4ee6a4SAndroid Build Coastguard Worker task_futures = [executor.submit(lambda: t.execute()) for t in tasks] 289*bb4ee6a4SAndroid Build Coastguard Worker 290*bb4ee6a4SAndroid Build Coastguard Worker # Render task updates while they are executing in the background. 291*bb4ee6a4SAndroid Build Coastguard Worker with rich.live.Live(refresh_per_second=30) as live: 292*bb4ee6a4SAndroid Build Coastguard Worker while True: 293*bb4ee6a4SAndroid Build Coastguard Worker live.update( 294*bb4ee6a4SAndroid Build Coastguard Worker rich.console.Group( 295*bb4ee6a4SAndroid Build Coastguard Worker *(t.status_widget() for t in tasks), 296*bb4ee6a4SAndroid Build Coastguard Worker rich.text.Text(), 297*bb4ee6a4SAndroid Build Coastguard Worker rich.text.Text.from_markup( 298*bb4ee6a4SAndroid Build Coastguard Worker "[green]Tip:[/green] Press CTRL-C to abort execution and see all logs." 299*bb4ee6a4SAndroid Build Coastguard Worker ), 300*bb4ee6a4SAndroid Build Coastguard Worker ) 301*bb4ee6a4SAndroid Build Coastguard Worker ) 302*bb4ee6a4SAndroid Build Coastguard Worker if all(future.done() for future in task_futures): 303*bb4ee6a4SAndroid Build Coastguard Worker break 304*bb4ee6a4SAndroid Build Coastguard Worker sleep(0.1) 305*bb4ee6a4SAndroid Build Coastguard Worker except KeyboardInterrupt: 306*bb4ee6a4SAndroid Build Coastguard Worker print_logs(tasks) 307*bb4ee6a4SAndroid Build Coastguard Worker # Force exit to skip waiting for the executor to shutdown. This will kill all 308*bb4ee6a4SAndroid Build Coastguard Worker # running subprocesses. 309*bb4ee6a4SAndroid Build Coastguard Worker os._exit(1) # type: ignore 310*bb4ee6a4SAndroid Build Coastguard Worker 311*bb4ee6a4SAndroid Build Coastguard Worker # Render error logs and summary after execution 312*bb4ee6a4SAndroid Build Coastguard Worker print_logs(tasks) 313*bb4ee6a4SAndroid Build Coastguard Worker print_summary(tasks) 314*bb4ee6a4SAndroid Build Coastguard Worker 315*bb4ee6a4SAndroid Build Coastguard Worker if any(not t.success for t in tasks): 316*bb4ee6a4SAndroid Build Coastguard Worker raise Exception("Some checks failed") 317*bb4ee6a4SAndroid Build Coastguard Worker 318*bb4ee6a4SAndroid Build Coastguard Worker 319*bb4ee6a4SAndroid Build Coastguard Workerdef execute_tasks_serial(tasks: List[Task]): 320*bb4ee6a4SAndroid Build Coastguard Worker "Executes the list of tasks one-by-one" 321*bb4ee6a4SAndroid Build Coastguard Worker for task in tasks: 322*bb4ee6a4SAndroid Build Coastguard Worker console.rule(task.title) 323*bb4ee6a4SAndroid Build Coastguard Worker for command in task.commands: 324*bb4ee6a4SAndroid Build Coastguard Worker command.fg() 325*bb4ee6a4SAndroid Build Coastguard Worker console.print() 326*bb4ee6a4SAndroid Build Coastguard Worker 327*bb4ee6a4SAndroid Build Coastguard Worker 328*bb4ee6a4SAndroid Build Coastguard Workerdef generate_plan( 329*bb4ee6a4SAndroid Build Coastguard Worker checks_list: List[Check], 330*bb4ee6a4SAndroid Build Coastguard Worker fix: bool, 331*bb4ee6a4SAndroid Build Coastguard Worker run_on_all_files: bool, 332*bb4ee6a4SAndroid Build Coastguard Worker): 333*bb4ee6a4SAndroid Build Coastguard Worker "Generates a list of `Task`s to execute the checks provided in `checks_list`" 334*bb4ee6a4SAndroid Build Coastguard Worker all_files = [*all_tracked_files()] 335*bb4ee6a4SAndroid Build Coastguard Worker file_diff = [*list_file_diff()] 336*bb4ee6a4SAndroid Build Coastguard Worker new_files = [f for (s, f) in file_diff if s == "A"] 337*bb4ee6a4SAndroid Build Coastguard Worker if run_on_all_files: 338*bb4ee6a4SAndroid Build Coastguard Worker modified_files = all_files 339*bb4ee6a4SAndroid Build Coastguard Worker else: 340*bb4ee6a4SAndroid Build Coastguard Worker modified_files = [f for (s, f) in file_diff if s in ("M", "A")] 341*bb4ee6a4SAndroid Build Coastguard Worker tasks: List[Task] = [] 342*bb4ee6a4SAndroid Build Coastguard Worker unsupported_checks: List[str] = [] 343*bb4ee6a4SAndroid Build Coastguard Worker for check in checks_list: 344*bb4ee6a4SAndroid Build Coastguard Worker if fix and not check.can_fix: 345*bb4ee6a4SAndroid Build Coastguard Worker continue 346*bb4ee6a4SAndroid Build Coastguard Worker context = CheckContext( 347*bb4ee6a4SAndroid Build Coastguard Worker fix=fix, 348*bb4ee6a4SAndroid Build Coastguard Worker all_files=[f for f in all_files if should_run_check_on_file(check, f)], 349*bb4ee6a4SAndroid Build Coastguard Worker modified_files=[f for f in modified_files if should_run_check_on_file(check, f)], 350*bb4ee6a4SAndroid Build Coastguard Worker new_files=[f for f in new_files if should_run_check_on_file(check, f)], 351*bb4ee6a4SAndroid Build Coastguard Worker ) 352*bb4ee6a4SAndroid Build Coastguard Worker if context.modified_files: 353*bb4ee6a4SAndroid Build Coastguard Worker maybe_commands = check.check_function(context) 354*bb4ee6a4SAndroid Build Coastguard Worker if maybe_commands is None: 355*bb4ee6a4SAndroid Build Coastguard Worker unsupported_checks.append(check.name) 356*bb4ee6a4SAndroid Build Coastguard Worker continue 357*bb4ee6a4SAndroid Build Coastguard Worker commands_list = maybe_commands if isinstance(maybe_commands, list) else [maybe_commands] 358*bb4ee6a4SAndroid Build Coastguard Worker title = f"fixing {check.name}" if fix else check.name 359*bb4ee6a4SAndroid Build Coastguard Worker tasks.append(Task(title, commands_list, check.priority)) 360*bb4ee6a4SAndroid Build Coastguard Worker 361*bb4ee6a4SAndroid Build Coastguard Worker if unsupported_checks: 362*bb4ee6a4SAndroid Build Coastguard Worker console.print("[yellow]Warning:[/yellow] The following checks cannot be run:") 363*bb4ee6a4SAndroid Build Coastguard Worker for unsupported_check in unsupported_checks: 364*bb4ee6a4SAndroid Build Coastguard Worker console.print(f" - {unsupported_check}") 365*bb4ee6a4SAndroid Build Coastguard Worker console.print() 366*bb4ee6a4SAndroid Build Coastguard Worker console.print("[green]Tip:[/green] Use the dev container to run presubmits:") 367*bb4ee6a4SAndroid Build Coastguard Worker console.print() 368*bb4ee6a4SAndroid Build Coastguard Worker console.print( 369*bb4ee6a4SAndroid Build Coastguard Worker f" [blue] $ tools/dev_container tools/presubmit {' '.join(sys.argv[1:])}[/blue]" 370*bb4ee6a4SAndroid Build Coastguard Worker ) 371*bb4ee6a4SAndroid Build Coastguard Worker console.print() 372*bb4ee6a4SAndroid Build Coastguard Worker 373*bb4ee6a4SAndroid Build Coastguard Worker if not os.access("/dev/kvm", os.W_OK): 374*bb4ee6a4SAndroid Build Coastguard Worker console.print("[yellow]Warning:[/yellow] Cannot access KVM. Integration tests are not run.") 375*bb4ee6a4SAndroid Build Coastguard Worker 376*bb4ee6a4SAndroid Build Coastguard Worker # Sort so that priority tasks are launched (and rendered) first 377*bb4ee6a4SAndroid Build Coastguard Worker tasks.sort(key=lambda t: (t.priority, t.title), reverse=True) 378*bb4ee6a4SAndroid Build Coastguard Worker return tasks 379*bb4ee6a4SAndroid Build Coastguard Worker 380*bb4ee6a4SAndroid Build Coastguard Worker 381*bb4ee6a4SAndroid Build Coastguard Workerdef run_checks( 382*bb4ee6a4SAndroid Build Coastguard Worker checks_list: List[Check], 383*bb4ee6a4SAndroid Build Coastguard Worker fix: bool, 384*bb4ee6a4SAndroid Build Coastguard Worker run_on_all_files: bool, 385*bb4ee6a4SAndroid Build Coastguard Worker parallel: bool, 386*bb4ee6a4SAndroid Build Coastguard Worker): 387*bb4ee6a4SAndroid Build Coastguard Worker """ 388*bb4ee6a4SAndroid Build Coastguard Worker Runs all checks in checks_list. 389*bb4ee6a4SAndroid Build Coastguard Worker 390*bb4ee6a4SAndroid Build Coastguard Worker Arguments: 391*bb4ee6a4SAndroid Build Coastguard Worker fix: Run fixes instead of checks on `Check`s that support it. 392*bb4ee6a4SAndroid Build Coastguard Worker run_on_all_files: Do not use git delta, but run on all files. 393*bb4ee6a4SAndroid Build Coastguard Worker nightly_fmt: Use nightly version of rust tooling. 394*bb4ee6a4SAndroid Build Coastguard Worker parallel: Run tasks in parallel. 395*bb4ee6a4SAndroid Build Coastguard Worker """ 396*bb4ee6a4SAndroid Build Coastguard Worker tasks = generate_plan(checks_list, fix, run_on_all_files) 397*bb4ee6a4SAndroid Build Coastguard Worker if len(tasks) == 1: 398*bb4ee6a4SAndroid Build Coastguard Worker parallel = False 399*bb4ee6a4SAndroid Build Coastguard Worker 400*bb4ee6a4SAndroid Build Coastguard Worker if parallel: 401*bb4ee6a4SAndroid Build Coastguard Worker execute_tasks_parallel(list(tasks)) 402*bb4ee6a4SAndroid Build Coastguard Worker else: 403*bb4ee6a4SAndroid Build Coastguard Worker execute_tasks_serial(list(tasks)) 404