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_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
195