| Index: third_party/recipe_engine/config_types.py
|
| diff --git a/third_party/recipe_engine/config_types.py b/third_party/recipe_engine/config_types.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5dd216e1fcaed3a0494c83efb62110c980415e84
|
| --- /dev/null
|
| +++ b/third_party/recipe_engine/config_types.py
|
| @@ -0,0 +1,164 @@
|
| +# 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 abc
|
| +import re
|
| +
|
| +from collections import namedtuple
|
| +
|
| +from infra.libs import infra_types
|
| +
|
| +
|
| +RECIPE_MODULE_PREFIX = 'RECIPE_MODULES'
|
| +
|
| +
|
| +def ResetTostringFns():
|
| + RecipeConfigType._TOSTRING_MAP.clear() # pylint: disable=W0212
|
| +
|
| +
|
| +def json_fixup(obj):
|
| + if isinstance(obj, RecipeConfigType):
|
| + return str(obj)
|
| + thawed = infra_types.thaw(obj)
|
| + if thawed is not obj: # i.e. it was a frozen type
|
| + return thawed
|
| + raise TypeError("%r is not JSON serializable" % obj)
|
| +
|
| +
|
| +class RecipeConfigType(object):
|
| + """Base class for custom Recipe config types, intended to be subclassed.
|
| +
|
| + RecipeConfigTypes are meant to be PURE data. There should be no dependency on
|
| + any external systems (i.e. no importing sys, os, etc.).
|
| +
|
| + The subclasses should override default_tostring_fn. This method should
|
| + produce a string representation of the object. This string representation
|
| + should contain all of the data members of the subclass. This representation
|
| + will be used during the execution of the recipe_config_tests.
|
| +
|
| + External entities (usually recipe modules), can override the default
|
| + tostring_fn method by calling <RecipeConfigType
|
| + subclass>.set_tostring_fn(<new method>). This new method will receive an
|
| + instance of the RecipeConfigType subclass as its single argument, and is
|
| + expected to return a string. There is no restriction on the data that the
|
| + override tostring_fn may use. For example, the Path class in this module has
|
| + its tostring_fn overridden by the 'path' recipe_module. This new tostring_fn
|
| + uses data from the current recipe run, like the host os, to return platform
|
| + specific strings using the data in the Path object.
|
| + """
|
| + _TOSTRING_MAP = {}
|
| +
|
| + @property
|
| + def tostring_fn(self):
|
| + cls = self.__class__
|
| + return self._TOSTRING_MAP.get(cls.__name__, cls.default_tostring_fn)
|
| +
|
| + @classmethod
|
| + def set_tostring_fn(cls, new_tostring_fn):
|
| + assert cls.__name__ not in cls._TOSTRING_MAP, (
|
| + 'tostring_fn already installed for %s' % cls)
|
| + cls._TOSTRING_MAP[cls.__name__] = new_tostring_fn
|
| +
|
| + def default_tostring_fn(self):
|
| + raise NotImplementedError
|
| +
|
| + def __str__(self):
|
| + return self.tostring_fn(self) # pylint: disable=not-callable
|
| +
|
| +
|
| +class BasePath(object):
|
| + __metaclass__ = abc.ABCMeta
|
| +
|
| +
|
| +class NamedBasePath(BasePath, namedtuple('NamedBasePath', 'name')):
|
| + # Restrict basenames to '[ALL_CAPS]'. This will help catch
|
| + # errors if someone attempts to provide an actual string path '/some/example'
|
| + # as the 'base'.
|
| + BASE_RE = re.compile(r'\[([A-Z][A-Z_]*)\]')
|
| +
|
| + @staticmethod
|
| + def parse(base):
|
| + base_match = NamedBasePath.BASE_RE.match(base)
|
| + assert base_match, 'Base should be [ALL_CAPS], got %r' % base
|
| + return NamedBasePath(base_match.group(1).lower())
|
| +
|
| + def __repr__(self):
|
| + return '[%s]' % self.name.upper()
|
| +
|
| +
|
| +class ModuleBasePath(BasePath, namedtuple('ModuleBasePath', 'module')):
|
| + def __repr__(self):
|
| + prefix = '%s.' % RECIPE_MODULE_PREFIX
|
| + assert self.module.__name__.startswith(prefix)
|
| + name = self.module.__name__[len(prefix):]
|
| + return 'RECIPE_MODULE[%s]' % name
|
| +
|
| +
|
| +class Path(RecipeConfigType):
|
| + """Represents a path which is relative to a semantically-named base.
|
| +
|
| + Because there's a lot of platform (separator style) and runtime-specific
|
| + context (working directory) which goes into assembling a final OS-specific
|
| + absolute path, we only store three context-free attributes in this Path
|
| + object.
|
| + """
|
| +
|
| + def __init__(self, base, *pieces, **kwargs):
|
| + """Creates a Path
|
| +
|
| + Args:
|
| + base (str) - The 'name' of a base path, to be filled in at recipe runtime
|
| + by the 'path' recipe module.
|
| + pieces (tuple(str)) - The components of the path relative to base. These
|
| + pieces must be non-relative (i.e. no '..' or '.', etc. as a piece).
|
| +
|
| + Kwargs:
|
| + platform_ext (dict(str, str)) - A mapping from platform name (as defined
|
| + by the 'platform' module), to a suffix for the path.
|
| + """
|
| + super(Path, self).__init__()
|
| + assert all(isinstance(x, basestring) for x in pieces), pieces
|
| + assert not any(x in ('..', '.', '/', '\\') for x in pieces)
|
| + self.pieces = pieces
|
| +
|
| + if isinstance(base, BasePath):
|
| + self.base = base
|
| + elif isinstance(base, basestring):
|
| + self.base = NamedBasePath.parse(base)
|
| + else:
|
| + raise ValueError('%s is not a valid base path' % base)
|
| +
|
| + self.platform_ext = kwargs.get('platform_ext', {})
|
| +
|
| + def __eq__(self, other):
|
| + return (self.base == other.base and
|
| + self.pieces == other.pieces and
|
| + self.platform_ext == other.platform_ext)
|
| +
|
| + def __ne__(self, other):
|
| + return not self.base == other
|
| +
|
| + def join(self, *pieces, **kwargs):
|
| + kwargs.setdefault('platform_ext', self.platform_ext)
|
| + return Path(self.base, *filter(bool, self.pieces + pieces), **kwargs)
|
| +
|
| + def is_parent_of(self, child):
|
| + """True if |child| is in a subdirectory of this path."""
|
| + # Assumes base paths are not nested.
|
| + # TODO(vadimsh): We should not rely on this assumption.
|
| + if self.base != child.base:
|
| + return False
|
| + # A path is not a parent to itself.
|
| + if len(self.pieces) >= len(child.pieces):
|
| + return False
|
| + return child.pieces[:len(self.pieces)] == self.pieces
|
| +
|
| + def default_tostring_fn(self):
|
| + suffix = ''
|
| + if self.platform_ext:
|
| + suffix = ', platform_ext=%r' % (self.platform_ext,)
|
| + pieces = ''
|
| + if self.pieces:
|
| + pieces = ', ' + (', '.join(map(repr, self.pieces)))
|
| + return 'Path(\'%s\'%s%s)' % (self.base, pieces, suffix)
|
|
|