Merge with trunk r 37746.
[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, abspath, splitext
30
31 print("Scanning:", SOURCE_DIR)
32
33 global_h = set()
34 global_c = set()
35 global_refs = {}
36
37
38 def source_list(path, filename_check=None):
39     for dirpath, dirnames, filenames in os.walk(path):
40
41         # skip '.svn'
42         if dirpath.startswith("."):
43             continue
44
45         for filename in filenames:
46             if filename_check is None or filename_check(filename):
47                 yield os.path.join(dirpath, filename)
48
49
50 # extension checking
51 def is_cmake(filename):
52     ext = splitext(filename)[1]
53     return (ext == ".cmake") or (filename == "CMakeLists.txt")
54
55
56 def is_c_header(filename):
57     ext = splitext(filename)[1]
58     return (ext in (".h", ".hpp", ".hxx"))
59
60
61 def is_c(filename):
62     ext = splitext(filename)[1]
63     return (ext in (".c", ".cpp", ".cxx", ".m", ".mm", ".rc"))
64
65
66 def is_c_any(filename):
67     return is_c(filename) or is_c_header(filename)
68
69
70 def cmake_get_src(f):
71
72     sources_h = []
73     sources_c = []
74
75     filen = open(f, "r", encoding="utf8")
76     it = iter(filen)
77     found = False
78     i = 0
79     # print(f)
80     while it is not None:
81         while it is not None:
82             i += 1
83             try:
84                 l = next(it)
85             except StopIteration:
86                 it = None
87                 break
88             l = l.strip()
89             if not l.startswith("#"):
90                 if 'set(SRC' in l or ('set(' in l and l.endswith("SRC")):
91                     if len(l.split()) > 1:
92                         raise Exception("strict formatting not kept 'set(SRC*' %s:%d" % (f, i))
93                     found = True
94                     break
95
96                 if "list(APPEND SRC" in l or ('list(APPEND ' in l and l.endswith("SRC")):
97                     if l.endswith(")"):
98                         raise Exception("strict formatting not kept 'list(APPEND SRC...)' on 1 line %s:%d" % (f, i))
99                     found = True
100                     break
101
102         if found:
103             cmake_base = dirname(f)
104
105             while it is not None:
106                 i += 1
107                 try:
108                     l = next(it)
109                 except StopIteration:
110                     it = None
111                     break
112
113                 l = l.strip()
114
115                 if not l.startswith("#"):
116
117                     if ")" in l:
118                         if l.strip() != ")":
119                             raise Exception("strict formatting not kept '*)' %s:%d" % (f, i))
120                         break
121
122                     # replace dirs
123                     l = l.replace("${CMAKE_CURRENT_SOURCE_DIR}", cmake_base)
124
125                     if not l:
126                         pass
127                     elif l.startswith("$"):
128                         # assume if it ends with SRC we know about it
129                         if not l.split("}")[0].endswith("SRC"):
130                             print("Can't use var '%s' %s:%d" % (l, f, i))
131                     elif len(l.split()) > 1:
132                         raise Exception("Multi-line define '%s' %s:%d" % (l, f, i))
133                     else:
134                         new_file = normpath(join(cmake_base, l))
135
136                         if is_c_header(new_file):
137                             sources_h.append(new_file)
138                             global_refs.setdefault(new_file, []).append((f, i))
139                         elif is_c(new_file):
140                             sources_c.append(new_file)
141                             global_refs.setdefault(new_file, []).append((f, i))
142                         elif l in ("PARENT_SCOPE", ):
143                             # cmake var, ignore
144                             pass
145                         elif new_file.endswith(".list"):
146                             pass
147                         elif new_file.endswith(".def"):
148                             pass
149                         else:
150                             raise Exception("unknown file type - not c or h %s -> %s" % (f, new_file))
151
152                         # print(new_file)
153
154             global_h.update(set(sources_h))
155             global_c.update(set(sources_c))
156             '''
157             if not sources_h and not sources_c:
158                 raise Exception("No sources %s" % f)
159
160             sources_h_fs = list(source_list(cmake_base, is_c_header))
161             sources_c_fs = list(source_list(cmake_base, is_c))
162             '''
163             # find missing C files:
164             '''
165             for ff in sources_c_fs:
166                 if ff not in sources_c:
167                     print("  missing: " + ff)
168             '''
169
170     filen.close()
171
172
173 for cmake in source_list(SOURCE_DIR, is_cmake):
174     cmake_get_src(cmake)
175
176
177 def is_ignore(f):
178     for ig in IGNORE:
179         if ig in f:
180             return True
181     return False
182
183
184 # First do stupid check, do these files exist?
185 print("\nChecking for missing references:")
186 import sys
187 is_err = False
188 errs = []
189 for f in (global_h | global_c):
190     if f.endswith("dna.c"):
191         continue
192
193     if not os.path.exists(f):
194         refs = global_refs[f]
195         if refs:
196             for cf, i in refs:
197                 errs.append((cf, i))
198         else:
199             raise Exception("CMake referenecs missing, internal error, aborting!")
200         is_err = True
201
202 errs.sort()
203 errs.reverse()
204 for cf, i in errs:
205     print("%s:%d" % (cf, i))
206     # Write a 'sed' script, useful if we get a lot of these
207     # print("sed '%dd' '%s' > '%s.tmp' ; mv '%s.tmp' '%s'" % (i, cf, cf, cf, cf))
208
209
210 if is_err:
211     raise Exception("CMake referenecs missing files, aborting!")
212 del is_err
213 del errs
214
215 # now check on files not accounted for.
216 print("\nC/C++ Files CMake doesnt know about...")
217 for cf in sorted(source_list(SOURCE_DIR, is_c)):
218     if not is_ignore(cf):
219         if cf not in global_c:
220             print("missing_c: ", cf)
221
222         # check if automake builds a corrasponding .o file.
223         '''
224         if cf in global_c:
225             out1 = os.path.splitext(cf)[0] + ".o"
226             out2 = os.path.splitext(cf)[0] + ".Po"
227             out2_dir, out2_file = out2 = os.path.split(out2)
228             out2 = os.path.join(out2_dir, ".deps", out2_file)
229             if not os.path.exists(out1) and not os.path.exists(out2):
230                 print("bad_c: ", cf)
231         '''
232
233 print("\nC/C++ Headers CMake doesnt know about...")
234 for hf in sorted(source_list(SOURCE_DIR, is_c_header)):
235     if not is_ignore(hf):
236         if hf not in global_h:
237             print("missing_h: ", hf)
238
239 if UTF8_CHECK:
240     # test encoding
241     import traceback
242     for files in (global_c, global_h):
243         for f in sorted(files):
244             if os.path.exists(f):
245                 # ignore outside of our source tree
246                 if "extern" not in f:
247                     i = 1
248                     try:
249                         for l in open(f, "r", encoding="utf8"):
250                             i += 1
251                     except:
252                         print("Non utf8: %s:%d" % (f, i))
253                         if i > 1:
254                             traceback.print_exc()