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

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

Issue 1151423002: Move recipe engine to third_party/recipe_engine. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Copyright notices Created 5 years, 7 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
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Entry point for fully-annotated builds.
7
8 This script is part of the effort to move all builds to annotator-based
9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory()
10 found in scripts/master/factory/annotator_factory.py executes a single
11 AddAnnotatedScript step. That step (found in annotator_commands.py) calls
12 this script with the build- and factory-properties passed on the command
13 line.
14
15 The main mode of operation is for factory_properties to contain a single
16 property 'recipe' whose value is the basename (without extension) of a python
17 script in one of the following locations (looked up in this order):
18 * build_internal/scripts/slave-internal/recipes
19 * build_internal/scripts/slave/recipes
20 * build/scripts/slave/recipes
21
22 For example, these factory_properties would run the 'run_presubmit' recipe
23 located in build/scripts/slave/recipes:
24 { 'recipe': 'run_presubmit' }
25
26 TODO(vadimsh, iannucci): The following docs are very outdated.
27
28 Annotated_run.py will then import the recipe and expect to call a function whose
29 signature is:
30 GenSteps(api, properties) -> iterable_of_things.
31
32 properties is a merged view of factory_properties with build_properties.
33
34 Items in iterable_of_things must be one of:
35 * A step dictionary (as accepted by annotator.py)
36 * A sequence of step dictionaries
37 * A step generator
38 Iterable_of_things is also permitted to be a raw step generator.
39
40 A step generator is called with the following protocol:
41 * The generator is initialized with 'step_history' and 'failed'.
42 * Each iteration of the generator is passed the current value of 'failed'.
43
44 On each iteration, a step generator may yield:
45 * A single step dictionary
46 * A sequence of step dictionaries
47 * If a sequence of dictionaries is yielded, and the first step dictionary
48 does not have a 'seed_steps' key, the first step will be augmented with
49 a 'seed_steps' key containing the names of all the steps in the sequence.
50
51 For steps yielded by the generator, if annotated_run enters the failed state,
52 it will only continue to call the generator if the generator sets the
53 'keep_going' key on the steps which it has produced. Otherwise annotated_run
54 will cease calling the generator and move on to the next item in
55 iterable_of_things.
56
57 'step_history' is an OrderedDict of {stepname -> StepData}, always representing
58 the current history of what steps have run, what they returned, and any
59 json data they emitted. Additionally, the OrderedDict has the following
60 convenience functions defined:
61 * last_step - Returns the last step that ran or None
62 * nth_step(n) - Returns the N'th step that ran or None
63
64 'failed' is a boolean representing if the build is in a 'failed' state.
65 """
66
67 import copy
68 import functools
69 import json
70 import optparse 6 import optparse
71 import os 7 import os
72 import subprocess 8 import subprocess
73 import sys 9 import sys
74 import traceback
75 10
76 import cStringIO 11 BUILD_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
77 12 os.path.abspath(__file__))))
78 import common.python26_polyfill # pylint: disable=W0611 13 sys.path.append(os.path.join(BUILD_ROOT, 'scripts'))
79 import collections # Import after polyfill to get OrderedDict on 2.6 14 sys.path.append(os.path.join(BUILD_ROOT, 'third_party'))
80 15
81 from common import annotator 16 from common import annotator
82 from common import chromium_utils 17 from common import chromium_utils
18 from slave import recipe_universe
83 19
84 from slave import recipe_loader 20 from recipe_engine import annotated_run
85 from slave import recipe_test_api
86 from slave import recipe_util
87 from slave import recipe_api
88
89
90 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
91
92
93 class StepPresentation(object):
94 STATUSES = set(('SUCCESS', 'FAILURE', 'WARNING', 'EXCEPTION'))
95
96 def __init__(self):
97 self._finalized = False
98
99 self._logs = collections.OrderedDict()
100 self._links = collections.OrderedDict()
101 self._perf_logs = collections.OrderedDict()
102 self._status = None
103 self._step_summary_text = ''
104 self._step_text = ''
105 self._properties = {}
106
107 # (E0202) pylint bug: http://www.logilab.org/ticket/89092
108 @property
109 def status(self): # pylint: disable=E0202
110 return self._status
111
112 @status.setter
113 def status(self, val): # pylint: disable=E0202
114 assert not self._finalized
115 assert val in self.STATUSES
116 self._status = val
117
118 @property
119 def step_text(self):
120 return self._step_text
121
122 @step_text.setter
123 def step_text(self, val):
124 assert not self._finalized
125 self._step_text = val
126
127 @property
128 def step_summary_text(self):
129 return self._step_summary_text
130
131 @step_summary_text.setter
132 def step_summary_text(self, val):
133 assert not self._finalized
134 self._step_summary_text = val
135
136 @property
137 def logs(self):
138 if not self._finalized:
139 return self._logs
140 else:
141 return copy.deepcopy(self._logs)
142
143 @property
144 def links(self):
145 if not self._finalized:
146 return self._links
147 else:
148 return copy.deepcopy(self._links)
149
150 @property
151 def perf_logs(self):
152 if not self._finalized:
153 return self._perf_logs
154 else:
155 return copy.deepcopy(self._perf_logs)
156
157 @property
158 def properties(self): # pylint: disable=E0202
159 if not self._finalized:
160 return self._properties
161 else:
162 return copy.deepcopy(self._properties)
163
164 @properties.setter
165 def properties(self, val): # pylint: disable=E0202
166 assert not self._finalized
167 assert isinstance(val, dict)
168 self._properties = val
169
170 def finalize(self, annotator_step):
171 self._finalized = True
172 if self.step_text:
173 annotator_step.step_text(self.step_text)
174 if self.step_summary_text:
175 annotator_step.step_summary_text(self.step_summary_text)
176 for name, lines in self.logs.iteritems():
177 annotator_step.write_log_lines(name, lines)
178 for name, lines in self.perf_logs.iteritems():
179 annotator_step.write_log_lines(name, lines, perf=True)
180 for label, url in self.links.iteritems():
181 annotator_step.step_link(label, url)
182 status_mapping = {
183 'WARNING': annotator_step.step_warnings,
184 'FAILURE': annotator_step.step_failure,
185 'EXCEPTION': annotator_step.step_exception,
186 }
187 status_mapping.get(self.status, lambda: None)()
188 for key, value in self._properties.iteritems():
189 annotator_step.set_build_property(key, json.dumps(value, sort_keys=True))
190
191
192 class StepData(object):
193 def __init__(self, step, retcode):
194 self._retcode = retcode
195 self._step = step
196
197 self._presentation = StepPresentation()
198 self.abort_reason = None
199
200 @property
201 def step(self):
202 return copy.deepcopy(self._step)
203
204 @property
205 def retcode(self):
206 return self._retcode
207
208 @property
209 def presentation(self):
210 return self._presentation
211
212 # TODO(martiniss) update comment
213 # Result of 'render_step', fed into 'step_callback'.
214 Placeholders = collections.namedtuple(
215 'Placeholders', ['cmd', 'stdout', 'stderr', 'stdin'])
216
217
218 def render_step(step, step_test):
219 """Renders a step so that it can be fed to annotator.py.
220
221 Args:
222 step_test: The test data json dictionary for this step, if any.
223 Passed through unaltered to each placeholder.
224
225 Returns any placeholder instances that were found while rendering the step.
226 """
227 # Process 'cmd', rendering placeholders there.
228 placeholders = collections.defaultdict(lambda: collections.defaultdict(list))
229 new_cmd = []
230 for item in step.get('cmd', []):
231 if isinstance(item, recipe_util.Placeholder):
232 module_name, placeholder_name = item.name_pieces
233 tdata = step_test.pop_placeholder(item.name_pieces)
234 new_cmd.extend(item.render(tdata))
235 placeholders[module_name][placeholder_name].append((item, tdata))
236 else:
237 new_cmd.append(item)
238 step['cmd'] = new_cmd
239
240 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given.
241 stdio_placeholders = {}
242 for key in ('stdout', 'stderr', 'stdin'):
243 placeholder = step.get(key)
244 tdata = None
245 if placeholder:
246 assert isinstance(placeholder, recipe_util.Placeholder), key
247 tdata = getattr(step_test, key)
248 placeholder.render(tdata)
249 assert placeholder.backing_file
250 step[key] = placeholder.backing_file
251 stdio_placeholders[key] = (placeholder, tdata)
252
253 return Placeholders(cmd=placeholders, **stdio_placeholders)
254
255
256 def get_placeholder_results(step_result, placeholders):
257 class BlankObject(object):
258 pass
259
260 # Placeholders inside step |cmd|.
261 for module_name, pholders in placeholders.cmd.iteritems():
262 assert not hasattr(step_result, module_name)
263 o = BlankObject()
264 setattr(step_result, module_name, o)
265
266 for placeholder_name, items in pholders.iteritems():
267 lst = [ph.result(step_result.presentation, td) for ph, td in items]
268 setattr(o, placeholder_name+"_all", lst)
269 setattr(o, placeholder_name, lst[0])
270
271 # Placeholders that are used with IO redirection.
272 for key in ('stdout', 'stderr', 'stdin'):
273 assert not hasattr(step_result, key)
274 ph, td = getattr(placeholders, key)
275 result = ph.result(step_result.presentation, td) if ph else None
276 setattr(step_result, key, result)
277
278
279 def get_callable_name(func):
280 """Returns __name__ of a callable, handling functools.partial types."""
281 if isinstance(func, functools.partial):
282 return get_callable_name(func.func)
283 else:
284 return func.__name__
285
286
287 def get_args(argv):
288 """Process command-line arguments."""
289
290 parser = optparse.OptionParser(
291 description='Entry point for annotated builds.')
292 parser.add_option('--build-properties',
293 action='callback', callback=chromium_utils.convert_json,
294 type='string', default={},
295 help='build properties in JSON format')
296 parser.add_option('--factory-properties',
297 action='callback', callback=chromium_utils.convert_json,
298 type='string', default={},
299 help='factory properties in JSON format')
300 parser.add_option('--build-properties-gz',
301 action='callback', callback=chromium_utils.convert_gz_json,
302 type='string', default={}, dest='build_properties',
303 help='build properties in b64 gz JSON format')
304 parser.add_option('--factory-properties-gz',
305 action='callback', callback=chromium_utils.convert_gz_json,
306 type='string', default={}, dest='factory_properties',
307 help='factory properties in b64 gz JSON format')
308 parser.add_option('--keep-stdin', action='store_true', default=False,
309 help='don\'t close stdin when running recipe steps')
310 return parser.parse_args(argv)
311
312
313 def main(argv=None):
314 opts, _ = get_args(argv)
315
316 stream = annotator.StructuredAnnotationStream()
317 universe = recipe_loader.RecipeUniverse()
318
319 ret = run_steps(stream, opts.build_properties, opts.factory_properties,
320 universe)
321 return ret.status_code
322
323
324 # Return value of run_steps and RecipeEngine.run.
325 RecipeExecutionResult = collections.namedtuple(
326 'RecipeExecutionResult', 'status_code steps_ran')
327 21
328 22
329 def get_recipe_properties(factory_properties, build_properties): 23 def get_recipe_properties(factory_properties, build_properties):
330 """Constructs the recipe's properties from buildbot's properties. 24 """Constructs the recipe's properties from buildbot's properties.
331 25
332 This merges factory_properties and build_properties. Furthermore, it 26 This merges factory_properties and build_properties. Furthermore, it
333 tries to reconstruct the 'recipe' property from builders.pyl if it isn't 27 tries to reconstruct the 'recipe' property from builders.pyl if it isn't
334 already there, and in that case merges in properties form builders.pyl. 28 already there, and in that case merges in properties form builders.pyl.
335 """ 29 """
336 properties = factory_properties.copy() 30 properties = factory_properties.copy()
(...skipping 15 matching lines...) Expand all
352 # Update properties with builders.pyl data. 46 # Update properties with builders.pyl data.
353 properties['recipe'] = builder['recipe'] 47 properties['recipe'] = builder['recipe']
354 properties.update(builder.get('properties', {})) 48 properties.update(builder.get('properties', {}))
355 else: 49 else:
356 raise LookupError('Cannot find recipe for %s on %s' % 50 raise LookupError('Cannot find recipe for %s on %s' %
357 (build_properties['buildername'], 51 (build_properties['buildername'],
358 build_properties['mastername'])) 52 build_properties['mastername']))
359 return properties 53 return properties
360 54
361 55
362 def run_steps(stream, build_properties, factory_properties, 56 def get_args(argv):
363 universe, test_data=recipe_test_api.DisabledTestData()): 57 """Process command-line arguments."""
364 """Returns a tuple of (status_code, steps_ran).
365 58
366 Only one of these values will be set at a time. This is mainly to support the 59 parser = optparse.OptionParser(
367 testing interface used by unittests/recipes_test.py. 60 description='Entry point for annotated builds.')
368 """ 61 parser.add_option('--build-properties',
369 stream.honor_zero_return_code() 62 action='callback', callback=chromium_utils.convert_json,
370 63 type='string', default={},
371 # TODO(iannucci): Stop this when blamelist becomes sane data. 64 help='build properties in JSON format')
372 if ('blamelist_real' in build_properties and 65 parser.add_option('--factory-properties',
373 'blamelist' in build_properties): 66 action='callback', callback=chromium_utils.convert_json,
374 build_properties['blamelist'] = build_properties['blamelist_real'] 67 type='string', default={},
375 del build_properties['blamelist_real'] 68 help='factory properties in JSON format')
376 69 parser.add_option('--build-properties-gz',
377 # NOTE(iannucci): 'root' was a terribly bad idea and has been replaced by 70 action='callback', callback=chromium_utils.convert_gz_json,
378 # 'patch_project'. 'root' had Rietveld knowing about the implementation of 71 type='string', default={}, dest='build_properties',
379 # the builders. 'patch_project' lets the builder (recipe) decide its own 72 help='build properties in b64 gz JSON format')
380 # destiny. 73 parser.add_option('--factory-properties-gz',
381 build_properties.pop('root', None) 74 action='callback', callback=chromium_utils.convert_gz_json,
382 75 type='string', default={}, dest='factory_properties',
383 properties = get_recipe_properties( 76 help='factory properties in b64 gz JSON format')
384 factory_properties=factory_properties, 77 parser.add_option('--keep-stdin', action='store_true', default=False,
385 build_properties=build_properties) 78 help='don\'t close stdin when running recipe steps')
386 79 return parser.parse_args(argv)
387 # TODO(iannucci): A much better way to do this would be to dynamically
388 # detect if the mirrors are actually available during the execution of the
389 # recipe.
390 if ('use_mirror' not in properties and (
391 'TESTING_MASTERNAME' in os.environ or
392 'TESTING_SLAVENAME' in os.environ)):
393 properties['use_mirror'] = False
394
395 # It's an integration point with a new recipe engine that can run steps
396 # in parallel (that is not implemented yet). Use new engine only if explicitly
397 # asked by setting 'engine' property to 'ParallelRecipeEngine'.
398 engine = RecipeEngine.create(stream, properties, test_data)
399
400 # Create all API modules and an instance of top level GenSteps generator.
401 # It doesn't launch any recipe code yet (generator needs to be iterated upon
402 # to start executing code).
403 api = None
404 with stream.step('setup_build') as s:
405 assert 'recipe' in properties # Should be ensured by get_recipe_properties.
406 recipe = properties['recipe']
407
408 properties_to_print = properties.copy()
409 if 'use_mirror' in properties:
410 del properties_to_print['use_mirror']
411
412 run_recipe_help_lines = [
413 'To repro this locally, run the following line from a build checkout:',
414 '',
415 './scripts/tools/run_recipe.py %s --properties-file - <<EOF' % recipe,
416 repr(properties_to_print),
417 'EOF',
418 '',
419 'To run on Windows, you can put the JSON in a file and redirect the',
420 'contents of the file into run_recipe.py, with the < operator.',
421 ]
422
423 for line in run_recipe_help_lines:
424 s.step_log_line('run_recipe', line)
425 s.step_log_end('run_recipe')
426
427 try:
428 recipe_module = universe.load_recipe(recipe)
429 stream.emit('Running recipe with %s' % (properties,))
430 api = recipe_loader.create_recipe_api(recipe_module.LOADED_DEPS,
431 engine,
432 test_data)
433 steps = recipe_module.GenSteps
434 s.step_text('<br/>running recipe: "%s"' % recipe)
435 except recipe_loader.NoSuchRecipe as e:
436 s.step_text('<br/>recipe not found: %s' % e)
437 s.step_failure()
438 return RecipeExecutionResult(2, None)
439
440 # Run the steps emitted by a recipe via the engine, emitting annotations
441 # into |stream| along the way.
442 return engine.run(steps, api)
443
444
445 class RecipeEngine(object):
446 """Knows how to execute steps emitted by a recipe, holds global state such as
447 step history and build properties. Each recipe module API has a reference to
448 this object.
449
450 Recipe modules that are aware of the engine:
451 * properties - uses engine.properties.
452 * step_history - uses engine.step_history.
453 * step - uses engine.create_step(...).
454
455 This class acts mostly as a documentation of expected public engine interface.
456 """
457
458 @staticmethod
459 def create(stream, properties, test_data):
460 """Create a new instance of RecipeEngine based on 'engine' property."""
461 engine_cls_name = properties.get('engine', 'SequentialRecipeEngine')
462 for cls in RecipeEngine.__subclasses__():
463 if cls.__name__ == engine_cls_name:
464 return cls(stream, properties, test_data)
465 raise ValueError('Invalid engine class: %s' % (engine_cls_name,))
466
467 @property
468 def properties(self):
469 """Global properties, merged --build_properties and --factory_properties."""
470 raise NotImplementedError
471
472 # TODO(martiniss) update documentation for this class
473 def run(self, steps_function, api):
474 """Run a recipe represented by top level GenSteps generator.
475
476 This function blocks until recipe finishes.
477
478 Args:
479 generator: instance of GenSteps generator.
480
481 Returns:
482 RecipeExecutionResult with status code and list of steps ran.
483 """
484 raise NotImplementedError
485
486 def create_step(self, step):
487 """Called by step module to instantiate a new step. Return value of this
488 function eventually surfaces as object yielded by GenSteps generator.
489
490 Args:
491 step: ConfigGroup object with information about the step, see
492 recipe_modules/step/config.py.
493
494 Returns:
495 Opaque engine specific object that is understood by 'run_steps' method.
496 """
497 raise NotImplementedError
498
499
500 class SequentialRecipeEngine(RecipeEngine):
501 """Always runs step sequentially. Currently the engine used by default."""
502 def __init__(self, stream, properties, test_data):
503 super(SequentialRecipeEngine, self).__init__()
504 self._stream = stream
505 self._properties = properties
506 self._test_data = test_data
507 self._step_history = collections.OrderedDict()
508
509 self._previous_step_annotation = None
510 self._previous_step_result = None
511 self._api = None
512
513 @property
514 def properties(self):
515 return self._properties
516
517 @property
518 def previous_step_result(self):
519 """Allows api.step to get the active result from any context."""
520 return self._previous_step_result
521
522 def _emit_results(self):
523 annotation = self._previous_step_annotation
524 step_result = self._previous_step_result
525
526 self._previous_step_annotation = None
527 self._previous_step_result = None
528
529 if not annotation or not step_result:
530 return
531
532 step_result.presentation.finalize(annotation)
533 if self._test_data.enabled:
534 val = annotation.stream.getvalue()
535 lines = filter(None, val.splitlines())
536 if lines:
537 # note that '~' sorts after 'z' so that this will be last on each
538 # step. also use _step to get access to the mutable step
539 # dictionary.
540 # pylint: disable=w0212
541 step_result._step['~followup_annotations'] = lines
542 annotation.step_ended()
543
544 def run_step(self, step):
545 ok_ret = step.pop('ok_ret')
546 infra_step = step.pop('infra_step')
547
548 test_data_fn = step.pop('step_test_data', recipe_test_api.StepTestData)
549 step_test = self._test_data.pop_step_test_data(step['name'],
550 test_data_fn)
551 placeholders = render_step(step, step_test)
552
553 self._step_history[step['name']] = step
554 self._emit_results()
555
556 step_result = None
557
558 if not self._test_data.enabled:
559 self._previous_step_annotation, retcode = annotator.run_step(
560 self._stream, **step)
561
562 step_result = StepData(step, retcode)
563 self._previous_step_annotation.annotation_stream.step_cursor(step['name'])
564 else:
565 self._previous_step_annotation = annotation = self._stream.step(
566 step['name'])
567 annotation.step_started()
568 try:
569 annotation.stream = cStringIO.StringIO()
570
571 step_result = StepData(step, step_test.retcode)
572 except OSError:
573 exc_type, exc_value, exc_tb = sys.exc_info()
574 trace = traceback.format_exception(exc_type, exc_value, exc_tb)
575 trace_lines = ''.join(trace).split('\n')
576 annotation.write_log_lines('exception', filter(None, trace_lines))
577 annotation.step_exception()
578
579 get_placeholder_results(step_result, placeholders)
580 self._previous_step_result = step_result
581
582 if step_result.retcode in ok_ret:
583 step_result.presentation.status = 'SUCCESS'
584 return step_result
585 else:
586 if not infra_step:
587 state = 'FAILURE'
588 exc = recipe_api.StepFailure
589 else:
590 state = 'EXCEPTION'
591 exc = recipe_api.InfraFailure
592
593 step_result.presentation.status = state
594 if step_test.enabled:
595 # To avoid cluttering the expectations, don't emit this in testmode.
596 self._previous_step_annotation.emit(
597 'step returned non-zero exit code: %d' % step_result.retcode)
598
599 raise exc(step['name'], step_result)
600
601
602 def run(self, steps_function, api):
603 self._api = api
604 retcode = None
605 final_result = None
606
607 try:
608 try:
609 retcode = steps_function(api)
610 assert retcode is None, (
611 "Non-None return from GenSteps is not supported yet")
612
613 assert not self._test_data.enabled or not self._test_data.step_data, (
614 "Unconsumed test data! %s" % (self._test_data.step_data,))
615 finally:
616 self._emit_results()
617 except recipe_api.StepFailure as f:
618 retcode = f.retcode or 1
619 final_result = {
620 "name": "$final_result",
621 "reason": f.reason,
622 "status_code": retcode
623 }
624
625 except Exception as ex:
626 unexpected_exception = self._test_data.is_unexpected_exception(ex)
627
628 retcode = -1
629 final_result = {
630 "name": "$final_result",
631 "reason": "Uncaught Exception: %r" % ex,
632 "status_code": retcode
633 }
634
635 with self._stream.step('Uncaught Exception') as s:
636 s.step_exception()
637 s.write_log_lines('exception', traceback.format_exc().splitlines())
638
639 if unexpected_exception:
640 raise
641
642 if final_result is not None:
643 self._step_history[final_result['name']] = final_result
644
645 return RecipeExecutionResult(retcode, self._step_history)
646
647 def create_step(self, step): # pylint: disable=R0201
648 # This version of engine doesn't do anything, just converts step to dict
649 # (that is consumed by annotator engine).
650 return step.as_jsonish()
651
652
653 class ParallelRecipeEngine(RecipeEngine):
654 """New engine that knows how to run steps in parallel.
655
656 TODO(vadimsh): Implement it.
657 """
658
659 def __init__(self, stream, properties, test_data):
660 super(ParallelRecipeEngine, self).__init__()
661 self._stream = stream
662 self._properties = properties
663 self._test_data = test_data
664
665 @property
666 def properties(self):
667 return self._properties
668
669 def run(self, steps_function, api):
670 raise NotImplementedError
671
672 def create_step(self, step):
673 raise NotImplementedError
674 80
675 81
676 def update_scripts(): 82 def update_scripts():
677 if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'): 83 if os.environ.get('RUN_SLAVE_UPDATED_SCRIPTS'):
678 os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS') 84 os.environ.pop('RUN_SLAVE_UPDATED_SCRIPTS')
679 return False 85 return False
680 86
681 stream = annotator.StructuredAnnotationStream() 87 stream = annotator.StructuredAnnotationStream()
682 88
683 with stream.step('update_scripts') as s: 89 with stream.step('update_scripts') as s:
684 build_root = os.path.join(SCRIPT_PATH, '..', '..')
685 gclient_name = 'gclient' 90 gclient_name = 'gclient'
686 if sys.platform.startswith('win'): 91 if sys.platform.startswith('win'):
687 gclient_name += '.bat' 92 gclient_name += '.bat'
688 gclient_path = os.path.join(build_root, '..', 'depot_tools', gclient_name) 93 gclient_path = os.path.join(BUILD_ROOT, '..', 'depot_tools', gclient_name)
689 gclient_cmd = [gclient_path, 'sync', '--force', '--verbose'] 94 gclient_cmd = [gclient_path, 'sync', '--force', '--verbose']
690 cmd_dict = { 95 cmd_dict = {
691 'name': 'update_scripts', 96 'name': 'update_scripts',
692 'cmd': gclient_cmd, 97 'cmd': gclient_cmd,
693 'cwd': build_root, 98 'cwd': BUILD_ROOT,
694 } 99 }
695 annotator.print_step(cmd_dict, os.environ, stream) 100 annotator.print_step(cmd_dict, os.environ, stream)
696 if subprocess.call(gclient_cmd, cwd=build_root) != 0: 101 if subprocess.call(gclient_cmd, cwd=BUILD_ROOT) != 0:
697 s.step_text('gclient sync failed!') 102 s.step_text('gclient sync failed!')
698 s.step_warnings() 103 s.step_warnings()
iannucci 2015/05/27 02:03:27 wtf spacing?
luqui 2015/05/28 21:47:37 No trouble here. I think it's the new rietveld ui.
699 os.environ['RUN_SLAVE_UPDATED_SCRIPTS'] = '1' 104 os.environ['RUN_SLAVE_UPDATED_SCRIPTS'] = '1'
700 105
701 # After running update scripts, set PYTHONIOENCODING=UTF-8 for the real 106 # After running update scripts, set PYTHONIOENCODING=UTF-8 for the real
702 # annotated_run. 107 # annotated_run.
703 os.environ['PYTHONIOENCODING'] = 'UTF-8' 108 os.environ['PYTHONIOENCODING'] = 'UTF-8'
704 109
705 return True 110 return True
706 111
707 112
113 def main(argv):
114 opts, _ = get_args(argv)
115 properties = get_recipe_properties(
116 opts.factory_properties, opts.build_properties)
117 stream = annotator.StructuredAnnotationStream()
118 annotated_run.run_steps(properties, stream,
119 universe=recipe_universe.get_universe())
120
121
708 def shell_main(argv): 122 def shell_main(argv):
709 if update_scripts(): 123 if update_scripts():
710 return subprocess.call([sys.executable] + argv) 124 return subprocess.call([sys.executable] + argv)
711 else: 125 else:
712 return main(argv) 126 return main(argv)
713 127
714
715 if __name__ == '__main__': 128 if __name__ == '__main__':
716 sys.exit(shell_main(sys.argv)) 129 sys.exit(shell_main(sys.argv))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698