File: Synopsis/Parsers/Cpp/Emulator.py 1
2
3
4
5
6
7
8__docformat__ = 'reStructuredText'
9
10import sys, os, os.path, re, string, stat, tempfile
11from Synopsis.config import version
12
13class TempFile:
14
15 def __init__(self, suffix):
16
17 self.name = tempfile.mktemp(suffix)
18 self.file = open(self.name, 'w')
19 self.file.close()
20
21
22 def __del__(self):
23
24 os.unlink(self.name)
25
26
27def find_ms_compiler_info():
28 """
29 Try to find a (C++) MSVC compiler.
30 Return tuple of include path list and macro dictionary."""
31
32 vc6 = ('SOFTWARE\\Microsoft\\DevStudio\\6.0\\Products\\Microsoft Visual C++', 'ProductDir')
33 vc7 = ('SOFTWARE\\Microsoft\\VisualStudio\\7.0', 'InstallDir')
34 vc71 = ('SOFTWARE\\Microsoft\\VisualStudio\\7.1', 'InstallDir')
35 vc8 = ('SOFTWARE\\Microsoft\\VisualStudio\\8.0', 'InstallDir')
36 vc9e = ('SOFTWARE\\Microsoft\\VCExpress\\9.0\\Setup\\VC', 'ProductDir')
37
38 vc6_macros = [('__uuidof(x)', 'IID()'),
39 ('__int64', 'long long'),
40 ('_MSC_VER', '1200'),
41 ('_MSC_EXTENSIONS', ''),
42 ('_WIN32', ''),
43 ('_M_IX86', ''),
44 ('_WCHAR_T_DEFINED', ''),
45 ('_INTEGRAL_MAX_BITS', '64'),
46 ('PASCAL', ''),
47 ('RPC_ENTRY', ''),
48 ('SHSTDAPI', 'HRESULT'),
49 ('SHSTDAPI_(x)', 'x')]
50 vc6_paths = ['Include']
51
52 vc7_macros = [('__forceinline', '__inline'),
53 ('__uuidof(x)', 'IID()'),
54 ('__w64', ''),
55 ('__int64', 'long long'),
56 ('_MSC_VER', '1300'),
57 ('_MSC_EXTENSIONS', ''),
58 ('_WIN32', ''),
59 ('_M_IX86', ''),
60 ('_WCHAR_T_DEFINED', ''),
61 ('_INTEGRAL_MAX_BITS', '64'),
62 ('PASCAL', ''),
63 ('RPC_ENTRY', ''),
64 ('SHSTDAPI', 'HRESULT'),
65 ('SHSTDAPI_(x)', 'x')]
66 vc7_paths = ['..\\..\\Vc7\\Include',
67 '..\\..\\Vc7\\PlatformSDK\\Include']
68
69 vc71_macros = [('__forceinline', '__inline'),
70 ('__uuidof(x)', 'IID()'),
71 ('__w64', ''),
72 ('__int64', 'long long'),
73 ('_MSC_VER', '1310'),
74 ('_MSC_EXTENSIONS', ''),
75 ('_WIN32', ''),
76 ('_M_IX86', ''),
77 ('_WCHAR_T_DEFINED', ''),
78 ('_INTEGRAL_MAX_BITS', '64'),
79 ('PASCAL', ''),
80 ('RPC_ENTRY', ''),
81 ('SHSTDAPI', 'HRESULT'),
82 ('SHSTDAPI_(x)', 'x')]
83 vc71_paths = ['..\\..\\Vc7\\Include',
84 '..\\..\\Vc7\\PlatformSDK\\Include']
85
86 vc8_macros = [('__cplusplus', '1'),
87 ('__forceinline', '__inline'),
88 ('__uuidof(x)', 'IID()'),
89 ('__w64', ''),
90 ('__int8', 'char'),
91 ('__int16', 'short'),
92 ('__int32', 'int'),
93 ('__int64', 'long long'),
94 ('__ptr64', ''),
95 ('_MSC_VER', '1400'),
96 ('_MSC_EXTENSIONS', ''),
97 ('_WIN32', ''),
98 ('_M_IX86', ''),
99 ('_WCHAR_T_DEFINED', ''),
100 ('_INTEGRAL_MAX_BITS', '64'),
101 ('PASCAL', ''),
102 ('RPC_ENTRY', ''),
103 ('SHSTDAPI', 'HRESULT'),
104 ('SHSTDAPI_(x)', 'x')]
105 vc8_paths = ['..\\..\\Vc\\Include',
106 '..\\..\\Vc\\PlatformSDK\\Include']
107
108 vc9e_macros = [('__cplusplus', '1'),
109 ('__forceinline', '__inline'),
110 ('__uuidof(x)', 'IID()'),
111 ('__w64', ''),
112 ('__int8', 'char'),
113 ('__int16', 'short'),
114 ('__int32', 'int'),
115 ('__int64', 'long long'),
116 ('__ptr64', ''),
117 ('_MSC_VER', '1400'),
118 ('_MSC_EXTENSIONS', ''),
119 ('_WIN32', ''),
120 ('_M_IX86', ''),
121 ('_WCHAR_T_DEFINED', ''),
122 ('_INTEGRAL_MAX_BITS', '64'),
123 ('PASCAL', ''),
124 ('RPC_ENTRY', ''),
125 ('SHSTDAPI', 'HRESULT'),
126 ('SHSTDAPI_(x)', 'x')]
127 vc9e_paths = ['..\\..\\Vc\\Include',
128 '..\\..\\Vc\\PlatformSDK\\Include']
129
130 compilers = [(vc9e, vc9e_macros, vc9e_paths),
131 (vc8, vc8_macros, vc8_paths),
132 (vc71, vc71_macros, vc71_paths),
133 (vc7, vc7_macros, vc7_paths),
134 (vc6, vc6_macros, vc6_paths)]
135
136 found, paths, macros = False, [], []
137
138 import _winreg
139 for c in compilers:
140 try:
141 key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, c[0][0])
142 path, type = _winreg.QueryValueEx(key, c[0][1])
143 found = True
144 paths.extend([os.path.join(str(path), p) for p in c[2]])
145 macros.extend(c[1])
146 break
147 except:
148 continue
149
150 return found, paths, macros
151
152def find_gcc_compiler_info(language, compiler, flags):
153 """
154 Try to find a GCC-based C or C++ compiler.
155 Return tuple of include path list and macro dictionary."""
156
157 found, paths, macros = False, [], []
158 flags = ' '.join(flags)
159 temp = TempFile(language == 'C++' and '.cc' or '.c')
160
161 command = 'LANG=en_US %s %s -E -v -dD %s'%(compiler, flags, temp.name)
162 try:
163 import subprocess
164 p = subprocess.Popen(command, shell=True,
165 stdout=subprocess.PIPE,
166 stderr=subprocess.PIPE, close_fds=True)
167 stdout, stderr = p.communicate()
168 out, err = stdout.split('\n'), stderr.split('\n')
169 except:
170 stdin, stdout, stderr = os.popen3(command)
171 out, err = stdout.readlines(), stderr.readlines()
172 stdin.close()
173
174 state = 0
175 for line in err:
176 line = line.rstrip()
177 if state == 0:
178 if line[:11] == 'gcc version': state = 1
179 elif state == 1:
180 state = 2
181 elif state == 2:
182 if line == '#include <...> search starts here:':
183 state = 3
184 elif state == 3:
185 if line == 'End of search list.':
186 state = 4
187 else:
188 paths.append(line.strip())
189
190 state = 0
191 for line in out:
192 line = line.rstrip()
193 if state == 0:
194 if line == '# 1 "<built-in>"' or line == '# 1 "<command line>"':
195 state = 1
196 elif state == 1:
197 if line.startswith('#define '):
198 tokens = line[8:].split(' ', 1)
199 if len(tokens) == 1: tokens.append('')
200 macros.append(tuple(tokens))
201 elif line == '# 1 "%s"'%temp:
202 state = 0
203
204
205 for name, value in tuple(macros):
206 if name == '__GNUC__' and value == '2':
207
208 macros.append(('__STRICT_ANSI__', ''))
209
210 return True, paths, macros
211
212
213def find_compiler_info(language, compiler, flags):
214
215 found, paths, macros = False, [], []
216
217 if compiler == 'cl' and os.name == 'nt':
218 if flags:
219 sys.stderr.write('Warning: ignoring unknown flags for MSVC compiler\n')
220 found, paths, macros = find_ms_compiler_info()
221
222 else:
223 found, paths, macros = find_gcc_compiler_info(language, compiler, flags)
224
225 return found, paths, macros
226
227
228def get_compiler_timestamp(compiler):
229 """Returns the timestamp for the given compiler, or 0 if not found"""
230
231 path = os.getenv('PATH', os.defpath)
232 path = string.split(path, os.pathsep)
233 for directory in path:
234
235 filename = os.path.join(directory, compiler)
236 if os.name == 'nt': filename += '.exe'
237 try: stats = os.stat(filename)
238 except OSError: continue
239 return stats[stat.ST_CTIME]
240
241 return 0
242
243
244class CompilerInfo:
245 """Info about one compiler."""
246
247 compiler = ''
248 """
249 The name of the compiler, typically the executable name,
250 which must either be in the path or given as an absolute,
251 pathname."""
252 flags = []
253 "Compiler flags that impact its characteristics."
254 language = ''
255 "The programming language the compiler is used for."
256 kind = ''
257 """
258 A string indicating the type of this info:
259 one of 'system', 'custom', ''.
260 'custom' compilers will never be automatically updated,
261 and an empty string indicates a failure to look up the
262 given compiler."""
263 timestamp = ''
264 "The timestamp of the compiler binary."
265 include_paths = []
266 "A list of strings indicating the include paths."
267 macros = []
268 """
269 A list of (name,value) pairs. Values may be empty, or None.
270 The latter ase indicates that the macro is to be undefined."""
271
272 def _write(self, os):
273 item = id(self) >= 0 and id(self) or -id(self)
274 os.write('class Item%u:\n'%item)
275 for name, value in CompilerInfo.__dict__.iteritems():
276 if name[0] != '_':
277 os.write(' %s=%r\n'%(name, getattr(self, name)))
278 os.write('\n')
279
280
281class CompilerList(object):
282
283 user_emulations_file = '~/.synopsis/parsers/cpp/emulator'
284 "The cache file."
285
286 def __init__(self, filename = ''):
287
288 self.compilers = []
289 self.no_cache = os.environ.has_key('SYNOPSIS_NO_CACHE')
290 self.load(filename)
291
292 def list(self):
293
294 return [c.compiler for c in self.compilers]
295
296
297 def _query(self, language, compiler, flags):
298 """Construct and return a CompilerInfo object for the given compiler."""
299
300 ci = CompilerInfo()
301 ci.compiler = compiler
302 ci.flags = flags
303 ci.language = language
304 try:
305 found, paths, macros = find_compiler_info(language, compiler, flags)
306 if found:
307 ci.kind = 'system'
308 ci.timestamp = get_compiler_timestamp(compiler)
309 ci.include_paths = paths
310 ci.macros = macros
311 except:
312 ci.kind = ''
313 ci.timestamp = 0
314 ci.include_paths = []
315 ci.macros = []
316 return ci
317
318 def add_default_compilers(self):
319
320 self.compilers.append(self._query('C++', 'c++', []))
321 self.compilers.append(self._query('C++', 'g++', []))
322 self.compilers.append(self._query('C', 'cc', []))
323 self.compilers.append(self._query('C', 'gcc', []))
324
325
326 def load(self, filename = ''):
327 """Loads the compiler infos from a file."""
328
329 if self.no_cache:
330 self.add_default_compilers()
331 return
332
333 compilers = []
334
335 glob = {}
336 glob['version'] = version
337 class Type(type):
338 """Factory for CompilerInfo objects.
339 This is used to read in an emulation file."""
340
341 def __init__(cls, name, bases, dict):
342
343 if glob['version'] != version:
344 print('Warning: reading compiler emulation cache from different Synopsis version\n')
345 compiler = CompilerInfo()
346 for name, value in CompilerInfo.__dict__.items():
347 if name[0] != '_':
348 setattr(compiler, name, dict.get(name, value))
349 compilers.append(compiler)
350
351
352 if not filename:
353 filename = CompilerList.user_emulations_file
354 filename = os.path.expanduser(filename)
355 glob['__builtins__'] = __builtins__
356 glob['__name__'] = '__main__'
357 glob['__metaclass__'] = Type
358 try:
359 execfile(filename, glob, glob)
360 except IOError:
361
362 self.add_default_compilers()
363 self.save()
364 else:
365 self.compilers = compilers
366
367 def save(self, filename = ''):
368
369 if self.no_cache:
370 return
371
372 if not filename:
373 filename = CompilerList.user_emulations_file
374 filename = os.path.expanduser(filename)
375 dirname = os.path.dirname(filename)
376 if not os.path.exists(dirname):
377 os.makedirs(dirname)
378 emu = open(filename, 'wt')
379 emu.write("""# This file was generated by Synopsis.Parsers.Cpp.Emulator.
380# When making any manual modifications to any of the classes
381# be sure to set the 'kind' field to 'custom' so it doesn't get
382# accidentally overwritten !\n""")
383 emu.write('\n')
384 emu.write('version=%r\n'%version)
385 emu.write('\n')
386 for c in self.compilers:
387 c._write(emu)
388
389
390 def refresh(self):
391 """Refreshes the compiler list.
392 Regenerate all non-custom compilers without destroying custom
393 compilers."""
394
395 compilers = []
396 for ci in self.compilers:
397 if ci.is_custom:
398 compilers.append(ci)
399 ci = _query(ci.language, ci.compiler, ci.flags)
400 if ci:
401 compilers.append(ci)
402
403 self.compilers = compilers
404 self.save()
405
406 def find(self, language, compiler, flags):
407
408 if not flags:
409 flags = []
410 for ci in self.compilers:
411 if (not compiler and language == ci.language
412 or (compiler == ci.compiler and flags == ci.flags)):
413 return ci
414 ci = self._query(language, compiler, flags)
415 self.compilers.append(ci)
416 self.save()
417 return ci
418
419
420
421compiler_list = None
422
423
424def get_compiler_info(language, compiler = '', flags = None):
425 """
426 Returns the compiler info for the given compiler. If none is
427 specified (''), return the first available one for the given language.
428 The info is returned as a CompilerInfo object, or None if the compiler
429 isn't found.
430 """
431 global compiler_list
432
433 if not compiler_list:
434 compiler_list = CompilerList()
435
436 ci = compiler_list.find(language, compiler, flags)
437 return ci
438
Generated on Tue Jul 20 09:07:12 2010 by
synopsis (version devel)