1 # ***** BEGIN GPL LICENSE BLOCK *****
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ***** END GPL LICENSE BLOCK *****
28 if sys.version_info.major < 3:
29 print("\nPython3.x or newer needed, found %s.\nAborting!\n" %
30 sys.version.partition(" ")[0])
35 from os.path import join, dirname, normpath, abspath
52 SOURCE_DIR = join(dirname(__file__), "..", "..")
53 SOURCE_DIR = normpath(SOURCE_DIR)
54 SOURCE_DIR = abspath(SOURCE_DIR)
57 def is_c_header(filename: str) -> bool:
58 ext = os.path.splitext(filename)[1]
59 return (ext in {".h", ".hpp", ".hxx", ".hh"})
62 def is_c(filename: str) -> bool:
63 ext = os.path.splitext(filename)[1]
64 return (ext in {".c", ".cpp", ".cxx", ".m", ".mm", ".rc", ".cc", ".inl", ".osl"})
67 def is_c_any(filename: str) -> bool:
68 return is_c(filename) or is_c_header(filename)
71 # copied from project_info.py
75 def cmake_cache_var_iter() -> Generator[Tuple[str, str, str], None, None]:
77 re_cache = re.compile(r'([A-Za-z0-9_\-]+)?:?([A-Za-z0-9_\-]+)?=(.*)$')
78 with open(join(CMAKE_DIR, "CMakeCache.txt"), 'r', encoding='utf-8') as cache_file:
80 match = re_cache.match(l.strip())
82 var, type_, val = match.groups()
83 yield (var, type_ or "", val)
86 def cmake_cache_var(var: str) -> Optional[str]:
87 for var_iter, type_iter, value_iter in cmake_cache_var_iter():
93 def cmake_cache_var_or_exit(var: str) -> str:
94 value = cmake_cache_var(var)
96 print("Unable to find %r exiting!" % value)
101 def do_ignore(filepath: str, ignore_prefix_list: Optional[Sequence[str]]) -> bool:
102 if ignore_prefix_list is None:
105 relpath = os.path.relpath(filepath, SOURCE_DIR)
106 return any([relpath.startswith(prefix) for prefix in ignore_prefix_list])
109 def makefile_log() -> List[str]:
113 # support both make and ninja
114 make_exe = cmake_cache_var_or_exit("CMAKE_MAKE_PROGRAM")
116 make_exe_basename = os.path.basename(make_exe)
118 if make_exe_basename.startswith(("make", "gmake")):
119 print("running 'make' with --dry-run ...")
120 process = subprocess.Popen([make_exe, "--always-make", "--dry-run", "--keep-going", "VERBOSE=1"],
121 stdout=subprocess.PIPE,
123 elif make_exe_basename.startswith("ninja"):
124 print("running 'ninja' with -t commands ...")
125 process = subprocess.Popen([make_exe, "-t", "commands"],
126 stdout=subprocess.PIPE,
130 print("Can't execute process")
134 while process.poll():
137 # We know this is always true based on the input arguments to `Popen`.
138 stdout: IO[bytes] = process.stdout # type: ignore
142 print("done!", len(out), "bytes")
143 return cast(List[str], out.decode("utf-8", errors="ignore").split("\n"))
148 use_cxx: bool = True,
149 ignore_prefix_list: Optional[List[str]] = None,
150 ) -> List[Tuple[str, List[str], List[str]]]:
151 makelog = makefile_log()
157 compilers.append(cmake_cache_var_or_exit("CMAKE_C_COMPILER"))
159 compilers.append(cmake_cache_var_or_exit("CMAKE_CXX_COMPILER"))
161 print("compilers:", " ".join(compilers))
163 fake_compiler = "%COMPILER%"
165 print("parsing make log ...")
169 args: Union[str, List[str]] = line.split()
171 if not any([(c in args) for c in compilers]):
174 # join args incase they are not.
175 args = ' '.join(args)
176 args = args.replace(" -isystem", " -I")
177 args = args.replace(" -D ", " -D")
178 args = args.replace(" -I ", " -I")
181 args = args.replace(c, fake_compiler)
186 args[:args.index(fake_compiler) + 1] = []
188 c_files = [f for f in args if is_c(f)]
189 inc_dirs = [f[2:].strip() for f in args if f.startswith('-I')]
190 defs = [f[2:].strip() for f in args if f.startswith('-D')]
191 for c in sorted(c_files):
193 if do_ignore(c, ignore_prefix_list):
196 source.append((c, inc_dirs, defs))
198 # make relative includes absolute
199 # not totally essential but useful
200 for i, f in enumerate(inc_dirs):
201 if not os.path.isabs(f):
202 inc_dirs[i] = os.path.abspath(os.path.join(CMAKE_DIR, f))
204 # safety check that our includes are ok
206 if not os.path.exists(f):
207 raise Exception("%s missing" % f)
214 def build_defines_as_source() -> str:
216 Returns a string formatted as an include:
217 '#defines A=B\n#define....'
220 # works for both gcc and clang
221 cmd = (cmake_cache_var_or_exit("CMAKE_C_COMPILER"), "-dM", "-E", "-")
222 process = subprocess.Popen(
224 stdout=subprocess.PIPE,
225 stdin=subprocess.DEVNULL,
228 # We know this is always true based on the input arguments to `Popen`.
229 stdout: IO[bytes] = process.stdout # type: ignore
231 return cast(str, stdout.read().strip().decode('ascii'))
234 def build_defines_as_args() -> List[str]:
236 ("-D" + "=".join(l.split(maxsplit=2)[1:]))
237 for l in build_defines_as_source().split("\n")
238 if l.startswith('#define')
242 # could be moved elsewhere!, this just happens to be used by scripts that also
245 process_funcs: Sequence[Tuple[Callable[..., subprocess.Popen[Any]], Tuple[Any, ...]]],
248 """ Takes a list of function arg pairs, each function must return a process
252 import multiprocessing
253 job_total = multiprocessing.cpu_count()
257 for func, args in process_funcs:
261 process = func(*args)
266 processes: List[subprocess.Popen[Any]] = []
267 for func, args in process_funcs:
268 # wait until a thread is free
270 processes[:] = [p for p in processes if p.poll() is None]
272 if len(processes) <= job_total:
280 processes.append(func(*args))
284 if not os.path.exists(join(CMAKE_DIR, "CMakeCache.txt")):
285 print("This script must run from the cmake build dir")
288 for s in build_info():
292 if __name__ == "__main__":