svn merge ^/trunk/blender -r41227:41230 . --accept=theirs-full
[blender.git] / build_files / cmake / cmake_consistency_check.py
1 #!/usr/bin/env python
2
3 # ***** BEGIN GPL LICENSE BLOCK *****
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 #
19 # Contributor(s): Campbell Barton
20 #
21 # ***** END GPL LICENSE BLOCK *****
22
23 # <pep8 compliant>
24
25 from cmake_consistency_check_config import IGNORE, UTF8_CHECK, SOURCE_DIR
26
27 import os
28 from os.path import join, dirname, normpath, splitext
29
30 print("Scanning:", SOURCE_DIR)
31
32 global_h = set()
33 global_c = set()
34 global_refs = {}
35
36
37 def replace_line(f, i, text, keep_indent=True):
38     file_handle = open(f, 'r')
39     data = file_handle.readlines()
40     file_handle.close()
41
42     l = data[i]
43     ws = l[:len(l) - len(l.lstrip())]
44
45     data[i] = "%s%s\n" % (ws, text)
46
47     file_handle = open(f, 'w')
48     file_handle.writelines(data)
49     file_handle.close()
50
51
52 def source_list(path, filename_check=None):
53     for dirpath, dirnames, filenames in os.walk(path):
54
55         # skip '.svn'
56         if dirpath.startswith("."):
57             continue
58
59         for filename in filenames:
60             if filename_check is None or filename_check(filename):
61                 yield os.path.join(dirpath, filename)
62
63
64 # extension checking
65 def is_cmake(filename):
66     ext = splitext(filename)[1]
67     return (ext == ".cmake") or (filename == "CMakeLists.txt")
68
69
70 def is_c_header(filename):
71     ext = splitext(filename)[1]
72     return (ext in (".h", ".hpp", ".hxx"))
73
74
75 def is_c(filename):
76     ext = splitext(filename)[1]
77     return (ext in (".c", ".cpp", ".cxx", ".m", ".mm", ".rc"))
78
79
80 def is_c_any(filename):
81     return is_c(filename) or is_c_header(filename)
82
83
84 def cmake_get_src(f):
85
86     sources_h = []
87     sources_c = []
88
89     filen = open(f, "r", encoding="utf8")
90     it = iter(filen)
91     found = False
92     i = 0
93     # print(f)
94
95     def is_definition(l, f, i, name):
96         if ('set(%s' % name) in l or ('set(' in l and l.endswith(name)):
97             if len(l.split()) > 1:
98                 raise Exception("strict formatting not kept 'set(%s*' %s:%d" % (name, f, i))
99             return True
100
101         if ("list(APPEND %s" % name) in l or ('list(APPEND ' in l and l.endswith(name)):
102             if l.endswith(")"):
103                 raise Exception("strict formatting not kept 'list(APPEND %s...)' on 1 line %s:%d" % (name, f, i))
104             return True
105
106     while it is not None:
107         context_name = ""
108         while it is not None:
109             i += 1
110             try:
111                 l = next(it)
112             except StopIteration:
113                 it = None
114                 break
115             l = l.strip()
116             if not l.startswith("#"):
117                 found = is_definition(l, f, i, "SRC")
118                 if found:
119                     context_name = "SRC"
120                     break
121                 found = is_definition(l, f, i, "INC")
122                 if found:
123                     context_name = "INC"
124                     break
125
126         if found:
127             cmake_base = dirname(f)
128
129             while it is not None:
130                 i += 1
131                 try:
132                     l = next(it)
133                 except StopIteration:
134                     it = None
135                     break
136
137                 l = l.strip()
138
139                 if not l.startswith("#"):
140
141                     if ")" in l:
142                         if l.strip() != ")":
143                             raise Exception("strict formatting not kept '*)' %s:%d" % (f, i))
144                         break
145
146                     # replace dirs
147                     l = l.replace("${CMAKE_CURRENT_SOURCE_DIR}", cmake_base)
148
149                     if not l:
150                         pass
151                     elif l.startswith("$"):
152                         if context_name == "SRC":
153                             # assume if it ends with context_name we know about it
154                             if not l.split("}")[0].endswith(context_name):
155                                 print("Can't use var '%s' %s:%d" % (l, f, i))
156                     elif len(l.split()) > 1:
157                         raise Exception("Multi-line define '%s' %s:%d" % (l, f, i))
158                     else:
159                         new_file = normpath(join(cmake_base, l))
160
161                         if context_name == "SRC":
162                             if is_c_header(new_file):
163                                 sources_h.append(new_file)
164                                 global_refs.setdefault(new_file, []).append((f, i))
165                             elif is_c(new_file):
166                                 sources_c.append(new_file)
167                                 global_refs.setdefault(new_file, []).append((f, i))
168                             elif l in ("PARENT_SCOPE", ):
169                                 # cmake var, ignore
170                                 pass
171                             elif new_file.endswith(".list"):
172                                 pass
173                             elif new_file.endswith(".def"):
174                                 pass
175                             else:
176                                 raise Exception("unknown file type - not c or h %s -> %s" % (f, new_file))
177
178                         elif context_name == "INC":
179                             if os.path.isdir(new_file):
180                                 new_path_rel = os.path.relpath(new_file, cmake_base)
181
182                                 if new_path_rel != l:
183                                     print("overly relative path:\n  %s:%d\n  %s\n  %s" % (f, i, l, new_path_rel))
184
185                                     ## Save time. just replace the line
186                                     # replace_line(f, i - 1, new_path_rel)
187
188                             else:
189                                 raise Exception("non existant include %s:%d -> %s" % (f, i, new_file))
190
191                         # print(new_file)
192
193             global_h.update(set(sources_h))
194             global_c.update(set(sources_c))
195             '''
196             if not sources_h and not sources_c:
197                 raise Exception("No sources %s" % f)
198
199             sources_h_fs = list(source_list(cmake_base, is_c_header))
200             sources_c_fs = list(source_list(cmake_base, is_c))
201             '''
202             # find missing C files:
203             '''
204             for ff in sources_c_fs:
205                 if ff not in sources_c:
206                     print("  missing: " + ff)
207             '''
208
209             # reset
210             sources_h[:] = []
211             sources_c[:] = []
212
213     filen.close()
214
215
216 for cmake in source_list(SOURCE_DIR, is_cmake):
217     cmake_get_src(cmake)
218
219
220 def is_ignore(f):
221     for ig in IGNORE:
222         if ig in f:
223             return True
224     return False
225
226
227 # First do stupid check, do these files exist?
228 print("\nChecking for missing references:")
229 is_err = False
230 errs = []
231 for f in (global_h | global_c):
232     if f.endswith("dna.c"):
233         continue
234
235     if not os.path.exists(f):
236         refs = global_refs[f]
237         if refs:
238             for cf, i in refs:
239                 errs.append((cf, i))
240         else:
241             raise Exception("CMake referenecs missing, internal error, aborting!")
242         is_err = True
243
244 errs.sort()
245 errs.reverse()
246 for cf, i in errs:
247     print("%s:%d" % (cf, i))
248     # Write a 'sed' script, useful if we get a lot of these
249     # print("sed '%dd' '%s' > '%s.tmp' ; mv '%s.tmp' '%s'" % (i, cf, cf, cf, cf))
250
251
252 if is_err:
253     raise Exception("CMake referenecs missing files, aborting!")
254 del is_err
255 del errs
256
257 # now check on files not accounted for.
258 print("\nC/C++ Files CMake doesnt know about...")
259 for cf in sorted(source_list(SOURCE_DIR, is_c)):
260     if not is_ignore(cf):
261         if cf not in global_c:
262             print("missing_c: ", cf)
263
264         # check if automake builds a corrasponding .o file.
265         '''
266         if cf in global_c:
267             out1 = os.path.splitext(cf)[0] + ".o"
268             out2 = os.path.splitext(cf)[0] + ".Po"
269             out2_dir, out2_file = out2 = os.path.split(out2)
270             out2 = os.path.join(out2_dir, ".deps", out2_file)
271             if not os.path.exists(out1) and not os.path.exists(out2):
272                 print("bad_c: ", cf)
273         '''
274
275 print("\nC/C++ Headers CMake doesnt know about...")
276 for hf in sorted(source_list(SOURCE_DIR, is_c_header)):
277     if not is_ignore(hf):
278         if hf not in global_h:
279             print("missing_h: ", hf)
280
281 if UTF8_CHECK:
282     # test encoding
283     import traceback
284     for files in (global_c, global_h):
285         for f in sorted(files):
286             if os.path.exists(f):
287                 # ignore outside of our source tree
288                 if "extern" not in f:
289                     i = 1
290                     try:
291                         for l in open(f, "r", encoding="utf8"):
292                             i += 1
293                     except:
294                         print("Non utf8: %s:%d" % (f, i))
295                         if i > 1:
296                             traceback.print_exc()