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