Home | History | Annotate | Download | only in tools
      1 # Copyright 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 
      6 """ Utilities for dealing with builder names. This module obtains its attributes
      7 dynamically from builder_name_schema.json. """
      8 
      9 
     10 import json
     11 import os
     12 
     13 
     14 # All of these global variables are filled in by _LoadSchema().
     15 
     16 # The full schema.
     17 BUILDER_NAME_SCHEMA = None
     18 
     19 # Character which separates parts of a builder name.
     20 BUILDER_NAME_SEP = None
     21 
     22 # Builder roles.
     23 BUILDER_ROLE_CANARY = 'Canary'
     24 BUILDER_ROLE_BUILD = 'Build'
     25 BUILDER_ROLE_HOUSEKEEPER = 'Housekeeper'
     26 BUILDER_ROLE_PERF = 'Perf'
     27 BUILDER_ROLE_TEST = 'Test'
     28 BUILDER_ROLES = (BUILDER_ROLE_CANARY,
     29                  BUILDER_ROLE_BUILD,
     30                  BUILDER_ROLE_HOUSEKEEPER,
     31                  BUILDER_ROLE_PERF,
     32                  BUILDER_ROLE_TEST)
     33 
     34 # Suffix which distinguishes trybots from normal bots.
     35 TRYBOT_NAME_SUFFIX = None
     36 
     37 
     38 def _LoadSchema():
     39   """ Load the builder naming schema from the JSON file. """
     40 
     41   def _UnicodeToStr(obj):
     42     """ Convert all unicode strings in obj to Python strings. """
     43     if isinstance(obj, unicode):
     44       return str(obj)
     45     elif isinstance(obj, dict):
     46       return dict(map(_UnicodeToStr, obj.iteritems()))
     47     elif isinstance(obj, list):
     48       return list(map(_UnicodeToStr, obj))
     49     elif isinstance(obj, tuple):
     50       return tuple(map(_UnicodeToStr, obj))
     51     else:
     52       return obj
     53 
     54   builder_name_json_filename = os.path.join(
     55       os.path.dirname(__file__), 'builder_name_schema.json')
     56   builder_name_schema_json = json.load(open(builder_name_json_filename))
     57 
     58   global BUILDER_NAME_SCHEMA
     59   BUILDER_NAME_SCHEMA = _UnicodeToStr(
     60       builder_name_schema_json['builder_name_schema'])
     61 
     62   global BUILDER_NAME_SEP
     63   BUILDER_NAME_SEP = _UnicodeToStr(
     64       builder_name_schema_json['builder_name_sep'])
     65 
     66   global TRYBOT_NAME_SUFFIX
     67   TRYBOT_NAME_SUFFIX = _UnicodeToStr(
     68       builder_name_schema_json['trybot_name_suffix'])
     69 
     70   # Since the builder roles are dictionary keys, just assert that the global
     71   # variables above account for all of them.
     72   assert len(BUILDER_ROLES) == len(BUILDER_NAME_SCHEMA)
     73   for role in BUILDER_ROLES:
     74     assert role in BUILDER_NAME_SCHEMA
     75 
     76 
     77 _LoadSchema()
     78 
     79 
     80 def MakeBuilderName(role, extra_config=None, is_trybot=False, **kwargs):
     81   schema = BUILDER_NAME_SCHEMA.get(role)
     82   if not schema:
     83     raise ValueError('%s is not a recognized role.' % role)
     84   for k, v in kwargs.iteritems():
     85     if BUILDER_NAME_SEP in v:
     86       raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP, v))
     87     if not k in schema:
     88       raise ValueError('Schema does not contain "%s": %s' %(k, schema))
     89   if extra_config and BUILDER_NAME_SEP in extra_config:
     90     raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP,
     91                                                 extra_config))
     92   name_parts = [role]
     93   name_parts.extend([kwargs[attribute] for attribute in schema])
     94   if extra_config:
     95     name_parts.append(extra_config)
     96   if is_trybot:
     97     name_parts.append(TRYBOT_NAME_SUFFIX)
     98   return BUILDER_NAME_SEP.join(name_parts)
     99 
    100 
    101 def BuilderNameFromObject(obj, is_trybot=False):
    102   """Create a builder name based on properties of the given object.
    103 
    104   Args:
    105       obj: the object from which to create the builder name. The object must
    106           have as properties:
    107           - A valid builder role, as defined in the JSON file
    108           - All properties listed in the JSON file for that role
    109           - Optionally, an extra_config property
    110       is_trybot: bool; whether or not the builder is a trybot.
    111   Returns:
    112       string which combines the properties of the given object into a valid
    113           builder name.
    114   """
    115   schema = BUILDER_NAME_SCHEMA.get(obj.role)
    116   if not schema:
    117     raise ValueError('%s is not a recognized role.' % obj.role)
    118   name_parts = [obj.role]
    119   for attr_name in schema:
    120     attr_val = getattr(obj, attr_name)
    121     name_parts.append(attr_val)
    122   extra_config = getattr(obj, 'extra_config', None)
    123   if extra_config:
    124     name_parts.append(extra_config)
    125   if is_trybot:
    126     name_parts.append(TRYBOT_NAME_SUFFIX)
    127   return BUILDER_NAME_SEP.join(name_parts)
    128 
    129 
    130 def IsTrybot(builder_name):
    131   """ Returns true if builder_name refers to a trybot (as opposed to a
    132   waterfall bot). """
    133   return builder_name.endswith(TRYBOT_NAME_SUFFIX)
    134 
    135 
    136 def GetWaterfallBot(builder_name):
    137   """Returns the name of the waterfall bot for this builder. If it is not a
    138   trybot, builder_name is returned unchanged. If it is a trybot the name is
    139   returned without the trybot suffix."""
    140   if not IsTrybot(builder_name):
    141     return builder_name
    142   return _WithoutSuffix(builder_name, BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX)
    143 
    144 
    145 def TrybotName(builder_name):
    146   """Returns the name of the trybot clone of this builder.
    147 
    148   If the given builder is a trybot, the name is returned unchanged. If not, the
    149   TRYBOT_NAME_SUFFIX is appended.
    150   """
    151   if builder_name.endswith(TRYBOT_NAME_SUFFIX):
    152     return builder_name
    153   return builder_name + BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX
    154 
    155 
    156 def _WithoutSuffix(string, suffix):
    157   """ Returns a copy of string 'string', but with suffix 'suffix' removed.
    158   Raises ValueError if string does not end with suffix. """
    159   if not string.endswith(suffix):
    160     raise ValueError('_WithoutSuffix: string %s does not end with suffix %s' % (
    161         string, suffix))
    162   return string[:-len(suffix)]
    163 
    164 
    165 def DictForBuilderName(builder_name):
    166   """Makes a dictionary containing details about the builder from its name."""
    167   split_name = builder_name.split(BUILDER_NAME_SEP)
    168 
    169   def pop_front():
    170     try:
    171       return split_name.pop(0)
    172     except:
    173       raise ValueError('Invalid builder name: %s' % builder_name)
    174 
    175   result = {'is_trybot': False}
    176 
    177   if split_name[-1] == TRYBOT_NAME_SUFFIX:
    178     result['is_trybot'] = True
    179     split_name.pop()
    180 
    181   if split_name[0] in BUILDER_NAME_SCHEMA.keys():
    182     key_list = BUILDER_NAME_SCHEMA[split_name[0]]
    183     result['role'] = pop_front()
    184     for key in key_list:
    185       result[key] = pop_front()
    186     if split_name:
    187       result['extra_config'] = pop_front()
    188     if split_name:
    189       raise ValueError('Invalid builder name: %s' % builder_name)
    190   else:
    191     raise ValueError('Invalid builder name: %s' % builder_name)
    192   return result
    193 
    194 
    195