| # Copyright 2014 The Chromium Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 |  | 
 | """ Utilities for dealing with builder names. This module obtains its attributes | 
 | dynamically from builder_name_schema.json. """ | 
 |  | 
 |  | 
 | import json | 
 | import os | 
 |  | 
 |  | 
 | # All of these global variables are filled in by _LoadSchema(). | 
 |  | 
 | # The full schema. | 
 | BUILDER_NAME_SCHEMA = None | 
 |  | 
 | # Character which separates parts of a builder name. | 
 | BUILDER_NAME_SEP = None | 
 |  | 
 | # Builder roles. | 
 | BUILDER_ROLE_CANARY = 'Canary' | 
 | BUILDER_ROLE_BUILD = 'Build' | 
 | BUILDER_ROLE_HOUSEKEEPER = 'Housekeeper' | 
 | BUILDER_ROLE_PERF = 'Perf' | 
 | BUILDER_ROLE_TEST = 'Test' | 
 | BUILDER_ROLES = (BUILDER_ROLE_CANARY, | 
 |                  BUILDER_ROLE_BUILD, | 
 |                  BUILDER_ROLE_HOUSEKEEPER, | 
 |                  BUILDER_ROLE_PERF, | 
 |                  BUILDER_ROLE_TEST) | 
 |  | 
 | # Suffix which distinguishes trybots from normal bots. | 
 | TRYBOT_NAME_SUFFIX = None | 
 |  | 
 |  | 
 | def _LoadSchema(): | 
 |   """ Load the builder naming schema from the JSON file. """ | 
 |  | 
 |   def _UnicodeToStr(obj): | 
 |     """ Convert all unicode strings in obj to Python strings. """ | 
 |     if isinstance(obj, unicode): | 
 |       return str(obj) | 
 |     elif isinstance(obj, dict): | 
 |       return dict(map(_UnicodeToStr, obj.iteritems())) | 
 |     elif isinstance(obj, list): | 
 |       return list(map(_UnicodeToStr, obj)) | 
 |     elif isinstance(obj, tuple): | 
 |       return tuple(map(_UnicodeToStr, obj)) | 
 |     else: | 
 |       return obj | 
 |  | 
 |   builder_name_json_filename = os.path.join( | 
 |       os.path.dirname(__file__), 'builder_name_schema.json') | 
 |   builder_name_schema_json = json.load(open(builder_name_json_filename)) | 
 |  | 
 |   global BUILDER_NAME_SCHEMA | 
 |   BUILDER_NAME_SCHEMA = _UnicodeToStr( | 
 |       builder_name_schema_json['builder_name_schema']) | 
 |  | 
 |   global BUILDER_NAME_SEP | 
 |   BUILDER_NAME_SEP = _UnicodeToStr( | 
 |       builder_name_schema_json['builder_name_sep']) | 
 |  | 
 |   global TRYBOT_NAME_SUFFIX | 
 |   TRYBOT_NAME_SUFFIX = _UnicodeToStr( | 
 |       builder_name_schema_json['trybot_name_suffix']) | 
 |  | 
 |   # Since the builder roles are dictionary keys, just assert that the global | 
 |   # variables above account for all of them. | 
 |   assert len(BUILDER_ROLES) == len(BUILDER_NAME_SCHEMA) | 
 |   for role in BUILDER_ROLES: | 
 |     assert role in BUILDER_NAME_SCHEMA | 
 |  | 
 |  | 
 | _LoadSchema() | 
 |  | 
 |  | 
 | def MakeBuilderName(role, extra_config=None, is_trybot=False, **kwargs): | 
 |   schema = BUILDER_NAME_SCHEMA.get(role) | 
 |   if not schema: | 
 |     raise ValueError('%s is not a recognized role.' % role) | 
 |   for k, v in kwargs.iteritems(): | 
 |     if BUILDER_NAME_SEP in v: | 
 |       raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP, v)) | 
 |     if not k in schema: | 
 |       raise ValueError('Schema does not contain "%s": %s' %(k, schema)) | 
 |   if extra_config and BUILDER_NAME_SEP in extra_config: | 
 |     raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP, | 
 |                                                 extra_config)) | 
 |   name_parts = [role] | 
 |   name_parts.extend([kwargs[attribute] for attribute in schema]) | 
 |   if extra_config: | 
 |     name_parts.append(extra_config) | 
 |   if is_trybot: | 
 |     name_parts.append(TRYBOT_NAME_SUFFIX) | 
 |   return BUILDER_NAME_SEP.join(name_parts) | 
 |  | 
 |  | 
 | def BuilderNameFromObject(obj, is_trybot=False): | 
 |   """Create a builder name based on properties of the given object. | 
 |  | 
 |   Args: | 
 |       obj: the object from which to create the builder name. The object must | 
 |           have as properties: | 
 |           - A valid builder role, as defined in the JSON file | 
 |           - All properties listed in the JSON file for that role | 
 |           - Optionally, an extra_config property | 
 |       is_trybot: bool; whether or not the builder is a trybot. | 
 |   Returns: | 
 |       string which combines the properties of the given object into a valid | 
 |           builder name. | 
 |   """ | 
 |   schema = BUILDER_NAME_SCHEMA.get(obj.role) | 
 |   if not schema: | 
 |     raise ValueError('%s is not a recognized role.' % obj.role) | 
 |   name_parts = [obj.role] | 
 |   for attr_name in schema: | 
 |     attr_val = getattr(obj, attr_name) | 
 |     name_parts.append(attr_val) | 
 |   extra_config = getattr(obj, 'extra_config', None) | 
 |   if extra_config: | 
 |     name_parts.append(extra_config) | 
 |   if is_trybot: | 
 |     name_parts.append(TRYBOT_NAME_SUFFIX) | 
 |   return BUILDER_NAME_SEP.join(name_parts) | 
 |  | 
 |  | 
 | def IsTrybot(builder_name): | 
 |   """ Returns true if builder_name refers to a trybot (as opposed to a | 
 |   waterfall bot). """ | 
 |   return builder_name.endswith(TRYBOT_NAME_SUFFIX) | 
 |  | 
 |  | 
 | def GetWaterfallBot(builder_name): | 
 |   """Returns the name of the waterfall bot for this builder. If it is not a | 
 |   trybot, builder_name is returned unchanged. If it is a trybot the name is | 
 |   returned without the trybot suffix.""" | 
 |   if not IsTrybot(builder_name): | 
 |     return builder_name | 
 |   return _WithoutSuffix(builder_name, BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX) | 
 |  | 
 |  | 
 | def TrybotName(builder_name): | 
 |   """Returns the name of the trybot clone of this builder. | 
 |  | 
 |   If the given builder is a trybot, the name is returned unchanged. If not, the | 
 |   TRYBOT_NAME_SUFFIX is appended. | 
 |   """ | 
 |   if builder_name.endswith(TRYBOT_NAME_SUFFIX): | 
 |     return builder_name | 
 |   return builder_name + BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX | 
 |  | 
 |  | 
 | def _WithoutSuffix(string, suffix): | 
 |   """ Returns a copy of string 'string', but with suffix 'suffix' removed. | 
 |   Raises ValueError if string does not end with suffix. """ | 
 |   if not string.endswith(suffix): | 
 |     raise ValueError('_WithoutSuffix: string %s does not end with suffix %s' % ( | 
 |         string, suffix)) | 
 |   return string[:-len(suffix)] | 
 |  | 
 |  | 
 | def DictForBuilderName(builder_name): | 
 |   """Makes a dictionary containing details about the builder from its name.""" | 
 |   split_name = builder_name.split(BUILDER_NAME_SEP) | 
 |  | 
 |   def pop_front(): | 
 |     try: | 
 |       return split_name.pop(0) | 
 |     except: | 
 |       raise ValueError('Invalid builder name: %s' % builder_name) | 
 |  | 
 |   result = {'is_trybot': False} | 
 |  | 
 |   if split_name[-1] == TRYBOT_NAME_SUFFIX: | 
 |     result['is_trybot'] = True | 
 |     split_name.pop() | 
 |  | 
 |   if split_name[0] in BUILDER_NAME_SCHEMA.keys(): | 
 |     key_list = BUILDER_NAME_SCHEMA[split_name[0]] | 
 |     result['role'] = pop_front() | 
 |     for key in key_list: | 
 |       result[key] = pop_front() | 
 |     if split_name: | 
 |       result['extra_config'] = pop_front() | 
 |     if split_name: | 
 |       raise ValueError('Invalid builder name: %s' % builder_name) | 
 |   else: | 
 |     raise ValueError('Invalid builder name: %s' % builder_name) | 
 |   return result | 
 |  | 
 |  |