1e5dd7070Spatrickimport os 2e5dd7070Spatrickimport sys 3*ec727ea7Spatrickimport time 4*ec727ea7Spatrick 5*ec727ea7Spatrickfrom subprocess import CalledProcessError, check_call 6*ec727ea7Spatrickfrom typing import List, IO, Optional, Tuple 7e5dd7070Spatrick 8e5dd7070Spatrick 9*ec727ea7Spatrickdef which(command: str, paths: Optional[str] = None) -> Optional[str]: 10e5dd7070Spatrick """which(command, [paths]) - Look up the given command in the paths string 11e5dd7070Spatrick (or the PATH environment variable, if unspecified).""" 12e5dd7070Spatrick 13e5dd7070Spatrick if paths is None: 14e5dd7070Spatrick paths = os.environ.get('PATH', '') 15e5dd7070Spatrick 16e5dd7070Spatrick # Check for absolute match first. 17e5dd7070Spatrick if os.path.exists(command): 18e5dd7070Spatrick return command 19e5dd7070Spatrick 20e5dd7070Spatrick # Would be nice if Python had a lib function for this. 21e5dd7070Spatrick if not paths: 22e5dd7070Spatrick paths = os.defpath 23e5dd7070Spatrick 24e5dd7070Spatrick # Get suffixes to search. 25e5dd7070Spatrick # On Cygwin, 'PATHEXT' may exist but it should not be used. 26e5dd7070Spatrick if os.pathsep == ';': 27e5dd7070Spatrick pathext = os.environ.get('PATHEXT', '').split(';') 28e5dd7070Spatrick else: 29e5dd7070Spatrick pathext = [''] 30e5dd7070Spatrick 31e5dd7070Spatrick # Search the paths... 32e5dd7070Spatrick for path in paths.split(os.pathsep): 33e5dd7070Spatrick for ext in pathext: 34e5dd7070Spatrick p = os.path.join(path, command + ext) 35e5dd7070Spatrick if os.path.exists(p): 36e5dd7070Spatrick return p 37e5dd7070Spatrick 38e5dd7070Spatrick return None 39e5dd7070Spatrick 40e5dd7070Spatrick 41*ec727ea7Spatrickdef has_no_extension(file_name: str) -> bool: 42*ec727ea7Spatrick root, ext = os.path.splitext(file_name) 43*ec727ea7Spatrick return ext == "" 44e5dd7070Spatrick 45e5dd7070Spatrick 46*ec727ea7Spatrickdef is_valid_single_input_file(file_name: str) -> bool: 47*ec727ea7Spatrick root, ext = os.path.splitext(file_name) 48*ec727ea7Spatrick return ext in (".i", ".ii", ".c", ".cpp", ".m", "") 49e5dd7070Spatrick 50e5dd7070Spatrick 51*ec727ea7Spatrickdef time_to_str(time: float) -> str: 52*ec727ea7Spatrick """ 53*ec727ea7Spatrick Convert given time in seconds into a human-readable string. 54*ec727ea7Spatrick """ 55*ec727ea7Spatrick return f"{time:.2f}s" 56*ec727ea7Spatrick 57*ec727ea7Spatrick 58*ec727ea7Spatrickdef memory_to_str(memory: int) -> str: 59*ec727ea7Spatrick """ 60*ec727ea7Spatrick Convert given number of bytes into a human-readable string. 61*ec727ea7Spatrick """ 62*ec727ea7Spatrick if memory: 63*ec727ea7Spatrick try: 64*ec727ea7Spatrick import humanize 65*ec727ea7Spatrick return humanize.naturalsize(memory, gnu=True) 66*ec727ea7Spatrick except ImportError: 67*ec727ea7Spatrick # no formatter installed, let's keep it in bytes 68*ec727ea7Spatrick return f"{memory}B" 69*ec727ea7Spatrick 70*ec727ea7Spatrick # If memory is 0, we didn't succeed measuring it. 71*ec727ea7Spatrick return "N/A" 72*ec727ea7Spatrick 73*ec727ea7Spatrick 74*ec727ea7Spatrickdef check_and_measure_call(*popenargs, **kwargs) -> Tuple[float, int]: 75*ec727ea7Spatrick """ 76*ec727ea7Spatrick Run command with arguments. Wait for command to complete and measure 77*ec727ea7Spatrick execution time and peak memory consumption. 78*ec727ea7Spatrick If the exit code was zero then return, otherwise raise 79*ec727ea7Spatrick CalledProcessError. The CalledProcessError object will have the 80*ec727ea7Spatrick return code in the returncode attribute. 81*ec727ea7Spatrick 82*ec727ea7Spatrick The arguments are the same as for the call and check_call functions. 83*ec727ea7Spatrick 84*ec727ea7Spatrick Return a tuple of execution time and peak memory. 85*ec727ea7Spatrick """ 86*ec727ea7Spatrick peak_mem = 0 87*ec727ea7Spatrick start_time = time.time() 88*ec727ea7Spatrick 89*ec727ea7Spatrick try: 90*ec727ea7Spatrick import psutil as ps 91*ec727ea7Spatrick 92*ec727ea7Spatrick def get_memory(process: ps.Process) -> int: 93*ec727ea7Spatrick mem = 0 94*ec727ea7Spatrick 95*ec727ea7Spatrick # we want to gather memory usage from all of the child processes 96*ec727ea7Spatrick descendants = list(process.children(recursive=True)) 97*ec727ea7Spatrick descendants.append(process) 98*ec727ea7Spatrick 99*ec727ea7Spatrick for subprocess in descendants: 100*ec727ea7Spatrick try: 101*ec727ea7Spatrick mem += subprocess.memory_info().rss 102*ec727ea7Spatrick except (ps.NoSuchProcess, ps.AccessDenied): 103*ec727ea7Spatrick continue 104*ec727ea7Spatrick 105*ec727ea7Spatrick return mem 106*ec727ea7Spatrick 107*ec727ea7Spatrick with ps.Popen(*popenargs, **kwargs) as process: 108*ec727ea7Spatrick # while the process is running calculate resource utilization. 109*ec727ea7Spatrick while (process.is_running() and 110*ec727ea7Spatrick process.status() != ps.STATUS_ZOMBIE): 111*ec727ea7Spatrick # track the peak utilization of the process 112*ec727ea7Spatrick peak_mem = max(peak_mem, get_memory(process)) 113*ec727ea7Spatrick time.sleep(.5) 114*ec727ea7Spatrick 115*ec727ea7Spatrick if process.is_running(): 116*ec727ea7Spatrick process.kill() 117*ec727ea7Spatrick 118*ec727ea7Spatrick if process.returncode != 0: 119*ec727ea7Spatrick cmd = kwargs.get("args") 120*ec727ea7Spatrick if cmd is None: 121*ec727ea7Spatrick cmd = popenargs[0] 122*ec727ea7Spatrick raise CalledProcessError(process.returncode, cmd) 123*ec727ea7Spatrick 124*ec727ea7Spatrick except ImportError: 125*ec727ea7Spatrick # back off to subprocess if we don't have psutil installed 126*ec727ea7Spatrick peak_mem = 0 127*ec727ea7Spatrick check_call(*popenargs, **kwargs) 128*ec727ea7Spatrick 129*ec727ea7Spatrick return time.time() - start_time, peak_mem 130*ec727ea7Spatrick 131*ec727ea7Spatrick 132*ec727ea7Spatrickdef run_script(script_path: str, build_log_file: IO, cwd: str, 133*ec727ea7Spatrick out=sys.stdout, err=sys.stderr, verbose: int = 0): 134e5dd7070Spatrick """ 135e5dd7070Spatrick Run the provided script if it exists. 136e5dd7070Spatrick """ 137*ec727ea7Spatrick if os.path.exists(script_path): 138e5dd7070Spatrick try: 139*ec727ea7Spatrick if verbose == 1: 140*ec727ea7Spatrick out.write(f" Executing: {script_path}\n") 141*ec727ea7Spatrick 142*ec727ea7Spatrick check_call(f"chmod +x '{script_path}'", cwd=cwd, 143*ec727ea7Spatrick stderr=build_log_file, 144*ec727ea7Spatrick stdout=build_log_file, 145e5dd7070Spatrick shell=True) 146*ec727ea7Spatrick 147*ec727ea7Spatrick check_call(f"'{script_path}'", cwd=cwd, 148*ec727ea7Spatrick stderr=build_log_file, 149*ec727ea7Spatrick stdout=build_log_file, 150e5dd7070Spatrick shell=True) 151*ec727ea7Spatrick 152*ec727ea7Spatrick except CalledProcessError: 153*ec727ea7Spatrick err.write(f"Error: Running {script_path} failed. " 154*ec727ea7Spatrick f"See {build_log_file.name} for details.\n") 155e5dd7070Spatrick sys.exit(-1) 156e5dd7070Spatrick 157e5dd7070Spatrick 158*ec727ea7Spatrickdef is_comment_csv_line(entries: List[str]) -> bool: 159e5dd7070Spatrick """ 160e5dd7070Spatrick Treat CSV lines starting with a '#' as a comment. 161e5dd7070Spatrick """ 162*ec727ea7Spatrick return len(entries) > 0 and entries[0].startswith("#") 163