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

Side by Side Diff: recipe_engine/recipe_api.py

Issue 2934583003: [step] Remove config indirection. (Closed)
Patch Set: cleanup Created 3 years, 6 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
« no previous file with comments | « no previous file | recipe_engine/run.py » ('j') | recipe_engine/run.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2016 The LUCI Authors. All rights reserved. 1 # Copyright 2016 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 from __future__ import absolute_import 5 from __future__ import absolute_import
6 import bisect 6 import bisect
7 import collections 7 import collections
8 import contextlib 8 import contextlib
9 import copy 9 import copy
10 import json 10 import json
11 import keyword 11 import keyword
12 import re 12 import re
13 import types 13 import types
14 14
15 from functools import wraps 15 from functools import wraps
16 16
17 from .recipe_test_api import DisabledTestData, ModuleTestData 17 from .recipe_test_api import DisabledTestData, ModuleTestData
18 from .config import Single 18 from .config import Single
19 19 from .util import ModuleInjectionSite, Placeholder
20 from .util import ModuleInjectionSite
21 20
22 21
23 class UnknownRequirementError(object): 22 class UnknownRequirementError(object):
24 """Raised by a requirement function when the referenced requirement is 23 """Raised by a requirement function when the referenced requirement is
25 unknown. 24 unknown.
26 """ 25 """
27 26
28 def __init__(self, req): 27 def __init__(self, req):
29 super(UnknownRequirementError, self).__init__( 28 super(UnknownRequirementError, self).__init__(
30 'Unknown requirement [%s]' % (req,)) 29 'Unknown requirement [%s]' % (req,))
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after
179 """Allows api.step to get the active result from any context. 178 """Allows api.step to get the active result from any context.
180 179
181 This always returns the innermost nested step that is still open -- 180 This always returns the innermost nested step that is still open --
182 presumably the one that just failed if we are in an exception handler.""" 181 presumably the one that just failed if we are in an exception handler."""
183 if not self._engine._step_stack: 182 if not self._engine._step_stack:
184 raise ValueError( 183 raise ValueError(
185 'No steps have been run yet, and you are asking for a previous step ' 184 'No steps have been run yet, and you are asking for a previous step '
186 'result.') 185 'result.')
187 return self._engine._step_stack[-1].step_result 186 return self._engine._step_stack[-1].step_result
188 187
189 def run_step(self, step_dict): 188 def make_trigger_spec(self, **trigger_spec):
190 """ 189 """Returns (TriggerSpec): A trigger spec for a StepConfig.
191 Runs a step.
192 190
193 Args: 191 Args:
194 step_dict (dict): A step dictionary to run. 192 trigger_spec: Keyword arguments to use to instantiate a TriggerSpec.
193
194 Returns:
195 An instantiated, normalized TriggerSpec object.
196 """
197 return TriggerSpec.create(**trigger_spec)
iannucci 2017/06/12 18:20:08 I don't like this method... the caller should just
dnj 2017/06/12 19:00:03 Done.
198
199 def run_step(self, **step_config):
200 """
201 Runs a step from a StepConfig.
202
203 Args:
204 step_config: Keyword arguments to use to instantiate a StepConfig.
195 205
196 Returns: 206 Returns:
197 A StepData object containing the result of running the step. 207 A StepData object containing the result of running the step.
198 """ 208 """
199 return self._engine.run_step(StepConfig.create(**step_dict)) 209 return self._engine.run_step(StepConfig.create(**step_config))
iannucci 2017/06/12 18:20:08 Same here, we should just assert that step_config
dnj 2017/06/12 19:00:03 Done.
200 210
201 211
202 class DependencyManagerClient(object): 212 class DependencyManagerClient(object):
203 """A recipe engine client representing the dependency manager.""" 213 """A recipe engine client representing the dependency manager."""
204 214
205 IDENT = 'dependency_manager' 215 IDENT = 'dependency_manager'
206 216
207 def __init__(self, engine): 217 def __init__(self, engine):
208 self._engine = engine 218 self._engine = engine
209 219
210 def depend_on(self, recipe, properties, **kwargs): 220 def depend_on(self, recipe, properties, **kwargs):
211 return self._engine.depend_on(recipe, properties, **kwargs) 221 return self._engine.depend_on(recipe, properties, **kwargs)
212 222
213 223
214 _TriggerSpec = collections.namedtuple('_TriggerSpec', 224 class StepConfig(collections.namedtuple('_StepConfig', (
215 ('bucket', 'builder_name', 'properties', 'buildbot_changes', 'tags', 225 'name', 'base_name', 'cmd', 'cwd', 'env', 'allow_subannotations',
216 'critical')) 226 'trigger_specs', 'timeout', 'infra_step', 'stdout', 'stderr', 'stdin',
227 'ok_ret', 'step_test_data', 'nest_level'))):
217 228
218 class TriggerSpec(_TriggerSpec):
219 """
220 TriggerSpec is the internal representation of a raw trigger step. You should
221 use the standard 'step' recipe module, which will construct trigger specs
222 via API.
223 """
224
225 @classmethod
226 def _create(cls, builder_name, bucket=None, properties=None,
227 buildbot_changes=None, tags=None, critical=None):
228 """Creates a new TriggerSpec instance from its step API dictionary
229 keys/values.
230
231 Args:
232 builder_name (str): The name of the builder to trigger.
233 bucket (str or None): The name of the trigger bucket.
234 properties (dict or None): Key/value properties dictionary.
235 buildbot_changes (list or None): Optional list of BuildBot change dicts.
236 tags (list or None): Optional list of tag strings.
237 critical (bool or None): If true and triggering fails asynchronously, fail
238 the entire build. If None, the step defaults to being True.
239 """
240 if not isinstance(buildbot_changes, (types.NoneType, list)):
241 raise ValueError('buildbot_changes must be a list')
242
243 return cls(
244 bucket=bucket,
245 builder_name=builder_name,
246 properties=properties,
247 buildbot_changes=buildbot_changes,
248 tags=tags,
249 critical=bool(critical) if critical is not None else (True),
250 )
251
252 def _render_to_dict(self):
253 d = dict((k, v) for k, v in self._asdict().iteritems() if v)
254 if d['critical']:
255 d.pop('critical')
256 return d
257
258
259 _StepConfig = collections.namedtuple('_StepConfig',
260 ('name', 'base_name', 'cmd', 'cwd', 'env', 'allow_subannotations',
261 'trigger_specs', 'timeout', 'infra_step', 'stdout', 'stderr', 'stdin',
262 'ok_ret', 'step_test_data', 'nest_level'))
263
264 class StepConfig(_StepConfig):
265 """ 229 """
266 StepConfig is the representation of a raw step as the recipe_engine sees it. 230 StepConfig is the representation of a raw step as the recipe_engine sees it.
267 You should use the standard 'step' recipe module, which will construct and 231 You should use the standard 'step' recipe module, which will construct and
268 pass this data to the engine for you, instead. The only reason why you would 232 pass this data to the engine for you, instead. The only reason why you would
269 need to worry about this object is if you're modifying the step module itself. 233 need to worry about this object is if you're modifying the step module
234 itself.
235
236 Fields:
237 name (str): name of the step, will appear in buildbots waterfall
238 base_name (str): the base name of the step. If the step has a derived
239 name (e.g., nested may be concatenated with its parent), this is the
240 name component of just this step. If None, this will be set to "name".
241 cmd: command to run. Acceptable types: str, Path, Placeholder, or None.
242 cwd (str or None): absolute path to working directory for the command
243 env (dict): overrides for environment variables, described above.
244 allow_subannotations (bool): if True, lets the step emit its own
245 annotations. NOTE: Enabling this can cause some buggy behavior. Please
246 strongly consider using step_result.presentation instead. If you have
247 questions, please contact infra-dev@chromium.org.
248 trigger_specs: a list of trigger specifications, see also _trigger_builds.
249 timeout: if not None, a datetime.timedelta for the step timeout.
250 infra_step: if True, this is an infrastructure step. Failures will raise
251 InfraFailure instead of StepFailure.
252 stdout: Placeholder to put step stdout into. If used, stdout won't appear
253 in annotator's stdout (and |allow_subannotations| is ignored).
254 stderr: Placeholder to put step stderr into. If used, stderr won't appear
255 in annotator's stderr.
256 stdin: Placeholder to read step stdin from.
257 ok_ret (iter): set of return codes allowed. If the step process returns
258 something not on this list, it will raise a StepFailure (or
259 InfraFailure if infra_step is True). If omitted, {0} will be used.
260 step_test_data (func -> recipe_test_api.StepTestData): A factory which
261 returns a StepTestData object that will be used as the default test
262 data for this step. The recipe author can override/augment this object
263 in the GenTests function.
264 nest_level (int): the step's nesting level.
270 265
271 The optional "env" parameter provides optional overrides for environment 266 The optional "env" parameter provides optional overrides for environment
272 variables. Each value is % formatted with the entire existing os.environ. A 267 variables. Each value is % formatted with the entire existing os.environ. A
273 value of `None` will remove that envvar from the environ. e.g. 268 value of `None` will remove that envvar from the environ. e.g.
274 269
275 { 270 {
276 "envvar": "%(envvar)s;%(envvar2)s;extra", 271 "envvar": "%(envvar)s;%(envvar2)s;extra",
277 "delete_this": None, 272 "delete_this": None,
278 "static_value": "something", 273 "static_value": "something",
279 } 274 }
280 """ 275 """
281 276
282 _RENDER_WHITELIST=frozenset(( 277 _RENDER_WHITELIST=frozenset((
283 'cmd', 278 'cmd',
284 )) 279 ))
285 280
286 _RENDER_BLACKLIST=frozenset(( 281 _RENDER_BLACKLIST=frozenset((
287 'base_name', 282 'base_name',
288 'nest_level', 283 'nest_level',
289 'ok_ret', 284 'ok_ret',
290 'step_test_data', 285 'step_test_data',
291 )) 286 ))
292 287
293 @classmethod 288 @classmethod
294 def create(cls, name, base_name=None, cmd=None, cwd=None, env=None, 289 def create(cls, **kwargs):
295 allow_subannotations=None, trigger_specs=None, timeout=None, 290 for field in cls._fields:
296 infra_step=None, stdout=None, stderr=None, stdin=None, 291 kwargs.setdefault(field, None)
297 ok_ret=None, step_test_data=None, step_nest_level=None): 292 sc = cls(**kwargs)
298 """
299 Initializes a new StepConfig step API dictionary.
300 293
301 Args: 294 return sc._replace(
302 name (str): name of the step, will appear in buildbots waterfall 295 cmd=[(x if isinstance(x, Placeholder) else str(x))
303 base_name (str): the base name of the step. If the step has a derived 296 for x in (sc.cmd or ())],
304 name (e.g., nested may be concatenated with its parent), this is the 297 cwd=(str(sc.cwd) if sc.cwd else (None)),
305 name component of just this step. If None, this will be set to "name". 298 base_name=sc.base_name or sc.name,
306 cmd: command to run. Acceptable types: str, Path, Placeholder, or None. 299 allow_subannotations=bool(sc.allow_subannotations),
307 cwd (str or None): absolute path to working directory for the command 300 trigger_specs=sc.trigger_specs or (),
308 env (dict): overrides for environment variables, described above. 301 infra_step=bool(sc.infra_step),
309 allow_subannotations (bool): if True, lets the step emit its own 302 ok_ret=frozenset(sc.ok_ret or (0,)),
310 annotations. NOTE: Enabling this can cause some buggy behavior. Please 303 nest_level=int(sc.nest_level or 0),
311 strongly consider using step_result.presentation instead. If you have
312 questions, please contact infra-dev@chromium.org.
313 trigger_specs: a list of trigger specifications, see also _trigger_builds.
314 timeout: if not None, a datetime.timedelta for the step timeout.
315 infra_step: if True, this is an infrastructure step. Failures will raise
316 InfraFailure instead of StepFailure.
317 stdout: Placeholder to put step stdout into. If used, stdout won't appear
318 in annotator's stdout (and |allow_subannotations| is ignored).
319 stderr: Placeholder to put step stderr into. If used, stderr won't appear
320 in annotator's stderr.
321 stdin: Placeholder to read step stdin from.
322 ok_ret (iter): set of return codes allowed. If the step process returns
323 something not on this list, it will raise a StepFailure (or
324 InfraFailure if infra_step is True). If omitted, {0} will be used.
325 step_test_data (func -> recipe_test_api.StepTestData): A factory which
326 returns a StepTestData object that will be used as the default test
327 data for this step. The recipe author can override/augment this object
328 in the GenTests function.
329 step_nest_level (int): the step's nesting level.
330 """
331 return cls(
332 name=name,
333 base_name=(base_name or name),
334 cmd=cmd,
335 cwd=cwd,
336 env=env,
337 allow_subannotations=bool(allow_subannotations),
338 trigger_specs=[TriggerSpec._create(**trig)
339 for trig in (trigger_specs or ())],
340 timeout=timeout,
341 infra_step=bool(infra_step),
342 stdout=stdout,
343 stderr=stderr,
344 stdin=stdin,
345 ok_ret=frozenset(ok_ret or (0,)),
346 step_test_data=step_test_data,
347 nest_level=int(step_nest_level or 0),
348 ) 304 )
349 305
350 def render_to_dict(self): 306 def render_to_dict(self):
351 self = self._replace( 307 sc = self._replace(
352 trigger_specs=[trig._render_to_dict() 308 trigger_specs=[trig._render_to_dict()
353 for trig in (self.trigger_specs or ())], 309 for trig in (self.trigger_specs or ())],
354 ) 310 )
355 return dict((k, v) for k, v in self._asdict().iteritems() 311 return dict((k, v) for k, v in sc._asdict().iteritems()
356 if (v or k in self._RENDER_WHITELIST) 312 if (v or k in sc._RENDER_WHITELIST)
357 and k not in self._RENDER_BLACKLIST) 313 and k not in sc._RENDER_BLACKLIST)
314
315
316 class TriggerSpec(collections.namedtuple('_TriggerSpec', (
317 'bucket', 'builder_name', 'properties', 'buildbot_changes', 'tags',
318 'critical'))):
319
320 """
321 TriggerSpec is the internal representation of a raw trigger step. You should
322 use the standard 'step' recipe module, which will construct trigger specs
323 via API.
324
325 Fields:
326 builder_name (str): The name of the builder to trigger.
327 bucket (str or None): The name of the trigger bucket.
328 properties (dict or None): Key/value properties dictionary.
329 buildbot_changes (list or None): Optional list of BuildBot change dicts.
330 tags (list or None): Optional list of tag strings.
331 critical (bool or None): If true and triggering fails asynchronously, fail
332 the entire build. If None, the step defaults to being True.
333 """
334
335 @classmethod
336 def create(cls, **kwargs):
337 for field in cls._fields:
338 kwargs.setdefault(field, None)
339 trig = cls(**kwargs)
340 return trig._replace(
341 critical=bool(trig.critical),
342 )
343
344 def _render_to_dict(self):
345 d = dict((k, v) for k, v in self._asdict().iteritems() if v)
346 if d['critical']:
347 d.pop('critical')
348 return d
358 349
359 350
360 class StepFailure(Exception): 351 class StepFailure(Exception):
361 """ 352 """
362 This is the base class for all step failures. 353 This is the base class for all step failures.
363 354
364 Raising a StepFailure counts as 'running a step' for the purpose of 355 Raising a StepFailure counts as 'running a step' for the purpose of
365 infer_composite_step's logic. 356 infer_composite_step's logic.
366 """ 357 """
367 def __init__(self, name_or_reason, result=None): 358 def __init__(self, name_or_reason, result=None):
(...skipping 707 matching lines...) Expand 10 before | Expand all | Expand 10 after
1075 def bind(self, name, property_type, full_decl_name): 1066 def bind(self, name, property_type, full_decl_name):
1076 """ 1067 """
1077 Gets the BoundProperty version of this Property. Requires a name. 1068 Gets the BoundProperty version of this Property. Requires a name.
1078 """ 1069 """
1079 return BoundProperty( 1070 return BoundProperty(
1080 self._default, self.help, self.kind, name, property_type, full_decl_name, 1071 self._default, self.help, self.kind, name, property_type, full_decl_name,
1081 self.param_name) 1072 self.param_name)
1082 1073
1083 class UndefinedPropertyException(TypeError): 1074 class UndefinedPropertyException(TypeError):
1084 pass 1075 pass
OLDNEW
« no previous file with comments | « no previous file | recipe_engine/run.py » ('j') | recipe_engine/run.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698