Modules | Files | Inheritance Tree | Inheritance Graph | Name Index | Config
File: Synopsis/Parser/C++/emul.py
    1| #
    2| # $Id: emul.py,v 1.8 2002/10/29 07:15:35 chalky Exp $
    3| #
    4| # This file is a part of Synopsis.
    5| # Copyright (C) 2002 Stephen Davies
    6| #
    7| # Synopsis is free software; you can redistribute it and/or modify it
    8| # under the terms of the GNU General Public License as published by
    9| # the Free Software Foundation; either version 2 of the License, or
   10| # (at your option) any later version.
   11| #
   12| # This program is distributed in the hope that it will be useful,
   13| # but WITHOUT ANY WARRANTY; without even the implied warranty of
   14| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   15| # General Public License for more details.
   16| #
   17| # You should have received a copy of the GNU General Public License
   18| # along with this program; if not, write to the Free Software
   19| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   20| # 02111-1307, USA.
   21| 
   22| # This module contains functions for getting the info needed to emulate
   23| # different compilers
   24| 
   25| # $Log: emul.py,v $
   26| # Revision 1.8  2002/10/29 07:15:35  chalky
   27| # Define __STRICT_ANSI__ for gcc 2.x to avoid parse errors
   28| #
   29| # Revision 1.7  2002/10/29 06:56:52  chalky
   30| # Fixes to work on cygwin
   31| #
   32| # Revision 1.6  2002/10/27 07:23:29  chalky
   33| # Typeof support. Generate Function when appropriate. Better emulation support.
   34| #
   35| # Revision 1.5  2002/10/25 02:46:46  chalky
   36| # Try to fallback to gcc etc if compiler info fails, and display warning
   37| #
   38| # Revision 1.4  2002/10/24 23:41:09  chalky
   39| # Cache information for the lifetime of the module about which compilers are
   40| # available and which are not
   41| #
   42| # Revision 1.3  2002/10/11 11:09:39  chalky
   43| # Add cygwin support to compiler emulations
   44| #
   45| # Revision 1.2  2002/10/02 03:51:01  chalky
   46| # Allow more flexible gcc version (eg: 2.95.3-5 for cygwin)
   47| #
   48| # Revision 1.1  2002/09/28 05:51:06  chalky
   49| # Moved compiler emulation stuff to this module
   50| #
   51| 
   52| import sys, os, os.path, re, string, stat, tempfile
   53| from Synopsis.Core import Util
   54| 
   55| # The filename where infos are stored
   56| user_emulations_file = '~/.synopsis/cpp_emulations'
   57| 
   58| # The list of default compilers. Note that the C syntax is not quite
   59| # supported, so including a bazillion different C compilers is futile.
   60| default_compilers = [
   61|     'cc', 'c++', 'gcc', 'g++', 'g++-2.95', 'g++-3.0', 'g++-3.1', 'g++-3.2'
   62| ]
   63| 
   64| # A cache of the compiler info, loaded from the emulations file or calculated
   65| # by inspecting available compilers
   66| compiler_infos = {}
   67| 
   68| # A map of failed compilers. This map is kept for the lifetype of Synopsis, so
   69| # you may have to restart it if a compiler is 'fixed'
   70| failed = {}
   71| 
   72| # The temporary filename
   73| temp_filename = None
   74| 
   75| def get_temp_file():
   76|     """Returns the temporary filename. The temp file is created, but is left
   77|     empty"""
   78|     global temp_filename
   79|     if temp_filename: return temp_filename
   80|     temp_filename = tempfile.mktemp('.cc')
   81|     f = open(temp_filename, 'w')
   82|     f.close()
   83|     return temp_filename
   84| 
   85| def cleanup_temp_file():
   86|     """Removes the temporary file and resets the filename"""
   87|     global temp_filename
   88|     if temp_filename:
   89|         os.unlink(temp_filename)
   90|         temp_filename = None
   91| 
   92| class CompilerInfo:
   93|     """Info about one compiler.
   94| 
   95|     @attr compiler The name of the compiler, typically the executable name,
   96|     which must either be in the path or given as an absolute pathname
   97|     @attr is_custom True if this is a custom compiler - in which case it will
   98|     never be updated automatically.
   99|     @attr timestamp The timestamp of the compiler binary
  100|     @attr include_paths A list of strings indicating the include paths
  101|     @attr macros A list of string 2-tuples indicating key=value pairs for
  102|     macros. A value of '' (empty string) indicates an empty definition. A
  103|     value of None (not a string) indicates that the macro should be undefined.
  104|     """
  105|     def __init__(self, compiler, is_custom, timestamp, include_paths, macros):
  106|         self.compiler = compiler
  107|         self.is_custom = is_custom
  108|         self.timestamp = timestamp
  109|         self.include_paths = include_paths
  110|         self.macros = macros
  111| 
  112| def main():
  113|     """The main function - parses the arguments and controls the program"""
  114|     if len(sys.argv) < 2:
  115|         print usage
  116|         return
  117| 
  118|     filename = sys.argv[1]
  119|     print "Filename is ", filename
  120|     if len(sys.argv) > 2:
  121|         compilers = sys.argv[2:]
  122|     else:
  123|         compilers = default_compilers
  124|     print "compilers are:", compilers
  125| 
  126|     infos = get_compiler_infos(compilers)
  127|     if not infos:
  128|         print "No compilers found. Not writing file!"
  129|         return
  130| 
  131|     file = open(filename, 'wt')
  132|     write_compiler_infos(infos, file)
  133|     file.close()
  134| 
  135|     print "Found info about the following compilers:"
  136|     print string.join(map(lambda x: x[0], infos), ', ')
  137| 
  138| def get_fallback(preferred, is_first_time):
  139|     """Tries to return info from a fallback compiler, and prints a warning
  140|     message to the user, unless their preferred compiler was 'none'"""
  141|     if is_first_time and preferred != 'none':
  142|         print "Warning: The specified compiler (%s) could not be found."%(preferred,)
  143|         print "You may want to retry with the full pathname of the compiler"
  144|         print "or with it in your path. If you don't have this compiler, you"
  145|         print "will need to modify the C++ Parser part of your config file."
  146|     for compiler in ('g++', 'gcc', 'c++', 'cc'):
  147|         if compiler_infos.has_key(compiler):
  148|             if is_first_time:
  149|               print "Warning: Falling back to compiler '%s'"%(compiler,)
  150|             return compiler_infos[compiler]
  151|     if preferred != 'none':
  152|         print "Warning: Unable to fallback to a default compiler emulation."
  153|         print "Unless you have set appropriate paths, expect errors."
  154|     return None
  155| 
  156| def get_compiler_info(compiler):
  157|     """Returns the compiler info for the given compiler. The info is returned
  158|     as a CompilerInfo object, or None if the compiler isn't found. 
  159|     """
  160|     global failed, compiler_infos
  161|     # Check if this compiler has already been found to not exist
  162|     if failed.has_key(compiler):
  163|         return get_fallback(compiler, 0)
  164|     # See if already found it
  165|     if len(compiler_infos) == 0:
  166|         # Try to load the emulations file
  167|         compiler_infos = load_compiler_infos()
  168|     # See if wanted compiler was in file
  169|     if compiler_infos.has_key(compiler):
  170|         info = compiler_infos[compiler]
  171|         if info.is_custom: return info
  172|         file_stamp = get_compiler_timestamp(compiler)
  173|         # If compiler hasn't changed since then, return cached info
  174|         if file_stamp and info.timestamp == file_stamp:
  175|             return info
  176|     else:
  177|         # Add compiler to map, but with a dummy value to indicate nothing is
  178|         # known about it
  179|         compiler_infos[compiler] = None
  180|     
  181|     # Regenerate infos
  182|     refresh_compiler_infos(compiler_infos)
  183| 
  184|     # Cache results to disk
  185|     write_compiler_infos(compiler_infos)
  186| 
  187|     # Return discovered info, if compiler was found
  188|     if compiler_infos.has_key(compiler):
  189|         return compiler_infos[compiler]
  190|     else:
  191|         return get_fallback(compiler, 1)
  192| 
  193| def load_compiler_infos():
  194|     """Loads the compiler infos from a file"""
  195|     glob = {'__builtins__':{}, '__name__':'__main__', 'setattr':setattr}
  196|     filename = os.path.expanduser(user_emulations_file)
  197|     try: execfile(filename, glob, glob)
  198|     except IOError: return {}
  199|     if glob.has_key('infos'):
  200|         return glob['infos']
  201|     return {} 
  202| 
  203| def get_compiler_timestamp(compiler):
  204|     """Returns the timestamp for the given compiler, or 0 if not found"""
  205|     path = os.getenv('PATH', os.defpath)
  206|     path = string.split(path, os.pathsep)
  207|     for directory in path:
  208|         # Try to stat the compiler in this directory, if it exists
  209|         filename = os.path.join(directory, compiler)
  210|         try: stats = os.stat(filename)
  211|         except OSError: continue
  212|         return stats[stat.ST_CTIME]
  213|     # Not found
  214|     return 0
  215| 
  216| def refresh_compiler_infos(infos):
  217|     """Refreshes the list of infos, by rediscovering all non-custom compilers
  218|     in the map. The map is modified in-place."""
  219|     global failed
  220|     # Refresh each non-custom compiler in the map 
  221|     for compiler, info in infos.items():
  222|         if info and info.is_custom:
  223|             # Skip custom compilers
  224|             continue
  225|         if failed.has_key(compiler):
  226|             # Skip compilers that have already failed
  227|             del infos[compiler]
  228|             continue
  229|         info = find_compiler_info(compiler)
  230|         if info: infos[compiler] = info
  231|         else:
  232|             del infos[compiler]
  233|             failed[compiler] = None
  234|     # Now try to add defaults
  235|     for compiler in default_compilers:
  236|         # Don't retry already-failed compilers
  237|         if failed.has_key(compiler):
  238|             continue
  239|         info = find_compiler_info(compiler)
  240|         if info: infos[compiler] = info
  241| 
  242|     # Cleanup the temp file
  243|     cleanup_temp_file()
  244|         
  245| 
  246| #def get_compiler_infos(compilers):
  247| #    infos = filter(None, map(find_compiler_info, compilers))
  248| #    return infos
  249| 
  250| def write_compiler_infos(infos):
  251|     filename = os.path.expanduser(user_emulations_file)
  252|     dirname = os.path.dirname(filename)
  253|     if not os.path.exists(dirname):
  254|         os.mkdir(dirname)
  255|     file = open(filename, 'wt')
  256|     writer = Util.PyWriter(file)
  257|     writer.write_top('"""This file contains info about compilers for use by '+
  258|     'Synopsis.\nSee the Synopsis RefManual for info on customizing this list."""\n')
  259|     writer.ensure_struct()
  260|     writer.write('infos = {\n')
  261|     writer.indent()
  262|     ccomma = 0
  263|     for compiler, info in infos.items():
  264|         if ccomma: writer.write(",\n")
  265|         else: ccomma = 1
  266|         writer.write("'%s' : struct(\n"%compiler)
  267|         writer.write('is_custom = %d,\n' % info.is_custom)
  268|         writer.write('timestamp = %d,\n' % info.timestamp)
  269|         writer.write("include_paths = ")
  270|         writer.write_long_list(info.include_paths)
  271|         writer.write(",\nmacros = ")
  272|         writer.write_long_list(info.macros)
  273|         writer.write(")")
  274|     writer.outdent()
  275|     writer.write("\n}\n")
  276|     writer.flush()
  277|     file.close()
  278|     
  279|     
  280| 
  281| re_specs = re.compile('^Reading specs from (.*/)lib/gcc-lib/(.*)/([0-9]+\.[0-9]+\.[0-9]+.*)/specs')
  282| re_version = re.compile('([0-9]+)\.([0-9]+)\.([0-9]+)')
  283| 
  284| def find_compiler_info(compiler):
  285|     print "Finding info for '%s' ..."%compiler,
  286| 
  287|     # Run the compiler with -v and get the displayed info
  288|     cin, out,err = os.popen3(compiler + " -E -v " + get_temp_file())
  289|     lines = err.readlines()
  290|     cin.close()
  291|     out.close()
  292|     err.close()
  293| 
  294|     paths = []
  295|     macros = []
  296| 
  297|     state = 0
  298|     for line in lines:
  299|         line = line.rstrip()
  300|         if state == 0:
  301|             if line[:11] == 'gcc version': state = 1
  302|         elif state == 1:
  303|             # cpp command line
  304|             args = string.split(line)
  305|             for arg in args:
  306|                if arg[0] != '-':
  307|                continue
  308|                if arg[1] == 'D':
  309|                    if arg.find('=') != -1:
  310|                       macros.append( tuple(string.split(arg[2:], '=', 1)))
  311|                else:
  312|                       macros.append( (arg[2:], '') )
  313|                # TODO: do we need the asserts?
  314|             state = 2
  315|         elif state == 2:
  316|             if line == '#include <...> search starts here:':
  317|                state = 3
  318|         elif state == 3:
  319|             if line == 'End of search list.':
  320|                state = 4
  321|          else:
  322|                paths.append(line.strip())
  323|         
  324|     if not paths or not macros:
  325|         print "Failed"
  326|         return None
  327|     print "Found"
  328| 
  329|     # Per-compiler adjustments
  330|     for name, value in tuple(macros):
  331|         if name == '__GNUC__' and value == '2':
  332|             # gcc 2.x needs this or it uses nonstandard syntax in the headers
  333|             macros.append(('__STRICT_ANSI__', ''))
  334|     
  335|     timestamp = get_compiler_timestamp(compiler)
  336|     return CompilerInfo(compiler, 0, timestamp, paths, macros)
  337| 
  338|