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