# Copyright 2016-2024 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
# ReFrame Project Developers. See the top-level LICENSE file for details.
#
# SPDX-License-Identifier: BSD-3-Clause
import collections
import os
import reframe.core.fields as fields
import reframe.utility as util
import reframe.utility.jsonext as jsonext
import reframe.utility.typecheck as typ
from reframe.core.warnings import user_deprecation_warning
def normalize_module_list(modules):
'''Normalize module list.
:meta private:
'''
ret = []
for m in modules:
if isinstance(m, str):
ret.append({'name': m, 'collection': False, 'path': None})
else:
ret.append(m)
return ret
[docs]
class Environment(jsonext.JSONSerializable):
'''This class abstracts away an environment to run regression tests.
It is simply a collection of modules to be loaded and environment variables
to be set when this environment is loaded by the framework.
.. warning::
Users may not create :class:`Environment` objects directly.
'''
def __init__(self, name, modules=None, env_vars=None,
extras=None, features=None, prepare_cmds=None):
modules = modules or []
env_vars = env_vars or []
self._name = name
self._modules = normalize_module_list(modules)
self._module_names = [m['name'] for m in self._modules]
# Convert values of env_vars to strings before storing
if isinstance(env_vars, dict):
env_vars = env_vars.items()
self._env_vars = collections.OrderedDict()
for k, v in env_vars:
self._env_vars[k] = str(v)
self._extras = extras or {}
self._features = features or []
self._prepare_cmds = prepare_cmds or []
@property
def name(self):
'''The name of this environment.
:type: :class:`str`
'''
return self._name
@property
def modules(self):
'''The modules associated with this environment.
:type: :class:`List[str]`
'''
return util.SequenceView(self._module_names)
@property
def modules_detailed(self):
'''A view of the modules associated with this environment in a detailed
format.
Each module is represented as a dictionary with the following
attributes:
- ``name``: the name of the module.
- ``collection``: :class:`True` if the module name refers to a module
collection.
:type: :class:`List[Dict[str, object]]`
.. versionadded:: 3.3
'''
return util.SequenceView(self._modules)
@property
def env_vars(self):
'''The environment variables associated with this environment.
:type: :class:`OrderedDict[str, str]`
.. versionadded:: 4.0.0
'''
return util.MappingView(self._env_vars)
@property
def variables(self):
'''The environment variables associated with this environment.
.. deprecated:: 4.0.0
Please :attr:`env_vars` instead.
'''
user_deprecation_warning("the 'variables' attribute is deprecated; "
"please use the 'env_vars' instead")
return util.MappingView(self._env_vars)
@property
def extras(self):
'''User defined properties specified in the configuration.
.. versionadded:: 3.9.1
:type: :class:`Dict[str, object]`
'''
return self._extras
@property
def features(self):
'''Used defined features specified in the configuration.
.. versionadded:: 3.11.0
:type: :class:`List[str]`
'''
return self._features
@property
def prepare_cmds(self):
'''The prepare commands associated with this environment.
.. versionadded:: 4.3.0
:type: :class:`List[str]`
'''
return util.SequenceView(self._prepare_cmds)
def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
if (self.name != other.name or
set(self.modules) != set(other.modules)):
return False
# Env. variables are checked against their string representation
for kv0, kv1 in zip(self.env_vars.items(),
other.env_vars.items()):
k0, v0 = kv0
k1, v1 = kv1
if k0 != k1 or str(v0) != str(v1):
return False
return True
def __str__(self):
return self.name
def __repr__(self):
return (f'{type(self).__name__}('
f'name={self._name!r}, '
f'modules={self._modules!r}, '
f'env_vars={list(self._env_vars.items())!r}, '
f'extras={self._extras!r}, features={self._features!r})')
[docs]
class _EnvironmentSnapshot(Environment):
'''An environment snapshot.'''
def __init__(self, name='env_snapshot'):
super().__init__(name, [], os.environ.items())
[docs]
def restore(self):
'''Restore this environment snapshot.'''
os.environ.clear()
os.environ.update(self._env_vars)
def __eq__(self, other):
if not isinstance(other, Environment):
return NotImplemented
# Order of env. variables is not important when comparing snapshots
for k, v in self.env_vars.items():
if other.env_vars[k] != v:
return False
return self.name == other.name
[docs]
def snapshot():
'''Create an environment snapshot
:returns: An instance of :class:`_EnvironmentSnapshot`.
'''
return _EnvironmentSnapshot()
[docs]
class ProgEnvironment(Environment):
'''A class representing a programming environment.
This type of environment adds also properties for retrieving the compiler
and compilation flags.
.. warning::
Users may not create :class:`ProgEnvironment` objects directly.
'''
_cc = fields.TypedField(str)
_cxx = fields.TypedField(str)
_ftn = fields.TypedField(str)
_nvcc = fields.TypedField(str)
_cppflags = fields.TypedField(typ.List[str])
_cflags = fields.TypedField(typ.List[str])
_cxxflags = fields.TypedField(typ.List[str])
_fflags = fields.TypedField(typ.List[str])
_ldflags = fields.TypedField(typ.List[str])
def __init__(self,
name,
modules=None,
env_vars=None,
extras=None,
features=None,
prepare_cmds=None,
cc='cc',
cxx='CC',
ftn='ftn',
nvcc='nvcc',
cppflags=None,
cflags=None,
cxxflags=None,
fflags=None,
ldflags=None,
resources=None,
**kwargs):
super().__init__(name, modules, env_vars, extras, features,
prepare_cmds)
self._cc = cc
self._cxx = cxx
self._ftn = ftn
self._nvcc = nvcc
self._cppflags = cppflags or []
self._cflags = cflags or []
self._cxxflags = cxxflags or []
self._fflags = fflags or []
self._ldflags = ldflags or []
self._resources = resources or {}
@property
def cc(self):
'''The C compiler of this programming environment.
:type: :class:`str`
'''
return self._cc
@property
def cxx(self):
'''The C++ compiler of this programming environment.
:type: :class:`str`
'''
return self._cxx
@property
def ftn(self):
'''The Fortran compiler of this programming environment.
:type: :class:`str`
'''
return self._ftn
@property
def cppflags(self):
'''The preprocessor flags of this programming environment.
:type: :class:`List[str]`
'''
return self._cppflags
@property
def cflags(self):
'''The C compiler flags of this programming environment.
:type: :class:`List[str]`
'''
return self._cflags
@property
def cxxflags(self):
'''The C++ compiler flags of this programming environment.
:type: :class:`List[str]`
'''
return self._cxxflags
@property
def fflags(self):
'''The Fortran compiler flags of this programming environment.
:type: :class:`List[str]`
'''
return self._fflags
@property
def ldflags(self):
'''The linker flags of this programming environment.
:type: :class:`List[str]`
'''
return self._ldflags
@property
def nvcc(self):
'''The NVIDIA CUDA compiler of this programming environment.
:type: :class:`str`
'''
return self._nvcc
@property
def resources(self):
'''The scheduler resources associated with this environment.
.. versionadded:: 4.6.0
:type: :class:`Dict[str, object]`
'''
return self._resources