5 from subprocess
import CalledProcessError
, check_call
6 from typing
import List
, IO
, Optional
, Tuple
9 def which(command
: str, paths
: Optional
[str] = None) -> Optional
[str]:
10 """which(command, [paths]) - Look up the given command in the paths string
11 (or the PATH environment variable, if unspecified)."""
14 paths
= os
.environ
.get("PATH", "")
16 # Check for absolute match first.
17 if os
.path
.exists(command
):
20 # Would be nice if Python had a lib function for this.
24 # Get suffixes to search.
25 # On Cygwin, 'PATHEXT' may exist but it should not be used.
27 pathext
= os
.environ
.get("PATHEXT", "").split(";")
32 for path
in paths
.split(os
.pathsep
):
34 p
= os
.path
.join(path
, command
+ ext
)
41 def has_no_extension(file_name
: str) -> bool:
42 root
, ext
= os
.path
.splitext(file_name
)
46 def is_valid_single_input_file(file_name
: str) -> bool:
47 root
, ext
= os
.path
.splitext(file_name
)
48 return ext
in (".i", ".ii", ".c", ".cpp", ".m", "")
51 def time_to_str(time
: float) -> str:
53 Convert given time in seconds into a human-readable string.
58 def memory_to_str(memory
: int) -> str:
60 Convert given number of bytes into a human-readable string.
66 return humanize
.naturalsize(memory
, gnu
=True)
68 # no formatter installed, let's keep it in bytes
71 # If memory is 0, we didn't succeed measuring it.
75 def check_and_measure_call(*popenargs
, **kwargs
) -> Tuple
[float, int]:
77 Run command with arguments. Wait for command to complete and measure
78 execution time and peak memory consumption.
79 If the exit code was zero then return, otherwise raise
80 CalledProcessError. The CalledProcessError object will have the
81 return code in the returncode attribute.
83 The arguments are the same as for the call and check_call functions.
85 Return a tuple of execution time and peak memory.
88 start_time
= time
.time()
93 def get_memory(process
: ps
.Process
) -> int:
96 # we want to gather memory usage from all of the child processes
97 descendants
= list(process
.children(recursive
=True))
98 descendants
.append(process
)
100 for subprocess
in descendants
:
102 mem
+= subprocess
.memory_info().rss
103 except (ps
.NoSuchProcess
, ps
.AccessDenied
):
108 with ps
.Popen(*popenargs
, **kwargs
) as process
:
109 # while the process is running calculate resource utilization.
110 while process
.is_running() and process
.status() != ps
.STATUS_ZOMBIE
:
111 # track the peak utilization of the process
112 peak_mem
= max(peak_mem
, get_memory(process
))
115 if process
.is_running():
118 if process
.returncode
!= 0:
119 cmd
= kwargs
.get("args")
122 raise CalledProcessError(process
.returncode
, cmd
)
125 # back off to subprocess if we don't have psutil installed
127 check_call(*popenargs
, **kwargs
)
129 return time
.time() - start_time
, peak_mem
141 Run the provided script if it exists.
143 if os
.path
.exists(script_path
):
146 out
.write(f
" Executing: {script_path}\n")
149 f
"chmod +x '{script_path}'",
151 stderr
=build_log_file
,
152 stdout
=build_log_file
,
159 stderr
=build_log_file
,
160 stdout
=build_log_file
,
164 except CalledProcessError
:
166 f
"Error: Running {script_path} failed. "
167 f
"See {build_log_file.name} for details.\n"
172 def is_comment_csv_line(entries
: List
[str]) -> bool:
174 Treat CSV lines starting with a '#' as a comment.
176 return len(entries
) > 0 and entries
[0].startswith("#")