aboutsummaryrefslogtreecommitdiffhomepage
path: root/platform_tools/android/gyp_gen/gypd_parser.py
blob: 082d2645a4d364434c954544eff039f81ec01681 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/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<name>' get assigned to var_dict.LOCAL_SHARED_LIBRARIES as 'lib<name>',
  and entries in the form '[lib]<name>.a' get assigned to
  var_dict.LOCAL_STATIC_LIBRARIES as 'lib<name>'.

  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<name>'. Change it to 'lib<name>'
      lib = lib.replace('-l', 'lib', 1)
      var_dict.LOCAL_SHARED_LIBRARIES.add(lib)

  for dependency in d.get('dependencies', []):
    # Each dependency is listed as
    #   <path_to_file>:<target>#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)