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