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 # Contributor(s): Campbell Barton
19 # ***** END GPL LICENSE BLOCK *****
30 if not sys.version.startswith("3"):
31 print("\nPython3.x needed, found %s.\nAborting!\n" %
32 sys.version.partition(" ")[0])
37 from os.path import join, dirname, normpath, abspath
39 SOURCE_DIR = join(dirname(__file__), "..", "..")
40 SOURCE_DIR = normpath(SOURCE_DIR)
41 SOURCE_DIR = abspath(SOURCE_DIR)
44 def is_c_header(filename):
45 ext = os.path.splitext(filename)[1]
46 return (ext in {".h", ".hpp", ".hxx", ".hh"})
50 ext = os.path.splitext(filename)[1]
51 return (ext in {".c", ".cpp", ".cxx", ".m", ".mm", ".rc", ".cc", ".inl", ".osl"})
54 def is_c_any(filename):
55 return os.path.s_c(filename) or is_c_header(filename)
58 # copied from project_info.py
62 def cmake_cache_var_iter():
64 re_cache = re.compile(r'([A-Za-z0-9_\-]+)?:?([A-Za-z0-9_\-]+)?=(.*)$')
65 with open(join(CMAKE_DIR, "CMakeCache.txt"), 'r', encoding='utf-8') as cache_file:
67 match = re_cache.match(l.strip())
69 var, type_, val = match.groups()
70 yield (var, type_ or "", val)
73 def cmake_cache_var(var):
74 for var_iter, type_iter, value_iter in cmake_cache_var_iter():
80 def do_ignore(filepath, ignore_prefix_list):
81 if ignore_prefix_list is None:
84 relpath = os.path.relpath(filepath, SOURCE_DIR)
85 return any([relpath.startswith(prefix) for prefix in ignore_prefix_list])
92 # support both make and ninja
93 make_exe = cmake_cache_var("CMAKE_MAKE_PROGRAM")
94 make_exe_basename = os.path.basename(make_exe)
96 if make_exe_basename.startswith(("make", "gmake")):
97 print("running 'make' with --dry-run ...")
98 process = subprocess.Popen([make_exe, "--always-make", "--dry-run", "--keep-going", "VERBOSE=1"],
99 stdout=subprocess.PIPE,
101 elif make_exe_basename.startswith("ninja"):
102 print("running 'ninja' with -t commands ...")
103 process = subprocess.Popen([make_exe, "-t", "commands"],
104 stdout=subprocess.PIPE,
107 while process.poll():
110 out = process.stdout.read()
111 process.stdout.close()
112 print("done!", len(out), "bytes")
113 return out.decode("utf-8", errors="ignore").split("\n")
116 def build_info(use_c=True, use_cxx=True, ignore_prefix_list=None):
118 makelog = makefile_log()
124 compilers.append(cmake_cache_var("CMAKE_C_COMPILER"))
126 compilers.append(cmake_cache_var("CMAKE_CXX_COMPILER"))
128 print("compilers:", " ".join(compilers))
130 fake_compiler = "%COMPILER%"
132 print("parsing make log ...")
138 if not any([(c in args) for c in compilers]):
141 # join args incase they are not.
142 args = ' '.join(args)
143 args = args.replace(" -isystem", " -I")
144 args = args.replace(" -D ", " -D")
145 args = args.replace(" -I ", " -I")
148 args = args.replace(c, fake_compiler)
153 args[:args.index(fake_compiler) + 1] = []
155 c_files = [f for f in args if is_c(f)]
156 inc_dirs = [f[2:].strip() for f in args if f.startswith('-I')]
157 defs = [f[2:].strip() for f in args if f.startswith('-D')]
158 for c in sorted(c_files):
160 if do_ignore(c, ignore_prefix_list):
163 source.append((c, inc_dirs, defs))
165 # make relative includes absolute
166 # not totally essential but useful
167 for i, f in enumerate(inc_dirs):
168 if not os.path.isabs(f):
169 inc_dirs[i] = os.path.abspath(os.path.join(CMAKE_DIR, f))
171 # safety check that our includes are ok
173 if not os.path.exists(f):
174 raise Exception("%s missing" % f)
181 def build_defines_as_source():
183 Returns a string formatted as an include:
184 '#defines A=B\n#define....'
187 # works for both gcc and clang
188 cmd = (cmake_cache_var("CMAKE_C_COMPILER"), "-dM", "-E", "-")
189 return subprocess.Popen(cmd,
190 stdout=subprocess.PIPE,
191 stdin=subprocess.DEVNULL,
192 ).stdout.read().strip().decode('ascii')
195 def build_defines_as_args():
196 return [("-D" + "=".join(l.split(maxsplit=2)[1:]))
197 for l in build_defines_as_source().split("\n")
198 if l.startswith('#define')]
201 # could be moved elsewhere!, this just happens to be used by scripts that also
203 def queue_processes(process_funcs, job_total=-1):
204 """ Takes a list of function arg pairs, each function must return a process
208 import multiprocessing
209 job_total = multiprocessing.cpu_count()
213 for func, args in process_funcs:
217 process = func(*args)
223 for func, args in process_funcs:
224 # wait until a thread is free
226 processes[:] = [p for p in processes if p.poll() is None]
228 if len(processes) <= job_total:
236 processes.append(func(*args))
240 if not os.path.exists(join(CMAKE_DIR, "CMakeCache.txt")):
241 print("This script must run from the cmake build dir")
244 for s in build_info():
247 if __name__ == "__main__":