Package pygccxml :: Package parser :: Module source_reader

Source Code for Module pygccxml.parser.source_reader

  1  # Copyright 2004-2008 Roman Yakovenko. 
  2  # Distributed under the Boost Software License, Version 1.0. (See 
  3  # accompanying file LICENSE_1_0.txt or copy at 
  4  # http://www.boost.org/LICENSE_1_0.txt) 
  5   
  6  import os 
  7  import sys 
  8  import linker 
  9  import config 
 10  import patcher 
 11  import pygccxml.utils 
 12   
 13  try: #select the faster xml parser 
 14      from etree_scanner import etree_scanner_t as scanner_t 
 15  except: 
 16      from scanner import scanner_t 
 17   
 18  import declarations_cache 
 19  from pygccxml import utils 
 20  from pygccxml.declarations import * 
 21   
22 -class gccxml_runtime_error_t( RuntimeError ):
23 - def __init__( self, msg ):
24 RuntimeError.__init__( self, msg )
25
26 -def bind_aliases( decls ):
27 """ 28 This function binds between class and it's typedefs. 29 30 @param decls: list of all declarations 31 @type all_classes: list of L{declaration_t} items 32 33 @return: None 34 """ 35 visited = set() 36 typedefs = filter( lambda decl: isinstance( decl, typedef_t ), decls ) 37 for decl in typedefs: 38 type_ = remove_alias( decl.type ) 39 if not isinstance( type_, declarated_t ): 40 continue 41 cls_inst = type_.declaration 42 if not isinstance( cls_inst, class_types ): 43 continue 44 if id( cls_inst ) not in visited: 45 visited.add( id( cls_inst ) ) 46 del cls_inst.aliases[:] 47 cls_inst.aliases.append( decl )
48
49 -class source_reader_t:
50 """ 51 This class reads C++ source code and returns declarations tree. 52 53 This class is the only class that have an intime knowledge about GCC-XML. 54 It has only one responsibility: it calls GCC-XML with a source file specified 55 by user and creates declarations tree. The implementation of this class is split 56 to 2 classes: 57 58 1. L{scanner_t} - this class scans the "XML" file, generated by GCC-XML and 59 creates `pygccxml`_ declarations and types classes. After the xml file has 60 been processed declarations and type class instances keeps references to 61 each other using GCC-XML generated id's. 62 63 2. L{linker_t} - this class contains logic for replacing GCC-XML generated 64 ids with references to declarations or type class instances. 65 """
66 - def __init__( self, config, cache=None, decl_factory=None ):
67 """ 68 @param config: instance of L{config_t} class, that contains GCC-XML 69 configuration 70 @type config: L{config_t} 71 72 @param cache: reference to cache object, that will be updated after 73 file has been parsed. 74 @param cache: instance of class, that derives from {cache_base_t} 75 76 @param decl_factory: declarations factory, if not given default 77 declarations factory L{decl_factory_t} will be used 78 """ 79 self.logger = utils.loggers.cxx_parser 80 self.__search_directories = [] 81 self.__config = config 82 self.__search_directories.append( config.working_directory ) 83 self.__search_directories.extend( config.include_paths ) 84 if not cache: 85 cache = declarations_cache.dummy_cache_t() 86 self.__dcache = cache 87 self.__config.raise_on_wrong_settings() 88 self.__decl_factory = decl_factory 89 if not decl_factory: 90 self.__decl_factory = decl_factory_t()
91
92 - def __create_command_line(self, file, xmlfile):
93 assert isinstance( self.__config, config.config_t ) 94 #returns 95 cmd = [] 96 #first is gccxml executable 97 if 'win32' in sys.platform: 98 cmd.append( '"%s"' % os.path.normpath( self.__config.gccxml_path ) ) 99 else: 100 cmd.append( '%s' % os.path.normpath( self.__config.gccxml_path ) ) 101 102 # Add all cflags passed 103 if self.__config.cflags != "": 104 cmd.append(" %s "%self.__config.cflags) 105 #second all additional includes directories 106 cmd.append( ''.join( [' -I"%s"' % search_dir for search_dir in self.__search_directories] ) ) 107 #third all additional defined symbols 108 cmd.append( ''.join( [' -D"%s"' % defined_symbol for defined_symbol in self.__config.define_symbols] ) ) 109 cmd.append( ''.join( [' -U"%s"' % undefined_symbol for undefined_symbol in self.__config.undefine_symbols] ) ) 110 #fourth source file 111 cmd.append( '"%s"' % file ) 112 #five destination file 113 cmd.append( '-fxml="%s"' % xmlfile ) 114 if self.__config.start_with_declarations: 115 cmd.append( '-fxml-start="%s"' % ','.join( self.__config.start_with_declarations ) ) 116 # Specify compiler if asked to 117 if self.__config.compiler: 118 cmd.append( " --gccxml-compiler %s" % self.__config.compiler ) 119 cmd_line = ' '.join(cmd) 120 if 'win32' in sys.platform : 121 cmd_line = '"%s"' % cmd_line 122 self.logger.info( 'gccxml cmd: %s' % cmd_line ) 123 return cmd_line
124
125 - def create_xml_file( self, header, destination=None ):
126 """ 127 This function will return the file name of the file, created by GCC-XML 128 for "header" file. If destination_file_path is not None, then this file 129 path will be used and returned. 130 131 @param header: path to source file, that should be parsed 132 @type header: str 133 134 @param destination: if given, will be used as target file/path for 135 GCC-XML generated file. 136 @type destination: str 137 138 @return: path to GCC-XML generated file 139 """ 140 gccxml_file = destination 141 # If file specified, remove it to start else create new file name 142 if gccxml_file: 143 pygccxml.utils.remove_file_no_raise( gccxml_file ) 144 else: 145 gccxml_file = pygccxml.utils.create_temp_file_name( suffix='.xml' ) 146 try: 147 ffname = header 148 if not os.path.isabs( ffname ): 149 ffname = self.__file_full_name(header) 150 command_line = self.__create_command_line( ffname, gccxml_file ) 151 input_, output = os.popen4( command_line ) 152 input_.close() 153 gccxml_reports = [] 154 while True: 155 data = output.readline() 156 gccxml_reports.append( data ) 157 if not data: 158 break 159 exit_status = output.close() 160 gccxml_msg = ''.join(gccxml_reports) 161 if self.__config.ignore_gccxml_output: 162 if not os.path.isfile(gccxml_file): 163 raise gccxml_runtime_error_t( "Error occured while running GCC-XML: %s status:%s" % (gccxml_msg, exit_status) ) 164 else: 165 if gccxml_msg or exit_status or not os.path.isfile(gccxml_file): 166 raise gccxml_runtime_error_t( "Error occured while running GCC-XML: %s" % gccxml_msg ) 167 except Exception, error: 168 pygccxml.utils.remove_file_no_raise( gccxml_file ) 169 raise error 170 return gccxml_file
171
172 - def create_xml_file_from_string( self, content, destination=None ):
173 """ 174 Creates XML file from text. 175 176 @param content: C++ source code 177 @type content: str 178 179 @param destination: file name for GCC-XML generated file 180 @type destination: str 181 182 @return: returns file name of GCC-XML generated file 183 """ 184 header_file = pygccxml.utils.create_temp_file_name( suffix='.h' ) 185 gccxml_file = None 186 try: 187 header_file_obj = file(header_file, 'w+') 188 header_file_obj.write( content ) 189 header_file_obj.close() 190 gccxml_file = self.create_xml_file( header_file, destination ) 191 finally: 192 pygccxml.utils.remove_file_no_raise( header_file ) 193 return gccxml_file
194
195 - def read_file( self, source_file ):
196 if isinstance( self.__config, config.gccxml_configuration_t ): 197 return self.read_gccxml_file( source_file ) 198 else: 199 return self.read_synopsis_file( source_file )
200
201 - def read_gccxml_file(self, source_file):
202 """ 203 Reads C++ source file and returns declarations tree 204 205 @param source_file: path to C++ source file 206 @type source_file: str 207 """ 208 declarations, types = None, None 209 gccxml_file = '' 210 try: 211 ffname = self.__file_full_name(source_file) 212 self.logger.debug( "Reading source file: [%s]." % ffname ) 213 declarations = self.__dcache.cached_value( ffname, self.__config ) 214 if not declarations: 215 self.logger.debug( "File has not been found in cache, parsing..." ) 216 gccxml_file = self.create_xml_file( ffname ) 217 declarations, files = self.__parse_gccxml_created_file( gccxml_file ) 218 self.__dcache.update( ffname, self.__config, declarations, files ) 219 else: 220 self.logger.debug( "File has not been changed, reading declarations from cache." ) 221 except Exception, error: 222 if gccxml_file: 223 pygccxml.utils.remove_file_no_raise( gccxml_file ) 224 raise error 225 if gccxml_file: 226 pygccxml.utils.remove_file_no_raise( gccxml_file ) 227 return declarations
228
229 - def read_xml_file(self, gccxml_created_file):
230 """ 231 Reads GCC-XML generated XML file. 232 233 @param gccxml_created_file: path to GCC-XML generated file 234 @type gccxml_created_file: str 235 236 @return: declarations tree 237 """ 238 assert(self.__config!=None) 239 240 ffname = self.__file_full_name(gccxml_created_file) 241 self.logger.debug( "Reading xml file: [%s]" % gccxml_created_file ) 242 declarations = self.__dcache.cached_value( ffname, self.__config ) 243 if not declarations: 244 self.logger.debug( "File has not been found in cache, parsing..." ) 245 declarations, files = self.__parse_gccxml_created_file( ffname ) 246 self.__dcache.update( ffname, self.__config, declarations, [] ) 247 else: 248 self.logger.debug( "File has not been changed, reading declarations from cache." ) 249 250 return declarations
251
252 - def read_string(self, content):
253 """ 254 Reads Python string, that contains valid C++ code, and returns 255 declarations tree. 256 """ 257 header_file = pygccxml.utils.create_temp_file_name( suffix='.h' ) 258 header_file_obj = file(header_file, 'w+') 259 header_file_obj.write( content ) 260 header_file_obj.close() 261 declarations = None 262 try: 263 declarations = self.read_file( header_file ) 264 except Exception, error: 265 pygccxml.utils.remove_file_no_raise( header_file ) 266 raise error 267 pygccxml.utils.remove_file_no_raise( header_file ) 268 return declarations
269
270 - def __file_full_name( self, file ):
271 if os.path.isfile( file ): 272 return file 273 for path in self.__search_directories: 274 file_path = os.path.join( path, file ) 275 if os.path.isfile( file_path ): 276 return file_path 277 raise RuntimeError( "pygccxml error: file '%s' does not exist" % file )
278
279 - def __produce_full_file( self, file_path ):
280 if 'win' in sys.platform or 'linux' in sys.platform: 281 file_path = file_path.replace( r'\/', os.path.sep ) 282 if os.path.isabs( file_path ): 283 return file_path 284 try: 285 abs_file_path = os.path.realpath( os.path.join( self.__config.working_directory, file_path ) ) 286 if os.path.exists( abs_file_path ): 287 return os.path.normpath( abs_file_path ) 288 return file_path 289 except Exception: 290 return file_path
291
292 - def __parse_gccxml_created_file( self, gccxml_file ):
293 scanner_ = scanner_t( gccxml_file, self.__decl_factory ) 294 scanner_.read() 295 decls = scanner_.declarations() 296 types = scanner_.types() 297 files = {} 298 for file_id, file_path in scanner_.files().iteritems(): 299 files[file_id] = self.__produce_full_file(file_path) 300 linker_ = linker.linker_t( decls=decls 301 , types=types 302 , access=scanner_.access() 303 , membership=scanner_.members() 304 , files=files ) 305 for type_ in types.values(): 306 #I need this copy because internaly linker change types collection 307 linker_.instance = type_ 308 apply_visitor( linker_, type_ ) 309 for decl in decls.itervalues(): 310 linker_.instance = decl 311 apply_visitor( linker_, decl ) 312 bind_aliases( decls.itervalues() ) 313 #some times gccxml report typedefs defined in no namespace 314 #it happens for example in next situation 315 #template< typename X> 316 #void ddd(){ typedef typename X::Y YY;} 317 #if I will fail on this bug next time, the right way to fix it may be different 318 patcher.fix_calldef_decls( scanner_.calldefs(), scanner_.enums() ) 319 decls = filter( lambda inst: isinstance( inst, namespace_t ) and not inst.parent 320 , decls.itervalues() ) 321 return ( decls, files.values() )
322
323 - def read_synopsis_file( self, source_file ):
324 import synopsis_scanner 325 from Synopsis import AST 326 from Synopsis.Parsers import Cxx 327 328 ffname = self.__file_full_name(source_file) 329 330 cppflags = [] 331 map( lambda dpath: cppflags.append( '-I %s' % dpath ) 332 , self.__config.include_paths ) 333 map( lambda define: cppflags.append( '-D %s' % define ) 334 , self.__config.define_symbols ) 335 map( lambda define: cppflags.append( '-U %s' % define ) 336 , self.__config.undefine_symbols ) 337 338 cxx = Cxx.Parser( preprocess=True, cppflags=cppflags ) 339 ast = AST.AST() 340 cxx.process( ast, input=[source_file] ) 341 scanner = synopsis_scanner.scanner_t( ast, self.__decl_factory ) 342 scanner.visitAST( ast ) 343 declarations = [scanner.global_ns] 344 self.__dcache.update( ffname, self.__config, declarations, [] ) 345 return declarations
346