Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / native_client_sdk / src / build_tools / parse_dsc.py
blob2fda59b8ef1774d50477e853032ea1f67a03ff95
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 argparse
7 import collections
8 import fnmatch
9 import os
10 import sys
12 VALID_TOOLCHAINS = [
13 'bionic',
14 'clang-newlib',
15 'newlib',
16 'glibc',
17 'pnacl',
18 'win',
19 'linux',
20 'mac',
23 # 'KEY' : ( <TYPE>, [Accepted Values], <Required?>)
24 DSC_FORMAT = {
25 'DISABLE': (bool, [True, False], False),
26 'SEL_LDR': (bool, [True, False], False),
27 # Disable this project from being included in the NaCl packaged app.
28 'DISABLE_PACKAGE': (bool, [True, False], False),
29 # Don't generate the additional files to allow this project to run as a
30 # packaged app (i.e. manifest.json, background.js, etc.).
31 'NO_PACKAGE_FILES': (bool, [True, False], False),
32 'TOOLS' : (list, VALID_TOOLCHAINS, False),
33 'CONFIGS' : (list, ['Debug', 'Release'], False),
34 'PREREQ' : (list, '', False),
35 'TARGETS' : (list, {
36 'NAME': (str, '', True),
37 # main = nexe target
38 # lib = library target
39 # so = shared object target, automatically added to NMF
40 # so-standalone = shared object target, not put into NMF
41 'TYPE': (str, ['main', 'lib', 'static-lib', 'so', 'so-standalone'],
42 True),
43 'SOURCES': (list, '', True),
44 'CFLAGS': (list, '', False),
45 'CFLAGS_GCC': (list, '', False),
46 'CXXFLAGS': (list, '', False),
47 'DEFINES': (list, '', False),
48 'LDFLAGS': (list, '', False),
49 'INCLUDES': (list, '', False),
50 'LIBS' : (dict, VALID_TOOLCHAINS, False),
51 'DEPS' : (list, '', False)
52 }, False),
53 'HEADERS': (list, {
54 'FILES': (list, '', True),
55 'DEST': (str, '', True),
56 }, False),
57 'SEARCH': (list, '', False),
58 'POST': (str, '', False),
59 'PRE': (str, '', False),
60 'DEST': (str, ['getting_started', 'examples/api',
61 'examples/demo', 'examples/tutorial',
62 'src', 'tests'], True),
63 'NAME': (str, '', False),
64 'DATA': (list, '', False),
65 'TITLE': (str, '', False),
66 'GROUP': (str, '', False),
67 'EXPERIMENTAL': (bool, [True, False], False),
68 'PERMISSIONS': (list, '', False),
69 'SOCKET_PERMISSIONS': (list, '', False),
70 'FILESYSTEM_PERMISSIONS': (list, '', False),
71 'MULTI_PLATFORM': (bool, [True, False], False),
72 'MIN_CHROME_VERSION': (str, '', False),
76 class ValidationError(Exception):
77 pass
80 def ValidateFormat(src, dsc_format):
81 # Verify all required keys are there
82 for key in dsc_format:
83 exp_type, exp_value, required = dsc_format[key]
84 if required and key not in src:
85 raise ValidationError('Missing required key %s.' % key)
87 # For each provided key, verify it's valid
88 for key in src:
89 # Verify the key is known
90 if key not in dsc_format:
91 raise ValidationError('Unexpected key %s.' % key)
93 exp_type, exp_value, required = dsc_format[key]
94 value = src[key]
96 # Verify the value is non-empty if required
97 if required and not value:
98 raise ValidationError('Expected non-empty value for %s.' % key)
100 # If the expected type is a dict, but the provided type is a list
101 # then the list applies to all keys of the dictionary, so we reset
102 # the expected type and value.
103 if exp_type is dict:
104 if type(value) is list:
105 exp_type = list
106 exp_value = ''
108 # Verify the key is of the expected type
109 if exp_type != type(value):
110 raise ValidationError('Key %s expects %s not %s.' % (
111 key, exp_type.__name__.upper(), type(value).__name__.upper()))
113 # If it's a bool, the expected values are always True or False.
114 if exp_type is bool:
115 continue
117 # If it's a string and there are expected values, make sure it matches
118 if exp_type is str:
119 if type(exp_value) is list and exp_value:
120 if value not in exp_value:
121 raise ValidationError("Value '%s' not expected for %s." %
122 (value, key))
123 continue
125 # if it's a list, then we need to validate the values
126 if exp_type is list:
127 # If we expect a dictionary, then call this recursively
128 if type(exp_value) is dict:
129 for val in value:
130 ValidateFormat(val, exp_value)
131 continue
132 # If we expect a list of strings
133 if type(exp_value) is str:
134 for val in value:
135 if type(val) is not str:
136 raise ValidationError('Value %s in %s is not a string.' %
137 (val, key))
138 continue
139 # if we expect a particular string
140 if type(exp_value) is list:
141 for val in value:
142 if val not in exp_value:
143 raise ValidationError('Value %s not expected in %s.' %
144 (val, key))
145 continue
147 # if we are expecting a dict, verify the keys are allowed
148 if exp_type is dict:
149 print "Expecting dict\n"
150 for sub in value:
151 if sub not in exp_value:
152 raise ValidationError('Sub key %s not expected in %s.' %
153 (sub, key))
154 continue
156 # If we got this far, it's an unexpected type
157 raise ValidationError('Unexpected type %s for key %s.' %
158 (str(type(src[key])), key))
161 def LoadProject(filename):
162 with open(filename, 'r') as descfile:
163 try:
164 desc = eval(descfile.read(), {}, {})
165 except Exception as e:
166 raise ValidationError(e)
167 if desc.get('DISABLE', False):
168 return None
169 ValidateFormat(desc, DSC_FORMAT)
170 desc['FILEPATH'] = os.path.abspath(filename)
171 desc.setdefault('TOOLS', VALID_TOOLCHAINS)
172 return desc
175 def LoadProjectTreeUnfiltered(srcpath):
176 # Build the tree
177 out = collections.defaultdict(list)
178 for root, _, files in os.walk(srcpath):
179 for filename in files:
180 if fnmatch.fnmatch(filename, '*.dsc'):
181 filepath = os.path.join(root, filename)
182 try:
183 desc = LoadProject(filepath)
184 except ValidationError as e:
185 raise ValidationError("Failed to validate: %s: %s" % (filepath, e))
186 if desc:
187 key = desc['DEST']
188 out[key].append(desc)
189 return out
192 def LoadProjectTree(srcpath, include, exclude=None):
193 out = LoadProjectTreeUnfiltered(srcpath)
194 return FilterTree(out, MakeDefaultFilterFn(include, exclude))
197 def GenerateProjects(tree):
198 for key in tree:
199 for val in tree[key]:
200 yield key, val
203 def FilterTree(tree, filter_fn):
204 out = collections.defaultdict(list)
205 for branch, desc in GenerateProjects(tree):
206 if filter_fn(desc):
207 out[branch].append(desc)
208 return out
211 def MakeDefaultFilterFn(include, exclude):
212 def DefaultFilterFn(desc):
213 matches_include = not include or DescMatchesFilter(desc, include)
214 matches_exclude = exclude and DescMatchesFilter(desc, exclude)
216 # Exclude list overrides include list.
217 if matches_exclude:
218 return False
219 return matches_include
221 return DefaultFilterFn
224 def DescMatchesFilter(desc, filters):
225 for key, expected in filters.iteritems():
226 # For any filtered key which is unspecified, assumed False
227 value = desc.get(key, False)
229 # If we provide an expected list, match at least one
230 if type(expected) not in (list, tuple):
231 expected = set([expected])
232 if type(value) != list:
233 value = set([value])
235 if not set(expected) & set(value):
236 return False
238 # If we fall through, then we matched the filters
239 return True
242 def PrintProjectTree(tree):
243 for key in tree:
244 print key + ':'
245 for val in tree[key]:
246 print '\t' + val['NAME']
249 def main(args):
250 parser = argparse.ArgumentParser(description=__doc__)
251 parser.add_argument('-e', '--experimental',
252 help='build experimental examples and libraries', action='store_true')
253 parser.add_argument('-t', '--toolchain',
254 help='Build using toolchain. Can be passed more than once.',
255 action='append')
256 parser.add_argument('project_root', default='.')
258 options = parser.parse_args(args)
259 filters = {}
261 if options.toolchain:
262 filters['TOOLS'] = options.toolchain
264 if not options.experimental:
265 filters['EXPERIMENTAL'] = False
267 try:
268 tree = LoadProjectTree(options.project_root, include=filters)
269 except ValidationError as e:
270 sys.stderr.write(str(e) + '\n')
271 return 1
273 PrintProjectTree(tree)
274 return 0
277 if __name__ == '__main__':
278 sys.exit(main(sys.argv[1:]))