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                    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# Cache that makes multiple calls to 'get_compiler_info' more efficient.
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