#!/usr/bin/python # Copyright 2014 Google Inc. # # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Functions for parsing the gypd output from gyp. """ import os def parse_dictionary(var_dict, d, current_target_name, dest_dir): """Helper function to get the meaningful entries in a dictionary. Parse dictionary d, and store unique relevant entries in var_dict. Recursively parses internal dictionaries and files that are referenced. When parsing the 'libraries' list from gyp, entries in the form '-l' get assigned to var_dict.LOCAL_SHARED_LIBRARIES as 'lib', and entries in the form '[lib].a' get assigned to var_dict.LOCAL_STATIC_LIBRARIES as 'lib'. Args: var_dict: VarsDict object for storing the results of the parsing. d: Dictionary object to parse. current_target_name: The current target being parsed. If this dictionary is a target, this will be its entry 'target_name'. Otherwise, this will be the name of the target which contains this dictionary. dest_dir: Destination for the eventual Android.mk that will be created from this parse, relative to Skia trunk. Used to determine path for source files. """ for source in d.get('sources', []): # Compare against a lowercase version, in case files are named .H or .GYPI lowercase_source = source.lower() if lowercase_source.endswith('.h'): # Android.mk does not need the header files. continue if lowercase_source.endswith('gypi'): # The gypi files are included in sources, but the sources they included # are also included. No need to parse them again. continue # The path is relative to the gyp folder, but Android wants the path # relative to dest_dir. rel_source = os.path.relpath(source, os.pardir) rel_source = os.path.relpath(rel_source, dest_dir) var_dict.LOCAL_SRC_FILES.add(rel_source) for lib in d.get('libraries', []): if lib.endswith('.a'): # Remove the '.a' lib = lib[:-2] # Add 'lib', if necessary if not lib.startswith('lib'): lib = 'lib' + lib var_dict.LOCAL_STATIC_LIBRARIES.add(lib) else: # lib will be in the form of '-l'. Change it to 'lib' lib = lib.replace('-l', 'lib', 1) var_dict.LOCAL_SHARED_LIBRARIES.add(lib) for dependency in d.get('dependencies', []): # Each dependency is listed as # :#target li = dependency.split(':') assert(len(li) <= 2 and len(li) >= 1) sub_targets = [] if len(li) == 2 and li[1] != '*': sub_targets.append(li[1].split('#')[0]) sub_path = li[0] assert(sub_path.endswith('.gyp')) # Although the original reference is to a .gyp, parse the corresponding # gypd file, which was constructed by gyp. sub_path = sub_path + 'd' parse_gypd(var_dict, sub_path, dest_dir, sub_targets) if 'default_configuration' in d: config_name = d['default_configuration'] # default_configuration is meaningless without configurations assert('configurations' in d) config = d['configurations'][config_name] parse_dictionary(var_dict, config, current_target_name, dest_dir) for flag in d.get('cflags', []): var_dict.LOCAL_CFLAGS.add(flag) for flag in d.get('cflags_cc', []): var_dict.LOCAL_CPPFLAGS.add(flag) for include in d.get('include_dirs', []): if include.startswith('external') or include.startswith('frameworks'): # This path is relative to the Android root. Leave it alone. rel_include = include else: # As with source, the input path will be relative to gyp/, but Android # wants relative to dest_dir. rel_include = os.path.relpath(include, os.pardir) rel_include = os.path.relpath(rel_include, dest_dir) # No need to include the base directory. if rel_include is os.curdir: continue rel_include = os.path.join('$(LOCAL_PATH)', rel_include) # Remove a trailing slash, if present. if rel_include.endswith('/'): rel_include = rel_include[:-1] var_dict.LOCAL_C_INCLUDES.add(rel_include) # For the top level, libskia, include directories should be exported. # FIXME (scroggo): Do not hard code this. if current_target_name == 'libskia': var_dict.LOCAL_EXPORT_C_INCLUDE_DIRS.add(rel_include) for define in d.get('defines', []): var_dict.DEFINES.add(define) def parse_gypd(var_dict, path, dest_dir, desired_targets=None): """Parse a gypd file. Open a file that consists of python dictionaries representing build targets. Parse those dictionaries using parse_dictionary. Recursively parses referenced files. Args: var_dict: VarsDict object for storing the result of the parse. path: Path to gypd file. dest_dir: Destination for the eventual Android.mk that will be created from this parse, relative to Skia trunk. Used to determine path for source files and include directories. desired_targets: List of targets to be parsed from this file. If empty, parse all targets. """ d = {} with open(path, 'r') as f: # Read the entire file as a dictionary d = eval(f.read()) # The gypd file is structured such that the top level dictionary has an entry # named 'targets' for target in d['targets']: target_name = target['target_name'] if target_name in var_dict.KNOWN_TARGETS: # Avoid circular dependencies continue if desired_targets and target_name not in desired_targets: # Our caller does not depend on this one continue # Add it to our known targets so we don't parse it again var_dict.KNOWN_TARGETS.add(target_name) parse_dictionary(var_dict, target, target_name, dest_dir)