1bf92c446SGeorge Karpenkovimport os 2bf92c446SGeorge Karpenkovimport sys 321bacc21SValeriy Savchenkoimport time 4bf92c446SGeorge Karpenkov 57cebfa4eSValeriy Savchenkofrom subprocess import CalledProcessError, check_call 621bacc21SValeriy Savchenkofrom typing import List, IO, Optional, Tuple 7bf92c446SGeorge Karpenkov 8c98872e3SValeriy Savchenko 97cebfa4eSValeriy Savchenkodef which(command: str, paths: Optional[str] = None) -> Optional[str]: 10bf92c446SGeorge Karpenkov """which(command, [paths]) - Look up the given command in the paths string 11bf92c446SGeorge Karpenkov (or the PATH environment variable, if unspecified).""" 12bf92c446SGeorge Karpenkov 13bf92c446SGeorge Karpenkov if paths is None: 14*dd3c26a0STobias Hieta paths = os.environ.get("PATH", "") 15bf92c446SGeorge Karpenkov 16bf92c446SGeorge Karpenkov # Check for absolute match first. 17bf92c446SGeorge Karpenkov if os.path.exists(command): 18bf92c446SGeorge Karpenkov return command 19bf92c446SGeorge Karpenkov 20bf92c446SGeorge Karpenkov # Would be nice if Python had a lib function for this. 21bf92c446SGeorge Karpenkov if not paths: 22bf92c446SGeorge Karpenkov paths = os.defpath 23bf92c446SGeorge Karpenkov 24bf92c446SGeorge Karpenkov # Get suffixes to search. 25bf92c446SGeorge Karpenkov # On Cygwin, 'PATHEXT' may exist but it should not be used. 26*dd3c26a0STobias Hieta if os.pathsep == ";": 27*dd3c26a0STobias Hieta pathext = os.environ.get("PATHEXT", "").split(";") 28bf92c446SGeorge Karpenkov else: 29*dd3c26a0STobias Hieta pathext = [""] 30bf92c446SGeorge Karpenkov 31bf92c446SGeorge Karpenkov # Search the paths... 32bf92c446SGeorge Karpenkov for path in paths.split(os.pathsep): 33bf92c446SGeorge Karpenkov for ext in pathext: 34bf92c446SGeorge Karpenkov p = os.path.join(path, command + ext) 35bf92c446SGeorge Karpenkov if os.path.exists(p): 36bf92c446SGeorge Karpenkov return p 37bf92c446SGeorge Karpenkov 38bf92c446SGeorge Karpenkov return None 39bf92c446SGeorge Karpenkov 40bf92c446SGeorge Karpenkov 417cebfa4eSValeriy Savchenkodef has_no_extension(file_name: str) -> bool: 427cebfa4eSValeriy Savchenko root, ext = os.path.splitext(file_name) 437cebfa4eSValeriy Savchenko return ext == "" 44bf92c446SGeorge Karpenkov 45bf92c446SGeorge Karpenkov 467cebfa4eSValeriy Savchenkodef is_valid_single_input_file(file_name: str) -> bool: 477cebfa4eSValeriy Savchenko root, ext = os.path.splitext(file_name) 487cebfa4eSValeriy Savchenko return ext in (".i", ".ii", ".c", ".cpp", ".m", "") 49bf92c446SGeorge Karpenkov 50bf92c446SGeorge Karpenkov 5121bacc21SValeriy Savchenkodef time_to_str(time: float) -> str: 5221bacc21SValeriy Savchenko """ 5321bacc21SValeriy Savchenko Convert given time in seconds into a human-readable string. 5421bacc21SValeriy Savchenko """ 5521bacc21SValeriy Savchenko return f"{time:.2f}s" 5621bacc21SValeriy Savchenko 5721bacc21SValeriy Savchenko 5821bacc21SValeriy Savchenkodef memory_to_str(memory: int) -> str: 5921bacc21SValeriy Savchenko """ 6021bacc21SValeriy Savchenko Convert given number of bytes into a human-readable string. 6121bacc21SValeriy Savchenko """ 6221bacc21SValeriy Savchenko if memory: 6321bacc21SValeriy Savchenko try: 6421bacc21SValeriy Savchenko import humanize 65*dd3c26a0STobias Hieta 6621bacc21SValeriy Savchenko return humanize.naturalsize(memory, gnu=True) 6721bacc21SValeriy Savchenko except ImportError: 6821bacc21SValeriy Savchenko # no formatter installed, let's keep it in bytes 6921bacc21SValeriy Savchenko return f"{memory}B" 7021bacc21SValeriy Savchenko 7121bacc21SValeriy Savchenko # If memory is 0, we didn't succeed measuring it. 7221bacc21SValeriy Savchenko return "N/A" 7321bacc21SValeriy Savchenko 7421bacc21SValeriy Savchenko 7521bacc21SValeriy Savchenkodef check_and_measure_call(*popenargs, **kwargs) -> Tuple[float, int]: 7621bacc21SValeriy Savchenko """ 7721bacc21SValeriy Savchenko Run command with arguments. Wait for command to complete and measure 7821bacc21SValeriy Savchenko execution time and peak memory consumption. 7921bacc21SValeriy Savchenko If the exit code was zero then return, otherwise raise 8021bacc21SValeriy Savchenko CalledProcessError. The CalledProcessError object will have the 8121bacc21SValeriy Savchenko return code in the returncode attribute. 8221bacc21SValeriy Savchenko 8321bacc21SValeriy Savchenko The arguments are the same as for the call and check_call functions. 8421bacc21SValeriy Savchenko 8521bacc21SValeriy Savchenko Return a tuple of execution time and peak memory. 8621bacc21SValeriy Savchenko """ 8721bacc21SValeriy Savchenko peak_mem = 0 8821bacc21SValeriy Savchenko start_time = time.time() 8921bacc21SValeriy Savchenko 9021bacc21SValeriy Savchenko try: 9121bacc21SValeriy Savchenko import psutil as ps 9221bacc21SValeriy Savchenko 9321bacc21SValeriy Savchenko def get_memory(process: ps.Process) -> int: 9421bacc21SValeriy Savchenko mem = 0 9521bacc21SValeriy Savchenko 9621bacc21SValeriy Savchenko # we want to gather memory usage from all of the child processes 9721bacc21SValeriy Savchenko descendants = list(process.children(recursive=True)) 9821bacc21SValeriy Savchenko descendants.append(process) 9921bacc21SValeriy Savchenko 10021bacc21SValeriy Savchenko for subprocess in descendants: 10121bacc21SValeriy Savchenko try: 10221bacc21SValeriy Savchenko mem += subprocess.memory_info().rss 10321bacc21SValeriy Savchenko except (ps.NoSuchProcess, ps.AccessDenied): 10421bacc21SValeriy Savchenko continue 10521bacc21SValeriy Savchenko 10621bacc21SValeriy Savchenko return mem 10721bacc21SValeriy Savchenko 10821bacc21SValeriy Savchenko with ps.Popen(*popenargs, **kwargs) as process: 10921bacc21SValeriy Savchenko # while the process is running calculate resource utilization. 110*dd3c26a0STobias Hieta while process.is_running() and process.status() != ps.STATUS_ZOMBIE: 11121bacc21SValeriy Savchenko # track the peak utilization of the process 11221bacc21SValeriy Savchenko peak_mem = max(peak_mem, get_memory(process)) 113*dd3c26a0STobias Hieta time.sleep(0.5) 11421bacc21SValeriy Savchenko 11521bacc21SValeriy Savchenko if process.is_running(): 11621bacc21SValeriy Savchenko process.kill() 11721bacc21SValeriy Savchenko 11821bacc21SValeriy Savchenko if process.returncode != 0: 11921bacc21SValeriy Savchenko cmd = kwargs.get("args") 12021bacc21SValeriy Savchenko if cmd is None: 12121bacc21SValeriy Savchenko cmd = popenargs[0] 12221bacc21SValeriy Savchenko raise CalledProcessError(process.returncode, cmd) 12321bacc21SValeriy Savchenko 12421bacc21SValeriy Savchenko except ImportError: 12521bacc21SValeriy Savchenko # back off to subprocess if we don't have psutil installed 12621bacc21SValeriy Savchenko peak_mem = 0 12721bacc21SValeriy Savchenko check_call(*popenargs, **kwargs) 12821bacc21SValeriy Savchenko 12921bacc21SValeriy Savchenko return time.time() - start_time, peak_mem 13021bacc21SValeriy Savchenko 13121bacc21SValeriy Savchenko 132*dd3c26a0STobias Hietadef run_script( 133*dd3c26a0STobias Hieta script_path: str, 134*dd3c26a0STobias Hieta build_log_file: IO, 135*dd3c26a0STobias Hieta cwd: str, 136*dd3c26a0STobias Hieta out=sys.stdout, 137*dd3c26a0STobias Hieta err=sys.stderr, 138*dd3c26a0STobias Hieta verbose: int = 0, 139*dd3c26a0STobias Hieta): 140bf92c446SGeorge Karpenkov """ 141bf92c446SGeorge Karpenkov Run the provided script if it exists. 142bf92c446SGeorge Karpenkov """ 1437cebfa4eSValeriy Savchenko if os.path.exists(script_path): 144bf92c446SGeorge Karpenkov try: 1457cebfa4eSValeriy Savchenko if verbose == 1: 1467cebfa4eSValeriy Savchenko out.write(f" Executing: {script_path}\n") 1477cebfa4eSValeriy Savchenko 148*dd3c26a0STobias Hieta check_call( 149*dd3c26a0STobias Hieta f"chmod +x '{script_path}'", 150*dd3c26a0STobias Hieta cwd=cwd, 1517cebfa4eSValeriy Savchenko stderr=build_log_file, 1527cebfa4eSValeriy Savchenko stdout=build_log_file, 153*dd3c26a0STobias Hieta shell=True, 154*dd3c26a0STobias Hieta ) 1557cebfa4eSValeriy Savchenko 156*dd3c26a0STobias Hieta check_call( 157*dd3c26a0STobias Hieta f"'{script_path}'", 158*dd3c26a0STobias Hieta cwd=cwd, 1597cebfa4eSValeriy Savchenko stderr=build_log_file, 1607cebfa4eSValeriy Savchenko stdout=build_log_file, 161*dd3c26a0STobias Hieta shell=True, 162*dd3c26a0STobias Hieta ) 1637cebfa4eSValeriy Savchenko 1647cebfa4eSValeriy Savchenko except CalledProcessError: 165*dd3c26a0STobias Hieta err.write( 166*dd3c26a0STobias Hieta f"Error: Running {script_path} failed. " 167*dd3c26a0STobias Hieta f"See {build_log_file.name} for details.\n" 168*dd3c26a0STobias Hieta ) 169bf92c446SGeorge Karpenkov sys.exit(-1) 170bf92c446SGeorge Karpenkov 171bf92c446SGeorge Karpenkov 1727cebfa4eSValeriy Savchenkodef is_comment_csv_line(entries: List[str]) -> bool: 173bf92c446SGeorge Karpenkov """ 174bf92c446SGeorge Karpenkov Treat CSV lines starting with a '#' as a comment. 175bf92c446SGeorge Karpenkov """ 1767cebfa4eSValeriy Savchenko return len(entries) > 0 and entries[0].startswith("#") 177