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

Side by Side Diff: recipe_engine/step_runner.py

Issue 2253943003: Formally define step config, pass to stream. (Closed) Base URL: https://github.com/luci/recipes-py@nest-single-event
Patch Set: Rebase Created 4 years, 3 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
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 import StringIO 5 import StringIO
6 import collections 6 import collections
7 import contextlib 7 import contextlib
8 import datetime 8 import datetime
9 import json 9 import json
10 import os 10 import os
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 pretend to run the steps with mock output (SimulationStepRunner). 79 pretend to run the steps with mock output (SimulationStepRunner).
80 """ 80 """
81 @property 81 @property
82 def stream_engine(self): 82 def stream_engine(self):
83 """Return the stream engine that this StepRunner uses, if meaningful. 83 """Return the stream engine that this StepRunner uses, if meaningful.
84 84
85 Users of this method must be prepared to handle None. 85 Users of this method must be prepared to handle None.
86 """ 86 """
87 return None 87 return None
88 88
89 def open_step(self, step_dict): 89 def open_step(self, step_config):
90 """Constructs an OpenStep object which can be used to actually run a step. 90 """Constructs an OpenStep object which can be used to actually run a step.
91 91
92 step_dict parameters: 92 Args:
93 name: name of the step, will appear in buildbots waterfall 93 step_config (StepConfig): The step data.
94 cmd: command to run, list of one or more strings
95 cwd: absolute path to working directory for the command
96 env: dict with overrides for environment variables
97 allow_subannotations: if True, lets the step emit its own annotations
98 trigger_specs: a list of trigger specifications, see also _trigger_builds.
99 stdout: Path to a file to put step stdout into. If used, stdout won't
100 appear in annotator's stdout (and |allow_subannotations| is
101 ignored).
102 stderr: Path to a file to put step stderr into. If used, stderr won't
103 appear in annotator's stderr.
104 stdin: Path to a file to read step stdin from.
105 step_nest_level: the step's nesting level.
106 94
107 Returns an OpenStep object. 95 Returns: an OpenStep object.
108 """ 96 """
109 raise NotImplementedError() 97 raise NotImplementedError()
110 98
111 def run_recipe(self, universe, recipe, properties): 99 def run_recipe(self, universe, recipe, properties):
112 """Run the recipe named |recipe|. 100 """Run the recipe named |recipe|.
113 101
114 Args: 102 Args:
115 universe: The RecipeUniverse where the recipe lives. 103 universe: The RecipeUniverse where the recipe lives.
116 recipe: The recipe name (e.g. 'infra/luci_py') 104 recipe: The recipe name (e.g. 'infra/luci_py')
117 properties: a dictionary of properties to pass to the recipe 105 properties: a dictionary of properties to pass to the recipe
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
156 """Responsible for actually running steps as subprocesses, filtering their 144 """Responsible for actually running steps as subprocesses, filtering their
157 output into a stream.""" 145 output into a stream."""
158 146
159 def __init__(self, stream_engine): 147 def __init__(self, stream_engine):
160 self._stream_engine = stream_engine 148 self._stream_engine = stream_engine
161 149
162 @property 150 @property
163 def stream_engine(self): 151 def stream_engine(self):
164 return self._stream_engine 152 return self._stream_engine
165 153
166 def open_step(self, step_dict): 154 def open_step(self, step_config):
167 allow_subannotations = step_dict.get('allow_subannotations', False) 155 step_stream = self._stream_engine.new_step_stream(step_config)
168 nest_level = step_dict.pop('step_nest_level', 0) 156 if not step_config.cmd:
169
170 step_stream = self._stream_engine.new_step_stream(
171 step_dict['name'],
172 allow_subannotations=allow_subannotations,
173 nest_level=nest_level)
174 if not step_dict.get('cmd'):
175 class EmptyOpenStep(OpenStep): 157 class EmptyOpenStep(OpenStep):
176 def run(inner): 158 def run(inner):
177 if 'trigger_specs' in step_dict: 159 if step_config.trigger_specs:
178 self._trigger_builds(step_stream, step_dict['trigger_specs']) 160 self._trigger_builds(step_stream, step_config.trigger_specs)
179 return types.StepData(step_dict, 0) 161 return types.StepData(step_config, 0)
180 162
181 def finalize(inner): 163 def finalize(inner):
182 step_stream.close() 164 step_stream.close()
183 165
184 @property 166 @property
185 def stream(inner): 167 def stream(inner):
186 return step_stream 168 return step_stream
187 169
188 return EmptyOpenStep() 170 return EmptyOpenStep()
189 171
190 step_dict, placeholders = render_step( 172 rendered_step = render_step(
191 step_dict, recipe_test_api.DisabledTestData()) 173 step_config, recipe_test_api.DisabledTestData()
192 cmd = map(str, step_dict['cmd']) 174 )
193 step_env = _merge_envs(os.environ, step_dict.get('env', {})) 175 step_config = None # Make sure we use rendered step config.
194 self._print_step(step_stream, step_dict, step_env) 176
177 rendered_step = rendered_step._replace(
178 config=rendered_step.config._replace(
179 cmd=map(str, rendered_step.config.cmd),
180 ),
181 )
182
183 step_env = _merge_envs(os.environ, (rendered_step.config.env or {}))
184 self._print_step(step_stream, rendered_step, step_env)
195 185
196 class ReturnOpenStep(OpenStep): 186 class ReturnOpenStep(OpenStep):
197 def run(inner): 187 def run(inner):
188 step_config = rendered_step.config
198 try: 189 try:
199 # Open file handles for IO redirection based on file names in 190 # Open file handles for IO redirection based on file names in
200 # step_dict. 191 # step_config.
201 handles = { 192 handles = {
202 'stdout': step_stream, 193 'stdout': step_stream,
203 'stderr': step_stream, 194 'stderr': step_stream,
204 'stdin': None, 195 'stdin': None,
205 } 196 }
206 for key in handles: 197 for key in handles:
207 fileName = step_dict.get(key) 198 fileName = getattr(step_config, key)
208 if fileName: 199 if fileName:
209 handles[key] = open(fileName, 'rb' if key == 'stdin' else 'wb') 200 handles[key] = open(fileName, 'rb' if key == 'stdin' else 'wb')
210 # The subprocess will inherit and close these handles. 201 # The subprocess will inherit and close these handles.
211 retcode = self._run_cmd( 202 retcode = self._run_cmd(
212 cmd=cmd, timeout=step_dict.get('timeout'), handles=handles, 203 cmd=step_config.cmd, timeout=step_config.timeout, handles=handles,
213 env=step_env, cwd=step_dict.get('cwd')) 204 env=step_env, cwd=step_config.cwd)
214 except subprocess42.TimeoutExpired as e: 205 except subprocess42.TimeoutExpired as e:
215 # TODO(martiniss): mark as exception? 206 # TODO(martiniss): mark as exception?
216 raise recipe_api.StepTimeout(step_dict['name'], e.timeout) 207 raise recipe_api.StepTimeout(step_config.name, e.timeout)
217 except OSError: 208 except OSError:
218 with step_stream.new_log_stream('exception') as l: 209 with step_stream.new_log_stream('exception') as l:
219 trace = traceback.format_exc().splitlines() 210 trace = traceback.format_exc().splitlines()
220 for line in trace: 211 for line in trace:
221 l.write_line(line) 212 l.write_line(line)
222 step_stream.set_step_status('EXCEPTION') 213 step_stream.set_step_status('EXCEPTION')
223 raise 214 raise
224 finally: 215 finally:
225 # NOTE(luqui) See the accompanying note in stream.py. 216 # NOTE(luqui) See the accompanying note in stream.py.
226 step_stream.reset_subannotation_state() 217 step_stream.reset_subannotation_state()
227 218
228 if 'trigger_specs' in step_dict: 219 if step_config.trigger_specs:
229 self._trigger_builds(step_stream, step_dict['trigger_specs']) 220 self._trigger_builds(step_stream, step_config.trigger_specs)
230 221
231 return construct_step_result(step_dict, retcode, placeholders) 222 return construct_step_result(rendered_step, retcode)
232 223
233 def finalize(inner): 224 def finalize(inner):
234 step_stream.close() 225 step_stream.close()
235 226
236 @property 227 @property
237 def stream(inner): 228 def stream(inner):
238 return step_stream 229 return step_stream
239 230
240 return ReturnOpenStep() 231 return ReturnOpenStep()
241 232
(...skipping 30 matching lines...) Expand all
272 while hasattr(value, 'func'): 263 while hasattr(value, 'func'):
273 value = value.func 264 value = value.func
274 return getattr(value, '__name__', 'UNKNOWN_CALLABLE')+'(...)' 265 return getattr(value, '__name__', 'UNKNOWN_CALLABLE')+'(...)'
275 266
276 def _print_step(self, step_stream, step, env): 267 def _print_step(self, step_stream, step, env):
277 """Prints the step command and relevant metadata. 268 """Prints the step command and relevant metadata.
278 269
279 Intended to be similar to the information that Buildbot prints at the 270 Intended to be similar to the information that Buildbot prints at the
280 beginning of each non-annotator step. 271 beginning of each non-annotator step.
281 """ 272 """
282 step_stream.write_line(' '.join(map(_shell_quote, step['cmd']))) 273 step_stream.write_line(' '.join(map(_shell_quote, step.config.cmd)))
283 step_stream.write_line('in dir %s:' % (step.get('cwd') or os.getcwd())) 274 step_stream.write_line('in dir %s:' % (step.config.cwd or os.getcwd()))
284 for key, value in sorted(step.items()): 275 for key, value in sorted(step.config._asdict().items()):
285 if value is not None: 276 if value is not None:
286 step_stream.write_line( 277 step_stream.write_line(
287 ' %s: %s' % (key, self._render_step_value(value))) 278 ' %s: %s' % (key, self._render_step_value(value)))
288 step_stream.write_line('full environment:') 279 step_stream.write_line('full environment:')
289 for key, value in sorted(env.items()): 280 for key, value in sorted(env.items()):
290 step_stream.write_line(' %s: %s' % (key, value)) 281 step_stream.write_line(' %s: %s' % (key, value))
291 step_stream.write_line('') 282 step_stream.write_line('')
292 283
293 def _run_cmd(self, cmd, timeout, handles, env, cwd): 284 def _run_cmd(self, cmd, timeout, handles, env, cwd):
294 """Runs cmd (subprocess-style). 285 """Runs cmd (subprocess-style).
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after
394 return change 385 return change
395 386
396 387
397 class SimulationStepRunner(StepRunner): 388 class SimulationStepRunner(StepRunner):
398 """Pretends to run steps, instead recording what would have been run. 389 """Pretends to run steps, instead recording what would have been run.
399 390
400 This is the main workhorse of recipes.py simulation_test. Returns the log of 391 This is the main workhorse of recipes.py simulation_test. Returns the log of
401 steps that would have been run in steps_ran. Uses test_data to mock return 392 steps that would have been run in steps_ran. Uses test_data to mock return
402 values. 393 values.
403 """ 394 """
395
396 # List of attributes in a recipe_api.StepConfig to omit when rendering
397 # step history.
398 _STEP_CONFIG_RENDER_BLACKLIST = set((
iannucci 2016/09/12 19:41:07 frozenset
399 'nest_level',
400 'ok_ret',
401 'infra_step',
402 'step_test_data',
403 ))
404
404 def __init__(self, stream_engine, test_data, annotator): 405 def __init__(self, stream_engine, test_data, annotator):
405 self._test_data = test_data 406 self._test_data = test_data
406 self._stream_engine = stream_engine 407 self._stream_engine = stream_engine
407 self._annotator = annotator 408 self._annotator = annotator
408 self._step_history = collections.OrderedDict() 409 self._step_history = collections.OrderedDict()
409 410
410 @property 411 @property
411 def stream_engine(self): 412 def stream_engine(self):
412 return self._stream_engine 413 return self._stream_engine
413 414
414 def open_step(self, step_dict): 415 def open_step(self, step_config):
415 # We modify step_dict. In particular, we add ~followup_annotations during 416 test_data_fn = step_config.step_test_data or recipe_test_api.StepTestData
416 # finalize, and depend on that side-effect being carried into what we 417 step_test = self._test_data.pop_step_test_data(step_config.name,
417 # added to self._step_history, earlier. So copy it here so at least we 418 test_data_fn)
418 # keep the modifications local. 419 rendered_step = render_step(step_config, step_test)
419 step_dict = dict(step_dict) 420 step_config = None # Make sure we use rendered step config.
420 nest_level = step_dict.pop('step_nest_level', 0)
421
422 test_data_fn = step_dict.pop('step_test_data', recipe_test_api.StepTestData)
423 step_test = self._test_data.pop_step_test_data(
424 step_dict['name'], test_data_fn)
425 step_dict, placeholders = render_step(step_dict, step_test)
426 421
427 # Layer the simulation step on top of the given stream engine. 422 # Layer the simulation step on top of the given stream engine.
428 step_stream = self._stream_engine.new_step_stream( 423 step_stream = self._stream_engine.new_step_stream(rendered_step.config)
429 step_dict['name'],
430 nest_level=nest_level)
431 424
432 class ReturnOpenStep(OpenStep): 425 class ReturnOpenStep(OpenStep):
433 def run(inner): 426 def run(inner):
434 timeout = step_dict.get('timeout') 427 timeout = rendered_step.config.timeout
435 if (timeout and step_test.times_out_after and 428 if (timeout and step_test.times_out_after and
436 step_test.times_out_after > timeout): 429 step_test.times_out_after > timeout):
437 raise recipe_api.StepTimeout(step_dict['name'], timeout) 430 raise recipe_api.StepTimeout(rendered_step.config.name, timeout)
438 431
439 self._step_history[step_dict['name']] = step_dict 432 # Install a placeholder for order.
440 return construct_step_result(step_dict, step_test.retcode, placeholders) 433 self._step_history[rendered_step.config.name] = None
434 return construct_step_result(rendered_step, step_test.retcode)
441 435
442 def finalize(inner): 436 def finalize(inner):
437 rs = rendered_step
438
443 # note that '~' sorts after 'z' so that this will be last on each 439 # note that '~' sorts after 'z' so that this will be last on each
444 # step. also use _step to get access to the mutable step 440 # step. also use _step to get access to the mutable step
445 # dictionary. 441 # dictionary.
446 buf = self._annotator.step_buffer(step_dict['name']) 442 buf = self._annotator.step_buffer(rendered_step.config.name)
iannucci 2016/09/12 19:41:07 why still use rendered_step here instead of rs?
447 lines = filter(None, buf.getvalue()).splitlines() 443 lines = filter(None, buf.getvalue()).splitlines()
448 lines = [stream.encode_str(x) for x in lines] 444 lines = [stream.encode_str(x) for x in lines]
449 if lines: 445 if lines:
450 # This magically floats into step_history, which we have already 446 # This magically floats into step_history, which we have already
451 # added step_dict to. 447 # added step_config to.
452 step_dict['~followup_annotations'] = lines 448 rs = rs._replace(followup_annotations=lines)
453 step_stream.close() 449 step_stream.close()
450 self._step_history[rendered_step.config.name] = rs
iannucci 2016/09/12 19:41:07 here too?
454 451
455 @property 452 @property
456 def stream(inner): 453 def stream(inner):
457 return step_stream 454 return step_stream
458 455
459 return ReturnOpenStep() 456 return ReturnOpenStep()
460 457
461 def run_recipe(self, universe, recipe, properties): 458 def run_recipe(self, universe, recipe, properties):
462 return self._test_data.depend_on_data.pop(types.freeze((recipe, properties), )) 459 return self._test_data.depend_on_data.pop(types.freeze((recipe, properties), ))
463 460
464 @contextlib.contextmanager 461 @contextlib.contextmanager
465 def run_context(self): 462 def run_context(self):
466 try: 463 try:
467 yield 464 yield
468 except Exception as ex: 465 except Exception as ex:
469 with self._test_data.should_raise_exception(ex) as should_raise: 466 with self._test_data.should_raise_exception(ex) as should_raise:
470 if should_raise: 467 if should_raise:
471 raise 468 raise
472 469
473 assert self._test_data.consumed, ( 470 assert self._test_data.consumed, (
474 "Unconsumed test data for steps: %s, (exception %s)" % ( 471 "Unconsumed test data for steps: %s, (exception %s)" % (
475 self._test_data.step_data.keys(), 472 self._test_data.step_data.keys(),
476 self._test_data.expected_exception)) 473 self._test_data.expected_exception))
477 474
475 def _rendered_step_to_dict(self, rs):
476 d = dict((k, v) for k, v in rs.config._asdict().iteritems()
477 if v and k not in self._STEP_CONFIG_RENDER_BLACKLIST)
478 if rs.followup_annotations:
479 d['~followup_annotations'] = rs.followup_annotations
480 return d
481
478 @property 482 @property
479 def steps_ran(self): 483 def steps_ran(self):
480 return self._step_history.values() 484 return [self._rendered_step_to_dict(rs)
485 for rs in self._step_history.itervalues()]
481 486
482 487
488 # Placeholders associated with a rendered step.
489 Placeholders = collections.namedtuple('Placeholders',
490 ('inputs_cmd', 'outputs_cmd', 'stdout', 'stderr', 'stdin'))
491
483 # Result of 'render_step'. 492 # Result of 'render_step'.
484 Placeholders = collections.namedtuple( 493 #
485 'Placeholders', ['inputs_cmd', 'outputs_cmd', 'stdout', 'stderr', 'stdin']) 494 # Fields:
495 # config (recipe_api.StepConfig): The step configuration.
496 # placeholders (Placeholders): Placeholders for this rendered step.
497 # followup_annotations (list): A list of followup annotation, populated during
498 # simulation test.
499 RenderedStep = collections.namedtuple('RenderedStep',
500 ('config', 'placeholders', 'followup_annotations'))
486 501
487 502
488 # Singleton object to indicate a value is not set. 503 # Singleton object to indicate a value is not set.
489 UNSET_VALUE = object() 504 UNSET_VALUE = object()
490 505
491 506
492 def render_step(step, step_test): 507 def render_step(step_config, step_test):
493 """Renders a step so that it can be fed to annotator.py. 508 """Renders a step so that it can be fed to annotator.py.
494 509
495 Args: 510 Args:
496 step: The step to render. 511 step_config (StepConfig): The step config to render.
497 step_test: The test data json dictionary for this step, if any. 512 step_test: The test data json dictionary for this step, if any.
498 Passed through unaltered to each placeholder. 513 Passed through unaltered to each placeholder.
499 514
500 Returns the rendered step and a Placeholders object representing any 515 Returns (RenderedStep): the rendered step, including a Placeholders object
501 placeholder instances that were found while rendering. 516 representing any placeholder instances that were found while rendering.
502 """ 517 """
503 rendered_step = dict(step)
504
505 # Process 'cmd', rendering placeholders there. 518 # Process 'cmd', rendering placeholders there.
506 input_phs = collections.defaultdict(lambda: collections.defaultdict(list)) 519 input_phs = collections.defaultdict(lambda: collections.defaultdict(list))
507 output_phs = collections.defaultdict( 520 output_phs = collections.defaultdict(
508 lambda: collections.defaultdict(collections.OrderedDict)) 521 lambda: collections.defaultdict(collections.OrderedDict))
509 new_cmd = [] 522 new_cmd = []
510 for item in step.get('cmd', []): 523 for item in (step_config.cmd or ()):
511 if isinstance(item, util.Placeholder): 524 if isinstance(item, util.Placeholder):
512 module_name, placeholder_name = item.namespaces 525 module_name, placeholder_name = item.namespaces
513 tdata = step_test.pop_placeholder( 526 tdata = step_test.pop_placeholder(
514 module_name, placeholder_name, item.name) 527 module_name, placeholder_name, item.name)
515 new_cmd.extend(item.render(tdata)) 528 new_cmd.extend(item.render(tdata))
516 if isinstance(item, util.InputPlaceholder): 529 if isinstance(item, util.InputPlaceholder):
517 input_phs[module_name][placeholder_name].append((item, tdata)) 530 input_phs[module_name][placeholder_name].append((item, tdata))
518 else: 531 else:
519 assert isinstance(item, util.OutputPlaceholder), ( 532 assert isinstance(item, util.OutputPlaceholder), (
520 'Not an OutputPlaceholder: %r' % item) 533 'Not an OutputPlaceholder: %r' % item)
521 # This assert ensures that: 534 # This assert ensures that:
522 # no two placeholders have the same name 535 # no two placeholders have the same name
523 # at most one placeholder has the default name 536 # at most one placeholder has the default name
524 assert item.name not in output_phs[module_name][placeholder_name], ( 537 assert item.name not in output_phs[module_name][placeholder_name], (
525 'Step "%s" has multiple output placeholders of %s.%s. Please ' 538 'Step "%s" has multiple output placeholders of %s.%s. Please '
526 'specify explicit and different names for them.' % ( 539 'specify explicit and different names for them.' % (
527 step['name'], module_name, placeholder_name)) 540 step_config.name, module_name, placeholder_name))
528 output_phs[module_name][placeholder_name][item.name] = (item, tdata) 541 output_phs[module_name][placeholder_name][item.name] = (item, tdata)
529 else: 542 else:
530 new_cmd.append(item) 543 new_cmd.append(item)
531 rendered_step['cmd'] = new_cmd 544 step_config = step_config._replace(cmd=new_cmd)
532 545
533 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given. 546 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given.
534 stdio_placeholders = {} 547 stdio_placeholders = {}
535 for key in ('stdout', 'stderr', 'stdin'): 548 for key in ('stdout', 'stderr', 'stdin'):
536 placeholder = step.get(key) 549 placeholder = getattr(step_config, key)
537 tdata = None 550 tdata = None
538 if placeholder: 551 if placeholder:
539 if key == 'stdin': 552 if key == 'stdin':
540 assert isinstance(placeholder, util.InputPlaceholder), ( 553 assert isinstance(placeholder, util.InputPlaceholder), (
541 '%s(%r) should be an InputPlaceholder.' % (key, placeholder)) 554 '%s(%r) should be an InputPlaceholder.' % (key, placeholder))
542 else: 555 else:
543 assert isinstance(placeholder, util.OutputPlaceholder), ( 556 assert isinstance(placeholder, util.OutputPlaceholder), (
544 '%s(%r) should be an OutputPlaceholder.' % (key, placeholder)) 557 '%s(%r) should be an OutputPlaceholder.' % (key, placeholder))
545 tdata = getattr(step_test, key) 558 tdata = getattr(step_test, key)
546 placeholder.render(tdata) 559 placeholder.render(tdata)
547 assert placeholder.backing_file is not None 560 assert placeholder.backing_file is not None
548 rendered_step[key] = placeholder.backing_file 561 step_config = step_config._replace(**{key:placeholder.backing_file})
549 stdio_placeholders[key] = (placeholder, tdata) 562 stdio_placeholders[key] = (placeholder, tdata)
550 563
551 return rendered_step, Placeholders( 564 return RenderedStep(
552 inputs_cmd=input_phs, outputs_cmd=output_phs, **stdio_placeholders) 565 config=step_config,
566 placeholders=Placeholders(
567 inputs_cmd=input_phs,
568 outputs_cmd=output_phs,
569 **stdio_placeholders),
570 followup_annotations=None,
571 )
553 572
554 573
555 def construct_step_result(step, retcode, placeholders): 574 def construct_step_result(rendered_step, retcode):
556 """Constructs a StepData step result from step return data. 575 """Constructs a StepData step result from step return data.
557 576
558 The main purpose of this function is to add output placeholder results into 577 The main purpose of this function is to add output placeholder results into
559 the step result where output placeholders appeared in the input step. 578 the step result where output placeholders appeared in the input step.
560 Also give input placeholders the chance to do the clean-up if needed. 579 Also give input placeholders the chance to do the clean-up if needed.
561 """ 580 """
562 581 step_result = types.StepData(rendered_step.config, retcode)
563 step_result = types.StepData(step, retcode)
564 582
565 class BlankObject(object): 583 class BlankObject(object):
566 pass 584 pass
567 585
568 # Input placeholders inside step |cmd|. 586 # Input placeholders inside step |cmd|.
587 placeholders = rendered_step.placeholders
569 for _, pholders in placeholders.inputs_cmd.iteritems(): 588 for _, pholders in placeholders.inputs_cmd.iteritems():
570 for _, items in pholders.iteritems(): 589 for _, items in pholders.iteritems():
571 for ph, td in items: 590 for ph, td in items:
572 ph.cleanup(td.enabled) 591 ph.cleanup(td.enabled)
573 592
574 # Output placeholders inside step |cmd|. 593 # Output placeholders inside step |cmd|.
575 for module_name, pholders in placeholders.outputs_cmd.iteritems(): 594 for module_name, pholders in placeholders.outputs_cmd.iteritems():
576 assert not hasattr(step_result, module_name) 595 assert not hasattr(step_result, module_name)
577 o = BlankObject() 596 o = BlankObject()
578 setattr(step_result, module_name, o) 597 setattr(step_result, module_name, o)
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
661 supplied command, and only uses the |env| kwarg for modifying the environment 680 supplied command, and only uses the |env| kwarg for modifying the environment
662 of the child process. 681 of the child process.
663 """ 682 """
664 saved_path = os.environ['PATH'] 683 saved_path = os.environ['PATH']
665 try: 684 try:
666 if path is not None: 685 if path is not None:
667 os.environ['PATH'] = path 686 os.environ['PATH'] = path
668 yield 687 yield
669 finally: 688 finally:
670 os.environ['PATH'] = saved_path 689 os.environ['PATH'] = saved_path
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698