Synopsis - Synopsis/Parsers/Cpp/Emulator.py

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
     13class TempFile:
     14    # Use tempfile.NamedTemporaryFile once we can rely on Python 2.4
     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    # The output below assumes the en_US locale, so make sure we use that.
    161    command = 'LANG=en_US %s %s -E -v -dD %s'%(compiler, flags, temp.name)
    162    try:
    163        import subprocess # available only since Python 2.4
    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) # for Python < 2.4
    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    # now read built-in macros
    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    # Per-compiler adjustments
    205    for name, value in tuple(macros):
    206        if name == '__GNUC__' and value == '2':
    207            # gcc 2.x needs this or it uses nonstandard syntax in the headers
    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        # Try to stat the compiler in this directory, if it exists
    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    # Not found
    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 = '' # failure
    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# Cache that makes multiple calls to 'get_compiler_info' more efficient.
    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)