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

Unified Diff: scripts/common/annotator.py

Issue 1151423002: Move recipe engine to third_party/recipe_engine. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Moved field_composer_test with its buddies 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | scripts/common/unittests/annotator_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: scripts/common/annotator.py
diff --git a/scripts/common/annotator.py b/scripts/common/annotator.py
old mode 100755
new mode 100644
index 497f7c131169bc12dee48772604c91ab43f6ed04..d52afb0c838994a3e15568db503e56671ed96f7e
--- a/scripts/common/annotator.py
+++ b/scripts/common/annotator.py
@@ -1,30 +1,13 @@
-#!/usr/bin/env python
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Copyright (c) 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-"""Contains generating and parsing systems of the Chromium Buildbot Annotator.
+"""Contains the parsing system of the Chromium Buildbot Annotator."""
-When executed as a script, this reads step name / command pairs from a file and
-executes those lines while annotating the output. The input is json:
-
-[{"name": "step_name", "cmd": ["command", "arg1", "arg2"]},
- {"name": "step_name2", "cmd": ["command2", "arg1"]}]
-
-"""
-
-import calendar
-import contextlib
-import datetime
-import json
-import optparse
import os
-import subprocess
import sys
-import threading
import traceback
-
# These are maps of annotation key -> number of expected arguments.
STEP_ANNOTATIONS = {
'SET_BUILD_PROPERTY': 2,
@@ -192,6 +175,7 @@ class StepCommands(AnnotationPrinter):
else:
self.step_log_end(logname)
+
class StepControlCommands(AnnotationPrinter):
"""Subclass holding step control commands. Intended to be subclassed.
@@ -201,7 +185,7 @@ class StepControlCommands(AnnotationPrinter):
ANNOTATIONS = CONTROL_ANNOTATIONS
-class StructuredAnnotationStep(StepCommands):
+class StructuredAnnotationStep(StepCommands, StepControlCommands):
"""Helper class to provide context for a step."""
def __init__(self, annotation_stream, *args, **kwargs):
@@ -241,29 +225,8 @@ class StructuredAnnotationStep(StepCommands):
return True
-class AdvancedAnnotationStep(StepCommands, StepControlCommands):
- """Holds additional step functions for finer step control.
-
- Most users will want to use StructuredAnnotationSteps generated from a
- StructuredAnnotationStream as these handle state automatically.
- """
-
- def __init__(self, *args, **kwargs):
- super(AdvancedAnnotationStep, self).__init__(*args, **kwargs)
-
-
-class AdvancedAnnotationStream(AnnotationPrinter):
- """Holds individual annotation generating functions for streams.
- Most callers should use StructuredAnnotationStream to simplify coding and
- avoid errors. For the rare cases where StructuredAnnotationStream is
- insufficient (parallel step execution), the individual functions are exposed
- here.
- """
- ANNOTATIONS = STREAM_ANNOTATIONS
-
-
-class StructuredAnnotationStream(AdvancedAnnotationStream):
+class StructuredAnnotationStream(AnnotationPrinter):
"""Provides an interface to handle an annotated build.
StructuredAnnotationStream handles most of the step setup and closure calls
@@ -283,6 +246,7 @@ class StructuredAnnotationStream(AdvancedAnnotationStream):
if warnings:
s.step_warnings()
"""
+ ANNOTATIONS = STREAM_ANNOTATIONS
def __init__(self, stream=sys.stdout,
flush_before=sys.stderr,
@@ -357,39 +321,6 @@ def MatchAnnotation(line, callback_implementor):
fn(*args)
-def _merge_envs(original, override):
- """Merges two environments.
-
- Returns a new environment dict with entries from |override| overwriting
- corresponding entries in |original|. Keys whose value is None will completely
- remove the environment variable. Values can contain %(KEY)s strings, which
- will be substituted with the values from the original (useful for amending, as
- opposed to overwriting, variables like PATH).
- """
- result = original.copy()
- if not override:
- return result
- for k, v in override.items():
- if v is None:
- if k in result:
- del result[k]
- else:
- result[str(k)] = str(v) % original
- return result
-
-
-def _validate_step(step):
- """Validates parameters of the step.
- Returns None if it's OK, error message if not.
- """
- for req in ['cmd', 'name']:
- if req not in step:
- return 'missing \'%s\' parameter' % (req,)
- if 'cwd' in step and not os.path.isabs(step['cwd']):
- return '\'cwd\' should be an absolute path'
- return None
-
-
def print_step(step, env, stream):
"""Prints the step command and relevant metadata.
@@ -412,281 +343,3 @@ def print_step(step, env, stream):
step_info_lines.append(' %s: %s' % (key, value))
step_info_lines.append('')
stream.emit('\n'.join(step_info_lines))
-
-
-@contextlib.contextmanager
-def modify_lookup_path(path):
- """Places the specified path into os.environ.
-
- Necessary because subprocess.Popen uses os.environ to perform lookup on the
- supplied command, and only uses the |env| kwarg for modifying the environment
- of the child process.
- """
- saved_path = os.environ['PATH']
- try:
- if path is not None:
- os.environ['PATH'] = path
- yield
- finally:
- os.environ['PATH'] = saved_path
-
-
-def normalizeChange(change):
- assert isinstance(change, dict), 'Change is not a dict'
- change = change.copy()
-
- # Convert when_timestamp to UNIX timestamp.
- when = change.get('when_timestamp')
- if isinstance(when, datetime.datetime):
- when = calendar.timegm(when.utctimetuple())
- change['when_timestamp'] = when
-
- return change
-
-
-def triggerBuilds(step, trigger_specs):
- assert trigger_specs is not None
- for trig in trigger_specs:
- builder_name = trig.get('builder_name')
- if not builder_name:
- raise ValueError('Trigger spec: builder_name is not set')
-
- changes = trig.get('buildbot_changes', [])
- assert isinstance(changes, list), 'buildbot_changes must be a list'
- changes = map(normalizeChange, changes)
-
- step.step_trigger(json.dumps({
- 'builderNames': [builder_name],
- 'bucket': trig.get('bucket'),
- 'changes': changes,
- 'properties': trig.get('properties'),
- }, sort_keys=True))
-
-
-def run_step(stream, name, cmd,
- cwd=None, env=None,
- allow_subannotations=False,
- trigger_specs=None,
- **kwargs):
- """Runs a single step.
-
- Context:
- stream: StructuredAnnotationStream to use to emit step
-
- Step parameters:
- name: name of the step, will appear in buildbots waterfall
- cmd: command to run, list of one or more strings
- cwd: absolute path to working directory for the command
- env: dict with overrides for environment variables
- allow_subannotations: if True, lets the step emit its own annotations
- trigger_specs: a list of trigger specifications, which are dict with keys:
- properties: a dict of properties.
- Buildbot requires buildername property.
-
- Known kwargs:
- stdout: Path to a file to put step stdout into. If used, stdout won't appear
- in annotator's stdout (and |allow_subannotations| is ignored).
- stderr: Path to a file to put step stderr into. If used, stderr won't appear
- in annotator's stderr.
- stdin: Path to a file to read step stdin from.
-
- Returns the returncode of the step.
- """
- if isinstance(cmd, basestring):
- cmd = (cmd,)
- cmd = map(str, cmd)
-
- # For error reporting.
- step_dict = kwargs.copy()
- step_dict.update({
- 'name': name,
- 'cmd': cmd,
- 'cwd': cwd,
- 'env': env,
- 'allow_subannotations': allow_subannotations,
- })
- step_env = _merge_envs(os.environ, env)
-
- step_annotation = stream.step(name)
- step_annotation.step_started()
-
- print_step(step_dict, step_env, stream)
- returncode = 0
- if cmd:
- try:
- # Open file handles for IO redirection based on file names in step_dict.
- fhandles = {
- 'stdout': subprocess.PIPE,
- 'stderr': subprocess.PIPE,
- 'stdin': None,
- }
- for key in fhandles:
- if key in step_dict:
- fhandles[key] = open(step_dict[key],
- 'rb' if key == 'stdin' else 'wb')
-
- if sys.platform.startswith('win'):
- # Windows has a bad habit of opening a dialog when a console program
- # crashes, rather than just letting it crash. Therefore, when a program
- # crashes on Windows, we don't find out until the build step times out.
- # This code prevents the dialog from appearing, so that we find out
- # immediately and don't waste time waiting for a user to close the
- # dialog.
- import ctypes
- # SetErrorMode(SEM_NOGPFAULTERRORBOX). For more information, see:
- # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx
- ctypes.windll.kernel32.SetErrorMode(0x0002)
- # CREATE_NO_WINDOW. For more information, see:
- # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
- creationflags = 0x8000000
- else:
- creationflags = 0
-
- with modify_lookup_path(step_env.get('PATH')):
- proc = subprocess.Popen(
- cmd,
- env=step_env,
- cwd=cwd,
- universal_newlines=True,
- creationflags=creationflags,
- **fhandles)
-
- # Safe to close file handles now that subprocess has inherited them.
- for handle in fhandles.itervalues():
- if isinstance(handle, file):
- handle.close()
-
- outlock = threading.Lock()
- def filter_lines(lock, allow_subannotations, inhandle, outhandle):
- while True:
- line = inhandle.readline()
- if not line:
- break
- lock.acquire()
- try:
- if not allow_subannotations and line.startswith('@@@'):
- outhandle.write('!')
- outhandle.write(line)
- outhandle.flush()
- finally:
- lock.release()
-
- # Pump piped stdio through filter_lines. IO going to files on disk is
- # not filtered.
- threads = []
- for key in ('stdout', 'stderr'):
- if fhandles[key] == subprocess.PIPE:
- inhandle = getattr(proc, key)
- outhandle = getattr(sys, key)
- threads.append(threading.Thread(
- target=filter_lines,
- args=(outlock, allow_subannotations, inhandle, outhandle)))
-
- for th in threads:
- th.start()
- proc.wait()
- for th in threads:
- th.join()
- returncode = proc.returncode
- except OSError:
- # File wasn't found, error will be reported to stream when the exception
- # crosses the context manager.
- step_annotation.step_exception_occured(*sys.exc_info())
- raise
-
- # TODO(martiniss) move logic into own module?
- if trigger_specs:
- triggerBuilds(step_annotation, trigger_specs)
-
- return step_annotation, returncode
-
-def update_build_failure(failure, retcode, **_kwargs):
- """Potentially moves failure from False to True, depending on returncode of
- the run step and the step's configuration.
-
- can_fail_build: A boolean indicating that a bad retcode for this step should
- be intepreted as a build failure.
-
- Returns new value for failure.
-
- Called externally from annotated_run, which is why it's a separate function.
- """
- # TODO(iannucci): Allow step to specify "OK" return values besides 0?
- return failure or retcode
-
-def run_steps(steps, build_failure):
- for step in steps:
- error = _validate_step(step)
- if error:
- print 'Invalid step - %s\n%s' % (error, json.dumps(step, indent=2))
- sys.exit(1)
-
- stream = StructuredAnnotationStream()
- ret_codes = []
- build_failure = False
- prev_annotation = None
- for step in steps:
- if build_failure and not step.get('always_run', False):
- ret = None
- else:
- prev_annotation, ret = run_step(stream, **step)
- stream = prev_annotation.annotation_stream
- if ret > 0:
- stream.step_cursor(stream.current_step)
- stream.emit('step returned non-zero exit code: %d' % ret)
- prev_annotation.step_failure()
-
- prev_annotation.step_ended()
- build_failure = update_build_failure(build_failure, ret)
- ret_codes.append(ret)
- if prev_annotation:
- prev_annotation.step_ended()
- return build_failure, ret_codes
-
-
-def main():
- usage = '%s <command list file or - for stdin>' % sys.argv[0]
- parser = optparse.OptionParser(usage=usage)
- _, args = parser.parse_args()
- if not args:
- parser.error('Must specify an input filename.')
- if len(args) > 1:
- parser.error('Too many arguments specified.')
-
- steps = []
-
- def force_list_str(lst):
- ret = []
- for v in lst:
- if isinstance(v, basestring):
- v = str(v)
- elif isinstance(v, list):
- v = force_list_str(v)
- elif isinstance(v, dict):
- v = force_dict_strs(v)
- ret.append(v)
- return ret
-
- def force_dict_strs(obj):
- ret = {}
- for k, v in obj.iteritems():
- if isinstance(v, basestring):
- v = str(v)
- elif isinstance(v, list):
- v = force_list_str(v)
- elif isinstance(v, dict):
- v = force_dict_strs(v)
- ret[str(k)] = v
- return ret
-
- if args[0] == '-':
- steps.extend(json.load(sys.stdin, object_hook=force_dict_strs))
- else:
- with open(args[0], 'rb') as f:
- steps.extend(json.load(f, object_hook=force_dict_strs))
-
- return 1 if run_steps(steps, False)[0] else 0
-
-
-if __name__ == '__main__':
- sys.exit(main())
« no previous file with comments | « no previous file | scripts/common/unittests/annotator_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698