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

Unified Diff: third_party/recipe_engine/recipe_api.py

Issue 1241323004: Cross-repo recipe package system. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Roll to latest recipes-py Created 5 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/recipe_engine/main.py ('k') | third_party/recipe_engine/recipe_test_api.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/recipe_engine/recipe_api.py
diff --git a/third_party/recipe_engine/recipe_api.py b/third_party/recipe_engine/recipe_api.py
deleted file mode 100644
index 72661a9eda4b737a25d80f5c4b2ae27bedd0039c..0000000000000000000000000000000000000000
--- a/third_party/recipe_engine/recipe_api.py
+++ /dev/null
@@ -1,525 +0,0 @@
-# Copyright 2013-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.
-
-import contextlib
-import keyword
-import types
-
-from functools import wraps
-
-from .recipe_test_api import DisabledTestData, ModuleTestData
-from .config import Single
-
-from .util import ModuleInjectionSite
-
-from . import field_composer
-
-
-class StepFailure(Exception):
- """
- This is the base class for all step failures.
-
- Raising a StepFailure counts as 'running a step' for the purpose of
- infer_composite_step's logic.
- """
- def __init__(self, name_or_reason, result=None):
- _STEP_CONTEXT['ran_step'][0] = True
- if result:
- self.name = name_or_reason
- self.result = result
- self.reason = self.reason_message()
- else:
- self.name = None
- self.result = None
- self.reason = name_or_reason
-
- super(StepFailure, self).__init__(self.reason)
-
- def reason_message(self):
- return "Step({!r}) failed with return_code {}".format(
- self.name, self.result.retcode)
-
- def __str__(self): # pragma: no cover
- return "Step Failure in %s" % self.name
-
- @property
- def retcode(self):
- """
- Returns the retcode of the step which failed. If this was a manual
- failure, returns None
- """
- if not self.result:
- return None
- return self.result.retcode
-
-
-class StepWarning(StepFailure):
- """
- A subclass of StepFailure, which still fails the build, but which is
- a warning. Need to figure out how exactly this will be useful.
- """
- def reason_message(self): # pragma: no cover
- return "Warning: Step({!r}) returned {}".format(
- self.name, self.result.retcode)
-
- def __str__(self): # pragma: no cover
- return "Step Warning in %s" % self.name
-
-
-class InfraFailure(StepFailure):
- """
- A subclass of StepFailure, which fails the build due to problems with the
- infrastructure.
- """
- def reason_message(self):
- return "Infra Failure: Step({!r}) returned {}".format(
- self.name, self.result.retcode)
-
- def __str__(self):
- return "Infra Failure in %s" % self.name
-
-
-class AggregatedStepFailure(StepFailure):
- def __init__(self, result):
- super(AggregatedStepFailure, self).__init__(
- "Aggregate step failure.", result=result)
-
- def reason_message(self):
- msg = "{!r} out of {!r} aggregated steps failed. Failures: ".format(
- len(self.result.failures), len(self.result.all_results))
- msg += ', '.join((f.reason or f.name) for f in self.result.failures)
- return msg
-
- def __str__(self): # pragma: no cover
- return "Aggregate Step Failure"
-
-
-_FUNCTION_REGISTRY = {
- 'aggregated_result': {'combine': lambda a, b: b},
- 'env': {'combine': lambda a, b: dict(a, **b)},
- 'name': {'combine': lambda a, b: '%s.%s' % (a, b)},
- 'nest_level': {'combine': lambda a, b: a + b},
- 'ran_step': {'combine': lambda a, b: b},
-}
-
-
-class AggregatedResult(object):
- """Holds the result of an aggregated run of steps.
-
- Currently this is only used internally by defer_results, but it may be exposed
- to the consumer of defer_results at some point in the future. For now it's
- expected to be easier for defer_results consumers to do their own result
- aggregation, as they may need to pick and chose (or label) which results they
- really care about.
- """
- def __init__(self):
- self.successes = []
- self.failures = []
-
- # Needs to be here to be able to treat this as a step result
- self.retcode = None
-
- @property
- def all_results(self):
- """
- Return a list of two item tuples (x, y), where
- x is whether or not the step succeeded, and
- y is the result of the run
- """
- res = [(True, result) for result in self.successes]
- res.extend([(False, result) for result in self.failures])
- return res
-
- def add_success(self, result):
- self.successes.append(result)
-
- def add_failure(self, exception):
- self.failures.append(exception)
-
-
-class DeferredResult(object):
- def __init__(self, result, failure):
- self._result = result
- self._failure = failure
-
- @property
- def is_ok(self):
- return self._failure is None
-
- def get_result(self):
- if not self.is_ok:
- raise self.get_error()
- return self._result
-
- def get_error(self):
- assert self._failure, "WHAT IS IT ARE YOU DOING???!?!?!? SHTAP NAO"
- return self._failure
-
-
-_STEP_CONTEXT = field_composer.FieldComposer(
- {'ran_step': [False]}, _FUNCTION_REGISTRY)
-
-
-def non_step(func):
- """A decorator which prevents a method from automatically being wrapped as
- a infer_composite_step by RecipeApiMeta.
-
- This is needed for utility methods which don't run any steps, but which are
- invoked within the context of a defer_results().
-
- @see infer_composite_step, defer_results, RecipeApiMeta
- """
- assert not hasattr(func, "_skip_inference"), \
- "Double-wrapped method %r?" % func
- func._skip_inference = True # pylint: disable=protected-access
- return func
-
-_skip_inference = non_step
-
-
-@contextlib.contextmanager
-def context(fields):
- global _STEP_CONTEXT
- old = _STEP_CONTEXT
- try:
- _STEP_CONTEXT = old.compose(fields)
- yield
- finally:
- _STEP_CONTEXT = old
-
-
-def infer_composite_step(func):
- """A decorator which possibly makes this step act as a single step, for the
- purposes of the defer_results function.
-
- Behaves as if this function were wrapped by composite_step, unless this
- function:
- * is already wrapped by non_step
- * returns a result without calling api.step
- * raises an exception which is not derived from StepFailure
-
- In any of these cases, this function will behave like a normal function.
-
- This decorator is automatically applied by RecipeApiMeta (or by inheriting
- from RecipeApi). If you want to decalare a method's behavior explicitly, you
- may decorate it with either composite_step or with non_step.
- """
- if getattr(func, "_skip_inference", False):
- return func
-
- @_skip_inference # to prevent double-wraps
- @wraps(func)
- def _inner(*a, **kw):
- # We're not in a defer_results context, so just run the function normally.
- if _STEP_CONTEXT.get('aggregated_result') is None:
- return func(*a, **kw)
-
- agg = _STEP_CONTEXT['aggregated_result']
-
- # Setting the aggregated_result to None allows the contents of func to be
- # written in the same style (e.g. with exceptions) no matter how func is
- # being called.
- with context({'aggregated_result': None, 'ran_step': [False]}):
- try:
- ret = func(*a, **kw)
- if not _STEP_CONTEXT.get('ran_step', [False])[0]:
- return ret
- agg.add_success(ret)
- return DeferredResult(ret, None)
- except StepFailure as ex:
- agg.add_failure(ex)
- return DeferredResult(None, ex)
- return _inner
-
-
-def composite_step(func):
- """A decorator which makes this step act as a single step, for the purposes of
- the defer_results function.
-
- This means that this function will not quit during the middle of its execution
- because of a StepFailure, if there is an aggregator active.
-
- You may use this decorator explicitly if infer_composite_step is detecting
- the behavior of your method incorrectly to force it to behave as a step. You
- may also need to use this if your Api class inherits from RecipeApiPlain and
- so doesn't have its methods automatically wrapped by infer_composite_step.
- """
- @_skip_inference # to avoid double-wraps
- @wraps(func)
- def _inner(*a, **kw):
- # always counts as running a step
- _STEP_CONTEXT['ran_step'][0] = True
-
- if _STEP_CONTEXT.get('aggregated_result') is None:
- return func(*a, **kw)
-
- agg = _STEP_CONTEXT['aggregated_result']
-
- # Setting the aggregated_result to None allows the contents of func to be
- # written in the same style (e.g. with exceptions) no matter how func is
- # being called.
- with context({'aggregated_result': None}):
- try:
- ret = func(*a, **kw)
- agg.add_success(ret)
- return DeferredResult(ret, None)
- except StepFailure as ex:
- agg.add_failure(ex)
- return DeferredResult(None, ex)
- return _inner
-
-
-@contextlib.contextmanager
-def defer_results():
- """
- Use this to defer step results in your code. All steps which would previously
- return a result or throw an exception will instead return a DeferredResult.
-
- Any exceptions which were thrown during execution will be thrown when either:
- a. You call get_result() on the step's result.
- b. You exit the suite inside of the with statement
-
- Example:
- with defer_results():
- api.step('a', ..)
- api.step('b', ..)
- result = api.m.module.im_a_composite_step(...)
- api.m.echo('the data is', result.get_result())
-
- If 'a' fails, 'b' and 'im a composite step' will still run.
- If 'im a composite step' fails, then the get_result() call will raise
- an exception.
- If you don't try to use the result (don't call get_result()), an aggregate
- failure will still be raised once you exit the suite inside
- the with statement.
- """
- assert _STEP_CONTEXT.get('aggregated_result') is None, (
- "may not call defer_results in an active defer_results context")
- agg = AggregatedResult()
- with context({'aggregated_result': agg}):
- yield
- if agg.failures:
- raise AggregatedStepFailure(agg)
-
-
-class RecipeApiMeta(type):
- WHITELIST = ('__init__',)
- def __new__(mcs, name, bases, attrs):
- """Automatically wraps all methods of subclasses of RecipeApi with
- @infer_composite_step. This allows defer_results to work as intended without
- manually decorating every method.
- """
- wrap = lambda f: infer_composite_step(f) if f else f
- for attr in attrs:
- if attr in RecipeApiMeta.WHITELIST:
- continue
- val = attrs[attr]
- if isinstance(val, types.FunctionType):
- attrs[attr] = wrap(val)
- elif isinstance(val, property):
- attrs[attr] = property(
- wrap(val.fget),
- wrap(val.fset),
- wrap(val.fdel),
- val.__doc__)
- return super(RecipeApiMeta, mcs).__new__(mcs, name, bases, attrs)
-
-
-class RecipeApiPlain(ModuleInjectionSite):
- """
- Framework class for handling recipe_modules.
-
- Inherit from this in your recipe_modules/<name>/api.py . This class provides
- wiring for your config context (in self.c and methods, and for dependency
- injection (in self.m).
-
- Dependency injection takes place in load_recipe_modules() in recipe_loader.py.
-
- USE RecipeApi INSTEAD, UNLESS your RecipeApi subclass derives from something
- which defines its own __metaclass__. Deriving from RecipeApi instead of
- RecipeApiPlain allows your RecipeApi subclass to automatically work with
- defer_results without needing to decorate every methods with
- @infer_composite_step.
- """
-
- def __init__(self, module=None, engine=None,
- test_data=DisabledTestData(), **_kwargs):
- """Note: Injected dependencies are NOT available in __init__()."""
- super(RecipeApiPlain, self).__init__()
-
- # |engine| is an instance of annotated_run.RecipeEngine. Modules should not
- # generally use it unless they're low-level framework level modules.
- self._engine = engine
- self._module = module
-
- assert isinstance(test_data, (ModuleTestData, DisabledTestData))
- self._test_data = test_data
-
- # If we're the 'root' api, inject directly into 'self'.
- # Otherwise inject into 'self.m'
- self.m = self if module is None else ModuleInjectionSite(self)
-
- # If our module has a test api, it gets injected here.
- self.test_api = None
-
- # Config goes here.
- self.c = None
-
- def get_config_defaults(self): # pylint: disable=R0201
- """
- Allows your api to dynamically determine static default values for configs.
- """
- return {}
-
- def make_config(self, config_name=None, optional=False, **CONFIG_VARS):
- """Returns a 'config blob' for the current API."""
- return self.make_config_params(config_name, optional, **CONFIG_VARS)[0]
-
- def make_config_params(self, config_name, optional=False, **CONFIG_VARS):
- """Returns a 'config blob' for the current API, and the computed params
- for all dependent configurations.
-
- The params have the following order of precendence. Each subsequent param
- is dict.update'd into the final parameters, so the order is from lowest to
- higest precedence on a per-key basis:
- * if config_name in CONFIG_CTX
- * get_config_defaults()
- * CONFIG_CTX[config_name].DEFAULT_CONFIG_VARS()
- * CONFIG_VARS
- * else
- * get_config_defaults()
- * CONFIG_VARS
- """
- generic_params = self.get_config_defaults() # generic defaults
- generic_params.update(CONFIG_VARS) # per-invocation values
-
- ctx = self._module.CONFIG_CTX
- if optional and not ctx:
- return None, generic_params
-
- assert ctx, '%s has no config context' % self
- try:
- params = self.get_config_defaults() # generic defaults
- itm = ctx.CONFIG_ITEMS[config_name] if config_name else None
- if itm:
- params.update(itm.DEFAULT_CONFIG_VARS()) # per-item defaults
- params.update(CONFIG_VARS) # per-invocation values
-
- base = ctx.CONFIG_SCHEMA(**params)
- if config_name is None:
- return base, params
- else:
- return itm(base), params
- except KeyError:
- if optional:
- return None, generic_params
- else: # pragma: no cover
- raise # TODO(iannucci): raise a better exception.
-
- def set_config(self, config_name=None, optional=False, **CONFIG_VARS):
- """Sets the modules and its dependencies to the named configuration."""
- assert self._module
- config, params = self.make_config_params(config_name, optional,
- **CONFIG_VARS)
- if config:
- self.c = config
-
- def apply_config(self, config_name, config_object=None, optional=False):
- """Apply a named configuration to the provided config object or self."""
- self._module.CONFIG_CTX.CONFIG_ITEMS[config_name](
- config_object or self.c, optional=optional)
-
- def resource(self, *path):
- """Returns path to a file under <recipe module>/resources/ directory.
-
- Args:
- path: path relative to module's resources/ directory.
- """
- # TODO(vadimsh): Verify that file exists. Including a case like:
- # module.resource('dir').join('subdir', 'file.py')
- return self._module.MODULE_DIRECTORY.join('resources', *path)
-
- @property
- def name(self):
- return self._module.NAME
-
-
-class RecipeApi(RecipeApiPlain):
- __metaclass__ = RecipeApiMeta
-
-
-class Property(object):
- sentinel = object()
-
- @staticmethod
- def legal_name(name):
- if name.startswith('_'):
- return False
-
- if name in ('self',):
- return False
-
- if keyword.iskeyword(name):
- return False
-
- return True
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- if not Property.legal_name(name):
- raise ValueError("Illegal name '{}'".format(name))
-
- self._name = name
-
- def __init__(self, default=sentinel, help="", kind=None):
- """
- Constructor for Property.
-
- Args:
- default: The default value for this Property. Note: A default
- value of None is allowed. To have no default value, omit
- this argument.
- help: The help text for this Property.
- type: The type of this Property. You can either pass in a raw python
- type, or a Config Type, using the recipe engine config system.
- """
- self._default = default
- self.help = help
- self._name = None
-
- if isinstance(kind, type):
- kind = Single(kind)
- self.kind = kind
-
- def interpret(self, value):
- """
- Interprets the value for this Property.
-
- Args:
- value: The value to interpret. May be None, which
- means no value provided.
-
- Returns:
- The value to use for this property. Raises an error if
- this property has no valid interpretation.
- """
- if value is not Property.sentinel:
- if self.kind is not None:
- # The config system handles type checking for us here.
- self.kind.set_val(value)
- return value
-
- if self._default is not Property.sentinel:
- return self._default
-
- raise ValueError(
- "No default specified and no value provided for '{}'".format(
- self.name))
-
-class UndefinedPropertyException(TypeError):
- pass
« no previous file with comments | « third_party/recipe_engine/main.py ('k') | third_party/recipe_engine/recipe_test_api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698