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

Unified Diff: recipe_modules/context/api.py

Issue 2925453002: [context] Add path prefix/suffix manipulation. (Closed)
Patch Set: advise Created 3 years, 6 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
Index: recipe_modules/context/api.py
diff --git a/recipe_modules/context/api.py b/recipe_modules/context/api.py
index c1119ea275e2477c788f0706b32a2076d5092287..aef0add477d7e45c5ba8b74b75fd713bda18a20c 100644
--- a/recipe_modules/context/api.py
+++ b/recipe_modules/context/api.py
@@ -26,6 +26,7 @@ Example:
import collections
+import os
from contextlib import contextmanager
@@ -41,6 +42,7 @@ def check_type(name, var, expect):
class ContextApi(RecipeApi):
+
# TODO(iannucci): move implementation of these data directly into this class.
def __init__(self, **kwargs):
super(RecipeApi, self).__init__(**kwargs)
@@ -51,10 +53,13 @@ class ContextApi(RecipeApi):
self._name_prefix = ['']
# this could be a number, but it makes the logic easier to use a stack.
self._nest_level = [0]
+ self._prefix_paths = [{}]
+ self._suffix_paths = [{}]
@contextmanager
def __call__(self, cwd=None, env=None, increment_nest_level=None,
- infra_steps=None, name_prefix=None):
+ infra_steps=None, name_prefix=None, path_prefix=None,
+ path_suffix=None):
"""Allows adjustment of multiple context values in a single call.
Contextual data:
@@ -71,6 +76,12 @@ class ContextApi(RecipeApi):
this context. These compose with '.' characters if multiple name prefix
contexts occur. See below for more info.
* env (dict) - Environmental variable overrides. See below for more info.
iannucci 2017/06/05 19:23:04 wdyt about something like: with api.context(env
dnj 2017/06/07 02:59:29 That seems fine. For now I think we need mixed-use
+ * path_prefix (dict) - Environmental variable prefix path overrides.
+ Consider using the "path_prefix" and "path_remove" helper functions
+ instead of directly manipulating this value.
+ * path_suffix (dict) - Environmental variable suffix path overrides.
+ Consider using the "path_suffix" and "path_remove" helper functions
+ instead of directly manipulating this value.
iannucci 2017/06/05 19:23:04 I don't like the helper function idea; we should k
dnj 2017/06/07 02:59:29 SGTM
Name prefixes:
@@ -99,8 +110,15 @@ class ContextApi(RecipeApi):
Which, at the time the step executes, will inject the current value of
$PATH.
- TODO(iannucci): implement env_paths which allows for easier manipulation of
- `pathsep` environment variables like $PATH, $PYTHONPATH, etc.
+ path_prefix and path_suffix are mappings of environment variables to lists
+ of Path elements, meant to be applied to "pathsep"-separated environment
+ variables such as PATH and PYTHONPATH. These values are prefixed and/or
+ appended to their environment variable key, separated by the OS-specific
+ path separator.
+ * If key begins with a "!" (e.g., "!PATH"), the named element will be
+ removed from its environment instead of added.
+ * Consider using helper functions instead of directly manipulating this
+ value.
TODO(iannucci): combine nest_level and name_prefix
@@ -174,6 +192,42 @@ class ContextApi(RecipeApi):
self._env.append(new)
to_pop.append(self._env)
+ for var, collection, name, is_prefix in (
+ (path_prefix, self._prefix_paths, 'path_prefix', True),
+ (path_suffix, self._suffix_paths, 'path_suffix', False)):
+ if var is None or var == {}:
+ continue
+ check_type(name, var, dict)
+ new = dict(collection[-1])
+ for k, paths in var.iteritems():
+ check_type(k, paths, collections.Iterable)
iannucci 2017/06/05 19:23:04 check_type("KEY %s[%r]" % (name, k), ...) so then
dnj 2017/06/07 02:59:28 No longer needed.
+ remove = k.startswith('!')
+ if remove:
+ k = k[1:]
iannucci 2017/06/05 19:23:04 let's remove the removal functionality for now
dnj 2017/06/07 02:59:29 Done.
+
+ # Remove any occurrences of the current value and any duplicates from
+ # "paths". This will keep the prefix/suffix lists clean.
+ nv = new.get(k, ())
+ seen = set()
+ unique_paths = []
+ for i, path in enumerate(paths):
+ check_type('%s element #%d' % (k, i), path, Path)
iannucci 2017/06/05 19:23:04 '%s[%r]' % (name, k)
dnj 2017/06/07 02:59:28 Also no longer needed.
+ nv = tuple(x for x in nv if x != path)
+ if path not in seen:
+ unique_paths.append(path)
+ seen.add(path)
+ if not remove:
+ if is_prefix:
+ nv = tuple(unique_paths) + nv
+ else:
+ nv += tuple(unique_paths)
+ if nv:
+ new[k] = nv
+ else:
+ new.pop(k, None)
+ collection.append(new)
+ to_pop.append(collection)
+
try:
yield
finally:
@@ -203,7 +257,19 @@ 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])
+ env = dict(self._env[-1])
+ for collection, is_prefix in (
+ (self._prefix_paths, True),
+ (self._suffix_paths, False)):
+ for key, paths in collection[-1].iteritems():
+ cur = env.get(key, '%%(%s)s' % (key,))
+ cur = cur.split(os.pathsep) if cur else []
iannucci 2017/06/05 19:24:04 this will fail in production/simulation; use self.
dnj 2017/06/07 02:59:29 Done.
+ if is_prefix:
+ cur = list(str(p) for p in paths) + cur
+ else:
+ cur += list(str(p) for p in paths)
+ env[key] = os.pathsep.join(cur)
+ return env
iannucci 2017/06/05 19:23:04 I'm not sure how I feel about this; I think that s
dnj 2017/06/07 02:59:29 Done.
@property
def infra_step(self):
@@ -232,3 +298,65 @@ class ContextApi(RecipeApi):
Returns (int) - The current nesting level.
"""
return self._nest_level[-1]
+
+ @contextmanager
+ def path_prefix(self, key, *paths):
+ """Adds specified paths to the beginning of an environment variable.
+
+ Each path in paths is added, in order, as a prefix to the environment
+ variable "key", delimited by the OS path separator. This can be used for
+ easy manipulation of path environment variables such as PATH and PYTHONPATH.
+
+ If "key" begins with an "!", the specified paths will be removed from the
+ prefix. The "path_remove" helper function may be a more elegant way to
+ remove a path.
+
+ Args:
+ key (str): The environment variable key.
+ paths (...Path): The list of paths to prefix.
+ """
+ check_type('key', key, str)
+ with self(path_prefix={key: paths}):
+ yield
+
+ @contextmanager
+ def path_suffix(self, key, *paths):
+ """Adds specified paths to the end of an environment variable.
+
+ Each path in paths is added, in order, as a suffix to the environment
+ variable "key", delimited by the OS path separator. This can be used for
+ easy manipulation of path environment variables such as PATH and PYTHONPATH.
+
+ If "key" begins with an "!", the specified paths will be removed from the
+ prefix. The "path_remove" helper function may be a more elegant way to
+ remove a path.
+
+ Args:
+ key (str): The environment variable key.
+ paths (...Path): The list of paths to suffix.
+ """
+ check_type('key', key, str)
+ with self(path_suffix={key: paths}):
+ yield
+
+ @contextmanager
+ def path_remove(self, key, *paths):
iannucci 2017/06/05 19:23:04 let's remove this functionality for now. I can't t
dnj 2017/06/07 02:59:29 Done.
+ """Removes specified paths from an environment variable's prefix/suffix.
+
+ Any path in "paths" will be removed from the environment's prefix and suffix
+ paths. If the path is not present, it will be ignored.
+
+ WARNING: this will NOT remove a path that was not, itself, added as a
+ prefix or suffix. For example, if a path is added to the environment as a
+ string, rather than through "path_prefix" or "path_suffix", it will not be
+ removed.
+
+ Args:
+ key (str): The environment variable key.
+ paths (...Path): The list of paths to suffix.
+ """
+ check_type('key', key, str)
+ if not key.startswith('!'):
+ key = '!'+key
+ with self(path_prefix={key: paths}, path_suffix={key: paths}):
+ yield

Powered by Google App Engine
This is Rietveld 408576698