Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(28)

Side by Side Diff: scripts/slave/recipe_config.py

Issue 187203005: Minor cleanup of some recipe framework code. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Rietveld >_< Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « scripts/slave/recipe_api.py ('k') | scripts/slave/recipe_loader.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2013 The Chromium Authors. All rights reserved. 1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Recipe Configuration Meta DSL. 5 """Recipe Configuration Meta DSL.
6 6
7 This module contains, essentially, a DSL for writing composable configurations. 7 This module contains, essentially, a DSL for writing composable configurations.
8 You start by defining a schema which describes how your configuration blobs will 8 You start by defining a schema which describes how your configuration blobs will
9 be structured, and what data they can contain. For example: 9 be structured, and what data they can contain. For example:
10 10
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
81 81
82 Using this system should allow you to create rich, composible, 82 Using this system should allow you to create rich, composible,
83 modular configurations. See the documentation on config_item_context and the 83 modular configurations. See the documentation on config_item_context and the
84 BaseConfig derivatives for more info. 84 BaseConfig derivatives for more info.
85 """ 85 """
86 86
87 import collections 87 import collections
88 import functools 88 import functools
89 import types 89 import types
90 90
91
91 class BadConf(Exception): 92 class BadConf(Exception):
92 pass 93 pass
93 94
94 def config_item_context(CONFIG_SCHEMA, VAR_TEST_MAP, TEST_NAME_FORMAT,
95 TEST_FILE_FORMAT=None):
96 """Create a configuration context.
97 95
98 Args: 96 class ConfigContext(object):
99 CONFIG_SCHEMA: This is a function which can take a minimum of zero arguments 97 """A configuration context for a recipe module.
100 and returns an instance of BaseConfig. This BaseConfig
101 defines the schema for all configuration objects manipulated
102 in this context.
103 VAR_TEST_MAP: A dict mapping arg_name to an iterable of values. This
104 provides the test harness with sufficient information to
105 generate all possible permutations of inputs for the
106 CONFIG_SCHEMA function.
107 TEST_NAME_FORMAT: A string format (or function) for naming tests and test
108 expectation files. It will be formatted/called with a
109 dictionary of arg_name to value (using arg_names and
110 values generated from VAR_TEST_MAP)
111 TEST_FILE_FORMAT: Similar to TEST_NAME_FORMAT, but for test files. Defaults
112 to TEST_NAME_FORMAT.
113 98
114 Returns a config_ctx decorator for this context. 99 Holds configuration schema and also acts as a config_ctx decorator.
100 A recipe module can define at most one such context.
115 """ 101 """
116 102
117 def config_ctx(group=None, includes=None, deps=None, no_test=False, 103 def __init__(self, CONFIG_SCHEMA, VAR_TEST_MAP, TEST_NAME_FORMAT):
118 is_root=False, config_vars=None): 104 self.CONFIG_ITEMS = {}
105 self.MUTEX_GROUPS = {}
106 self.CONFIG_SCHEMA = CONFIG_SCHEMA
107 self.ROOT_CONFIG_ITEM = None
108 self.VAR_TEST_MAP = VAR_TEST_MAP
109 self.TEST_NAME_FORMAT = create_formatter(TEST_NAME_FORMAT)
110
111 def __call__(self, group=None, includes=None, deps=None, no_test=False,
112 is_root=False, config_vars=None):
119 """ 113 """
120 A decorator for functions which modify a given schema of configs. 114 A decorator for functions which modify a given schema of configs.
121 Examples continue using the schema and config_items defined in the module 115 Examples continue using the schema and config_items defined in the module
122 docstring. 116 docstring.
123 117
124 This decorator provides a series of related functions: 118 This decorator provides a series of related functions:
125 * Any function decorated with this will be registered into this config 119 * Any function decorated with this will be registered into this config
126 context by __name__. This enables some of the other following features 120 context by __name__. This enables some of the other following features
127 to work. 121 to work.
128 * Alters the signature of the function so that it can recieve an extra 122 * Alters the signature of the function so that it can recieve an extra
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
173 ever be one root item. 167 ever be one root item.
174 168
175 Additionally, the test harness uses the root item to probe for invalid 169 Additionally, the test harness uses the root item to probe for invalid
176 configuration combinations by running the root item first (if there is 170 configuration combinations by running the root item first (if there is
177 one), and skipping the configuration combination if the root config 171 one), and skipping the configuration combination if the root config
178 item throws BadConf. 172 item throws BadConf.
179 173
180 config_vars(dict) - A dictionary mapping of { CONFIG_VAR: <value> }. This 174 config_vars(dict) - A dictionary mapping of { CONFIG_VAR: <value> }. This
181 sets the input contidions for the CONFIG_SCHEMA. 175 sets the input contidions for the CONFIG_SCHEMA.
182 176
183 Returns a new decorated version of this function (see inner()). 177 Returns a new decorated version of this function (see inner()).
184 """ 178 """
185 def decorator(f): 179 def decorator(f):
186 name = f.__name__ 180 name = f.__name__
187 @functools.wraps(f) 181 @functools.wraps(f)
188 def inner(config=None, final=True, optional=False, **kwargs): 182 def inner(config=None, final=True, optional=False, **kwargs):
189 """This is the function which is returned from the config_ctx 183 """This is the function which is returned from the config_ctx
190 decorator. 184 decorator.
191 185
192 It applies all of the logic mentioned in the config_ctx docstring 186 It applies all of the logic mentioned in the config_ctx docstring
193 above, and alters the function signature slightly. 187 above, and alters the function signature slightly.
(...skipping 17 matching lines...) Expand all
211 can implement the config items with less error checking, since you 205 can implement the config items with less error checking, since you
212 know that the item may only be applied once. For example, if your 206 know that the item may only be applied once. For example, if your
213 item appends something to a list, but is called with final=False, 207 item appends something to a list, but is called with final=False,
214 you'll have to make sure that you don't append the item twice, etc. 208 you'll have to make sure that you don't append the item twice, etc.
215 209
216 **kwargs - Passed through to the decorated function without harm. 210 **kwargs - Passed through to the decorated function without harm.
217 211
218 Returns config and ignores the return value of the decorated function. 212 Returns config and ignores the return value of the decorated function.
219 """ 213 """
220 if config is None: 214 if config is None:
221 config = config_ctx.CONFIG_SCHEMA() 215 config = self.CONFIG_SCHEMA()
222 assert isinstance(config, ConfigGroup) 216 assert isinstance(config, ConfigGroup)
223 inclusions = config._inclusions # pylint: disable=W0212 217 inclusions = config._inclusions # pylint: disable=W0212
224 218
225 # inner.IS_ROOT will be True or False at the time of invocation. 219 # inner.IS_ROOT will be True or False at the time of invocation.
226 if (config_ctx.ROOT_CONFIG_ITEM and not inner.IS_ROOT and 220 if (self.ROOT_CONFIG_ITEM and not inner.IS_ROOT and
227 config_ctx.ROOT_CONFIG_ITEM.__name__ not in inclusions): 221 self.ROOT_CONFIG_ITEM.__name__ not in inclusions):
228 config_ctx.ROOT_CONFIG_ITEM(config) 222 self.ROOT_CONFIG_ITEM(config)
229 223
230 if name in inclusions: 224 if name in inclusions:
231 if optional: 225 if optional:
232 return config 226 return config
233 raise BadConf('config_ctx "%s" is already in this config "%s"' % 227 raise BadConf('config_ctx "%s" is already in this config "%s"' %
234 (name, config.as_jsonish(include_hidden=True))) 228 (name, config.as_jsonish(include_hidden=True)))
235 if final: 229 if final:
236 inclusions.add(name) 230 inclusions.add(name)
237 231
238 for include in (includes or []): 232 for include in (includes or []):
239 if include in inclusions: 233 if include in inclusions:
240 continue 234 continue
241 try: 235 try:
242 config_ctx.CONFIG_ITEMS[include](config) 236 self.CONFIG_ITEMS[include](config)
243 except BadConf, e: 237 except BadConf as e:
244 raise BadConf('config "%s" includes "%s", but [%s]' % 238 raise BadConf('config "%s" includes "%s", but [%s]' %
245 (name, include, e)) 239 (name, include, e))
246 240
247 # deps are a list of group names. All groups must be represented 241 # deps are a list of group names. All groups must be represented
248 # in config already. 242 # in config already.
249 for dep_group in (deps or []): 243 for dep_group in (deps or []):
250 if not (inclusions & config_ctx.MUTEX_GROUPS[dep_group]): 244 if not (inclusions & self.MUTEX_GROUPS[dep_group]):
251 raise BadConf('dep group "%s" is unfulfilled for "%s"' % 245 raise BadConf('dep group "%s" is unfulfilled for "%s"' %
252 (dep_group, name)) 246 (dep_group, name))
253 247
254 if group: 248 if group:
255 overlap = inclusions & config_ctx.MUTEX_GROUPS[group] 249 overlap = inclusions & self.MUTEX_GROUPS[group]
256 overlap.discard(name) 250 overlap.discard(name)
257 if overlap: 251 if overlap:
258 raise BadConf('"%s" is a member of group "%s", but %s already ran' % 252 raise BadConf('"%s" is a member of group "%s", but %s already ran' %
259 (name, group, tuple(overlap))) 253 (name, group, tuple(overlap)))
260 254
261 ret = f(config, **kwargs) 255 ret = f(config, **kwargs)
262 assert ret is None, 'Got return value (%s) from "%s"?' % (ret, name) 256 assert ret is None, 'Got return value (%s) from "%s"?' % (ret, name)
263 257
264 return config 258 return config
265 259
266 def default_config_vars(): 260 def default_config_vars():
267 ret = {} 261 ret = {}
268 for include in (includes or []): 262 for include in (includes or []):
269 item = config_ctx.CONFIG_ITEMS[include] 263 item = self.CONFIG_ITEMS[include]
270 ret.update(item.DEFAULT_CONFIG_VARS()) 264 ret.update(item.DEFAULT_CONFIG_VARS())
271 if config_vars: 265 if config_vars:
272 ret.update(config_vars) 266 ret.update(config_vars)
273 return ret 267 return ret
274 inner.DEFAULT_CONFIG_VARS = default_config_vars 268 inner.DEFAULT_CONFIG_VARS = default_config_vars
275 269
276 assert name not in config_ctx.CONFIG_ITEMS 270 assert name not in self.CONFIG_ITEMS
277 config_ctx.CONFIG_ITEMS[name] = inner 271 self.CONFIG_ITEMS[name] = inner
278 if group: 272 if group:
279 config_ctx.MUTEX_GROUPS.setdefault(group, set()).add(name) 273 self.MUTEX_GROUPS.setdefault(group, set()).add(name)
280 inner.IS_ROOT = is_root 274 inner.IS_ROOT = is_root
281 if is_root: 275 if is_root:
282 assert not config_ctx.ROOT_CONFIG_ITEM, ( 276 assert not self.ROOT_CONFIG_ITEM, (
283 'may only have one root config_ctx!') 277 'may only have one root config_ctx!')
284 config_ctx.ROOT_CONFIG_ITEM = inner 278 self.ROOT_CONFIG_ITEM = inner
285 inner.IS_ROOT = True 279 inner.IS_ROOT = True
286 inner.NO_TEST = no_test or bool(deps) 280 inner.NO_TEST = no_test or bool(deps)
287 return inner 281 return inner
288 return decorator 282 return decorator
289 283
290 # Internal state and testing data
291 config_ctx.I_AM_A_CONFIG_CTX = True
292 config_ctx.CONFIG_ITEMS = {}
293 config_ctx.MUTEX_GROUPS = {}
294 config_ctx.CONFIG_SCHEMA = CONFIG_SCHEMA
295 config_ctx.ROOT_CONFIG_ITEM = None
296 config_ctx.VAR_TEST_MAP = VAR_TEST_MAP
297 284
298 def formatter(obj, ext=None): 285 def create_formatter(obj, ext=None):
299 """Converts format obj to a function taking var assignments. 286 """Converts format obj to a function taking var assignments.
300 287
301 Args: 288 Args:
302 obj (str or fn(assignments)): If obj is a str, it will be % formatted 289 obj (str or fn(assignments)): If obj is a str, it will be % formatted
303 with assignments (which is a dict of variables from VAR_TEST_MAP). 290 with assignments (which is a dict of variables from VAR_TEST_MAP).
304 Otherwise obj will be invoked with assignments, and expected to return 291 Otherwise obj will be invoked with assignments, and expected to return
305 a fully-rendered string. 292 a fully-rendered string.
306 ext (None or str): Optionally specify an extension to enforce on the 293 ext (None or str): Optionally specify an extension to enforce on the
307 format. This enforcement occurs after obj is finalized to a string. If 294 format. This enforcement occurs after obj is finalized to a string. If
308 the string doesn't end with ext, it will be appended. 295 the string doesn't end with ext, it will be appended.
309 """ 296 """
310 def inner(var_assignments): 297 def inner(var_assignments):
311 ret = '' 298 ret = ''
312 if isinstance(obj, basestring): 299 if isinstance(obj, basestring):
313 ret = obj % var_assignments 300 ret = obj % var_assignments
314 else: 301 else:
315 ret = obj(var_assignments) 302 ret = obj(var_assignments)
316 if ext and not ret.endswith(ext): 303 if ext and not ret.endswith(ext):
317 ret += ext 304 ret += ext
318 return ret 305 return ret
319 return inner 306 return inner
320 config_ctx.TEST_NAME_FORMAT = formatter(TEST_NAME_FORMAT) 307
321 return config_ctx 308
309 def config_item_context(CONFIG_SCHEMA, VAR_TEST_MAP, TEST_NAME_FORMAT):
310 """Create a configuration context.
311
312 Args:
313 CONFIG_SCHEMA: This is a function which can take a minimum of zero arguments
314 and returns an instance of BaseConfig. This BaseConfig
315 defines the schema for all configuration objects manipulated
316 in this context.
317 VAR_TEST_MAP: A dict mapping arg_name to an iterable of values. This
318 provides the test harness with sufficient information to
319 generate all possible permutations of inputs for the
320 CONFIG_SCHEMA function.
321 TEST_NAME_FORMAT: A string format (or function) for naming tests and test
322 expectation files. It will be formatted/called with a
323 dictionary of arg_name to value (using arg_names and
324 values generated from VAR_TEST_MAP).
325
326 Returns a config_ctx decorator for this context.
327 """
328 return ConfigContext(CONFIG_SCHEMA, VAR_TEST_MAP, TEST_NAME_FORMAT)
322 329
323 330
324 class AutoHide(object): 331 class AutoHide(object):
325 pass 332 pass
326 AutoHide = AutoHide() 333 AutoHide = AutoHide()
327 334
335
328 class ConfigBase(object): 336 class ConfigBase(object):
329 """This is the root interface for all config schema types.""" 337 """This is the root interface for all config schema types."""
330 338
331 def __init__(self, hidden=AutoHide): 339 def __init__(self, hidden=AutoHide):
332 """ 340 """
333 Args: 341 Args:
334 hidden - 342 hidden -
335 True: This object will be excluded from printing when the config blob 343 True: This object will be excluded from printing when the config blob
336 is rendered with ConfigGroup.as_jsonish(). You still have full 344 is rendered with ConfigGroup.as_jsonish(). You still have full
337 read/write access to this blob otherwise though. 345 read/write access to this blob otherwise though.
(...skipping 424 matching lines...) Expand 10 before | Expand all | Expand 10 after
762 return self.data 770 return self.data
763 771
764 def reset(self): 772 def reset(self):
765 assert False 773 assert False
766 774
767 def complete(self): 775 def complete(self):
768 return True 776 return True
769 777
770 def _is_default(self): 778 def _is_default(self):
771 return True 779 return True
OLDNEW
« no previous file with comments | « scripts/slave/recipe_api.py ('k') | scripts/slave/recipe_loader.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698