Home | History | Annotate | Download | only in gyp_gen
      1 #!/usr/bin/python
      2 
      3 # Copyright 2014 Google Inc.
      4 #
      5 # Use of this source code is governed by a BSD-style license that can be
      6 # found in the LICENSE file.
      7 
      8 """Functions for parsing the gypd output from gyp.
      9 """
     10 
     11 
     12 import os
     13 
     14 
     15 def parse_dictionary(var_dict, d, current_target_name, dest_dir):
     16   """Helper function to get the meaningful entries in a dictionary.
     17 
     18   Parse dictionary d, and store unique relevant entries in var_dict.
     19   Recursively parses internal dictionaries and files that are referenced.
     20   When parsing the 'libraries' list from gyp, entries in the form
     21   '-l<name>' get assigned to var_dict.LOCAL_SHARED_LIBRARIES as 'lib<name>',
     22   and entries in the form '[lib]<name>.a' get assigned to
     23   var_dict.LOCAL_STATIC_LIBRARIES as 'lib<name>'.
     24 
     25   Args:
     26     var_dict: VarsDict object for storing the results of the parsing.
     27     d: Dictionary object to parse.
     28     current_target_name: The current target being parsed. If this dictionary
     29       is a target, this will be its entry 'target_name'. Otherwise, this will
     30       be the name of the target which contains this dictionary.
     31     dest_dir: Destination for the eventual Android.mk that will be created from
     32       this parse, relative to Skia trunk. Used to determine path for source
     33       files.
     34   """
     35   for source in d.get('sources', []):
     36     # Compare against a lowercase version, in case files are named .H or .GYPI
     37     lowercase_source = source.lower()
     38     if lowercase_source.endswith('.h'):
     39       # Android.mk does not need the header files.
     40       continue
     41     if lowercase_source.endswith('gypi'):
     42       # The gypi files are included in sources, but the sources they included
     43       # are also included. No need to parse them again.
     44       continue
     45     # The path is relative to the gyp folder, but Android wants the path
     46     # relative to dest_dir.
     47     rel_source = os.path.relpath(source, os.pardir)
     48     rel_source = os.path.relpath(rel_source, dest_dir)
     49     var_dict.LOCAL_SRC_FILES.add(rel_source)
     50 
     51   for lib in d.get('libraries', []):
     52     if lib.endswith('.a'):
     53       # Remove the '.a'
     54       lib = lib[:-2]
     55       # Add 'lib', if necessary
     56       if not lib.startswith('lib'):
     57         lib = 'lib' + lib
     58       var_dict.LOCAL_STATIC_LIBRARIES.add(lib)
     59     else:
     60       # lib will be in the form of '-l<name>'. Change it to 'lib<name>'
     61       lib = lib.replace('-l', 'lib', 1)
     62       var_dict.LOCAL_SHARED_LIBRARIES.add(lib)
     63 
     64   for dependency in d.get('dependencies', []):
     65     # Each dependency is listed as
     66     #   <path_to_file>:<target>#target
     67     li = dependency.split(':')
     68     assert(len(li) <= 2 and len(li) >= 1)
     69     sub_targets = []
     70     if len(li) == 2 and li[1] != '*':
     71       sub_targets.append(li[1].split('#')[0])
     72     sub_path = li[0]
     73     assert(sub_path.endswith('.gyp'))
     74     # Although the original reference is to a .gyp, parse the corresponding
     75     # gypd file, which was constructed by gyp.
     76     sub_path = sub_path + 'd'
     77     parse_gypd(var_dict, sub_path, dest_dir, sub_targets)
     78 
     79   if 'default_configuration' in d:
     80     config_name = d['default_configuration']
     81     # default_configuration is meaningless without configurations
     82     assert('configurations' in d)
     83     config = d['configurations'][config_name]
     84     parse_dictionary(var_dict, config, current_target_name, dest_dir)
     85 
     86   for flag in d.get('cflags', []):
     87     var_dict.LOCAL_CFLAGS.add(flag)
     88   for flag in d.get('cflags_cc', []):
     89     var_dict.LOCAL_CPPFLAGS.add(flag)
     90 
     91   for include in d.get('include_dirs', []):
     92     if include.startswith('external'):
     93       # This path is relative to the Android root. Leave it alone.
     94       rel_include = include
     95     else:
     96       # As with source, the input path will be relative to gyp/, but Android
     97       # wants relative to dest_dir.
     98       rel_include = os.path.relpath(include, os.pardir)
     99       rel_include = os.path.relpath(rel_include, dest_dir)
    100       # No need to include the base directory.
    101       if rel_include is os.curdir:
    102         continue
    103       rel_include = os.path.join('$(LOCAL_PATH)', rel_include)
    104 
    105     # Remove a trailing slash, if present.
    106     if rel_include.endswith('/'):
    107       rel_include = rel_include[:-1]
    108     var_dict.LOCAL_C_INCLUDES.add(rel_include)
    109     # For the top level, libskia, include directories should be exported.
    110     # FIXME (scroggo): Do not hard code this.
    111     if current_target_name == 'libskia':
    112       var_dict.LOCAL_EXPORT_C_INCLUDE_DIRS.add(rel_include)
    113 
    114   for define in d.get('defines', []):
    115     var_dict.DEFINES.add(define)
    116 
    117 
    118 def parse_gypd(var_dict, path, dest_dir, desired_targets=None):
    119   """Parse a gypd file.
    120 
    121   Open a file that consists of python dictionaries representing build targets.
    122   Parse those dictionaries using parse_dictionary. Recursively parses
    123   referenced files.
    124 
    125   Args:
    126     var_dict: VarsDict object for storing the result of the parse.
    127     path: Path to gypd file.
    128     dest_dir: Destination for the eventual Android.mk that will be created from
    129       this parse, relative to Skia trunk. Used to determine path for source
    130       files and include directories.
    131     desired_targets: List of targets to be parsed from this file. If empty,
    132       parse all targets.
    133   """
    134   d = {}
    135   with open(path, 'r') as f:
    136     # Read the entire file as a dictionary
    137     d = eval(f.read())
    138 
    139   # The gypd file is structured such that the top level dictionary has an entry
    140   # named 'targets'
    141   for target in d['targets']:
    142     target_name = target['target_name']
    143     if target_name in var_dict.KNOWN_TARGETS:
    144       # Avoid circular dependencies
    145       continue
    146     if desired_targets and target_name not in desired_targets:
    147       # Our caller does not depend on this one
    148       continue
    149     # Add it to our known targets so we don't parse it again
    150     var_dict.KNOWN_TARGETS.add(target_name)
    151 
    152     parse_dictionary(var_dict, target, target_name, dest_dir)
    153 
    154