summaryrefslogtreecommitdiffstats
path: root/native_client_sdk/src/build_tools/parse_dsc.py
blob: 57fa705bf2ef9b7d264b03af127aa9df6606b408 (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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#!/usr/bin/env python
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import collections
import fnmatch
import optparse
import os
import sys

VALID_TOOLCHAINS = [
  'bionic',
  'newlib',
  'glibc',
  'pnacl',
  'win',
  'linux',
  'mac',
]

# 'KEY' : ( <TYPE>, [Accepted Values], <Required?>)
DSC_FORMAT = {
    'DISABLE': (bool, [True, False], False),
    'SEL_LDR': (bool, [True, False], False),
    # Disable this project from being included in the NaCl packaged app.
    'DISABLE_PACKAGE': (bool, [True, False], False),
    # Don't generate the additional files to allow this project to run as a
    # packaged app (i.e. manifest.json, background.js, etc.).
    'NO_PACKAGE_FILES': (bool, [True, False], False),
    'TOOLS' : (list, VALID_TOOLCHAINS, True),
    'CONFIGS' : (list, ['Debug', 'Release'], False),
    'PREREQ' : (list, '', False),
    'TARGETS' : (list, {
        'NAME': (str, '', True),
        # main = nexe target
        # lib = library target
        # so = shared object target, automatically added to NMF
        # so-standalone =  shared object target, not put into NMF
        'TYPE': (str, ['main', 'lib', 'static-lib', 'so', 'so-standalone'],
                 True),
        'SOURCES': (list, '', True),
        'CFLAGS': (list, '', False),
        'CFLAGS_GCC': (list, '', False),
        'CXXFLAGS': (list, '', False),
        'DEFINES': (list, '', False),
        'LDFLAGS': (list, '', False),
        'INCLUDES': (list, '', False),
        'LIBS' : (dict, VALID_TOOLCHAINS, False),
        'DEPS' : (list, '', False)
    }, False),
    'HEADERS': (list, {
        'FILES': (list, '', True),
        'DEST': (str, '', True),
    }, False),
    'SEARCH': (list, '', False),
    'POST': (str, '', False),
    'PRE': (str, '', False),
    'DEST': (str, ['getting_started', 'examples/api',
                   'examples/demo', 'examples/tutorial',
                   'src', 'tests'], True),
    'NAME': (str, '', False),
    'DATA': (list, '', False),
    'TITLE': (str, '', False),
    'GROUP': (str, '', False),
    'EXPERIMENTAL': (bool, [True, False], False),
    'PERMISSIONS': (list, '', False),
    'SOCKET_PERMISSIONS': (list, '', False),
    'MULTI_PLATFORM': (bool, [True, False], False),
    'MIN_CHROME_VERSION': (str, '', False),
}


class ValidationError(Exception):
  pass


def ValidateFormat(src, dsc_format):
  # Verify all required keys are there
  for key in dsc_format:
    exp_type, exp_value, required = dsc_format[key]
    if required and key not in src:
      raise ValidationError('Missing required key %s.' % key)

  # For each provided key, verify it's valid
  for key in src:
    # Verify the key is known
    if key not in dsc_format:
      raise ValidationError('Unexpected key %s.' % key)

    exp_type, exp_value, required = dsc_format[key]
    value = src[key]

    # Verify the value is non-empty if required
    if required and not value:
      raise ValidationError('Expected non-empty value for %s.' % key)

    # If the expected type is a dict, but the provided type is a list
    # then the list applies to all keys of the dictionary, so we reset
    # the expected type and value.
    if exp_type is dict:
      if type(value) is list:
        exp_type = list
        exp_value = ''

    # Verify the key is of the expected type
    if exp_type != type(value):
      raise ValidationError('Key %s expects %s not %s.' % (
          key, exp_type.__name__.upper(), type(value).__name__.upper()))

    # If it's a bool, the expected values are always True or False.
    if exp_type is bool:
      continue

    # If it's a string and there are expected values, make sure it matches
    if exp_type is str:
      if type(exp_value) is list and exp_value:
        if value not in exp_value:
          raise ValidationError("Value '%s' not expected for %s." %
                                (value, key))
      continue

    # if it's a list, then we need to validate the values
    if exp_type is list:
      # If we expect a dictionary, then call this recursively
      if type(exp_value) is dict:
        for val in value:
          ValidateFormat(val, exp_value)
        continue
      # If we expect a list of strings
      if type(exp_value) is str:
        for val in value:
          if type(val) is not str:
            raise ValidationError('Value %s in %s is not a string.' %
                                  (val, key))
        continue
      # if we expect a particular string
      if type(exp_value) is list:
        for val in value:
          if val not in exp_value:
            raise ValidationError('Value %s not expected in %s.' %
                                  (val, key))
        continue

    # if we are expecting a dict, verify the keys are allowed
    if exp_type is dict:
      print "Expecting dict\n"
      for sub in value:
        if sub not in exp_value:
          raise ValidationError('Sub key %s not expected in %s.' %
                                (sub, key))
      continue

    # If we got this far, it's an unexpected type
    raise ValidationError('Unexpected type %s for key %s.' %
                          (str(type(src[key])), key))


def LoadProject(filename):
  with open(filename, 'r') as descfile:
    try:
      desc = eval(descfile.read(), {}, {})
    except Exception as e:
      raise ValidationError(e)
  if desc.get('DISABLE', False):
    return None
  ValidateFormat(desc, DSC_FORMAT)
  desc['FILEPATH'] = os.path.abspath(filename)
  return desc


def LoadProjectTreeUnfiltered(srcpath):
  # Build the tree
  out = collections.defaultdict(list)
  for root, _, files in os.walk(srcpath):
    for filename in files:
      if fnmatch.fnmatch(filename, '*.dsc'):
        filepath = os.path.join(root, filename)
        try:
          desc = LoadProject(filepath)
        except ValidationError as e:
          raise ValidationError("Failed to validate: %s: %s" % (filepath, e))
        if desc:
          key = desc['DEST']
          out[key].append(desc)
  return out


def LoadProjectTree(srcpath, include, exclude=None):
  out = LoadProjectTreeUnfiltered(srcpath)
  return FilterTree(out, MakeDefaultFilterFn(include, exclude))


def GenerateProjects(tree):
  for key in tree:
    for val in tree[key]:
      yield key, val


def FilterTree(tree, filter_fn):
  out = collections.defaultdict(list)
  for branch, desc in GenerateProjects(tree):
    if filter_fn(desc):
      out[branch].append(desc)
  return out


def MakeDefaultFilterFn(include, exclude):
  def DefaultFilterFn(desc):
    matches_include = not include or DescMatchesFilter(desc, include)
    matches_exclude = exclude and DescMatchesFilter(desc, exclude)

    # Exclude list overrides include list.
    if matches_exclude:
      return False
    return matches_include

  return DefaultFilterFn


def DescMatchesFilter(desc, filters):
  for key, expected in filters.iteritems():
    # For any filtered key which is unspecified, assumed False
    value = desc.get(key, False)

    # If we provide an expected list, match at least one
    if type(expected) != list:
      expected = set([expected])
    if type(value) != list:
      value = set([value])

    if not set(expected) & set(value):
      return False

  # If we fall through, then we matched the filters
  return True


def PrintProjectTree(tree):
  for key in tree:
    print key + ':'
    for val in tree[key]:
      print '\t' + val['NAME']


def main(argv):
  parser = optparse.OptionParser(usage='%prog [options] <dir>')
  parser.add_option('-e', '--experimental',
      help='build experimental examples and libraries', action='store_true')
  parser.add_option('-t', '--toolchain',
      help='Build using toolchain. Can be passed more than once.',
      action='append')

  options, args = parser.parse_args(argv[1:])
  filters = {}

  load_from_dir = '.'
  if len(args) > 1:
    parser.error('Expected 0 or 1 args, got %d.' % len(args))

  if args:
    load_from_dir = args[0]

  if options.toolchain:
    filters['TOOLS'] = options.toolchain

  if not options.experimental:
    filters['EXPERIMENTAL'] = False

  try:
    tree = LoadProjectTree(load_from_dir, include=filters)
  except ValidationError as e:
    sys.stderr.write(str(e) + '\n')
    return 1

  PrintProjectTree(tree)
  return 0


if __name__ == '__main__':
  sys.exit(main(sys.argv))