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 7dynamically from builder_name_schema.json. """ 8 9 10import json 11import os 12 13 14# All of these global variables are filled in by _LoadSchema(). 15 16# The full schema. 17BUILDER_NAME_SCHEMA = None 18 19# Character which separates parts of a builder name. 20BUILDER_NAME_SEP = None 21 22# Builder roles. 23BUILDER_ROLE_BUILD = 'Build' 24BUILDER_ROLE_BUILDSTATS = 'BuildStats' 25BUILDER_ROLE_CANARY = 'Canary' 26BUILDER_ROLE_CODESIZE = 'CodeSize' 27BUILDER_ROLE_HOUSEKEEPER = 'Housekeeper' 28BUILDER_ROLE_INFRA = 'Infra' 29BUILDER_ROLE_PERF = 'Perf' 30BUILDER_ROLE_TEST = 'Test' 31BUILDER_ROLE_FM = 'FM' 32BUILDER_ROLE_UPLOAD = 'Upload' 33BUILDER_ROLES = (BUILDER_ROLE_BUILD, 34 BUILDER_ROLE_BUILDSTATS, 35 BUILDER_ROLE_CANARY, 36 BUILDER_ROLE_CODESIZE, 37 BUILDER_ROLE_HOUSEKEEPER, 38 BUILDER_ROLE_INFRA, 39 BUILDER_ROLE_PERF, 40 BUILDER_ROLE_TEST, 41 BUILDER_ROLE_FM, 42 BUILDER_ROLE_UPLOAD) 43 44 45def _LoadSchema(): 46 """ Load the builder naming schema from the JSON file. """ 47 48 def ToStr(obj): 49 """ Convert all unicode strings in obj to Python strings. """ 50 if isinstance(obj, str): 51 return obj # pragma: nocover 52 elif isinstance(obj, dict): 53 return dict(map(ToStr, obj.items())) 54 elif isinstance(obj, list): 55 return list(map(ToStr, obj)) 56 elif isinstance(obj, tuple): 57 return tuple(map(ToStr, obj)) 58 else: 59 return obj.decode('utf-8') 60 61 builder_name_json_filename = os.path.join( 62 os.path.dirname(__file__), 'builder_name_schema.json') 63 builder_name_schema_json = json.load(open(builder_name_json_filename)) 64 65 global BUILDER_NAME_SCHEMA 66 BUILDER_NAME_SCHEMA = ToStr( 67 builder_name_schema_json['builder_name_schema']) 68 69 global BUILDER_NAME_SEP 70 BUILDER_NAME_SEP = ToStr( 71 builder_name_schema_json['builder_name_sep']) 72 73 # Since the builder roles are dictionary keys, just assert that the global 74 # variables above account for all of them. 75 assert len(BUILDER_ROLES) == len(BUILDER_NAME_SCHEMA) 76 for role in BUILDER_ROLES: 77 assert role in BUILDER_NAME_SCHEMA 78 79 80_LoadSchema() 81 82 83def MakeBuilderName(**parts): 84 for v in parts.values(): 85 if BUILDER_NAME_SEP in v: 86 raise ValueError('Parts cannot contain "%s"' % BUILDER_NAME_SEP) 87 88 rv_parts = [] 89 90 def process(depth, parts): 91 role_key = 'role' 92 if depth != 0: 93 role_key = 'sub-role-%d' % depth 94 role = parts.get(role_key) 95 if not role: 96 raise ValueError('Invalid parts; missing key %s' % role_key) 97 s = BUILDER_NAME_SCHEMA.get(role) 98 if not s: 99 raise ValueError('Invalid parts; unknown role %s' % role) 100 rv_parts.append(role) 101 del parts[role_key] 102 103 for key in s.get('keys', []): 104 value = parts.get(key) 105 if not value: 106 raise ValueError('Invalid parts; missing %s' % key) 107 rv_parts.append(value) 108 del parts[key] 109 110 recurse_roles = s.get('recurse_roles', []) 111 if len(recurse_roles) > 0: 112 sub_role_key = 'sub-role-%d' % (depth+1) 113 sub_role = parts.get(sub_role_key) 114 if not sub_role: 115 raise ValueError('Invalid parts; missing %s' % sub_role_key) 116 117 found = False 118 for recurse_role in recurse_roles: 119 if recurse_role == sub_role: 120 found = True 121 parts = process(depth+1, parts) 122 break 123 if not found: 124 raise ValueError('Invalid parts; unknown sub-role %s' % sub_role) 125 126 for key in s.get('optional_keys', []): 127 if parts.get(key): 128 rv_parts.append(parts[key]) 129 del parts[key] 130 131 if len(parts) > 0: 132 raise ValueError('Invalid parts; too many parts: %s' % parts) 133 134 return parts 135 136 process(0, parts) 137 138 return BUILDER_NAME_SEP.join(rv_parts) 139 140 141def DictForBuilderName(builder_name): 142 """Makes a dictionary containing details about the builder from its name.""" 143 split = builder_name.split(BUILDER_NAME_SEP) 144 145 def pop_front(items): 146 try: 147 return items.pop(0), items 148 except: 149 raise ValueError( 150 'Invalid builder name: %s (not enough parts)' % builder_name) 151 152 result = {} 153 154 def _parse(depth, role, parts): 155 schema = BUILDER_NAME_SCHEMA.get(role) 156 if not schema: 157 raise ValueError('Invalid builder name: %s' % builder_name) 158 if depth == 0: 159 result['role'] = str(role) 160 else: 161 result['sub-role-%d' % depth] = str(role) 162 for key in schema.get('keys', []): 163 value, parts = pop_front(parts) 164 result[key] = str(value) 165 for sub_role in schema.get('recurse_roles', []): 166 if len(parts) > 0 and sub_role == parts[0]: 167 parts = _parse(depth+1, parts[0], parts[1:]) 168 for key in schema.get('optional_keys', []): 169 if parts: 170 value, parts = pop_front(parts) 171 result[key] = str(value) 172 if parts: 173 raise ValueError('Invalid builder name: %s' % builder_name) 174 return parts 175 176 _parse(0, split[0], split[1:]) 177 178 return result 179