Explicitly add python-numpy dependency to install-build-deps.
[chromium-blink-merge.git] / native_client_sdk / src / build_tools / parse_dsc.py
blob409eb402082eef495991807ac4487a8a7b19a71b
1 #!/usr/bin/env python
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 import collections
7 import fnmatch
8 import optparse
9 import os
10 import sys
12 VALID_TOOLCHAINS = [
13 'bionic',
14 'newlib',
15 'glibc',
16 'pnacl',
17 'win',
18 'linux',
19 'mac',
22 # 'KEY' : ( <TYPE>, [Accepted Values], <Required?>)
23 DSC_FORMAT = {
24 'DISABLE': (bool, [True, False], False),
25 'SEL_LDR': (bool, [True, False], False),
26 # Disable this project from being included in the NaCl packaged app.
27 'DISABLE_PACKAGE': (bool, [True, False], False),
28 # Don't generate the additional files to allow this project to run as a
29 # packaged app (i.e. manifest.json, background.js, etc.).
30 'NO_PACKAGE_FILES': (bool, [True, False], False),
31 'TOOLS' : (list, VALID_TOOLCHAINS, True),
32 'CONFIGS' : (list, ['Debug', 'Release'], False),
33 'PREREQ' : (list, '', False),
34 'TARGETS' : (list, {
35 'NAME': (str, '', True),
36 # main = nexe target
37 # lib = library target
38 # so = shared object target, automatically added to NMF
39 # so-standalone = shared object target, not put into NMF
40 'TYPE': (str, ['main', 'lib', 'static-lib', 'so', 'so-standalone'],
41 True),
42 'SOURCES': (list, '', True),
43 'CFLAGS': (list, '', False),
44 'CFLAGS_GCC': (list, '', False),
45 'CXXFLAGS': (list, '', False),
46 'DEFINES': (list, '', False),
47 'LDFLAGS': (list, '', False),
48 'INCLUDES': (list, '', False),
49 'LIBS' : (dict, VALID_TOOLCHAINS, False),
50 'DEPS' : (list, '', False)
51 }, False),
52 'HEADERS': (list, {
53 'FILES': (list, '', True),
54 'DEST': (str, '', True),
55 }, False),
56 'SEARCH': (list, '', False),
57 'POST': (str, '', False),
58 'PRE': (str, '', False),
59 'DEST': (str, ['getting_started', 'examples/api', 'examples/benchmarks',
60 'examples/demo', 'examples/tutorial',
61 'src', 'tests'], True),
62 'NAME': (str, '', False),
63 'DATA': (list, '', False),
64 'TITLE': (str, '', False),
65 'GROUP': (str, '', False),
66 'EXPERIMENTAL': (bool, [True, False], False),
67 'PERMISSIONS': (list, '', False),
68 'SOCKET_PERMISSIONS': (list, '', False),
69 'FILESYSTEM_PERMISSIONS': (list, '', False),
70 'MULTI_PLATFORM': (bool, [True, False], False),
71 'MIN_CHROME_VERSION': (str, '', False),
75 class ValidationError(Exception):
76 pass
79 def ValidateFormat(src, dsc_format):
80 # Verify all required keys are there
81 for key in dsc_format:
82 exp_type, exp_value, required = dsc_format[key]
83 if required and key not in src:
84 raise ValidationError('Missing required key %s.' % key)
86 # For each provided key, verify it's valid
87 for key in src:
88 # Verify the key is known
89 if key not in dsc_format:
90 raise ValidationError('Unexpected key %s.' % key)
92 exp_type, exp_value, required = dsc_format[key]
93 value = src[key]
95 # Verify the value is non-empty if required
96 if required and not value:
97 raise ValidationError('Expected non-empty value for %s.' % key)
99 # If the expected type is a dict, but the provided type is a list
100 # then the list applies to all keys of the dictionary, so we reset
101 # the expected type and value.
102 if exp_type is dict:
103 if type(value) is list:
104 exp_type = list
105 exp_value = ''
107 # Verify the key is of the expected type
108 if exp_type != type(value):
109 raise ValidationError('Key %s expects %s not %s.' % (
110 key, exp_type.__name__.upper(), type(value).__name__.upper()))
112 # If it's a bool, the expected values are always True or False.
113 if exp_type is bool:
114 continue
116 # If it's a string and there are expected values, make sure it matches
117 if exp_type is str:
118 if type(exp_value) is list and exp_value:
119 if value not in exp_value:
120 raise ValidationError("Value '%s' not expected for %s." %
121 (value, key))
122 continue
124 # if it's a list, then we need to validate the values
125 if exp_type is list:
126 # If we expect a dictionary, then call this recursively
127 if type(exp_value) is dict:
128 for val in value:
129 ValidateFormat(val, exp_value)
130 continue
131 # If we expect a list of strings
132 if type(exp_value) is str:
133 for val in value:
134 if type(val) is not str:
135 raise ValidationError('Value %s in %s is not a string.' %
136 (val, key))
137 continue
138 # if we expect a particular string
139 if type(exp_value) is list:
140 for val in value:
141 if val not in exp_value:
142 raise ValidationError('Value %s not expected in %s.' %
143 (val, key))
144 continue
146 # if we are expecting a dict, verify the keys are allowed
147 if exp_type is dict:
148 print "Expecting dict\n"
149 for sub in value:
150 if sub not in exp_value:
151 raise ValidationError('Sub key %s not expected in %s.' %
152 (sub, key))
153 continue
155 # If we got this far, it's an unexpected type
156 raise ValidationError('Unexpected type %s for key %s.' %
157 (str(type(src[key])), key))
160 def LoadProject(filename):
161 with open(filename, 'r') as descfile:
162 try:
163 desc = eval(descfile.read(), {}, {})
164 except Exception as e:
165 raise ValidationError(e)
166 if desc.get('DISABLE', False):
167 return None
168 ValidateFormat(desc, DSC_FORMAT)
169 desc['FILEPATH'] = os.path.abspath(filename)
170 return desc
173 def LoadProjectTreeUnfiltered(srcpath):
174 # Build the tree
175 out = collections.defaultdict(list)
176 for root, _, files in os.walk(srcpath):
177 for filename in files:
178 if fnmatch.fnmatch(filename, '*.dsc'):
179 filepath = os.path.join(root, filename)
180 try:
181 desc = LoadProject(filepath)
182 except ValidationError as e:
183 raise ValidationError("Failed to validate: %s: %s" % (filepath, e))
184 if desc:
185 key = desc['DEST']
186 out[key].append(desc)
187 return out
190 def LoadProjectTree(srcpath, include, exclude=None):
191 out = LoadProjectTreeUnfiltered(srcpath)
192 return FilterTree(out, MakeDefaultFilterFn(include, exclude))
195 def GenerateProjects(tree):
196 for key in tree:
197 for val in tree[key]:
198 yield key, val
201 def FilterTree(tree, filter_fn):
202 out = collections.defaultdict(list)
203 for branch, desc in GenerateProjects(tree):
204 if filter_fn(desc):
205 out[branch].append(desc)
206 return out
209 def MakeDefaultFilterFn(include, exclude):
210 def DefaultFilterFn(desc):
211 matches_include = not include or DescMatchesFilter(desc, include)
212 matches_exclude = exclude and DescMatchesFilter(desc, exclude)
214 # Exclude list overrides include list.
215 if matches_exclude:
216 return False
217 return matches_include
219 return DefaultFilterFn
222 def DescMatchesFilter(desc, filters):
223 for key, expected in filters.iteritems():
224 # For any filtered key which is unspecified, assumed False
225 value = desc.get(key, False)
227 # If we provide an expected list, match at least one
228 if type(expected) != list:
229 expected = set([expected])
230 if type(value) != list:
231 value = set([value])
233 if not set(expected) & set(value):
234 return False
236 # If we fall through, then we matched the filters
237 return True
240 def PrintProjectTree(tree):
241 for key in tree:
242 print key + ':'
243 for val in tree[key]:
244 print '\t' + val['NAME']
247 def main(argv):
248 parser = optparse.OptionParser(usage='%prog [options] <dir>')
249 parser.add_option('-e', '--experimental',
250 help='build experimental examples and libraries', action='store_true')
251 parser.add_option('-t', '--toolchain',
252 help='Build using toolchain. Can be passed more than once.',
253 action='append')
255 options, args = parser.parse_args(argv)
256 filters = {}
258 load_from_dir = '.'
259 if len(args) > 1:
260 parser.error('Expected 0 or 1 args, got %d.' % len(args))
262 if args:
263 load_from_dir = args[0]
265 if options.toolchain:
266 filters['TOOLS'] = options.toolchain
268 if not options.experimental:
269 filters['EXPERIMENTAL'] = False
271 try:
272 tree = LoadProjectTree(load_from_dir, include=filters)
273 except ValidationError as e:
274 sys.stderr.write(str(e) + '\n')
275 return 1
277 PrintProjectTree(tree)
278 return 0
281 if __name__ == '__main__':
282 sys.exit(main(sys.argv[1:]))