Chromium Code Reviews| Index: recipe_modules/context/api.py |
| diff --git a/recipe_modules/context/api.py b/recipe_modules/context/api.py |
| index c1119ea275e2477c788f0706b32a2076d5092287..9cf24623aa397ebefbd7b43c074ebd30994f5ef7 100644 |
| --- a/recipe_modules/context/api.py |
| +++ b/recipe_modules/context/api.py |
| @@ -26,6 +26,8 @@ Example: |
| import collections |
| +import os |
| +import types |
| from contextlib import contextmanager |
| @@ -40,7 +42,15 @@ def check_type(name, var, expect): |
| name, expect.__name__, var, type(var).__name__)) |
| +_EnvPathComponent = collections.namedtuple('_EnvPathComponent', ( |
| + 'paths', 'is_prefix')) |
| + |
| +_EnvValue = collections.namedtuple('_EnvValue', ( |
| + 'str', 'prefixes', 'suffixes')) |
|
iannucci
2017/06/07 23:17:59
prefixes, str, suffixes?
dnj
2017/06/07 23:48:46
Done.
|
| + |
| + |
| class ContextApi(RecipeApi): |
| + |
| # TODO(iannucci): move implementation of these data directly into this class. |
| def __init__(self, **kwargs): |
| super(RecipeApi, self).__init__(**kwargs) |
| @@ -88,19 +98,20 @@ class ContextApi(RecipeApi): |
| Environmental Variable Overrides: |
| Env is a mapping of environment variable name to the value you want that |
| - environment variable to have. The value is a string, with a couple |
| - exceptions: |
| - * If value is None, this environment variable will be removed from the |
| - environment when the step runs. |
| - * String values will be %-formatted with the current value of the |
| - environment at the time the step runs. This means that you can have |
| - a value like: |
| - "/path/to/my/stuff:%(PATH)s" |
| + environment variable to have. The value is either: |
|
iannucci
2017/06/07 23:17:59
s/either/one of
dnj
2017/06/07 23:48:46
Done.
|
| + * None, indicating that the environment variable should be removed from |
| + the environment when the step runs. |
| + * A string value. Note that string values will be %-formatted with the |
| + current value of the environment at the time the step runs. This means |
| + that you can have a value like: |
| + "/path/to/my/stuff:%(PATH)s" |
| Which, at the time the step executes, will inject the current value of |
| $PATH. |
| + * A sentinel value such as Prefix or Suffix to attach a specific component |
| + to a pathsep-delimited list variable. |
| - TODO(iannucci): implement env_paths which allows for easier manipulation of |
| - `pathsep` environment variables like $PATH, $PYTHONPATH, etc. |
| + TODO(iannucci,dnj): Disallow "env" values to be mixes of string or |
| + Prefix/Suffix. |
| TODO(iannucci): combine nest_level and name_prefix |
| @@ -154,23 +165,41 @@ class ContextApi(RecipeApi): |
| new = dict(self._env[-1]) |
| for k, v in env.iteritems(): |
| k = str(k) |
| + ev = new.get(k) |
| + if ev is None: |
| + ev = _EnvValue(str=None, prefixes=(), suffixes=()) |
| if v is not None: |
| - v = str(v) |
| - try: |
| - # This odd little piece of code does the following: |
| - # * add a bogus dictionary format %(foo)s to v. This forces % into |
| - # 'dictionary lookup' mode |
| - # * format the result with a defaultdict. This allows all |
| - # `%(key)s` format lookups to succeed, but any sequential `%s` |
| - # lookups to fail. |
| - # If the string contains any accidental sequential lookups, this |
| - # will raise an exception. If not, then this is a pluasible format |
| - # string. |
| - ('%(foo)s'+v) % collections.defaultdict(str) |
| - except Exception: |
| - raise ValueError(('Invalid %%-formatting parameter in envvar, ' |
| - 'only %%(ENVVAR)s allowed: %r') % (v,)) |
| - new[k] = v |
| + if isinstance(v, _EnvPathComponent): |
| + def _uniquify(*path_tuples): |
| + seen = set() |
| + for path_tuple in path_tuples: |
| + for path in path_tuple: |
| + if path not in seen: |
| + seen.add(path) |
| + yield path |
| + |
| + if v.is_prefix: |
| + ev = ev._replace(prefixes=tuple(_uniquify(v.paths, ev.prefixes))) |
|
iannucci
2017/06/07 23:17:59
let's just make it append
dnj
2017/06/07 23:48:47
Done.
|
| + else: |
| + ev = ev._replace(suffixes=tuple(_uniquify(ev.suffixes, v.paths))) |
| + else: |
| + v = str(v) |
| + try: |
| + # This odd little piece of code does the following: |
| + # * add a bogus dictionary format %(foo)s to v. This forces % |
| + # into 'dictionary lookup' mode |
| + # * format the result with a defaultdict. This allows all |
| + # `%(key)s` format lookups to succeed, but any sequential `%s` |
| + # lookups to fail. |
| + # If the string contains any accidental sequential lookups, this |
| + # will raise an exception. If not, then this is a pluasible format |
| + # string. |
| + ('%(foo)s'+v) % collections.defaultdict(str) |
| + except Exception: |
| + raise ValueError(('Invalid %%-formatting parameter in envvar, ' |
| + 'only %%(ENVVAR)s allowed: %r') % (v,)) |
| + ev = ev._replace(str=v) |
| + new[k] = ev |
| self._env.append(new) |
| to_pop.append(self._env) |
| @@ -203,7 +232,16 @@ class ContextApi(RecipeApi): |
| """ |
| # TODO(iannucci): store env in an immutable way to avoid excessive copies. |
| # TODO(iannucci): handle case-insensitive keys on windows |
| - return dict(self._env[-1]) |
| + def parts(ev): |
| + for p in ev.prefixes: |
| + yield str(p) |
| + if ev.str is not None: |
| + yield ev.str |
| + for p in ev.suffixes: |
| + yield str(p) |
| + |
| + return {k: self.m.path.pathsep.join(parts(ev)) |
| + for k, ev in self._env[-1].iteritems()} |
| @property |
| def infra_step(self): |
| @@ -232,3 +270,33 @@ class ContextApi(RecipeApi): |
| Returns (int) - The current nesting level. |
| """ |
| return self._nest_level[-1] |
| + |
| + def Prefix(self, *paths): |
| + """Returns: an assignable "env" value that prefixes the specified paths to |
| + the beginning of an environment variable. |
| + |
| + Each path in paths is added, in order, as a prefix to the environment |
| + variable, delimited by the OS path separator. This can be used for |
| + easy manipulation of path environment variables such as PATH and PYTHONPATH. |
| + |
| + Args: |
| + paths (...Path): The list of paths to prefix. |
| + """ |
| + for i, path in enumerate(paths): |
| + check_type('path element %d' % (i,), path, Path) |
| + return _EnvPathComponent(paths=paths, is_prefix=True) |
| + |
| + def Suffix(self, *paths): |
| + """Returns: an assignable "env" value that appends the specified paths to |
| + the beginning of an environment variable. |
| + |
| + Each path in paths is added, in order, as a suffix to the environment |
| + variable, delimited by the OS path separator. This can be used for |
| + easy manipulation of path environment variables such as PATH and PYTHONPATH. |
| + |
| + Args: |
| + paths (...Path): The list of paths to suffix. |
| + """ |
| + for i, path in enumerate(paths): |
| + check_type('path element %d' % (i,), path, Path) |
| + return _EnvPathComponent(paths=paths, is_prefix=False) |