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

Side by Side Diff: recipe_engine/recipe_api.py

Issue 2934583003: [step] Remove config indirection. (Closed)
Patch Set: fix test 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_modules/step/api.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 134 matching lines...) Expand 10 before | Expand all | Expand 10 after
165 164
166 def get_properties(self): 165 def get_properties(self):
167 return copy.deepcopy(self._engine.properties) 166 return copy.deepcopy(self._engine.properties)
168 167
169 168
170 class StepClient(object): 169 class StepClient(object):
171 """A recipe engine client representing step running and introspection.""" 170 """A recipe engine client representing step running and introspection."""
172 171
173 IDENT = 'step' 172 IDENT = 'step'
174 173
174
175 class StepConfig(collections.namedtuple('_StepConfig', (
176 'name', 'base_name', 'cmd', 'cwd', 'env', 'allow_subannotations',
177 'trigger_specs', 'timeout', 'infra_step', 'stdout', 'stderr', 'stdin',
178 'ok_ret', 'step_test_data', 'nest_level'))):
179
180 """
181 StepConfig is the representation of a raw step as the recipe_engine sees it.
182 You should use the standard 'step' recipe module, which will construct and
183 pass this data to the engine for you, instead. The only reason why you would
184 need to worry about this object is if you're modifying the step module
185 itself.
186
187 Fields:
188 name (str): name of the step, will appear in buildbots waterfall
189 base_name (str): the base name of the step. If the step has a derived
190 name (e.g., nested may be concatenated with its parent), this is the
191 name component of just this step. If None, this will be set to "name".
192 cmd: command to run. Acceptable types: str, Path, Placeholder, or None.
193 cwd (str or None): absolute path to working directory for the command
194 env (dict): overrides for environment variables, described above.
195 allow_subannotations (bool): if True, lets the step emit its own
196 annotations. NOTE: Enabling this can cause some buggy behavior. Please
197 strongly consider using step_result.presentation instead. If you have
198 questions, please contact infra-dev@chromium.org.
199 trigger_specs: a list of trigger specifications, see also _trigger_builds.
200 timeout: if not None, a datetime.timedelta for the step timeout.
201 infra_step: if True, this is an infrastructure step. Failures will raise
202 InfraFailure instead of StepFailure.
203 stdout: Placeholder to put step stdout into. If used, stdout won't appear
204 in annotator's stdout (and |allow_subannotations| is ignored).
205 stderr: Placeholder to put step stderr into. If used, stderr won't appear
206 in annotator's stderr.
207 stdin: Placeholder to read step stdin from.
208 ok_ret (iter): set of return codes allowed. If the step process returns
209 something not on this list, it will raise a StepFailure (or
210 InfraFailure if infra_step is True). If omitted, {0} will be used.
211 step_test_data (func -> recipe_test_api.StepTestData): A factory which
212 returns a StepTestData object that will be used as the default test
213 data for this step. The recipe author can override/augment this object
214 in the GenTests function.
215 nest_level (int): the step's nesting level.
216
217 The optional "env" parameter provides optional overrides for environment
218 variables. Each value is % formatted with the entire existing os.environ. A
219 value of `None` will remove that envvar from the environ. e.g.
220
221 {
222 "envvar": "%(envvar)s;%(envvar2)s;extra",
223 "delete_this": None,
224 "static_value": "something",
225 }
226 """
227
228 _RENDER_WHITELIST=frozenset((
229 'cmd',
230 ))
231
232 _RENDER_BLACKLIST=frozenset((
233 'base_name',
234 'nest_level',
235 'ok_ret',
236 'step_test_data',
237 ))
238
239 def __new__(cls, **kwargs):
240 for field in cls._fields:
241 kwargs.setdefault(field, None)
242 sc = super(StepClient.StepConfig, cls).__new__(cls, **kwargs)
243
244 return sc._replace(
245 cmd=[(x if isinstance(x, Placeholder) else str(x))
246 for x in (sc.cmd or ())],
247 cwd=(str(sc.cwd) if sc.cwd else (None)),
248 base_name=sc.base_name or sc.name,
249 allow_subannotations=bool(sc.allow_subannotations),
250 trigger_specs=sc.trigger_specs or (),
251 infra_step=bool(sc.infra_step),
252 ok_ret=frozenset(sc.ok_ret or (0,)),
253 nest_level=int(sc.nest_level or 0),
254 )
255
256 def render_to_dict(self):
257 sc = self._replace(
258 trigger_specs=[trig._render_to_dict()
259 for trig in (self.trigger_specs or ())],
260 )
261 return dict((k, v) for k, v in sc._asdict().iteritems()
262 if (v or k in sc._RENDER_WHITELIST)
263 and k not in sc._RENDER_BLACKLIST)
264
265
266 class TriggerSpec(collections.namedtuple('_TriggerSpec', (
267 'bucket', 'builder_name', 'properties', 'buildbot_changes', 'tags',
268 'critical'))):
269
270 """
271 TriggerSpec is the internal representation of a raw trigger step. You should
272 use the standard 'step' recipe module, which will construct trigger specs
273 via API.
274
275 Fields:
276 builder_name (str): The name of the builder to trigger.
277 bucket (str or None): The name of the trigger bucket.
278 properties (dict or None): Key/value properties dictionary.
279 buildbot_changes (list or None): Optional list of BuildBot change dicts.
280 tags (list or None): Optional list of tag strings.
281 critical (bool or None): If true and triggering fails asynchronously, fail
282 the entire build. If None, the step defaults to being True.
283 """
284
285 def __new__(cls, **kwargs):
286 for field in cls._fields:
287 kwargs.setdefault(field, None)
288 trig = super(StepClient.TriggerSpec, cls).__new__(cls, **kwargs)
289 return trig._replace(
290 critical=bool(trig.critical),
291 )
292
293 def _render_to_dict(self):
294 d = dict((k, v) for k, v in self._asdict().iteritems() if v)
295 if d['critical']:
296 d.pop('critical')
297 return d
298
299
175 def __init__(self, engine): 300 def __init__(self, engine):
176 self._engine = engine 301 self._engine = engine
177 302
178 def previous_step_result(self): 303 def previous_step_result(self):
179 """Allows api.step to get the active result from any context. 304 """Allows api.step to get the active result from any context.
180 305
181 This always returns the innermost nested step that is still open -- 306 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.""" 307 presumably the one that just failed if we are in an exception handler."""
183 if not self._engine._step_stack: 308 if not self._engine._step_stack:
184 raise ValueError( 309 raise ValueError(
185 'No steps have been run yet, and you are asking for a previous step ' 310 'No steps have been run yet, and you are asking for a previous step '
186 'result.') 311 'result.')
187 return self._engine._step_stack[-1].step_result 312 return self._engine._step_stack[-1].step_result
188 313
189 def run_step(self, step_dict): 314 def run_step(self, step_config):
190 """ 315 """
191 Runs a step. 316 Runs a step from a StepConfig.
192 317
193 Args: 318 Args:
194 step_dict (dict): A step dictionary to run. 319 step_config: Keyword arguments to use to instantiate a StepConfig.
195 320
196 Returns: 321 Returns:
197 A StepData object containing the result of running the step. 322 A StepData object containing the result of running the step.
198 """ 323 """
199 return self._engine.run_step(StepConfig.create(**step_dict)) 324 assert isinstance(step_config, self.StepConfig)
325 return self._engine.run_step(step_config)
200 326
201 327
202 class DependencyManagerClient(object): 328 class DependencyManagerClient(object):
203 """A recipe engine client representing the dependency manager.""" 329 """A recipe engine client representing the dependency manager."""
204 330
205 IDENT = 'dependency_manager' 331 IDENT = 'dependency_manager'
206 332
207 def __init__(self, engine): 333 def __init__(self, engine):
208 self._engine = engine 334 self._engine = engine
209 335
210 def depend_on(self, recipe, properties, **kwargs): 336 def depend_on(self, recipe, properties, **kwargs):
211 return self._engine.depend_on(recipe, properties, **kwargs) 337 return self._engine.depend_on(recipe, properties, **kwargs)
212 338
213 339
214 _TriggerSpec = collections.namedtuple('_TriggerSpec',
215 ('bucket', 'builder_name', 'properties', 'buildbot_changes', 'tags',
216 'critical'))
217
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 """
266 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
268 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.
270
271 The optional "env" parameter provides optional overrides for environment
272 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.
274
275 {
276 "envvar": "%(envvar)s;%(envvar2)s;extra",
277 "delete_this": None,
278 "static_value": "something",
279 }
280 """
281
282 _RENDER_WHITELIST=frozenset((
283 'cmd',
284 ))
285
286 _RENDER_BLACKLIST=frozenset((
287 'base_name',
288 'nest_level',
289 'ok_ret',
290 'step_test_data',
291 ))
292
293 @classmethod
294 def create(cls, name, base_name=None, cmd=None, cwd=None, env=None,
295 allow_subannotations=None, trigger_specs=None, timeout=None,
296 infra_step=None, stdout=None, stderr=None, stdin=None,
297 ok_ret=None, step_test_data=None, step_nest_level=None):
298 """
299 Initializes a new StepConfig step API dictionary.
300
301 Args:
302 name (str): name of the step, will appear in buildbots waterfall
303 base_name (str): the base name of the step. If the step has a derived
304 name (e.g., nested may be concatenated with its parent), this is the
305 name component of just this step. If None, this will be set to "name".
306 cmd: command to run. Acceptable types: str, Path, Placeholder, or None.
307 cwd (str or None): absolute path to working directory for the command
308 env (dict): overrides for environment variables, described above.
309 allow_subannotations (bool): if True, lets the step emit its own
310 annotations. NOTE: Enabling this can cause some buggy behavior. Please
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 )
349
350 def render_to_dict(self):
351 self = self._replace(
352 trigger_specs=[trig._render_to_dict()
353 for trig in (self.trigger_specs or ())],
354 )
355 return dict((k, v) for k, v in self._asdict().iteritems()
356 if (v or k in self._RENDER_WHITELIST)
357 and k not in self._RENDER_BLACKLIST)
358
359
360 class StepFailure(Exception): 340 class StepFailure(Exception):
361 """ 341 """
362 This is the base class for all step failures. 342 This is the base class for all step failures.
363 343
364 Raising a StepFailure counts as 'running a step' for the purpose of 344 Raising a StepFailure counts as 'running a step' for the purpose of
365 infer_composite_step's logic. 345 infer_composite_step's logic.
366 """ 346 """
367 def __init__(self, name_or_reason, result=None): 347 def __init__(self, name_or_reason, result=None):
368 # Raising a StepFailure counts as running a step. 348 # Raising a StepFailure counts as running a step.
369 _DEFER_CONTEXT.mark_ran_step() 349 _DEFER_CONTEXT.mark_ran_step()
(...skipping 705 matching lines...) Expand 10 before | Expand all | Expand 10 after
1075 def bind(self, name, property_type, full_decl_name): 1055 def bind(self, name, property_type, full_decl_name):
1076 """ 1056 """
1077 Gets the BoundProperty version of this Property. Requires a name. 1057 Gets the BoundProperty version of this Property. Requires a name.
1078 """ 1058 """
1079 return BoundProperty( 1059 return BoundProperty(
1080 self._default, self.help, self.kind, name, property_type, full_decl_name, 1060 self._default, self.help, self.kind, name, property_type, full_decl_name,
1081 self.param_name) 1061 self.param_name)
1082 1062
1083 class UndefinedPropertyException(TypeError): 1063 class UndefinedPropertyException(TypeError):
1084 pass 1064 pass
OLDNEW
« no previous file with comments | « no previous file | recipe_engine/run.py » ('j') | recipe_modules/step/api.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698