summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGreg Becker <becker33@llnl.gov>2020-05-15 11:12:52 -0700
committerGitHub <noreply@github.com>2020-05-15 11:12:52 -0700
commit32a9adcf606b1e6ab08da0eadad6bfa6d9d63db7 (patch)
tree6002025e7c9478868b14317a01e88f3863ea0144 /lib
parent955a3db20638ae50d902595829d6dc5d5d92a47c (diff)
downloadspack-32a9adcf606b1e6ab08da0eadad6bfa6d9d63db7.tar.gz
spack-32a9adcf606b1e6ab08da0eadad6bfa6d9d63db7.tar.bz2
spack-32a9adcf606b1e6ab08da0eadad6bfa6d9d63db7.tar.xz
spack-32a9adcf606b1e6ab08da0eadad6bfa6d9d63db7.zip
modules: use projections format for naming schemes (#16629)
* update tcl naming_scheme to use projections * add projections to lmod modules
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/build_systems/intelpackage.rst6
-rw-r--r--lib/spack/docs/module_file_support.rst56
-rw-r--r--lib/spack/spack/filesystem_view.py12
-rw-r--r--lib/spack/spack/modules/common.py25
-rw-r--r--lib/spack/spack/modules/lmod.py13
-rw-r--r--lib/spack/spack/modules/tcl.py7
-rw-r--r--lib/spack/spack/projections.py16
-rw-r--r--lib/spack/spack/schema/modules.py10
-rw-r--r--lib/spack/spack/test/data/modules/lmod/projections.yaml6
-rw-r--r--lib/spack/spack/test/data/modules/tcl/conflicts.yaml3
-rw-r--r--lib/spack/spack/test/data/modules/tcl/invalid_naming_scheme.yaml3
-rw-r--r--lib/spack/spack/test/data/modules/tcl/projections.yaml6
-rw-r--r--lib/spack/spack/test/data/modules/tcl/wrong_conflicts.yaml3
-rw-r--r--lib/spack/spack/test/modules/lmod.py36
-rw-r--r--lib/spack/spack/test/modules/tcl.py31
15 files changed, 171 insertions, 62 deletions
diff --git a/lib/spack/docs/build_systems/intelpackage.rst b/lib/spack/docs/build_systems/intelpackage.rst
index 153b41bdc7..66f473cbf8 100644
--- a/lib/spack/docs/build_systems/intelpackage.rst
+++ b/lib/spack/docs/build_systems/intelpackage.rst
@@ -1055,6 +1055,6 @@ Footnotes
2. Set the hash length in ``install-path-scheme``, also in ``config.yaml``
(:ref:`q.v. <config-yaml>`).
3. You will want to set the *same* hash length for
- :ref:`tcl module files <modules-naming-scheme>`
- if you have Spack produce them for you, under ``naming_scheme`` in
- ``modules.yaml``. Other module dialects cannot be altered in this manner.
+ :ref:`module files <modules-projections>`
+ if you have Spack produce them for you, under ``projections`` in
+ ``modules.yaml``.
diff --git a/lib/spack/docs/module_file_support.rst b/lib/spack/docs/module_file_support.rst
index 406715b07e..73047a7754 100644
--- a/lib/spack/docs/module_file_support.rst
+++ b/lib/spack/docs/module_file_support.rst
@@ -459,14 +459,14 @@ is compiled with ``gcc@4.4.7``, with the only exception of any ``gcc``
or any ``llvm`` installation.
-.. _modules-naming-scheme:
+.. _modules-projections:
-"""""""""""""""""""""""""""
-Customize the naming scheme
-"""""""""""""""""""""""""""
+"""""""""""""""""""""""""""""""
+Customize the naming of modules
+"""""""""""""""""""""""""""""""
The names of environment modules generated by spack are not always easy to
-fully comprehend due to the long hash in the name. There are two module
+fully comprehend due to the long hash in the name. There are three module
configuration options to help with that. The first is a global setting to
adjust the hash length. It can be set anywhere from 0 to 32 and has a default
length of 7. This is the representation of the hash in the module file name and
@@ -500,20 +500,46 @@ version of python a set of python extensions is associated with. Likewise, the
``openblas`` string is attached to any program that has openblas in the spec,
most likely via the ``+blas`` variant specification.
+The most heavyweight solution to module naming is to change the entire
+naming convention for module files. This uses the projections format
+covered in :ref:`adding_projections_to_views`.
+
+.. code-block:: yaml
+
+ modules:
+ tcl:
+ projections:
+ all: '{name}/{version}-{compiler.name}-{compiler.version}-module'
+ ^mpi: '{name}/{version}-{^mpi.name}-{^mpi.version}-{compiler.name}-{compiler.version}-module'
+
+will create module files that are nested in directories by package
+name, contain the version and compiler name and version, and have the
+word ``module`` before the hash for all specs that do not depend on
+mpi, and will have the same information plus the MPI implementation
+name and version for all packages that depend on mpi.
+
+When specifying module names by projection for Lmod modules, we
+recommend NOT including names of dependencies (e.g., MPI, compilers)
+that are already in the LMod hierarchy.
+
+
+
.. note::
- TCL module files
- A modification that is specific to ``tcl`` module files is the possibility
- to change the naming scheme of modules.
+ TCL modules
+ TCL modules also allow for explicit conflicts between modulefiles.
.. code-block:: yaml
- modules:
- tcl:
- naming_scheme: '{name}/{version}-{compiler.name}-{compiler.version}'
- all:
- conflict:
- - '{name}'
- - 'intel/14.0.1'
+ modules:
+ enable:
+ - tcl
+ tcl:
+ projections:
+ all: '{name}/{version}-{compiler.name}-{compiler.version}'
+ all:
+ conflict:
+ - '{name}'
+ - 'intel/14.0.1'
will create module files that will conflict with ``intel/14.0.1`` and with the
base directory of the same module, effectively preventing the possibility to
diff --git a/lib/spack/spack/filesystem_view.py b/lib/spack/spack/filesystem_view.py
index babe1b9c0e..b2bc30e1a5 100644
--- a/lib/spack/spack/filesystem_view.py
+++ b/lib/spack/spack/filesystem_view.py
@@ -22,6 +22,7 @@ import spack.util.spack_yaml as s_yaml
import spack.spec
import spack.store
import spack.schema.projections
+import spack.projections
import spack.config
from spack.error import SpackError
from spack.directory_layout import ExtensionAlreadyInstalledError
@@ -470,14 +471,9 @@ class YamlFilesystemView(FilesystemView):
if spec.package.extendee_spec:
locator_spec = spec.package.extendee_spec
- all_fmt_str = None
- for spec_like, fmt_str in self.projections.items():
- if locator_spec.satisfies(spec_like, strict=True):
- return os.path.join(self._root, locator_spec.format(fmt_str))
- elif spec_like == 'all':
- all_fmt_str = fmt_str
- if all_fmt_str:
- return os.path.join(self._root, locator_spec.format(all_fmt_str))
+ proj = spack.projections.get_projection(self.projections, locator_spec)
+ if proj:
+ return os.path.join(self._root, locator_spec.format(proj))
return self._root
def get_all_specs(self):
diff --git a/lib/spack/spack/modules/common.py b/lib/spack/spack/modules/common.py
index 8dee443eb3..7eca9292c9 100644
--- a/lib/spack/spack/modules/common.py
+++ b/lib/spack/spack/modules/common.py
@@ -41,6 +41,7 @@ import spack.build_environment as build_environment
import spack.error
import spack.paths
import spack.schema.environment
+import spack.projections as proj
import spack.tengine as tengine
import spack.util.environment
import spack.util.file_permissions as fp
@@ -381,6 +382,9 @@ class BaseConfiguration(object):
querying easier. It needs to be sub-classed for specific module types.
"""
+ default_projections = {
+ 'all': '{name}-{version}-{compiler.name}-{compiler.version}'}
+
def __init__(self, spec):
# Module where type(self) is defined
self.module = inspect.getmodule(self)
@@ -391,19 +395,18 @@ class BaseConfiguration(object):
self.conf = merge_config_rules(self.module.configuration(), self.spec)
@property
- def naming_scheme(self):
- """Naming scheme suitable for non-hierarchical layouts"""
- scheme = self.module.configuration().get(
- 'naming_scheme',
- '{name}-{version}-{compiler.name}-{compiler.version}'
- )
+ def projections(self):
+ """Projection from specs to module names"""
+ projections = self.module.configuration().get(
+ 'projections', self.default_projections)
# Ensure the named tokens we are expanding are allowed, see
# issue #2884 for reference
msg = 'some tokens cannot be part of the module naming scheme'
- _check_tokens_are_valid(scheme, message=msg)
+ for projection in projections.values():
+ _check_tokens_are_valid(projection, message=msg)
- return scheme
+ return projections
@property
def template(self):
@@ -551,7 +554,11 @@ class BaseFileLayout(object):
to console to use it. This implementation fits the needs of most
non-hierarchical layouts.
"""
- name = self.spec.format(self.conf.naming_scheme)
+ projection = proj.get_projection(self.conf.projections, self.spec)
+ if not projection:
+ projection = self.conf.default_projections['all']
+
+ name = self.spec.format(projection)
# Not everybody is working on linux...
parts = name.split('/')
name = os.path.join(*parts)
diff --git a/lib/spack/spack/modules/lmod.py b/lib/spack/spack/modules/lmod.py
index d0d14dac5b..018edb35ad 100644
--- a/lib/spack/spack/modules/lmod.py
+++ b/lib/spack/spack/modules/lmod.py
@@ -91,6 +91,7 @@ def guess_core_compilers(store=False):
class LmodConfiguration(BaseConfiguration):
"""Configuration class for lmod module files."""
+ default_projections = {'all': os.path.join('{name}', '{version}')}
@property
def core_compilers(self):
@@ -243,18 +244,6 @@ class LmodFileLayout(BaseFileLayout):
)
return fullname
- @property
- def use_name(self):
- """Returns the 'use' name of the module i.e. the name you have to type
- to console to use it.
- """
- # Package name and version
- base = os.path.join("{name}", "{version}")
- name_parts = [self.spec.format(base)]
- # The remaining elements are filename suffixes
- name_parts.extend(self.conf.suffixes)
- return '-'.join(name_parts)
-
def token_to_path(self, name, value):
"""Transforms a hierarchy token into the corresponding path part.
diff --git a/lib/spack/spack/modules/tcl.py b/lib/spack/spack/modules/tcl.py
index ec8032a0e1..0efc6332fe 100644
--- a/lib/spack/spack/modules/tcl.py
+++ b/lib/spack/spack/modules/tcl.py
@@ -12,6 +12,7 @@ import string
import llnl.util.tty as tty
import spack.config
+import spack.projections as proj
import spack.tengine as tengine
from .common import BaseConfiguration, BaseFileLayout
from .common import BaseContext, BaseModuleFileWriter
@@ -72,12 +73,12 @@ class TclContext(BaseContext):
def conflicts(self):
"""List of conflicts for the tcl module file."""
fmts = []
- naming_scheme = self.conf.naming_scheme
+ projection = proj.get_projection(self.conf.projections, self.spec)
f = string.Formatter()
for item in self.conf.conflicts:
if len([x for x in f.parse(item)]) > 1:
for naming_dir, conflict_dir in zip(
- naming_scheme.split('/'), item.split('/')
+ projection.split('/'), item.split('/')
):
if naming_dir != conflict_dir:
message = 'conflict scheme does not match naming '
@@ -87,7 +88,7 @@ class TclContext(BaseContext):
message += '** You may want to check your '
message += '`modules.yaml` configuration file **\n'
tty.error(message.format(spec=self.spec,
- nformat=naming_scheme,
+ nformat=projection,
cformat=item))
raise SystemExit('Module generation aborted.')
item = self.spec.format(item)
diff --git a/lib/spack/spack/projections.py b/lib/spack/spack/projections.py
new file mode 100644
index 0000000000..b91d321436
--- /dev/null
+++ b/lib/spack/spack/projections.py
@@ -0,0 +1,16 @@
+# Copyright 2013-2020 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)
+
+def get_projection(projections, spec):
+ """
+ Get the projection for a spec from a projections dict.
+ """
+ all_projection = None
+ for spec_like, projection in projections.items():
+ if spec.satisfies(spec_like, strict=True):
+ return projection
+ elif spec_like == 'all':
+ all_projection = projection
+ return all_projection
diff --git a/lib/spack/spack/schema/modules.py b/lib/spack/spack/schema/modules.py
index 4e11071757..ac9237609b 100644
--- a/lib/spack/spack/schema/modules.py
+++ b/lib/spack/spack/schema/modules.py
@@ -9,7 +9,7 @@
:lines: 13-
"""
import spack.schema.environment
-
+import spack.schema.projections
#: Matches a spec or a multi-valued variant but not another
#: valid keyword.
@@ -17,7 +17,7 @@ import spack.schema.environment
#: 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|naming_scheme|core_compilers|all)(^\w[\w-]*)'
+ r'blacklist|projections|core_compilers|all)(^\w[\w-]*)'
#: Matches an anonymous spec, i.e. a spec without a root name
anonymous_spec_regex = r'^[\^@%+~]'
@@ -72,6 +72,8 @@ module_file_configuration = {
}
}
+projections_scheme = spack.schema.projections.properties['projections']
+
module_type_configuration = {
'type': 'object',
'default': {},
@@ -92,9 +94,7 @@ module_type_configuration = {
'type': 'boolean',
'default': False
},
- 'naming_scheme': {
- 'type': 'string' # Can we be more specific here?
- },
+ 'projections': projections_scheme,
'all': module_file_configuration,
}
},
diff --git a/lib/spack/spack/test/data/modules/lmod/projections.yaml b/lib/spack/spack/test/data/modules/lmod/projections.yaml
new file mode 100644
index 0000000000..1efd93bc89
--- /dev/null
+++ b/lib/spack/spack/test/data/modules/lmod/projections.yaml
@@ -0,0 +1,6 @@
+enable:
+ - lmod
+lmod:
+ projections:
+ all: '{name}/v{version}'
+ mpileaks: '{name}-mpiprojection'
diff --git a/lib/spack/spack/test/data/modules/tcl/conflicts.yaml b/lib/spack/spack/test/data/modules/tcl/conflicts.yaml
index 66494b1ce1..0183753e55 100644
--- a/lib/spack/spack/test/data/modules/tcl/conflicts.yaml
+++ b/lib/spack/spack/test/data/modules/tcl/conflicts.yaml
@@ -1,7 +1,8 @@
enable:
- tcl
tcl:
- naming_scheme: '{name}/{version}-{compiler.name}'
+ projections:
+ all: '{name}/{version}-{compiler.name}'
all:
conflict:
- '{name}'
diff --git a/lib/spack/spack/test/data/modules/tcl/invalid_naming_scheme.yaml b/lib/spack/spack/test/data/modules/tcl/invalid_naming_scheme.yaml
index f523f0bbef..36b58670da 100644
--- a/lib/spack/spack/test/data/modules/tcl/invalid_naming_scheme.yaml
+++ b/lib/spack/spack/test/data/modules/tcl/invalid_naming_scheme.yaml
@@ -2,4 +2,5 @@ enable:
- tcl
tcl:
# {variants} is not allowed in the naming scheme, see #2884
- naming_scheme: '{name}/{version}-{compiler.name}-{variants}'
+ projections:
+ all: '{name}/{version}-{compiler.name}-{variants}'
diff --git a/lib/spack/spack/test/data/modules/tcl/projections.yaml b/lib/spack/spack/test/data/modules/tcl/projections.yaml
new file mode 100644
index 0000000000..11dbd053f9
--- /dev/null
+++ b/lib/spack/spack/test/data/modules/tcl/projections.yaml
@@ -0,0 +1,6 @@
+enable:
+ - tcl
+tcl:
+ projections:
+ all: '{name}/{version}-{compiler.name}'
+ mpileaks: '{name}-mpiprojection'
diff --git a/lib/spack/spack/test/data/modules/tcl/wrong_conflicts.yaml b/lib/spack/spack/test/data/modules/tcl/wrong_conflicts.yaml
index a4bd97257b..22377816c8 100644
--- a/lib/spack/spack/test/data/modules/tcl/wrong_conflicts.yaml
+++ b/lib/spack/spack/test/data/modules/tcl/wrong_conflicts.yaml
@@ -1,7 +1,8 @@
enable:
- tcl
tcl:
- naming_scheme: '{name}/{version}-{compiler.name}'
+ projections:
+ all: '{name}/{version}-{compiler.name}'
all:
conflict:
- '{name}/{compiler.name}'
diff --git a/lib/spack/spack/test/modules/lmod.py b/lib/spack/spack/test/modules/lmod.py
index 3daeeb068d..6d2ee7bc39 100644
--- a/lib/spack/spack/test/modules/lmod.py
+++ b/lib/spack/spack/test/modules/lmod.py
@@ -280,3 +280,39 @@ class TestLmod(object):
assert str(spec.target.family) in writer.layout.arch_dirname
if spec.target.family != spec.target:
assert str(spec.target) not in writer.layout.arch_dirname
+
+ def test_projections_specific(self, factory, module_configuration):
+ """Tests reading the correct naming scheme."""
+
+ # This configuration has no error, so check the conflicts directives
+ # are there
+ module_configuration('projections')
+
+ # Test we read the expected configuration for the naming scheme
+ writer, _ = factory('mpileaks')
+ expected = {
+ 'all': '{name}/v{version}',
+ 'mpileaks': '{name}-mpiprojection'
+ }
+
+ assert writer.conf.projections == expected
+ projection = writer.spec.format(writer.conf.projections['mpileaks'])
+ assert projection in writer.layout.use_name
+
+ def test_projections_all(self, factory, module_configuration):
+ """Tests reading the correct naming scheme."""
+
+ # This configuration has no error, so check the conflicts directives
+ # are there
+ module_configuration('projections')
+
+ # Test we read the expected configuration for the naming scheme
+ writer, _ = factory('libelf')
+ expected = {
+ 'all': '{name}/v{version}',
+ 'mpileaks': '{name}-mpiprojection'
+ }
+
+ assert writer.conf.projections == expected
+ projection = writer.spec.format(writer.conf.projections['all'])
+ assert projection in writer.layout.use_name
diff --git a/lib/spack/spack/test/modules/tcl.py b/lib/spack/spack/test/modules/tcl.py
index 40cb7be5ef..98d9363f87 100644
--- a/lib/spack/spack/test/modules/tcl.py
+++ b/lib/spack/spack/test/modules/tcl.py
@@ -142,18 +142,41 @@ class TestTcl(object):
assert len([x for x in content if 'is-loaded' in x]) == 1
assert len([x for x in content if 'module load ' in x]) == 1
- def test_naming_scheme(self, factory, module_configuration):
+ def test_projections_specific(self, factory, module_configuration):
"""Tests reading the correct naming scheme."""
# This configuration has no error, so check the conflicts directives
# are there
- module_configuration('conflicts')
+ module_configuration('projections')
# Test we read the expected configuration for the naming scheme
writer, _ = factory('mpileaks')
- expected = '{name}/{version}-{compiler.name}'
+ expected = {
+ 'all': '{name}/{version}-{compiler.name}',
+ 'mpileaks': '{name}-mpiprojection'
+ }
+
+ assert writer.conf.projections == expected
+ projection = writer.spec.format(writer.conf.projections['mpileaks'])
+ assert projection in writer.layout.use_name
+
+ def test_projections_all(self, factory, module_configuration):
+ """Tests reading the correct naming scheme."""
- assert writer.conf.naming_scheme == expected
+ # This configuration has no error, so check the conflicts directives
+ # are there
+ module_configuration('projections')
+
+ # Test we read the expected configuration for the naming scheme
+ writer, _ = factory('libelf')
+ expected = {
+ 'all': '{name}/{version}-{compiler.name}',
+ 'mpileaks': '{name}-mpiprojection'
+ }
+
+ assert writer.conf.projections == expected
+ projection = writer.spec.format(writer.conf.projections['all'])
+ assert projection in writer.layout.use_name
def test_invalid_naming_scheme(self, factory, module_configuration):
"""Tests the evaluation of an invalid naming scheme."""