Home | History | Annotate | Download | only in build_tools
      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