Ticket #1670: Emulator.py

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