pygccxml.parser.source_reader

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