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