modules: configurable module defaults (#24367)
Any spec satisfying a default will be symlinked to `default` If multiple specs have modulefiles in the same directory and satisfy configured module defaults, then whichever was written last will be default.
This commit is contained in:
parent
dee75a4945
commit
a8a08f66ad
@ -449,6 +449,36 @@ that are already in the LMod hierarchy.
|
||||
For hierarchies that are deeper than three layers ``lmod spider`` may have some issues.
|
||||
See `this discussion on the LMod project <https://github.com/TACC/Lmod/issues/114>`_.
|
||||
|
||||
""""""""""""""""""""""
|
||||
Select default modules
|
||||
""""""""""""""""""""""
|
||||
|
||||
By default, when multiple modules of the same name share a directory,
|
||||
the highest version number will be the default module. This behavior
|
||||
of the ``module`` command can be overridden with a symlink named
|
||||
``default`` to the desired default module. If you wish to configure
|
||||
default modules with Spack, add a ``defaults`` key to your modules
|
||||
configuration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
modules:
|
||||
my-module-set:
|
||||
tcl:
|
||||
defaults:
|
||||
- gcc@10.2.1
|
||||
- hdf5@1.2.10+mpi+hl%gcc
|
||||
|
||||
These defaults may be arbitrarily specific. For any package that
|
||||
satisfies a default, Spack will generate the module file in the
|
||||
appropriate path, and will generate a default symlink to the module
|
||||
file as well.
|
||||
|
||||
.. warning::
|
||||
If Spack is configured to generate multiple default packages in the
|
||||
same directory, the last modulefile to be generated will be the
|
||||
default module.
|
||||
|
||||
.. _customize-env-modifications:
|
||||
|
||||
"""""""""""""""""""""""""""""""""""
|
||||
|
@ -209,6 +209,10 @@ def merge_config_rules(configuration, spec):
|
||||
verbose = module_specific_configuration.get('verbose', False)
|
||||
spec_configuration['verbose'] = verbose
|
||||
|
||||
# module defaults per-package
|
||||
defaults = module_specific_configuration.get('defaults', [])
|
||||
spec_configuration['defaults'] = defaults
|
||||
|
||||
return spec_configuration
|
||||
|
||||
|
||||
@ -453,6 +457,11 @@ def template(self):
|
||||
"""
|
||||
return self.conf.get('template', None)
|
||||
|
||||
@property
|
||||
def defaults(self):
|
||||
"""Returns the specs configured as defaults or []."""
|
||||
return self.conf.get('defaults', [])
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
"""List of environment modifications that should be done in the
|
||||
@ -891,6 +900,18 @@ def write(self, overwrite=False):
|
||||
if os.path.exists(self.layout.filename):
|
||||
fp.set_permissions_by_spec(self.layout.filename, self.spec)
|
||||
|
||||
# Symlink defaults if needed
|
||||
if any(self.spec.satisfies(default) for default in self.conf.defaults):
|
||||
# This spec matches a default, it needs to be symlinked to default
|
||||
# Symlink to a tmp location first and move, so that existing
|
||||
# symlinks do not cause an error.
|
||||
default_path = os.path.join(os.path.dirname(self.layout.filename),
|
||||
'default')
|
||||
default_tmp = os.path.join(os.path.dirname(self.layout.filename),
|
||||
'.tmp_spack_default')
|
||||
os.symlink(self.layout.filename, default_tmp)
|
||||
os.rename(default_tmp, default_path)
|
||||
|
||||
def remove(self):
|
||||
"""Deletes the module file."""
|
||||
mod_file = self.layout.filename
|
||||
|
@ -17,8 +17,8 @@
|
||||
#: THIS NEEDS TO BE UPDATED FOR EVERY NEW KEYWORD THAT
|
||||
#: IS ADDED IMMEDIATELY BELOW THE MODULE TYPE ATTRIBUTE
|
||||
spec_regex = r'(?!hierarchy|core_specs|verbose|hash_length|whitelist|' \
|
||||
r'blacklist|projections|naming_scheme|core_compilers|all)' \
|
||||
r'(^\w[\w-]*)'
|
||||
r'blacklist|projections|naming_scheme|core_compilers|all|' \
|
||||
r'defaults)(^\w[\w-]*)'
|
||||
|
||||
#: Matches a valid name for a module set
|
||||
# Banned names are valid entries at that level in the previous schema
|
||||
@ -99,6 +99,7 @@
|
||||
'type': 'boolean',
|
||||
'default': False
|
||||
},
|
||||
'defaults': array_of_strings,
|
||||
'naming_scheme': {
|
||||
'type': 'string' # Can we be more specific here?
|
||||
},
|
||||
|
@ -46,13 +46,28 @@ def test_update_dictionary_extending_list():
|
||||
@pytest.fixture()
|
||||
def mock_module_filename(monkeypatch, tmpdir):
|
||||
filename = str(tmpdir.join('module'))
|
||||
monkeypatch.setattr(spack.modules.common.BaseFileLayout,
|
||||
# Set for both module types so we can test both
|
||||
monkeypatch.setattr(spack.modules.lmod.LmodFileLayout,
|
||||
'filename',
|
||||
filename)
|
||||
monkeypatch.setattr(spack.modules.tcl.TclFileLayout,
|
||||
'filename',
|
||||
filename)
|
||||
|
||||
yield filename
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_module_defaults(monkeypatch):
|
||||
def impl(*args):
|
||||
# No need to patch both types because neither override base
|
||||
monkeypatch.setattr(spack.modules.common.BaseConfiguration,
|
||||
'defaults',
|
||||
[arg for arg in args])
|
||||
|
||||
return impl
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_package_perms(monkeypatch):
|
||||
perms = stat.S_IRGRP | stat.S_IWGRP
|
||||
@ -77,6 +92,22 @@ def test_modules_written_with_proper_permissions(mock_module_filename,
|
||||
mock_module_filename).st_mode == mock_package_perms
|
||||
|
||||
|
||||
@pytest.mark.parametrize('module_type', ['tcl', 'lmod'])
|
||||
def test_modules_default_symlink(
|
||||
module_type, mock_packages, mock_module_filename, mock_module_defaults, config
|
||||
):
|
||||
spec = spack.spec.Spec('mpileaks@2.3').concretized()
|
||||
mock_module_defaults(spec.format('{name}{@version}'))
|
||||
|
||||
generator_cls = spack.modules.module_types[module_type]
|
||||
generator = generator_cls(spec, 'default')
|
||||
generator.write()
|
||||
|
||||
link_path = os.path.join(os.path.dirname(mock_module_filename), 'default')
|
||||
assert os.path.islink(link_path)
|
||||
assert os.readlink(link_path) == mock_module_filename
|
||||
|
||||
|
||||
class MockDb(object):
|
||||
def __init__(self, db_ids, spec_hash_to_db):
|
||||
self.upstream_dbs = db_ids
|
||||
|
Loading…
Reference in New Issue
Block a user