parse_dsc.py revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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. 5 6import collections 7import fnmatch 8import optparse 9import os 10import sys 11 12VALID_TOOLCHAINS = [ 13 'bionic', 14 'newlib', 15 'glibc', 16 'pnacl', 17 'win', 18 'linux', 19 'mac', 20] 21 22# 'KEY' : ( <TYPE>, [Accepted Values], <Required?>) 23DSC_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', 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 'MULTI_PLATFORM': (bool, [True, False], False), 70} 71 72 73class ValidationError(Exception): 74 pass 75 76 77def ValidateFormat(src, dsc_format): 78 # Verify all required keys are there 79 for key in dsc_format: 80 exp_type, exp_value, required = dsc_format[key] 81 if required and key not in src: 82 raise ValidationError('Missing required key %s.' % key) 83 84 # For each provided key, verify it's valid 85 for key in src: 86 # Verify the key is known 87 if key not in dsc_format: 88 raise ValidationError('Unexpected key %s.' % key) 89 90 exp_type, exp_value, required = dsc_format[key] 91 value = src[key] 92 93 # Verify the value is non-empty if required 94 if required and not value: 95 raise ValidationError('Expected non-empty value for %s.' % key) 96 97 # If the expected type is a dict, but the provided type is a list 98 # then the list applies to all keys of the dictionary, so we reset 99 # the expected type and value. 100 if exp_type is dict: 101 if type(value) is list: 102 exp_type = list 103 exp_value = '' 104 105 # Verify the key is of the expected type 106 if exp_type != type(value): 107 raise ValidationError('Key %s expects %s not %s.' % ( 108 key, exp_type.__name__.upper(), type(value).__name__.upper())) 109 110 # If it's a bool, the expected values are always True or False. 111 if exp_type is bool: 112 continue 113 114 # If it's a string and there are expected values, make sure it matches 115 if exp_type is str: 116 if type(exp_value) is list and exp_value: 117 if value not in exp_value: 118 raise ValidationError("Value '%s' not expected for %s." % 119 (value, key)) 120 continue 121 122 # if it's a list, then we need to validate the values 123 if exp_type is list: 124 # If we expect a dictionary, then call this recursively 125 if type(exp_value) is dict: 126 for val in value: 127 ValidateFormat(val, exp_value) 128 continue 129 # If we expect a list of strings 130 if type(exp_value) is str: 131 for val in value: 132 if type(val) is not str: 133 raise ValidationError('Value %s in %s is not a string.' % 134 (val, key)) 135 continue 136 # if we expect a particular string 137 if type(exp_value) is list: 138 for val in value: 139 if val not in exp_value: 140 raise ValidationError('Value %s not expected in %s.' % 141 (val, key)) 142 continue 143 144 # if we are expecting a dict, verify the keys are allowed 145 if exp_type is dict: 146 print "Expecting dict\n" 147 for sub in value: 148 if sub not in exp_value: 149 raise ValidationError('Sub key %s not expected in %s.' % 150 (sub, key)) 151 continue 152 153 # If we got this far, it's an unexpected type 154 raise ValidationError('Unexpected type %s for key %s.' % 155 (str(type(src[key])), key)) 156 157 158def LoadProject(filename): 159 with open(filename, 'r') as descfile: 160 try: 161 desc = eval(descfile.read(), {}, {}) 162 except Exception as e: 163 raise ValidationError(e) 164 if desc.get('DISABLE', False): 165 return None 166 ValidateFormat(desc, DSC_FORMAT) 167 desc['FILEPATH'] = os.path.abspath(filename) 168 return desc 169 170 171def LoadProjectTreeUnfiltered(srcpath): 172 # Build the tree 173 out = collections.defaultdict(list) 174 for root, _, files in os.walk(srcpath): 175 for filename in files: 176 if fnmatch.fnmatch(filename, '*.dsc'): 177 filepath = os.path.join(root, filename) 178 try: 179 desc = LoadProject(filepath) 180 except ValidationError as e: 181 raise ValidationError("Failed to validate: %s: %s" % (filepath, e)) 182 if desc: 183 key = desc['DEST'] 184 out[key].append(desc) 185 return out 186 187 188def LoadProjectTree(srcpath, include, exclude=None): 189 out = LoadProjectTreeUnfiltered(srcpath) 190 return FilterTree(out, MakeDefaultFilterFn(include, exclude)) 191 192 193def GenerateProjects(tree): 194 for key in tree: 195 for val in tree[key]: 196 yield key, val 197 198 199def FilterTree(tree, filter_fn): 200 out = collections.defaultdict(list) 201 for branch, desc in GenerateProjects(tree): 202 if filter_fn(desc): 203 out[branch].append(desc) 204 return out 205 206 207def MakeDefaultFilterFn(include, exclude): 208 def DefaultFilterFn(desc): 209 matches_include = not include or DescMatchesFilter(desc, include) 210 matches_exclude = exclude and DescMatchesFilter(desc, exclude) 211 212 # Exclude list overrides include list. 213 if matches_exclude: 214 return False 215 return matches_include 216 217 return DefaultFilterFn 218 219 220def DescMatchesFilter(desc, filters): 221 for key, expected in filters.iteritems(): 222 # For any filtered key which is unspecified, assumed False 223 value = desc.get(key, False) 224 225 # If we provide an expected list, match at least one 226 if type(expected) != list: 227 expected = set([expected]) 228 if type(value) != list: 229 value = set([value]) 230 231 if not set(expected) & set(value): 232 return False 233 234 # If we fall through, then we matched the filters 235 return True 236 237 238def PrintProjectTree(tree): 239 for key in tree: 240 print key + ':' 241 for val in tree[key]: 242 print '\t' + val['NAME'] 243 244 245def main(argv): 246 parser = optparse.OptionParser(usage='%prog [options] <dir>') 247 parser.add_option('-e', '--experimental', 248 help='build experimental examples and libraries', action='store_true') 249 parser.add_option('-t', '--toolchain', 250 help='Build using toolchain. Can be passed more than once.', 251 action='append') 252 253 options, args = parser.parse_args(argv[1:]) 254 filters = {} 255 256 load_from_dir = '.' 257 if len(args) > 1: 258 parser.error('Expected 0 or 1 args, got %d.' % len(args)) 259 260 if args: 261 load_from_dir = args[0] 262 263 if options.toolchain: 264 filters['TOOLS'] = options.toolchain 265 266 if not options.experimental: 267 filters['EXPERIMENTAL'] = False 268 269 try: 270 tree = LoadProjectTree(load_from_dir, include=filters) 271 except ValidationError as e: 272 sys.stderr.write(str(e) + '\n') 273 return 1 274 275 PrintProjectTree(tree) 276 return 0 277 278 279if __name__ == '__main__': 280 sys.exit(main(sys.argv)) 281