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
12import os
13
14
15def 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
118def 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