Synopsis - Cross-Reference
File: Synopsis/Parsers/Cpp/Emulator.py1# 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
Generated on Tue May 13 02:39:14 2008 by
synopsis (version 0.10)
synopsis (version 0.10)