Synopsis - Cross-Reference

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