new convenience makefile targets for static source code cheching: check_splint/check_...
[blender.git] / build_files / cmake / project_source_info.py
1 # $Id:
2 # ***** BEGIN GPL LICENSE BLOCK *****
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 #
18 # Contributor(s): Campbell Barton
19 #
20 # ***** END GPL LICENSE BLOCK *****
21
22 # <pep8 compliant>
23
24 __all__ = (
25     "build_info",
26     "SOURCE_DIR",
27     )
28
29 import os
30 import sys
31 from os.path import join, dirname, normpath, abspath
32
33 SOURCE_DIR = join(dirname(__file__), "..", "..")
34 SOURCE_DIR = normpath(SOURCE_DIR)
35 SOURCE_DIR = abspath(SOURCE_DIR)
36
37
38 def is_c_header(filename):
39     ext = os.path.splitext(filename)[1]
40     return (ext in (".h", ".hpp", ".hxx"))
41
42
43 def is_c_header(filename):
44     ext = os.path.splitext(filename)[1]
45     return (ext in (".h", ".hpp", ".hxx"))
46
47
48 def is_c(filename):
49     ext = os.path.splitext(filename)[1]
50     return (ext in (".c", ".cpp", ".cxx", ".m", ".mm", ".rc"))
51
52
53 def is_c_any(filename):
54     return os.path.s_c(filename) or is_c_header(filename)
55
56
57 # copied from project_info.py
58 CMAKE_DIR = "."
59
60
61 def cmake_cache_var(var):
62     cache_file = open(join(CMAKE_DIR, "CMakeCache.txt"))
63     lines = [l_strip for l in cache_file for l_strip in (l.strip(),) if l_strip if not l_strip.startswith("//") if not l_strip.startswith("#")]
64     cache_file.close()
65
66     for l in lines:
67         if l.split(":")[0] == var:
68             return l.split("=", 1)[-1]
69     return None
70
71
72 def do_ignore(filepath, ignore_prefix_list):
73     if ignore_prefix_list is None:
74         return False
75
76     relpath = os.path.relpath(filepath, SOURCE_DIR)
77     return any([relpath.startswith(prefix) for prefix in ignore_prefix_list])
78
79
80 def makefile_log():
81     import subprocess
82     # Check blender is not 2.5x until it supports playback again
83     print("running make with --dry-run ...")
84     process = subprocess.Popen(["make", "--always-make", "--dry-run", "--keep-going", "VERBOSE=1"],
85                                 stdout=subprocess.PIPE,
86                                 )
87
88     while process.poll():
89         time.sleep(1)
90
91     out = process.stdout.read()
92     process.stdout.close()
93     print("done!", len(out), "bytes")
94     return out.decode("ascii").split("\n")
95
96
97 def build_info(use_c=True, use_cxx=True, ignore_prefix_list=None):
98
99     makelog = makefile_log()
100
101     source = []
102
103     compilers = []
104     if use_c:
105         compilers.append(cmake_cache_var("CMAKE_C_COMPILER"))
106     if use_cxx:
107         compilers.append(cmake_cache_var("CMAKE_CXX_COMPILER"))
108
109     print("compilers:", " ".join(compilers))
110
111     fake_compiler = "%COMPILER%"
112
113     print("parsing make log ...")
114
115     for line in makelog:
116
117         args = line.split()
118
119         if not any([(c in args) for c in compilers]):
120             continue
121
122         # join args incase they are not.
123         args = ' '.join(args)
124         args = args.replace(" -isystem", " -I")
125         args = args.replace(" -D ", " -D")
126         args = args.replace(" -I ", " -I")
127
128         for c in compilers:
129             args = args.replace(c, fake_compiler)
130         args = args.split()
131         # end
132
133         # remove compiler
134         args[:args.index(fake_compiler) + 1] = []
135
136         c_files = [f for f in args if is_c(f)]
137         inc_dirs = [f[2:].strip() for f in args if f.startswith('-I')]
138         defs = [f[2:].strip() for f in args if f.startswith('-D')]
139         for c in sorted(c_files):
140
141             if do_ignore(c, ignore_prefix_list):
142                 continue
143
144             source.append((c, inc_dirs, defs))
145
146         # safety check that our includes are ok
147         for f in inc_dirs:
148             if not os.path.exists(f):
149                 raise Exception("%s missing" % f)
150
151     print("done!")
152
153     return source
154
155
156 def main():
157     if not os.path.exists(join(CMAKE_DIR, "CMakeCache.txt")):
158         print("This script must run from the cmake build dir")
159         return
160
161     for s in build_info():
162         print(s)
163
164 if __name__ == "__main__":
165     main()