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 compiler = CompilerInfo()
345 for name, value in CompilerInfo.__dict__.items():
346 if name[0] != '_':
347 setattr(compiler, name, dict.get(name, value))
348 compilers.append(compiler)
349
350
351 if not filename:
352 filename = CompilerList.user_emulations_file
353 filename = os.path.expanduser(filename)
354 glob['__builtins__'] = __builtins__
355 glob['__name__'] = '__main__'
356 glob['__metaclass__'] = Type
357 try:
358 execfile(filename, glob, glob)
359 except IOError:
360
361 self.add_default_compilers()
362 self.save()
363 else:
364 self.compilers = compilers
365
366 def save(self, filename = ''):
367
368 if self.no_cache:
369 return
370
371 if not filename:
372 filename = CompilerList.user_emulations_file
373 filename = os.path.expanduser(filename)
374 dirname = os.path.dirname(filename)
375 if not os.path.exists(dirname):
376 os.makedirs(dirname)
377 emu = open(filename, 'wt')
378 emu.write("""# This file was generated by Synopsis.Parsers.Cpp.Emulator.
379# When making any manual modifications to any of the classes
380# be sure to set the 'kind' field to 'custom' so it doesn't get
381# accidentally overwritten !\n""")
382 emu.write('\n')
383 emu.write('version=%r\n'%version)
384 emu.write('\n')
385 for c in self.compilers:
386 c._write(emu)
387
388
389 def refresh(self):
390 """Refreshes the compiler list.
391 Regenerate all non-custom compilers without destroying custom
392 compilers."""
393
394 compilers = []
395 for ci in self.compilers:
396 if ci.is_custom:
397 compilers.append(ci)
398 ci = _query(ci.language, ci.compiler, ci.flags)
399 if ci:
400 compilers.append(ci)
401
402 self.compilers = compilers
403 self.save()
404
405 def find(self, language, compiler, flags):
406
407 if not flags:
408 flags = []
409 for ci in self.compilers:
410 if (not compiler and language == ci.language
411 or (compiler == ci.compiler and flags == ci.flags)):
412 return ci
413 ci = self._query(language, compiler, flags)
414 self.compilers.append(ci)
415 self.save()
416 return ci
417
418
419
420compiler_list = None
421
422
423def get_compiler_info(language, compiler = '', flags = None):
424 """
425 Returns the compiler info for the given compiler. If none is
426 specified (''), return the first available one for the given language.
427 The info is returned as a CompilerInfo object, or None if the compiler
428 isn't found.
429 """
430 global compiler_list
431
432 if not compiler_list:
433 compiler_list = CompilerList()
434
435 ci = compiler_list.find(language, compiler, flags)
436 return ci
437
Generated on Thu Apr 16 16:27:13 2009 by
synopsis (version devel)