blob: 990aa6447cba9f50c33b0682f80b56847219204c [file] [log] [blame]
borenetb76c1f72015-07-29 07:38:49 -07001# 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_CANARY = 'Canary'
24BUILDER_ROLE_BUILD = 'Build'
25BUILDER_ROLE_HOUSEKEEPER = 'Housekeeper'
26BUILDER_ROLE_PERF = 'Perf'
27BUILDER_ROLE_TEST = 'Test'
28BUILDER_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.
35TRYBOT_NAME_SUFFIX = None
36
37
38def _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
80def 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
101def 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
130def 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
136def 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
145def 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
156def _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
165def 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