c13da8775b3d7fd6082d8ee4c85ab5cd1da83fe6
[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 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 is_err = False
187 errs = []
188 for f in (global_h | global_c):
189     if f.endswith("dna.c"):
190         continue
191
192     if not os.path.exists(f):
193         refs = global_refs[f]
194         if refs:
195             for cf, i in refs:
196                 errs.append((cf, i))
197         else:
198             raise Exception("CMake referenecs missing, internal error, aborting!")
199         is_err = True
200
201 errs.sort()
202 errs.reverse()
203 for cf, i in errs:
204     print("%s:%d" % (cf, i))
205     # Write a 'sed' script, useful if we get a lot of these
206     # print("sed '%dd' '%s' > '%s.tmp' ; mv '%s.tmp' '%s'" % (i, cf, cf, cf, cf))
207
208
209 if is_err:
210     raise Exception("CMake referenecs missing files, aborting!")
211 del is_err
212 del errs
213
214 # now check on files not accounted for.
215 print("\nC/C++ Files CMake doesnt know about...")
216 for cf in sorted(source_list(SOURCE_DIR, is_c)):
217     if not is_ignore(cf):
218         if cf not in global_c:
219             print("missing_c: ", cf)
220
221         # check if automake builds a corrasponding .o file.
222         '''
223         if cf in global_c:
224             out1 = os.path.splitext(cf)[0] + ".o"
225             out2 = os.path.splitext(cf)[0] + ".Po"
226             out2_dir, out2_file = out2 = os.path.split(out2)
227             out2 = os.path.join(out2_dir, ".deps", out2_file)
228             if not os.path.exists(out1) and not os.path.exists(out2):
229                 print("bad_c: ", cf)
230         '''
231
232 print("\nC/C++ Headers CMake doesnt know about...")
233 for hf in sorted(source_list(SOURCE_DIR, is_c_header)):
234     if not is_ignore(hf):
235         if hf not in global_h:
236             print("missing_h: ", hf)
237
238 if UTF8_CHECK:
239     # test encoding
240     import traceback
241     for files in (global_c, global_h):
242         for f in sorted(files):
243             if os.path.exists(f):
244                 # ignore outside of our source tree
245                 if "extern" not in f:
246                     i = 1
247                     try:
248                         for l in open(f, "r", encoding="utf8"):
249                             i += 1
250                     except:
251                         print("Non utf8: %s:%d" % (f, i))
252                         if i > 1:
253                             traceback.print_exc()