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.
|
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>`_.
|
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:
|
.. _customize-env-modifications:
|
||||||
|
|
||||||
"""""""""""""""""""""""""""""""""""
|
"""""""""""""""""""""""""""""""""""
|
||||||
|
@ -209,6 +209,10 @@ def merge_config_rules(configuration, spec):
|
|||||||
verbose = module_specific_configuration.get('verbose', False)
|
verbose = module_specific_configuration.get('verbose', False)
|
||||||
spec_configuration['verbose'] = verbose
|
spec_configuration['verbose'] = verbose
|
||||||
|
|
||||||
|
# module defaults per-package
|
||||||
|
defaults = module_specific_configuration.get('defaults', [])
|
||||||
|
spec_configuration['defaults'] = defaults
|
||||||
|
|
||||||
return spec_configuration
|
return spec_configuration
|
||||||
|
|
||||||
|
|
||||||
@ -453,6 +457,11 @@ def template(self):
|
|||||||
"""
|
"""
|
||||||
return self.conf.get('template', None)
|
return self.conf.get('template', None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def defaults(self):
|
||||||
|
"""Returns the specs configured as defaults or []."""
|
||||||
|
return self.conf.get('defaults', [])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def env(self):
|
def env(self):
|
||||||
"""List of environment modifications that should be done in the
|
"""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):
|
if os.path.exists(self.layout.filename):
|
||||||
fp.set_permissions_by_spec(self.layout.filename, self.spec)
|
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):
|
def remove(self):
|
||||||
"""Deletes the module file."""
|
"""Deletes the module file."""
|
||||||
mod_file = self.layout.filename
|
mod_file = self.layout.filename
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
#: THIS NEEDS TO BE UPDATED FOR EVERY NEW KEYWORD THAT
|
#: THIS NEEDS TO BE UPDATED FOR EVERY NEW KEYWORD THAT
|
||||||
#: IS ADDED IMMEDIATELY BELOW THE MODULE TYPE ATTRIBUTE
|
#: IS ADDED IMMEDIATELY BELOW THE MODULE TYPE ATTRIBUTE
|
||||||
spec_regex = r'(?!hierarchy|core_specs|verbose|hash_length|whitelist|' \
|
spec_regex = r'(?!hierarchy|core_specs|verbose|hash_length|whitelist|' \
|
||||||
r'blacklist|projections|naming_scheme|core_compilers|all)' \
|
r'blacklist|projections|naming_scheme|core_compilers|all|' \
|
||||||
r'(^\w[\w-]*)'
|
r'defaults)(^\w[\w-]*)'
|
||||||
|
|
||||||
#: Matches a valid name for a module set
|
#: Matches a valid name for a module set
|
||||||
# Banned names are valid entries at that level in the previous schema
|
# Banned names are valid entries at that level in the previous schema
|
||||||
@ -99,6 +99,7 @@
|
|||||||
'type': 'boolean',
|
'type': 'boolean',
|
||||||
'default': False
|
'default': False
|
||||||
},
|
},
|
||||||
|
'defaults': array_of_strings,
|
||||||
'naming_scheme': {
|
'naming_scheme': {
|
||||||
'type': 'string' # Can we be more specific here?
|
'type': 'string' # Can we be more specific here?
|
||||||
},
|
},
|
||||||
|
@ -46,13 +46,28 @@ def test_update_dictionary_extending_list():
|
|||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def mock_module_filename(monkeypatch, tmpdir):
|
def mock_module_filename(monkeypatch, tmpdir):
|
||||||
filename = str(tmpdir.join('module'))
|
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',
|
||||||
filename)
|
filename)
|
||||||
|
|
||||||
yield 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()
|
@pytest.fixture()
|
||||||
def mock_package_perms(monkeypatch):
|
def mock_package_perms(monkeypatch):
|
||||||
perms = stat.S_IRGRP | stat.S_IWGRP
|
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
|
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):
|
class MockDb(object):
|
||||||
def __init__(self, db_ids, spec_hash_to_db):
|
def __init__(self, db_ids, spec_hash_to_db):
|
||||||
self.upstream_dbs = db_ids
|
self.upstream_dbs = db_ids
|
||||||
|
Loading…
Reference in New Issue
Block a user