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