Merge branch 'blender2.7'
[blender.git] / build_files / cmake / project_info.py
1 #!/usr/bin/env python3
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, M.G. Kishalmi
20 #
21 # ***** END GPL LICENSE BLOCK *****
22
23 # <pep8 compliant>
24
25 """
26 Module for accessing project file data for Blender.
27
28 Before use, call init(cmake_build_dir).
29 """
30
31 # TODO: Use CMAKE_EXPORT_COMPILE_COMMANDS (compile_commands.json)
32 # Instead of Eclipse project format.
33
34 __all__ = (
35     "SIMPLE_PROJECTFILE",
36     "SOURCE_DIR",
37     "CMAKE_DIR",
38     "PROJECT_DIR",
39     "source_list",
40     "is_project_file",
41     "is_c_header",
42     "is_py",
43     "cmake_advanced_info",
44     "cmake_compiler_defines",
45     "project_name_get",
46     "init",
47 )
48
49
50 import sys
51 if sys.version_info.major < 3:
52     print("\nPython3.x needed, found %s.\nAborting!\n" %
53           sys.version.partition(" ")[0])
54     sys.exit(1)
55
56
57 import subprocess
58 import os
59 from os.path import (
60     abspath,
61     dirname,
62     exists,
63     join,
64     normpath,
65     splitext,
66 )
67
68 SOURCE_DIR = join(dirname(__file__), "..", "..")
69 SOURCE_DIR = normpath(SOURCE_DIR)
70 SOURCE_DIR = abspath(SOURCE_DIR)
71
72 SIMPLE_PROJECTFILE = False
73
74 # must initialize from 'init'
75 CMAKE_DIR = None
76
77
78 def init(cmake_path):
79     global CMAKE_DIR, PROJECT_DIR
80
81     # get cmake path
82     cmake_path = cmake_path or ""
83
84     if (not cmake_path) or (not exists(join(cmake_path, "CMakeCache.txt"))):
85         cmake_path = os.getcwd()
86     if not exists(join(cmake_path, "CMakeCache.txt")):
87         print("CMakeCache.txt not found in %r or %r\n"
88               "    Pass CMake build dir as an argument, or run from that dir, aborting" %
89               (cmake_path, os.getcwd()))
90         return False
91
92     PROJECT_DIR = CMAKE_DIR = cmake_path
93     return True
94
95
96 def source_list(path, filename_check=None):
97     for dirpath, dirnames, filenames in os.walk(path):
98         # skip '.git'
99         dirnames[:] = [d for d in dirnames if not d.startswith(".")]
100
101         for filename in filenames:
102             filepath = join(dirpath, filename)
103             if filename_check is None or filename_check(filepath):
104                 yield filepath
105
106
107 # extension checking
108 def is_cmake(filename):
109     ext = splitext(filename)[1]
110     return (ext == ".cmake") or (filename.endswith("CMakeLists.txt"))
111
112
113 def is_c_header(filename):
114     ext = splitext(filename)[1]
115     return (ext in {".h", ".hpp", ".hxx", ".hh"})
116
117
118 def is_py(filename):
119     ext = splitext(filename)[1]
120     return (ext == ".py")
121
122
123 def is_glsl(filename):
124     ext = splitext(filename)[1]
125     return (ext == ".glsl")
126
127
128 def is_c(filename):
129     ext = splitext(filename)[1]
130     return (ext in {".c", ".cpp", ".cxx", ".m", ".mm", ".rc", ".cc", ".inl", ".osl"})
131
132
133 def is_c_any(filename):
134     return is_c(filename) or is_c_header(filename)
135
136
137 def is_svn_file(filename):
138     dn, fn = os.path.split(filename)
139     filename_svn = join(dn, ".svn", "text-base", "%s.svn-base" % fn)
140     return exists(filename_svn)
141
142
143 def is_project_file(filename):
144     return (is_c_any(filename) or is_cmake(filename) or is_glsl(filename))  # and is_svn_file(filename)
145
146
147 def cmake_advanced_info():
148     """ Extract includes and defines from cmake.
149     """
150
151     make_exe = cmake_cache_var("CMAKE_MAKE_PROGRAM")
152     make_exe_basename = os.path.basename(make_exe)
153
154     def create_eclipse_project():
155         print("CMAKE_DIR %r" % CMAKE_DIR)
156         if sys.platform == "win32":
157             raise Exception("Error: win32 is not supported")
158         else:
159             if make_exe_basename.startswith(("make", "gmake")):
160                 cmd = ("cmake", CMAKE_DIR, "-GEclipse CDT4 - Unix Makefiles")
161             elif make_exe_basename.startswith("ninja"):
162                 cmd = ("cmake", CMAKE_DIR, "-GEclipse CDT4 - Ninja")
163             else:
164                 raise Exception("Unknown make program %r" % make_exe)
165
166         subprocess.check_call(cmd)
167         return join(CMAKE_DIR, ".cproject")
168
169     includes = []
170     defines = []
171
172     project_path = create_eclipse_project()
173
174     if not exists(project_path):
175         print("Generating Eclipse Prokect File Failed: %r not found" % project_path)
176         return None, None
177
178     from xml.dom.minidom import parse
179     tree = parse(project_path)
180
181     # to check on nicer xml
182     # f = open(".cproject_pretty", 'w')
183     # f.write(tree.toprettyxml(indent="    ", newl=""))
184
185     ELEMENT_NODE = tree.ELEMENT_NODE
186
187     cproject, = tree.getElementsByTagName("cproject")
188     for storage in cproject.childNodes:
189         if storage.nodeType != ELEMENT_NODE:
190             continue
191
192         if storage.attributes["moduleId"].value == "org.eclipse.cdt.core.settings":
193             cconfig = storage.getElementsByTagName("cconfiguration")[0]
194             for substorage in cconfig.childNodes:
195                 if substorage.nodeType != ELEMENT_NODE:
196                     continue
197
198                 moduleId = substorage.attributes["moduleId"].value
199
200                 # org.eclipse.cdt.core.settings
201                 # org.eclipse.cdt.core.language.mapping
202                 # org.eclipse.cdt.core.externalSettings
203                 # org.eclipse.cdt.core.pathentry
204                 # org.eclipse.cdt.make.core.buildtargets
205
206                 if moduleId == "org.eclipse.cdt.core.pathentry":
207                     for path in substorage.childNodes:
208                         if path.nodeType != ELEMENT_NODE:
209                             continue
210                         kind = path.attributes["kind"].value
211
212                         if kind == "mac":
213                             # <pathentry kind="mac" name="PREFIX" path="" value="&quot;/opt/blender25&quot;"/>
214                             defines.append((path.attributes["name"].value, path.attributes["value"].value))
215                         elif kind == "inc":
216                             # <pathentry include="/data/src/blender/blender/source/blender/editors/include" kind="inc" path="" system="true"/>
217                             includes.append(path.attributes["include"].value)
218                         else:
219                             pass
220
221     return includes, defines
222
223
224 def cmake_cache_var(var):
225     cache_file = open(join(CMAKE_DIR, "CMakeCache.txt"), encoding='utf-8')
226     lines = [
227         l_strip for l in cache_file
228         for l_strip in (l.strip(),)
229         if l_strip
230         if not l_strip.startswith(("//", "#"))
231     ]
232     cache_file.close()
233
234     for l in lines:
235         if l.split(":")[0] == var:
236             return l.split("=", 1)[-1]
237     return None
238
239
240 def cmake_compiler_defines():
241     compiler = cmake_cache_var("CMAKE_C_COMPILER")  # could do CXX too
242
243     if compiler is None:
244         print("Couldn't find the compiler, os defines will be omitted...")
245         return
246
247     import tempfile
248     temp_c = tempfile.mkstemp(suffix=".c")[1]
249     temp_def = tempfile.mkstemp(suffix=".def")[1]
250
251     os.system("%s -dM -E %s > %s" % (compiler, temp_c, temp_def))
252
253     temp_def_file = open(temp_def)
254     lines = [l.strip() for l in temp_def_file if l.strip()]
255     temp_def_file.close()
256
257     os.remove(temp_c)
258     os.remove(temp_def)
259     return lines
260
261
262 def project_name_get():
263     return cmake_cache_var("CMAKE_PROJECT_NAME")