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|