Home | History | Annotate | Download | only in scripts
      1 # Copyright 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Utility functions (file reading, simple IDL parsing by regexes) for IDL build.
      6 
      7 Design doc: http://www.chromium.org/developers/design-documents/idl-build
      8 """
      9 
     10 import os
     11 import cPickle as pickle
     12 import re
     13 import string
     14 import subprocess
     15 
     16 
     17 KNOWN_COMPONENTS = frozenset(['core', 'modules'])
     18 
     19 
     20 class IdlBadFilenameError(Exception):
     21     """Raised if an IDL filename disagrees with the interface name in the file."""
     22     pass
     23 
     24 
     25 def idl_filename_to_interface_name(idl_filename):
     26     # interface name is the root of the basename: InterfaceName.idl
     27     return os.path.splitext(os.path.basename(idl_filename))[0]
     28 
     29 
     30 def idl_filename_to_component(idl_filename):
     31     path = os.path.dirname(os.path.realpath(idl_filename))
     32     while path:
     33         dirname, basename = os.path.split(path)
     34         if basename.lower() in KNOWN_COMPONENTS:
     35             return basename.lower()
     36         path = dirname
     37     raise 'Unknown component type for %s' % idl_filename
     38 
     39 
     40 ################################################################################
     41 # Basic file reading/writing
     42 ################################################################################
     43 
     44 def get_file_contents(filename):
     45     with open(filename) as f:
     46         return f.read()
     47 
     48 
     49 def read_file_to_list(filename):
     50     """Returns a list of (stripped) lines for a given filename."""
     51     with open(filename) as f:
     52         return [line.rstrip('\n') for line in f]
     53 
     54 
     55 def resolve_cygpath(cygdrive_names):
     56     if not cygdrive_names:
     57         return []
     58     cmd = ['cygpath', '-f', '-', '-wa']
     59     process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     60     idl_file_names = []
     61     for file_name in cygdrive_names:
     62         process.stdin.write('%s\n' % file_name)
     63         process.stdin.flush()
     64         idl_file_names.append(process.stdout.readline().rstrip())
     65     process.stdin.close()
     66     process.wait()
     67     return idl_file_names
     68 
     69 
     70 def read_idl_files_list_from_file(filename):
     71     """Similar to read_file_to_list, but also resolves cygpath."""
     72     with open(filename) as input_file:
     73         file_names = sorted([os.path.realpath(line.rstrip('\n'))
     74                              for line in input_file])
     75         idl_file_names = [file_name for file_name in file_names
     76                           if not file_name.startswith('/cygdrive')]
     77         cygdrive_names = [file_name for file_name in file_names
     78                           if file_name.startswith('/cygdrive')]
     79         idl_file_names.extend(resolve_cygpath(cygdrive_names))
     80         return idl_file_names
     81 
     82 
     83 def read_pickle_files(pickle_filenames):
     84     for pickle_filename in pickle_filenames:
     85         with open(pickle_filename) as pickle_file:
     86             yield pickle.load(pickle_file)
     87 
     88 
     89 def write_file(new_text, destination_filename, only_if_changed):
     90     if only_if_changed and os.path.isfile(destination_filename):
     91         with open(destination_filename) as destination_file:
     92             if destination_file.read() == new_text:
     93                 return
     94     destination_dirname = os.path.dirname(destination_filename)
     95     if not os.path.exists(destination_dirname):
     96         os.makedirs(destination_dirname)
     97     with open(destination_filename, 'w') as destination_file:
     98         destination_file.write(new_text)
     99 
    100 
    101 def write_pickle_file(pickle_filename, data, only_if_changed):
    102     if only_if_changed and os.path.isfile(pickle_filename):
    103         with open(pickle_filename) as pickle_file:
    104             try:
    105                 if pickle.load(pickle_file) == data:
    106                     return
    107             except (EOFError, pickle.UnpicklingError):
    108                 # If trouble unpickling, overwrite
    109                 pass
    110     with open(pickle_filename, 'w') as pickle_file:
    111         pickle.dump(data, pickle_file)
    112 
    113 
    114 ################################################################################
    115 # IDL parsing
    116 #
    117 # We use regular expressions for parsing; this is incorrect (Web IDL is not a
    118 # regular language), but simple and sufficient in practice.
    119 # Leading and trailing context (e.g. following '{') used to avoid false matches.
    120 ################################################################################
    121 
    122 def get_partial_interface_name_from_idl(file_contents):
    123     match = re.search(r'partial\s+interface\s+(\w+)\s*{', file_contents)
    124     return match and match.group(1)
    125 
    126 
    127 def get_implements_from_idl(file_contents, interface_name):
    128     """Returns lists of implementing and implemented interfaces.
    129 
    130     Rule is: identifier-A implements identifier-B;
    131     i.e., implement*ing* implements implement*ed*;
    132     http://www.w3.org/TR/WebIDL/#idl-implements-statements
    133 
    134     Returns two lists of interfaces: identifier-As and identifier-Bs.
    135     An 'implements' statements can be present in the IDL file for either the
    136     implementing or the implemented interface, but not other files.
    137     """
    138     implements_re = (r'^\s*'
    139                      r'(\w+)\s+'
    140                      r'implements\s+'
    141                      r'(\w+)\s*'
    142                      r';')
    143     implements_matches = re.finditer(implements_re, file_contents, re.MULTILINE)
    144     implements_pairs = [match.groups() for match in implements_matches]
    145 
    146     foreign_implements = [pair for pair in implements_pairs
    147                           if interface_name not in pair]
    148     if foreign_implements:
    149         left, right = foreign_implements.pop()
    150         raise IdlBadFilenameError(
    151                 'implements statement found in unrelated IDL file.\n'
    152                 'Statement is:\n'
    153                 '    %s implements %s;\n'
    154                 'but filename is unrelated "%s.idl"' %
    155                 (left, right, interface_name))
    156 
    157     return (
    158         [left for left, right in implements_pairs if right == interface_name],
    159         [right for left, right in implements_pairs if left == interface_name])
    160 
    161 
    162 def is_callback_interface_from_idl(file_contents):
    163     match = re.search(r'callback\s+interface\s+\w+\s*{', file_contents)
    164     return bool(match)
    165 
    166 
    167 def is_dictionary_from_idl(file_contents):
    168     match = re.search(r'dictionary\s+\w+\s*{', file_contents)
    169     return bool(match)
    170 
    171 
    172 def get_parent_interface(file_contents):
    173     match = re.search(r'interface\s+'
    174                       r'\w+\s*'
    175                       r':\s*(\w+)\s*'
    176                       r'{',
    177                       file_contents)
    178     return match and match.group(1)
    179 
    180 
    181 def get_interface_extended_attributes_from_idl(file_contents):
    182     # Strip comments
    183     # re.compile needed b/c Python 2.6 doesn't support flags in re.sub
    184     single_line_comment_re = re.compile(r'//.*$', flags=re.MULTILINE)
    185     block_comment_re = re.compile(r'/\*.*?\*/', flags=re.MULTILINE | re.DOTALL)
    186     file_contents = re.sub(single_line_comment_re, '', file_contents)
    187     file_contents = re.sub(block_comment_re, '', file_contents)
    188 
    189     match = re.search(r'\[(.*)\]\s*'
    190                       r'((callback|partial)\s+)?'
    191                       r'(interface|exception)\s+'
    192                       r'\w+\s*'
    193                       r'(:\s*\w+\s*)?'
    194                       r'{',
    195                       file_contents, flags=re.DOTALL)
    196     if not match:
    197         return {}
    198 
    199     extended_attributes_string = match.group(1)
    200     extended_attributes = {}
    201     # FIXME: this splitting is WRONG: it fails on extended attributes where lists of
    202     # multiple values are used, which are seperated by a comma and a space.
    203     parts = [extended_attribute.strip()
    204              for extended_attribute in re.split(',\s+', extended_attributes_string)
    205              # Discard empty parts, which may exist due to trailing comma
    206              if extended_attribute.strip()]
    207     for part in parts:
    208         name, _, value = map(string.strip, part.partition('='))
    209         extended_attributes[name] = value
    210     return extended_attributes
    211 
    212 
    213 def get_put_forward_interfaces_from_idl(file_contents):
    214     put_forwards_pattern = (r'\[[^\]]*PutForwards=[^\]]*\]\s+'
    215                             r'readonly\s+'
    216                             r'attribute\s+'
    217                             r'(\w+)')
    218     return sorted(set(match.group(1)
    219                       for match in re.finditer(put_forwards_pattern,
    220                                                file_contents,
    221                                                flags=re.DOTALL)))
    222