utils: merge spack.environment into spack.util.environment
- `spack.util.environment` is the new home for routines that modify environment variables. - This is to make room for `spack.environment` to contain new routines for dealing with spack environments
This commit is contained in:
parent
cd075b04d2
commit
0e60fcccfb
@ -278,7 +278,7 @@ that spack does is not sufficient for python to import modules.
|
||||
|
||||
To provide environment setup for a dependent, a package can implement the
|
||||
:py:func:`setup_dependent_environment <spack.package.PackageBase.setup_dependent_environment>`
|
||||
function. This function takes as a parameter a :py:class:`EnvironmentModifications <spack.environment.EnvironmentModifications>`
|
||||
function. This function takes as a parameter a :py:class:`EnvironmentModifications <spack.util.environment.EnvironmentModifications>`
|
||||
object which includes convenience methods to update the environment. For
|
||||
example, an MPI implementation can set ``MPICC`` for packages that depend on it:
|
||||
|
||||
|
@ -53,8 +53,8 @@
|
||||
import spack.paths
|
||||
import spack.store
|
||||
from spack.util.string import plural
|
||||
from spack.environment import EnvironmentModifications, validate
|
||||
from spack.environment import preserve_environment
|
||||
from spack.util.environment import EnvironmentModifications, validate
|
||||
from spack.util.environment import preserve_environment
|
||||
from spack.util.environment import env_flag, filter_system_paths, get_path
|
||||
from spack.util.environment import system_dirs
|
||||
from spack.util.executable import Executable
|
||||
|
@ -1,623 +0,0 @@
|
||||
# Copyright 2013-2018 Lawrence Livermore National Security, LLC and other
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import os.path
|
||||
import subprocess
|
||||
|
||||
import llnl.util.tty as tty
|
||||
|
||||
from llnl.util.lang import dedupe
|
||||
|
||||
|
||||
class NameModifier(object):
|
||||
|
||||
def __init__(self, name, **kwargs):
|
||||
self.name = name
|
||||
self.args = {'name': name}
|
||||
self.args.update(kwargs)
|
||||
|
||||
def update_args(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
self.args.update(kwargs)
|
||||
|
||||
|
||||
class NameValueModifier(object):
|
||||
|
||||
def __init__(self, name, value, **kwargs):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.separator = kwargs.get('separator', ':')
|
||||
self.args = {'name': name, 'value': value, 'separator': self.separator}
|
||||
self.args.update(kwargs)
|
||||
|
||||
def update_args(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
self.args.update(kwargs)
|
||||
|
||||
|
||||
class SetEnv(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
os.environ[self.name] = str(self.value)
|
||||
|
||||
|
||||
class AppendFlagsEnv(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
if self.name in os.environ and os.environ[self.name]:
|
||||
os.environ[self.name] += self.separator + str(self.value)
|
||||
else:
|
||||
os.environ[self.name] = str(self.value)
|
||||
|
||||
|
||||
class UnsetEnv(NameModifier):
|
||||
|
||||
def execute(self):
|
||||
# Avoid throwing if the variable was not set
|
||||
os.environ.pop(self.name, None)
|
||||
|
||||
|
||||
class SetPath(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
string_path = concatenate_paths(self.value, separator=self.separator)
|
||||
os.environ[self.name] = string_path
|
||||
|
||||
|
||||
class AppendPath(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
environment_value = os.environ.get(self.name, '')
|
||||
directories = environment_value.split(
|
||||
self.separator) if environment_value else []
|
||||
directories.append(os.path.normpath(self.value))
|
||||
os.environ[self.name] = self.separator.join(directories)
|
||||
|
||||
|
||||
class PrependPath(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
environment_value = os.environ.get(self.name, '')
|
||||
directories = environment_value.split(
|
||||
self.separator) if environment_value else []
|
||||
directories = [os.path.normpath(self.value)] + directories
|
||||
os.environ[self.name] = self.separator.join(directories)
|
||||
|
||||
|
||||
class RemovePath(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
environment_value = os.environ.get(self.name, '')
|
||||
directories = environment_value.split(
|
||||
self.separator) if environment_value else []
|
||||
directories = [os.path.normpath(x) for x in directories
|
||||
if x != os.path.normpath(self.value)]
|
||||
os.environ[self.name] = self.separator.join(directories)
|
||||
|
||||
|
||||
class EnvironmentModifications(object):
|
||||
"""Keeps track of requests to modify the current environment.
|
||||
|
||||
Each call to a method to modify the environment stores the extra
|
||||
information on the caller in the request:
|
||||
|
||||
* 'filename' : filename of the module where the caller is defined
|
||||
* 'lineno': line number where the request occurred
|
||||
* 'context' : line of code that issued the request that failed
|
||||
"""
|
||||
|
||||
def __init__(self, other=None):
|
||||
"""Initializes a new instance, copying commands from 'other'
|
||||
if it is not None.
|
||||
|
||||
Args:
|
||||
other (EnvironmentModifications): list of environment modifications
|
||||
to be extended (optional)
|
||||
"""
|
||||
self.env_modifications = []
|
||||
if other is not None:
|
||||
self.extend(other)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.env_modifications)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.env_modifications)
|
||||
|
||||
def extend(self, other):
|
||||
self._check_other(other)
|
||||
self.env_modifications.extend(other.env_modifications)
|
||||
|
||||
@staticmethod
|
||||
def _check_other(other):
|
||||
if not isinstance(other, EnvironmentModifications):
|
||||
raise TypeError(
|
||||
'other must be an instance of EnvironmentModifications')
|
||||
|
||||
def _get_outside_caller_attributes(self):
|
||||
stack = inspect.stack()
|
||||
try:
|
||||
_, filename, lineno, _, context, index = stack[2]
|
||||
context = context[index].strip()
|
||||
except Exception:
|
||||
filename = 'unknown file'
|
||||
lineno = 'unknown line'
|
||||
context = 'unknown context'
|
||||
args = {'filename': filename, 'lineno': lineno, 'context': context}
|
||||
return args
|
||||
|
||||
def set(self, name, value, **kwargs):
|
||||
"""Stores a request to set an environment variable.
|
||||
|
||||
Args:
|
||||
name: name of the environment variable to be set
|
||||
value: value of the environment variable
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = SetEnv(name, value, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def append_flags(self, name, value, sep=' ', **kwargs):
|
||||
"""
|
||||
Stores in the current object a request to append to an env variable
|
||||
|
||||
Args:
|
||||
name: name of the environment variable to be appended to
|
||||
value: value to append to the environment variable
|
||||
Appends with spaces separating different additions to the variable
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
kwargs.update({'separator': sep})
|
||||
item = AppendFlagsEnv(name, value, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def unset(self, name, **kwargs):
|
||||
"""Stores a request to unset an environment variable.
|
||||
|
||||
Args:
|
||||
name: name of the environment variable to be set
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = UnsetEnv(name, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def set_path(self, name, elements, **kwargs):
|
||||
"""Stores a request to set a path generated from a list.
|
||||
|
||||
Args:
|
||||
name: name o the environment variable to be set.
|
||||
elements: elements of the path to set.
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = SetPath(name, elements, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def append_path(self, name, path, **kwargs):
|
||||
"""Stores a request to append a path to a path list.
|
||||
|
||||
Args:
|
||||
name: name of the path list in the environment
|
||||
path: path to be appended
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = AppendPath(name, path, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def prepend_path(self, name, path, **kwargs):
|
||||
"""Same as `append_path`, but the path is pre-pended.
|
||||
|
||||
Args:
|
||||
name: name of the path list in the environment
|
||||
path: path to be pre-pended
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = PrependPath(name, path, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def remove_path(self, name, path, **kwargs):
|
||||
"""Stores a request to remove a path from a path list.
|
||||
|
||||
Args:
|
||||
name: name of the path list in the environment
|
||||
path: path to be removed
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = RemovePath(name, path, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def group_by_name(self):
|
||||
"""Returns a dict of the modifications grouped by variable name.
|
||||
|
||||
Returns:
|
||||
dict mapping the environment variable name to the modifications to
|
||||
be done on it
|
||||
"""
|
||||
modifications = collections.defaultdict(list)
|
||||
for item in self:
|
||||
modifications[item.name].append(item)
|
||||
return modifications
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clears the current list of modifications
|
||||
"""
|
||||
self.env_modifications.clear()
|
||||
|
||||
def apply_modifications(self):
|
||||
"""Applies the modifications and clears the list."""
|
||||
modifications = self.group_by_name()
|
||||
# Apply modifications one variable at a time
|
||||
for name, actions in sorted(modifications.items()):
|
||||
for x in actions:
|
||||
x.execute()
|
||||
|
||||
@staticmethod
|
||||
def from_sourcing_file(filename, *args, **kwargs):
|
||||
"""Returns modifications that would be made by sourcing a file.
|
||||
|
||||
Parameters:
|
||||
filename (str): The file to source
|
||||
*args (list of str): Arguments to pass on the command line
|
||||
|
||||
Keyword Arguments:
|
||||
shell (str): The shell to use (default: ``bash``)
|
||||
shell_options (str): Options passed to the shell (default: ``-c``)
|
||||
source_command (str): The command to run (default: ``source``)
|
||||
suppress_output (str): Redirect used to suppress output of command
|
||||
(default: ``&> /dev/null``)
|
||||
concatenate_on_success (str): Operator used to execute a command
|
||||
only when the previous command succeeds (default: ``&&``)
|
||||
blacklist ([str or re]): Ignore any modifications of these
|
||||
variables (default: [])
|
||||
whitelist ([str or re]): Always respect modifications of these
|
||||
variables (default: []). Has precedence over blacklist.
|
||||
clean (bool): In addition to removing empty entries,
|
||||
also remove duplicate entries (default: False).
|
||||
|
||||
Returns:
|
||||
EnvironmentModifications: an object that, if executed, has
|
||||
the same effect on the environment as sourcing the file
|
||||
"""
|
||||
# Check if the file actually exists
|
||||
if not os.path.isfile(filename):
|
||||
msg = 'Trying to source non-existing file: {0}'.format(filename)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# Kwargs parsing and default values
|
||||
shell = kwargs.get('shell', '/bin/bash')
|
||||
shell_options = kwargs.get('shell_options', '-c')
|
||||
source_command = kwargs.get('source_command', 'source')
|
||||
suppress_output = kwargs.get('suppress_output', '&> /dev/null')
|
||||
concatenate_on_success = kwargs.get('concatenate_on_success', '&&')
|
||||
blacklist = kwargs.get('blacklist', [])
|
||||
whitelist = kwargs.get('whitelist', [])
|
||||
clean = kwargs.get('clean', False)
|
||||
|
||||
source_file = [source_command, filename]
|
||||
source_file.extend(args)
|
||||
source_file = ' '.join(source_file)
|
||||
|
||||
dump_cmd = 'import os, json; print(json.dumps(dict(os.environ)))'
|
||||
dump_environment = 'python -c "{0}"'.format(dump_cmd)
|
||||
|
||||
# Construct the command that will be executed
|
||||
command = [
|
||||
shell,
|
||||
shell_options,
|
||||
' '.join([
|
||||
source_file, suppress_output,
|
||||
concatenate_on_success, dump_environment,
|
||||
]),
|
||||
]
|
||||
|
||||
# Try to source the file
|
||||
proc = subprocess.Popen(
|
||||
command, stdout=subprocess.PIPE, env=os.environ)
|
||||
proc.wait()
|
||||
|
||||
if proc.returncode != 0:
|
||||
msg = 'Sourcing file {0} returned a non-zero exit code'.format(
|
||||
filename)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
output = ''.join([line.decode('utf-8') for line in proc.stdout])
|
||||
|
||||
# Construct dictionaries of the environment before and after
|
||||
# sourcing the file, so that we can diff them.
|
||||
env_before = dict(os.environ)
|
||||
env_after = json.loads(output)
|
||||
|
||||
# If we're in python2, convert to str objects instead of unicode
|
||||
# like json gives us. We can't put unicode in os.environ anyway.
|
||||
if sys.version_info[0] < 3:
|
||||
env_after = dict((k.encode('utf-8'), v.encode('utf-8'))
|
||||
for k, v in env_after.items())
|
||||
|
||||
# Other variables unrelated to sourcing a file
|
||||
blacklist.extend(['SHLVL', '_', 'PWD', 'OLDPWD', 'PS2'])
|
||||
|
||||
def set_intersection(fullset, *args):
|
||||
# A set intersection using string literals and regexs
|
||||
meta = '[' + re.escape('[$()*?[]^{|}') + ']'
|
||||
subset = fullset & set(args) # As literal
|
||||
for name in args:
|
||||
if re.search(meta, name):
|
||||
pattern = re.compile(name)
|
||||
for k in fullset:
|
||||
if re.match(pattern, k):
|
||||
subset.add(k)
|
||||
return subset
|
||||
|
||||
for d in env_after, env_before:
|
||||
# Retain (whitelist) has priority over prune (blacklist)
|
||||
prune = set_intersection(set(d), *blacklist)
|
||||
prune -= set_intersection(prune, *whitelist)
|
||||
for k in prune:
|
||||
d.pop(k, None)
|
||||
|
||||
# Fill the EnvironmentModifications instance
|
||||
env = EnvironmentModifications()
|
||||
|
||||
# New variables
|
||||
new_variables = list(set(env_after) - set(env_before))
|
||||
# Variables that have been unset
|
||||
unset_variables = list(set(env_before) - set(env_after))
|
||||
# Variables that have been modified
|
||||
common_variables = set(env_before).intersection(set(env_after))
|
||||
|
||||
modified_variables = [x for x in common_variables
|
||||
if env_before[x] != env_after[x]]
|
||||
|
||||
# Consistent output order - looks nicer, easier comparison...
|
||||
new_variables.sort()
|
||||
unset_variables.sort()
|
||||
modified_variables.sort()
|
||||
|
||||
def return_separator_if_any(*args):
|
||||
separators = ':', ';'
|
||||
for separator in separators:
|
||||
for arg in args:
|
||||
if separator in arg:
|
||||
return separator
|
||||
return None
|
||||
|
||||
# Add variables to env.
|
||||
# Assume that variables with 'PATH' in the name or that contain
|
||||
# separators like ':' or ';' are more likely to be paths
|
||||
for x in new_variables:
|
||||
sep = return_separator_if_any(env_after[x])
|
||||
if sep:
|
||||
env.prepend_path(x, env_after[x], separator=sep)
|
||||
elif 'PATH' in x:
|
||||
env.prepend_path(x, env_after[x])
|
||||
else:
|
||||
# We just need to set the variable to the new value
|
||||
env.set(x, env_after[x])
|
||||
|
||||
for x in unset_variables:
|
||||
env.unset(x)
|
||||
|
||||
for x in modified_variables:
|
||||
before = env_before[x]
|
||||
after = env_after[x]
|
||||
sep = return_separator_if_any(before, after)
|
||||
if sep:
|
||||
before_list = before.split(sep)
|
||||
after_list = after.split(sep)
|
||||
|
||||
# Filter out empty strings
|
||||
before_list = list(filter(None, before_list))
|
||||
after_list = list(filter(None, after_list))
|
||||
|
||||
# Remove duplicate entries (worse matching, bloats env)
|
||||
if clean:
|
||||
before_list = list(dedupe(before_list))
|
||||
after_list = list(dedupe(after_list))
|
||||
# The reassembled cleaned entries
|
||||
before = sep.join(before_list)
|
||||
after = sep.join(after_list)
|
||||
|
||||
# Paths that have been removed
|
||||
remove_list = [
|
||||
ii for ii in before_list if ii not in after_list]
|
||||
# Check that nothing has been added in the middle of
|
||||
# before_list
|
||||
remaining_list = [
|
||||
ii for ii in before_list if ii in after_list]
|
||||
try:
|
||||
start = after_list.index(remaining_list[0])
|
||||
end = after_list.index(remaining_list[-1])
|
||||
search = sep.join(after_list[start:end + 1])
|
||||
except IndexError:
|
||||
env.prepend_path(x, after)
|
||||
|
||||
if search not in before:
|
||||
# We just need to set the variable to the new value
|
||||
env.prepend_path(x, after)
|
||||
else:
|
||||
try:
|
||||
prepend_list = after_list[:start]
|
||||
prepend_list.reverse() # Preserve order after prepend
|
||||
except KeyError:
|
||||
prepend_list = []
|
||||
try:
|
||||
append_list = after_list[end + 1:]
|
||||
except KeyError:
|
||||
append_list = []
|
||||
|
||||
for item in remove_list:
|
||||
env.remove_path(x, item)
|
||||
for item in append_list:
|
||||
env.append_path(x, item)
|
||||
for item in prepend_list:
|
||||
env.prepend_path(x, item)
|
||||
else:
|
||||
# We just need to set the variable to the new value
|
||||
env.set(x, after)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def concatenate_paths(paths, separator=':'):
|
||||
"""Concatenates an iterable of paths into a string of paths separated by
|
||||
separator, defaulting to colon.
|
||||
|
||||
Args:
|
||||
paths: iterable of paths
|
||||
separator: the separator to use, default ':'
|
||||
|
||||
Returns:
|
||||
string
|
||||
"""
|
||||
return separator.join(str(item) for item in paths)
|
||||
|
||||
|
||||
def set_or_unset_not_first(variable, changes, errstream):
|
||||
"""Check if we are going to set or unset something after other
|
||||
modifications have already been requested.
|
||||
"""
|
||||
indexes = [ii for ii, item in enumerate(changes)
|
||||
if ii != 0 and
|
||||
not item.args.get('force', False) and
|
||||
type(item) in [SetEnv, UnsetEnv]]
|
||||
if indexes:
|
||||
good = '\t \t{context} at {filename}:{lineno}'
|
||||
nogood = '\t--->\t{context} at {filename}:{lineno}'
|
||||
message = "Suspicious requests to set or unset '{var}' found"
|
||||
errstream(message.format(var=variable))
|
||||
for ii, item in enumerate(changes):
|
||||
print_format = nogood if ii in indexes else good
|
||||
errstream(print_format.format(**item.args))
|
||||
|
||||
|
||||
def validate(env, errstream):
|
||||
"""Validates the environment modifications to check for the presence of
|
||||
suspicious patterns. Prompts a warning for everything that was found.
|
||||
|
||||
Current checks:
|
||||
- set or unset variables after other changes on the same variable
|
||||
|
||||
Args:
|
||||
env: list of environment modifications
|
||||
"""
|
||||
modifications = env.group_by_name()
|
||||
for variable, list_of_changes in sorted(modifications.items()):
|
||||
set_or_unset_not_first(variable, list_of_changes, errstream)
|
||||
|
||||
|
||||
def filter_environment_blacklist(env, variables):
|
||||
"""Generator that filters out any change to environment variables present in
|
||||
the input list.
|
||||
|
||||
Args:
|
||||
env: list of environment modifications
|
||||
variables: list of variable names to be filtered
|
||||
|
||||
Returns:
|
||||
items in env if they are not in variables
|
||||
"""
|
||||
for item in env:
|
||||
if item.name not in variables:
|
||||
yield item
|
||||
|
||||
|
||||
def inspect_path(root, inspections, exclude=None):
|
||||
"""Inspects ``root`` to search for the subdirectories in ``inspections``.
|
||||
Adds every path found to a list of prepend-path commands and returns it.
|
||||
|
||||
Args:
|
||||
root (str): absolute path where to search for subdirectories
|
||||
|
||||
inspections (dict): maps relative paths to a list of environment
|
||||
variables that will be modified if the path exists. The
|
||||
modifications are not performed immediately, but stored in a
|
||||
command object that is returned to client
|
||||
|
||||
exclude (callable): optional callable. If present it must accept an
|
||||
absolute path and return True if it should be excluded from the
|
||||
inspection
|
||||
|
||||
Examples:
|
||||
|
||||
The following lines execute an inspection in ``/usr`` to search for
|
||||
``/usr/include`` and ``/usr/lib64``. If found we want to prepend
|
||||
``/usr/include`` to ``CPATH`` and ``/usr/lib64`` to ``MY_LIB64_PATH``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Set up the dictionary containing the inspection
|
||||
inspections = {
|
||||
'include': ['CPATH'],
|
||||
'lib64': ['MY_LIB64_PATH']
|
||||
}
|
||||
|
||||
# Get back the list of command needed to modify the environment
|
||||
env = inspect_path('/usr', inspections)
|
||||
|
||||
# Eventually execute the commands
|
||||
env.apply_modifications()
|
||||
|
||||
Returns:
|
||||
instance of EnvironmentModifications containing the requested
|
||||
modifications
|
||||
"""
|
||||
if exclude is None:
|
||||
exclude = lambda x: False
|
||||
|
||||
env = EnvironmentModifications()
|
||||
# Inspect the prefix to check for the existence of common directories
|
||||
for relative_path, variables in inspections.items():
|
||||
expected = os.path.join(root, relative_path)
|
||||
|
||||
if os.path.isdir(expected) and not exclude(expected):
|
||||
for variable in variables:
|
||||
env.prepend_path(variable, expected)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def preserve_environment(*variables):
|
||||
"""Ensures that the value of the environment variables passed as
|
||||
arguments is the same before entering to the context manager and after
|
||||
exiting it.
|
||||
|
||||
Variables that are unset before entering the context manager will be
|
||||
explicitly unset on exit.
|
||||
|
||||
Args:
|
||||
variables (list of str): list of environment variables to be preserved
|
||||
"""
|
||||
cache = {}
|
||||
for var in variables:
|
||||
# The environment variable to be preserved might not be there.
|
||||
# In that case store None as a placeholder.
|
||||
cache[var] = os.environ.get(var, None)
|
||||
|
||||
yield
|
||||
|
||||
for var in variables:
|
||||
value = cache[var]
|
||||
msg = '[PRESERVE_ENVIRONMENT]'
|
||||
if value is not None:
|
||||
# Print a debug statement if the value changed
|
||||
if var not in os.environ:
|
||||
msg += ' {0} was unset, will be reset to "{1}"'
|
||||
tty.debug(msg.format(var, value))
|
||||
elif os.environ[var] != value:
|
||||
msg += ' {0} was set to "{1}", will be reset to "{2}"'
|
||||
tty.debug(msg.format(var, os.environ[var], value))
|
||||
os.environ[var] = value
|
||||
elif var in os.environ:
|
||||
msg += ' {0} was set to "{1}", will be unset'
|
||||
tty.debug(msg.format(var, os.environ[var]))
|
||||
del os.environ[var]
|
@ -40,7 +40,7 @@
|
||||
|
||||
import spack.paths
|
||||
import spack.build_environment as build_environment
|
||||
import spack.environment
|
||||
import spack.util.environment
|
||||
import spack.tengine as tengine
|
||||
import spack.util.path
|
||||
import spack.util.environment
|
||||
@ -256,7 +256,7 @@ def env(self):
|
||||
"""List of environment modifications that should be done in the
|
||||
module.
|
||||
"""
|
||||
env_mods = spack.environment.EnvironmentModifications()
|
||||
env_mods = spack.util.environment.EnvironmentModifications()
|
||||
actions = self.conf.get('environment', {})
|
||||
|
||||
def process_arglist(arglist):
|
||||
@ -500,14 +500,14 @@ def configure_options(self):
|
||||
def environment_modifications(self):
|
||||
"""List of environment modifications to be processed."""
|
||||
# Modifications guessed inspecting the spec prefix
|
||||
env = spack.environment.inspect_path(
|
||||
env = spack.util.environment.inspect_path(
|
||||
self.spec.prefix,
|
||||
prefix_inspections,
|
||||
exclude=spack.util.environment.is_system_path
|
||||
)
|
||||
|
||||
# Modifications that are coded at package level
|
||||
_ = spack.environment.EnvironmentModifications()
|
||||
_ = spack.util.environment.EnvironmentModifications()
|
||||
# TODO : the code down below is quite similar to
|
||||
# TODO : build_environment.setup_package and needs to be factored out
|
||||
# TODO : to a single place
|
||||
|
@ -6,11 +6,11 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import spack.environment as environment
|
||||
import spack.util.environment as environment
|
||||
from spack.paths import spack_root
|
||||
from spack.environment import EnvironmentModifications
|
||||
from spack.environment import RemovePath, PrependPath, AppendPath
|
||||
from spack.environment import SetEnv, UnsetEnv
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
from spack.util.environment import RemovePath, PrependPath, AppendPath
|
||||
from spack.util.environment import SetEnv, UnsetEnv
|
||||
from spack.util.environment import filter_system_paths, is_system_path
|
||||
|
||||
|
||||
|
@ -3,8 +3,20 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""Utilities for setting and modifying environment variables."""
|
||||
import collections
|
||||
import contextlib
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import os.path
|
||||
import subprocess
|
||||
|
||||
import llnl.util.tty as tty
|
||||
|
||||
from llnl.util.lang import dedupe
|
||||
|
||||
|
||||
system_paths = ['/', '/usr', '/usr/local']
|
||||
@ -96,3 +108,608 @@ def set_env(**kwargs):
|
||||
else:
|
||||
if var in os.environ:
|
||||
del os.environ[var]
|
||||
|
||||
|
||||
class NameModifier(object):
|
||||
|
||||
def __init__(self, name, **kwargs):
|
||||
self.name = name
|
||||
self.args = {'name': name}
|
||||
self.args.update(kwargs)
|
||||
|
||||
def update_args(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
self.args.update(kwargs)
|
||||
|
||||
|
||||
class NameValueModifier(object):
|
||||
|
||||
def __init__(self, name, value, **kwargs):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.separator = kwargs.get('separator', ':')
|
||||
self.args = {'name': name, 'value': value, 'separator': self.separator}
|
||||
self.args.update(kwargs)
|
||||
|
||||
def update_args(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
self.args.update(kwargs)
|
||||
|
||||
|
||||
class SetEnv(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
os.environ[self.name] = str(self.value)
|
||||
|
||||
|
||||
class AppendFlagsEnv(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
if self.name in os.environ and os.environ[self.name]:
|
||||
os.environ[self.name] += self.separator + str(self.value)
|
||||
else:
|
||||
os.environ[self.name] = str(self.value)
|
||||
|
||||
|
||||
class UnsetEnv(NameModifier):
|
||||
|
||||
def execute(self):
|
||||
# Avoid throwing if the variable was not set
|
||||
os.environ.pop(self.name, None)
|
||||
|
||||
|
||||
class SetPath(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
string_path = concatenate_paths(self.value, separator=self.separator)
|
||||
os.environ[self.name] = string_path
|
||||
|
||||
|
||||
class AppendPath(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
environment_value = os.environ.get(self.name, '')
|
||||
directories = environment_value.split(
|
||||
self.separator) if environment_value else []
|
||||
directories.append(os.path.normpath(self.value))
|
||||
os.environ[self.name] = self.separator.join(directories)
|
||||
|
||||
|
||||
class PrependPath(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
environment_value = os.environ.get(self.name, '')
|
||||
directories = environment_value.split(
|
||||
self.separator) if environment_value else []
|
||||
directories = [os.path.normpath(self.value)] + directories
|
||||
os.environ[self.name] = self.separator.join(directories)
|
||||
|
||||
|
||||
class RemovePath(NameValueModifier):
|
||||
|
||||
def execute(self):
|
||||
environment_value = os.environ.get(self.name, '')
|
||||
directories = environment_value.split(
|
||||
self.separator) if environment_value else []
|
||||
directories = [os.path.normpath(x) for x in directories
|
||||
if x != os.path.normpath(self.value)]
|
||||
os.environ[self.name] = self.separator.join(directories)
|
||||
|
||||
|
||||
class EnvironmentModifications(object):
|
||||
"""Keeps track of requests to modify the current environment.
|
||||
|
||||
Each call to a method to modify the environment stores the extra
|
||||
information on the caller in the request:
|
||||
|
||||
* 'filename' : filename of the module where the caller is defined
|
||||
* 'lineno': line number where the request occurred
|
||||
* 'context' : line of code that issued the request that failed
|
||||
"""
|
||||
|
||||
def __init__(self, other=None):
|
||||
"""Initializes a new instance, copying commands from 'other'
|
||||
if it is not None.
|
||||
|
||||
Args:
|
||||
other (EnvironmentModifications): list of environment modifications
|
||||
to be extended (optional)
|
||||
"""
|
||||
self.env_modifications = []
|
||||
if other is not None:
|
||||
self.extend(other)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.env_modifications)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.env_modifications)
|
||||
|
||||
def extend(self, other):
|
||||
self._check_other(other)
|
||||
self.env_modifications.extend(other.env_modifications)
|
||||
|
||||
@staticmethod
|
||||
def _check_other(other):
|
||||
if not isinstance(other, EnvironmentModifications):
|
||||
raise TypeError(
|
||||
'other must be an instance of EnvironmentModifications')
|
||||
|
||||
def _get_outside_caller_attributes(self):
|
||||
stack = inspect.stack()
|
||||
try:
|
||||
_, filename, lineno, _, context, index = stack[2]
|
||||
context = context[index].strip()
|
||||
except Exception:
|
||||
filename = 'unknown file'
|
||||
lineno = 'unknown line'
|
||||
context = 'unknown context'
|
||||
args = {'filename': filename, 'lineno': lineno, 'context': context}
|
||||
return args
|
||||
|
||||
def set(self, name, value, **kwargs):
|
||||
"""Stores a request to set an environment variable.
|
||||
|
||||
Args:
|
||||
name: name of the environment variable to be set
|
||||
value: value of the environment variable
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = SetEnv(name, value, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def append_flags(self, name, value, sep=' ', **kwargs):
|
||||
"""
|
||||
Stores in the current object a request to append to an env variable
|
||||
|
||||
Args:
|
||||
name: name of the environment variable to be appended to
|
||||
value: value to append to the environment variable
|
||||
Appends with spaces separating different additions to the variable
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
kwargs.update({'separator': sep})
|
||||
item = AppendFlagsEnv(name, value, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def unset(self, name, **kwargs):
|
||||
"""Stores a request to unset an environment variable.
|
||||
|
||||
Args:
|
||||
name: name of the environment variable to be set
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = UnsetEnv(name, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def set_path(self, name, elements, **kwargs):
|
||||
"""Stores a request to set a path generated from a list.
|
||||
|
||||
Args:
|
||||
name: name o the environment variable to be set.
|
||||
elements: elements of the path to set.
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = SetPath(name, elements, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def append_path(self, name, path, **kwargs):
|
||||
"""Stores a request to append a path to a path list.
|
||||
|
||||
Args:
|
||||
name: name of the path list in the environment
|
||||
path: path to be appended
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = AppendPath(name, path, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def prepend_path(self, name, path, **kwargs):
|
||||
"""Same as `append_path`, but the path is pre-pended.
|
||||
|
||||
Args:
|
||||
name: name of the path list in the environment
|
||||
path: path to be pre-pended
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = PrependPath(name, path, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def remove_path(self, name, path, **kwargs):
|
||||
"""Stores a request to remove a path from a path list.
|
||||
|
||||
Args:
|
||||
name: name of the path list in the environment
|
||||
path: path to be removed
|
||||
"""
|
||||
kwargs.update(self._get_outside_caller_attributes())
|
||||
item = RemovePath(name, path, **kwargs)
|
||||
self.env_modifications.append(item)
|
||||
|
||||
def group_by_name(self):
|
||||
"""Returns a dict of the modifications grouped by variable name.
|
||||
|
||||
Returns:
|
||||
dict mapping the environment variable name to the modifications to
|
||||
be done on it
|
||||
"""
|
||||
modifications = collections.defaultdict(list)
|
||||
for item in self:
|
||||
modifications[item.name].append(item)
|
||||
return modifications
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clears the current list of modifications
|
||||
"""
|
||||
self.env_modifications.clear()
|
||||
|
||||
def apply_modifications(self):
|
||||
"""Applies the modifications and clears the list."""
|
||||
modifications = self.group_by_name()
|
||||
# Apply modifications one variable at a time
|
||||
for name, actions in sorted(modifications.items()):
|
||||
for x in actions:
|
||||
x.execute()
|
||||
|
||||
@staticmethod
|
||||
def from_sourcing_file(filename, *args, **kwargs):
|
||||
"""Returns modifications that would be made by sourcing a file.
|
||||
|
||||
Parameters:
|
||||
filename (str): The file to source
|
||||
*args (list of str): Arguments to pass on the command line
|
||||
|
||||
Keyword Arguments:
|
||||
shell (str): The shell to use (default: ``bash``)
|
||||
shell_options (str): Options passed to the shell (default: ``-c``)
|
||||
source_command (str): The command to run (default: ``source``)
|
||||
suppress_output (str): Redirect used to suppress output of command
|
||||
(default: ``&> /dev/null``)
|
||||
concatenate_on_success (str): Operator used to execute a command
|
||||
only when the previous command succeeds (default: ``&&``)
|
||||
blacklist ([str or re]): Ignore any modifications of these
|
||||
variables (default: [])
|
||||
whitelist ([str or re]): Always respect modifications of these
|
||||
variables (default: []). Has precedence over blacklist.
|
||||
clean (bool): In addition to removing empty entries,
|
||||
also remove duplicate entries (default: False).
|
||||
|
||||
Returns:
|
||||
EnvironmentModifications: an object that, if executed, has
|
||||
the same effect on the environment as sourcing the file
|
||||
"""
|
||||
# Check if the file actually exists
|
||||
if not os.path.isfile(filename):
|
||||
msg = 'Trying to source non-existing file: {0}'.format(filename)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# Kwargs parsing and default values
|
||||
shell = kwargs.get('shell', '/bin/bash')
|
||||
shell_options = kwargs.get('shell_options', '-c')
|
||||
source_command = kwargs.get('source_command', 'source')
|
||||
suppress_output = kwargs.get('suppress_output', '&> /dev/null')
|
||||
concatenate_on_success = kwargs.get('concatenate_on_success', '&&')
|
||||
blacklist = kwargs.get('blacklist', [])
|
||||
whitelist = kwargs.get('whitelist', [])
|
||||
clean = kwargs.get('clean', False)
|
||||
|
||||
source_file = [source_command, filename]
|
||||
source_file.extend(args)
|
||||
source_file = ' '.join(source_file)
|
||||
|
||||
dump_cmd = 'import os, json; print(json.dumps(dict(os.environ)))'
|
||||
dump_environment = 'python -c "{0}"'.format(dump_cmd)
|
||||
|
||||
# Construct the command that will be executed
|
||||
command = [
|
||||
shell,
|
||||
shell_options,
|
||||
' '.join([
|
||||
source_file, suppress_output,
|
||||
concatenate_on_success, dump_environment,
|
||||
]),
|
||||
]
|
||||
|
||||
# Try to source the file
|
||||
proc = subprocess.Popen(
|
||||
command, stdout=subprocess.PIPE, env=os.environ)
|
||||
proc.wait()
|
||||
|
||||
if proc.returncode != 0:
|
||||
msg = 'Sourcing file {0} returned a non-zero exit code'.format(
|
||||
filename)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
output = ''.join([line.decode('utf-8') for line in proc.stdout])
|
||||
|
||||
# Construct dictionaries of the environment before and after
|
||||
# sourcing the file, so that we can diff them.
|
||||
env_before = dict(os.environ)
|
||||
env_after = json.loads(output)
|
||||
|
||||
# If we're in python2, convert to str objects instead of unicode
|
||||
# like json gives us. We can't put unicode in os.environ anyway.
|
||||
if sys.version_info[0] < 3:
|
||||
env_after = dict((k.encode('utf-8'), v.encode('utf-8'))
|
||||
for k, v in env_after.items())
|
||||
|
||||
# Other variables unrelated to sourcing a file
|
||||
blacklist.extend(['SHLVL', '_', 'PWD', 'OLDPWD', 'PS2'])
|
||||
|
||||
def set_intersection(fullset, *args):
|
||||
# A set intersection using string literals and regexs
|
||||
meta = '[' + re.escape('[$()*?[]^{|}') + ']'
|
||||
subset = fullset & set(args) # As literal
|
||||
for name in args:
|
||||
if re.search(meta, name):
|
||||
pattern = re.compile(name)
|
||||
for k in fullset:
|
||||
if re.match(pattern, k):
|
||||
subset.add(k)
|
||||
return subset
|
||||
|
||||
for d in env_after, env_before:
|
||||
# Retain (whitelist) has priority over prune (blacklist)
|
||||
prune = set_intersection(set(d), *blacklist)
|
||||
prune -= set_intersection(prune, *whitelist)
|
||||
for k in prune:
|
||||
d.pop(k, None)
|
||||
|
||||
# Fill the EnvironmentModifications instance
|
||||
env = EnvironmentModifications()
|
||||
|
||||
# New variables
|
||||
new_variables = list(set(env_after) - set(env_before))
|
||||
# Variables that have been unset
|
||||
unset_variables = list(set(env_before) - set(env_after))
|
||||
# Variables that have been modified
|
||||
common_variables = set(env_before).intersection(set(env_after))
|
||||
|
||||
modified_variables = [x for x in common_variables
|
||||
if env_before[x] != env_after[x]]
|
||||
|
||||
# Consistent output order - looks nicer, easier comparison...
|
||||
new_variables.sort()
|
||||
unset_variables.sort()
|
||||
modified_variables.sort()
|
||||
|
||||
def return_separator_if_any(*args):
|
||||
separators = ':', ';'
|
||||
for separator in separators:
|
||||
for arg in args:
|
||||
if separator in arg:
|
||||
return separator
|
||||
return None
|
||||
|
||||
# Add variables to env.
|
||||
# Assume that variables with 'PATH' in the name or that contain
|
||||
# separators like ':' or ';' are more likely to be paths
|
||||
for x in new_variables:
|
||||
sep = return_separator_if_any(env_after[x])
|
||||
if sep:
|
||||
env.prepend_path(x, env_after[x], separator=sep)
|
||||
elif 'PATH' in x:
|
||||
env.prepend_path(x, env_after[x])
|
||||
else:
|
||||
# We just need to set the variable to the new value
|
||||
env.set(x, env_after[x])
|
||||
|
||||
for x in unset_variables:
|
||||
env.unset(x)
|
||||
|
||||
for x in modified_variables:
|
||||
before = env_before[x]
|
||||
after = env_after[x]
|
||||
sep = return_separator_if_any(before, after)
|
||||
if sep:
|
||||
before_list = before.split(sep)
|
||||
after_list = after.split(sep)
|
||||
|
||||
# Filter out empty strings
|
||||
before_list = list(filter(None, before_list))
|
||||
after_list = list(filter(None, after_list))
|
||||
|
||||
# Remove duplicate entries (worse matching, bloats env)
|
||||
if clean:
|
||||
before_list = list(dedupe(before_list))
|
||||
after_list = list(dedupe(after_list))
|
||||
# The reassembled cleaned entries
|
||||
before = sep.join(before_list)
|
||||
after = sep.join(after_list)
|
||||
|
||||
# Paths that have been removed
|
||||
remove_list = [
|
||||
ii for ii in before_list if ii not in after_list]
|
||||
# Check that nothing has been added in the middle of
|
||||
# before_list
|
||||
remaining_list = [
|
||||
ii for ii in before_list if ii in after_list]
|
||||
try:
|
||||
start = after_list.index(remaining_list[0])
|
||||
end = after_list.index(remaining_list[-1])
|
||||
search = sep.join(after_list[start:end + 1])
|
||||
except IndexError:
|
||||
env.prepend_path(x, after)
|
||||
|
||||
if search not in before:
|
||||
# We just need to set the variable to the new value
|
||||
env.prepend_path(x, after)
|
||||
else:
|
||||
try:
|
||||
prepend_list = after_list[:start]
|
||||
prepend_list.reverse() # Preserve order after prepend
|
||||
except KeyError:
|
||||
prepend_list = []
|
||||
try:
|
||||
append_list = after_list[end + 1:]
|
||||
except KeyError:
|
||||
append_list = []
|
||||
|
||||
for item in remove_list:
|
||||
env.remove_path(x, item)
|
||||
for item in append_list:
|
||||
env.append_path(x, item)
|
||||
for item in prepend_list:
|
||||
env.prepend_path(x, item)
|
||||
else:
|
||||
# We just need to set the variable to the new value
|
||||
env.set(x, after)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def concatenate_paths(paths, separator=':'):
|
||||
"""Concatenates an iterable of paths into a string of paths separated by
|
||||
separator, defaulting to colon.
|
||||
|
||||
Args:
|
||||
paths: iterable of paths
|
||||
separator: the separator to use, default ':'
|
||||
|
||||
Returns:
|
||||
string
|
||||
"""
|
||||
return separator.join(str(item) for item in paths)
|
||||
|
||||
|
||||
def set_or_unset_not_first(variable, changes, errstream):
|
||||
"""Check if we are going to set or unset something after other
|
||||
modifications have already been requested.
|
||||
"""
|
||||
indexes = [ii for ii, item in enumerate(changes)
|
||||
if ii != 0 and
|
||||
not item.args.get('force', False) and
|
||||
type(item) in [SetEnv, UnsetEnv]]
|
||||
if indexes:
|
||||
good = '\t \t{context} at {filename}:{lineno}'
|
||||
nogood = '\t--->\t{context} at {filename}:{lineno}'
|
||||
message = "Suspicious requests to set or unset '{var}' found"
|
||||
errstream(message.format(var=variable))
|
||||
for ii, item in enumerate(changes):
|
||||
print_format = nogood if ii in indexes else good
|
||||
errstream(print_format.format(**item.args))
|
||||
|
||||
|
||||
def validate(env, errstream):
|
||||
"""Validates the environment modifications to check for the presence of
|
||||
suspicious patterns. Prompts a warning for everything that was found.
|
||||
|
||||
Current checks:
|
||||
- set or unset variables after other changes on the same variable
|
||||
|
||||
Args:
|
||||
env: list of environment modifications
|
||||
"""
|
||||
modifications = env.group_by_name()
|
||||
for variable, list_of_changes in sorted(modifications.items()):
|
||||
set_or_unset_not_first(variable, list_of_changes, errstream)
|
||||
|
||||
|
||||
def filter_environment_blacklist(env, variables):
|
||||
"""Generator that filters out any change to environment variables present in
|
||||
the input list.
|
||||
|
||||
Args:
|
||||
env: list of environment modifications
|
||||
variables: list of variable names to be filtered
|
||||
|
||||
Returns:
|
||||
items in env if they are not in variables
|
||||
"""
|
||||
for item in env:
|
||||
if item.name not in variables:
|
||||
yield item
|
||||
|
||||
|
||||
def inspect_path(root, inspections, exclude=None):
|
||||
"""Inspects ``root`` to search for the subdirectories in ``inspections``.
|
||||
Adds every path found to a list of prepend-path commands and returns it.
|
||||
|
||||
Args:
|
||||
root (str): absolute path where to search for subdirectories
|
||||
|
||||
inspections (dict): maps relative paths to a list of environment
|
||||
variables that will be modified if the path exists. The
|
||||
modifications are not performed immediately, but stored in a
|
||||
command object that is returned to client
|
||||
|
||||
exclude (callable): optional callable. If present it must accept an
|
||||
absolute path and return True if it should be excluded from the
|
||||
inspection
|
||||
|
||||
Examples:
|
||||
|
||||
The following lines execute an inspection in ``/usr`` to search for
|
||||
``/usr/include`` and ``/usr/lib64``. If found we want to prepend
|
||||
``/usr/include`` to ``CPATH`` and ``/usr/lib64`` to ``MY_LIB64_PATH``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Set up the dictionary containing the inspection
|
||||
inspections = {
|
||||
'include': ['CPATH'],
|
||||
'lib64': ['MY_LIB64_PATH']
|
||||
}
|
||||
|
||||
# Get back the list of command needed to modify the environment
|
||||
env = inspect_path('/usr', inspections)
|
||||
|
||||
# Eventually execute the commands
|
||||
env.apply_modifications()
|
||||
|
||||
Returns:
|
||||
instance of EnvironmentModifications containing the requested
|
||||
modifications
|
||||
"""
|
||||
if exclude is None:
|
||||
exclude = lambda x: False
|
||||
|
||||
env = EnvironmentModifications()
|
||||
# Inspect the prefix to check for the existence of common directories
|
||||
for relative_path, variables in inspections.items():
|
||||
expected = os.path.join(root, relative_path)
|
||||
|
||||
if os.path.isdir(expected) and not exclude(expected):
|
||||
for variable in variables:
|
||||
env.prepend_path(variable, expected)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def preserve_environment(*variables):
|
||||
"""Ensures that the value of the environment variables passed as
|
||||
arguments is the same before entering to the context manager and after
|
||||
exiting it.
|
||||
|
||||
Variables that are unset before entering the context manager will be
|
||||
explicitly unset on exit.
|
||||
|
||||
Args:
|
||||
variables (list of str): list of environment variables to be preserved
|
||||
"""
|
||||
cache = {}
|
||||
for var in variables:
|
||||
# The environment variable to be preserved might not be there.
|
||||
# In that case store None as a placeholder.
|
||||
cache[var] = os.environ.get(var, None)
|
||||
|
||||
yield
|
||||
|
||||
for var in variables:
|
||||
value = cache[var]
|
||||
msg = '[PRESERVE_ENVIRONMENT]'
|
||||
if value is not None:
|
||||
# Print a debug statement if the value changed
|
||||
if var not in os.environ:
|
||||
msg += ' {0} was unset, will be reset to "{1}"'
|
||||
tty.debug(msg.format(var, value))
|
||||
elif os.environ[var] != value:
|
||||
msg += ' {0} was set to "{1}", will be reset to "{2}"'
|
||||
tty.debug(msg.format(var, os.environ[var], value))
|
||||
os.environ[var] = value
|
||||
elif var in os.environ:
|
||||
msg += ' {0} was set to "{1}", will be unset'
|
||||
tty.debug(msg.format(var, os.environ[var]))
|
||||
del os.environ[var]
|
||||
|
@ -36,7 +36,7 @@
|
||||
import os
|
||||
|
||||
from spack import *
|
||||
from spack.environment import EnvironmentModifications
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
from spack.pkg.builtin.openfoam_com import OpenfoamArch
|
||||
from spack.pkg.builtin.openfoam_com import add_extra_files
|
||||
from spack.pkg.builtin.openfoam_com import write_environ
|
||||
|
@ -4,7 +4,7 @@
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
from spack import *
|
||||
from spack.environment import EnvironmentModifications
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
import os
|
||||
|
||||
|
||||
|
@ -43,7 +43,7 @@
|
||||
import os
|
||||
|
||||
from spack import *
|
||||
from spack.environment import EnvironmentModifications
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
import llnl.util.tty as tty
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user