summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Delaruelle <xavier.delaruelle@cea.fr>2023-10-26 13:49:13 +0200
committerGitHub <noreply@github.com>2023-10-26 11:49:13 +0000
commit86520abb68e64769d8feb87e4e4b151d5a2263ea (patch)
tree20fe13f69ab7100802ae7e622447567636160162
parentbf88ed45da1554bd55436780846623ba1afeb3a6 (diff)
downloadspack-86520abb68e64769d8feb87e4e4b151d5a2263ea.tar.gz
spack-86520abb68e64769d8feb87e4e4b151d5a2263ea.tar.bz2
spack-86520abb68e64769d8feb87e4e4b151d5a2263ea.tar.xz
spack-86520abb68e64769d8feb87e4e4b151d5a2263ea.zip
modules: hide implicit modulefiles (#36619)
Renames exclude_implicits to hide_implicits When hide_implicits option is enabled, generate modulefile of implicitly installed software and hide them. Even if implicit, those modulefiles may be referred as dependency in other modulefiles thus they should be generated to make module properly load dependent module. A new hidden property is added to BaseConfiguration class. To hide modulefiles, modulercs are generated along modulefiles. Such rc files contain specific module command to indicate a module should be hidden (for instance when using "module avail"). A modulerc property is added to TclFileLayout and LmodFileLayout classes to get fully qualified path name of the modulerc associated to a given modulefile. Modulerc files will be located in each module directory, next to the version modulefiles. This scheme is supported by both module tool implementations. modulerc_header and hide_cmd_format attributes are added to TclModulefileWriter and LmodModulefileWriter. They help to know how to generate a modulerc file with hidden commands for each module tool. Tcl modulerc file requires an header. As we use a command introduced on Modules 4.7 (module-hide --hidden-loaded), a version requirement is added to header string. For lmod, modules that open up a hierarchy are never hidden, even if they are implicitly installed. Modulerc is created, updated or removed when associated modulefile is written or removed. If an implicit modulefile becomes explicit, hidden command in modulerc for this modulefile is removed. If modulerc becomes empty, this file is removed. Modulerc file is not rewritten when no content change is detected. Co-authored-by: Harmen Stoppels <me@harmenstoppels.nl>
-rw-r--r--lib/spack/spack/modules/common.py118
-rw-r--r--lib/spack/spack/modules/lmod.py18
-rw-r--r--lib/spack/spack/modules/tcl.py10
-rw-r--r--lib/spack/spack/schema/modules.py52
-rw-r--r--lib/spack/spack/test/data/modules/lmod/hide_implicits.yaml11
-rw-r--r--lib/spack/spack/test/data/modules/tcl/exclude_implicits.yaml2
-rw-r--r--lib/spack/spack/test/data/modules/tcl/hide_implicits.yaml6
-rw-r--r--lib/spack/spack/test/modules/common.py22
-rw-r--r--lib/spack/spack/test/modules/lmod.py85
-rw-r--r--lib/spack/spack/test/modules/tcl.py103
10 files changed, 407 insertions, 20 deletions
diff --git a/lib/spack/spack/modules/common.py b/lib/spack/spack/modules/common.py
index 57b7da5ad5..98dcdb4fb1 100644
--- a/lib/spack/spack/modules/common.py
+++ b/lib/spack/spack/modules/common.py
@@ -491,10 +491,6 @@ class BaseConfiguration:
exclude_rules = conf.get("exclude", [])
exclude_matches = [x for x in exclude_rules if spec.satisfies(x)]
- # Should I exclude the module because it's implicit?
- exclude_implicits = conf.get("exclude_implicits", None)
- excluded_as_implicit = exclude_implicits and not self.explicit
-
def debug_info(line_header, match_list):
if match_list:
msg = "\t{0} : {1}".format(line_header, spec.cshort_spec)
@@ -505,17 +501,29 @@ class BaseConfiguration:
debug_info("INCLUDE", include_matches)
debug_info("EXCLUDE", exclude_matches)
- if excluded_as_implicit:
- msg = "\tEXCLUDED_AS_IMPLICIT : {0}".format(spec.cshort_spec)
- tty.debug(msg)
-
- is_excluded = exclude_matches or excluded_as_implicit
- if not include_matches and is_excluded:
+ if not include_matches and exclude_matches:
return True
return False
@property
+ def hidden(self):
+ """Returns True if the module has been hidden, False otherwise."""
+
+ # A few variables for convenience of writing the method
+ spec = self.spec
+ conf = self.module.configuration(self.name)
+
+ hidden_as_implicit = not self.explicit and conf.get(
+ "hide_implicits", conf.get("exclude_implicits", False)
+ )
+
+ if hidden_as_implicit:
+ tty.debug(f"\tHIDDEN_AS_IMPLICIT : {spec.cshort_spec}")
+
+ return hidden_as_implicit
+
+ @property
def context(self):
return self.conf.get("context", {})
@@ -849,6 +857,26 @@ class BaseModuleFileWriter:
name = type(self).__name__
raise DefaultTemplateNotDefined(msg.format(name))
+ # Check if format for module hide command has been defined,
+ # throw if not found
+ try:
+ self.hide_cmd_format
+ except AttributeError:
+ msg = "'{0}' object has no attribute 'hide_cmd_format'\n"
+ msg += "Did you forget to define it in the class?"
+ name = type(self).__name__
+ raise HideCmdFormatNotDefined(msg.format(name))
+
+ # Check if modulerc header content has been defined,
+ # throw if not found
+ try:
+ self.modulerc_header
+ except AttributeError:
+ msg = "'{0}' object has no attribute 'modulerc_header'\n"
+ msg += "Did you forget to define it in the class?"
+ name = type(self).__name__
+ raise ModulercHeaderNotDefined(msg.format(name))
+
def _get_template(self):
"""Gets the template that will be rendered for this spec."""
# Get templates and put them in the order of importance:
@@ -943,6 +971,9 @@ class BaseModuleFileWriter:
# Symlink defaults if needed
self.update_module_defaults()
+ # record module hiddenness if implicit
+ self.update_module_hiddenness()
+
def update_module_defaults(self):
if any(self.spec.satisfies(default) for default in self.conf.defaults):
# This spec matches a default, it needs to be symlinked to default
@@ -953,6 +984,60 @@ class BaseModuleFileWriter:
os.symlink(self.layout.filename, default_tmp)
os.rename(default_tmp, default_path)
+ def update_module_hiddenness(self, remove=False):
+ """Update modulerc file corresponding to module to add or remove
+ command that hides module depending on its hidden state.
+
+ Args:
+ remove (bool): if True, hiddenness information for module is
+ removed from modulerc.
+ """
+ modulerc_path = self.layout.modulerc
+ hide_module_cmd = self.hide_cmd_format % self.layout.use_name
+ hidden = self.conf.hidden and not remove
+ modulerc_exists = os.path.exists(modulerc_path)
+ updated = False
+
+ if modulerc_exists:
+ # retrieve modulerc content
+ with open(modulerc_path, "r") as f:
+ content = f.readlines()
+ content = "".join(content).split("\n")
+ # remove last empty item if any
+ if len(content[-1]) == 0:
+ del content[-1]
+ already_hidden = hide_module_cmd in content
+
+ # remove hide command if module not hidden
+ if already_hidden and not hidden:
+ content.remove(hide_module_cmd)
+ updated = True
+
+ # add hide command if module is hidden
+ elif not already_hidden and hidden:
+ if len(content) == 0:
+ content = self.modulerc_header.copy()
+ content.append(hide_module_cmd)
+ updated = True
+ else:
+ content = self.modulerc_header.copy()
+ if hidden:
+ content.append(hide_module_cmd)
+ updated = True
+
+ # no modulerc file change if no content update
+ if updated:
+ is_empty = content == self.modulerc_header or len(content) == 0
+ # remove existing modulerc if empty
+ if modulerc_exists and is_empty:
+ os.remove(modulerc_path)
+ # create or update modulerc
+ elif content != self.modulerc_header:
+ # ensure file ends with a newline character
+ content.append("")
+ with open(modulerc_path, "w") as f:
+ f.write("\n".join(content))
+
def remove(self):
"""Deletes the module file."""
mod_file = self.layout.filename
@@ -960,6 +1045,7 @@ class BaseModuleFileWriter:
try:
os.remove(mod_file) # Remove the module file
self.remove_module_defaults() # Remove default targeting module file
+ self.update_module_hiddenness(remove=True) # Remove hide cmd in modulerc
os.removedirs(
os.path.dirname(mod_file)
) # Remove all the empty directories from the leaf up
@@ -1003,5 +1089,17 @@ class DefaultTemplateNotDefined(AttributeError, ModulesError):
"""
+class HideCmdFormatNotDefined(AttributeError, ModulesError):
+ """Raised if the attribute 'hide_cmd_format' has not been specified
+ in the derived classes.
+ """
+
+
+class ModulercHeaderNotDefined(AttributeError, ModulesError):
+ """Raised if the attribute 'modulerc_header' has not been specified
+ in the derived classes.
+ """
+
+
class ModulesTemplateNotFoundError(ModulesError, RuntimeError):
"""Raised if the template for a module file was not found."""
diff --git a/lib/spack/spack/modules/lmod.py b/lib/spack/spack/modules/lmod.py
index d81e07e0bf..e2bcfa2973 100644
--- a/lib/spack/spack/modules/lmod.py
+++ b/lib/spack/spack/modules/lmod.py
@@ -232,6 +232,13 @@ class LmodConfiguration(BaseConfiguration):
"""Returns the list of tokens that are not available."""
return [x for x in self.hierarchy_tokens if x not in self.available]
+ @property
+ def hidden(self):
+ # Never hide a module that opens a hierarchy
+ if any(self.spec.package.provides(x) for x in self.hierarchy_tokens):
+ return False
+ return super().hidden
+
class LmodFileLayout(BaseFileLayout):
"""File layout for lmod module files."""
@@ -274,6 +281,13 @@ class LmodFileLayout(BaseFileLayout):
)
return fullname
+ @property
+ def modulerc(self):
+ """Returns the modulerc file associated with current module file"""
+ return os.path.join(
+ os.path.dirname(self.filename), ".".join([".modulerc", self.extension])
+ )
+
def token_to_path(self, name, value):
"""Transforms a hierarchy token into the corresponding path part.
@@ -470,6 +484,10 @@ class LmodModulefileWriter(BaseModuleFileWriter):
default_template = posixpath.join("modules", "modulefile.lua")
+ modulerc_header: list = []
+
+ hide_cmd_format = 'hide_version("%s")'
+
class CoreCompilersNotFoundError(spack.error.SpackError, KeyError):
"""Error raised if the key 'core_compilers' has not been specified
diff --git a/lib/spack/spack/modules/tcl.py b/lib/spack/spack/modules/tcl.py
index 58b0753792..ed12827c33 100644
--- a/lib/spack/spack/modules/tcl.py
+++ b/lib/spack/spack/modules/tcl.py
@@ -6,6 +6,7 @@
"""This module implements the classes necessary to generate Tcl
non-hierarchical modules.
"""
+import os.path
import posixpath
from typing import Any, Dict
@@ -56,6 +57,11 @@ class TclConfiguration(BaseConfiguration):
class TclFileLayout(BaseFileLayout):
"""File layout for tcl module files."""
+ @property
+ def modulerc(self):
+ """Returns the modulerc file associated with current module file"""
+ return os.path.join(os.path.dirname(self.filename), ".modulerc")
+
class TclContext(BaseContext):
"""Context class for tcl module files."""
@@ -73,3 +79,7 @@ class TclModulefileWriter(BaseModuleFileWriter):
# os.path.join due to spack.spec.Spec.format
# requiring forward slash path seperators at this stage
default_template = posixpath.join("modules", "modulefile.tcl")
+
+ modulerc_header = ["#%Module4.7"]
+
+ hide_cmd_format = "module-hide --soft --hidden-loaded %s"
diff --git a/lib/spack/spack/schema/modules.py b/lib/spack/spack/schema/modules.py
index 1d285f851b..adf1a93586 100644
--- a/lib/spack/spack/schema/modules.py
+++ b/lib/spack/spack/schema/modules.py
@@ -17,7 +17,7 @@ import spack.schema.projections
#: 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|defaults|filter_hierarchy_specs|"
+ r"(?!hierarchy|core_specs|verbose|hash_length|defaults|filter_hierarchy_specs|hide|"
r"whitelist|blacklist|" # DEPRECATED: remove in 0.20.
r"include|exclude|" # use these more inclusive/consistent options
r"projections|naming_scheme|core_compilers|all)(^\w[\w-]*)"
@@ -89,6 +89,7 @@ module_type_configuration = {
"exclude": array_of_strings,
"exclude_implicits": {"type": "boolean", "default": False},
"defaults": array_of_strings,
+ "hide_implicits": {"type": "boolean", "default": False},
"naming_scheme": {"type": "string"}, # Can we be more specific here?
"projections": projections_scheme,
"all": module_file_configuration,
@@ -187,3 +188,52 @@ schema = {
"additionalProperties": False,
"properties": properties,
}
+
+
+# deprecated keys and their replacements
+old_to_new_key = {"exclude_implicits": "hide_implicits"}
+
+
+def update_keys(data, key_translations):
+ """Change blacklist/whitelist to exclude/include.
+
+ Arguments:
+ data (dict): data from a valid modules configuration.
+ key_translations (dict): A dictionary of keys to translate to
+ their respective values.
+
+ Return:
+ (bool) whether anything was changed in data
+ """
+ changed = False
+
+ if isinstance(data, dict):
+ keys = list(data.keys())
+ for key in keys:
+ value = data[key]
+
+ translation = key_translations.get(key)
+ if translation:
+ data[translation] = data.pop(key)
+ changed = True
+
+ changed |= update_keys(value, key_translations)
+
+ elif isinstance(data, list):
+ for elt in data:
+ changed |= update_keys(elt, key_translations)
+
+ return changed
+
+
+def update(data):
+ """Update the data in place to remove deprecated properties.
+
+ Args:
+ data (dict): dictionary to be updated
+
+ Returns:
+ True if data was changed, False otherwise
+ """
+ # translate blacklist/whitelist to exclude/include
+ return update_keys(data, old_to_new_key)
diff --git a/lib/spack/spack/test/data/modules/lmod/hide_implicits.yaml b/lib/spack/spack/test/data/modules/lmod/hide_implicits.yaml
new file mode 100644
index 0000000000..d13c1a7b97
--- /dev/null
+++ b/lib/spack/spack/test/data/modules/lmod/hide_implicits.yaml
@@ -0,0 +1,11 @@
+enable:
+ - lmod
+lmod:
+ hide_implicits: true
+ core_compilers:
+ - 'clang@3.3'
+ hierarchy:
+ - mpi
+
+ all:
+ autoload: direct
diff --git a/lib/spack/spack/test/data/modules/tcl/exclude_implicits.yaml b/lib/spack/spack/test/data/modules/tcl/exclude_implicits.yaml
index 2d892c4351..5af22e6e40 100644
--- a/lib/spack/spack/test/data/modules/tcl/exclude_implicits.yaml
+++ b/lib/spack/spack/test/data/modules/tcl/exclude_implicits.yaml
@@ -1,3 +1,5 @@
+# DEPRECATED: remove this in ?
+# See `hide_implicits.yaml` for the new syntax
enable:
- tcl
tcl:
diff --git a/lib/spack/spack/test/data/modules/tcl/hide_implicits.yaml b/lib/spack/spack/test/data/modules/tcl/hide_implicits.yaml
new file mode 100644
index 0000000000..3ae7517b8f
--- /dev/null
+++ b/lib/spack/spack/test/data/modules/tcl/hide_implicits.yaml
@@ -0,0 +1,6 @@
+enable:
+ - tcl
+tcl:
+ hide_implicits: true
+ all:
+ autoload: direct
diff --git a/lib/spack/spack/test/modules/common.py b/lib/spack/spack/test/modules/common.py
index 0c8a98432f..15656dff25 100644
--- a/lib/spack/spack/test/modules/common.py
+++ b/lib/spack/spack/test/modules/common.py
@@ -14,6 +14,7 @@ import spack.modules.tcl
import spack.package_base
import spack.schema.modules
import spack.spec
+import spack.util.spack_yaml as syaml
from spack.modules.common import UpstreamModuleIndex
from spack.spec import Spec
@@ -190,11 +191,30 @@ def test_load_installed_package_not_in_repo(install_mockery, mock_fetch, monkeyp
spack.package_base.PackageBase.uninstall_by_spec(spec)
+@pytest.mark.parametrize(
+ "module_type, old_config,new_config",
+ [("tcl", "exclude_implicits.yaml", "hide_implicits.yaml")],
+)
+def test_exclude_include_update(module_type, old_config, new_config):
+ module_test_data_root = os.path.join(spack.paths.test_path, "data", "modules", module_type)
+ with open(os.path.join(module_test_data_root, old_config)) as f:
+ old_yaml = syaml.load(f)
+ with open(os.path.join(module_test_data_root, new_config)) as f:
+ new_yaml = syaml.load(f)
+
+ # ensure file that needs updating is translated to the right thing.
+ assert spack.schema.modules.update_keys(old_yaml, spack.schema.modules.old_to_new_key)
+ assert new_yaml == old_yaml
+ # ensure a file that doesn't need updates doesn't get updated
+ original_new_yaml = new_yaml.copy()
+ assert not spack.schema.modules.update_keys(new_yaml, spack.schema.modules.old_to_new_key)
+ assert original_new_yaml == new_yaml
+
+
@pytest.mark.regression("37649")
def test_check_module_set_name(mutable_config):
"""Tests that modules set name are validated correctly and an error is reported if the
name we require does not exist or is reserved by the configuration."""
-
# Minimal modules.yaml config.
spack.config.set(
"modules",
diff --git a/lib/spack/spack/test/modules/lmod.py b/lib/spack/spack/test/modules/lmod.py
index fcea6b0e79..510006f0a9 100644
--- a/lib/spack/spack/test/modules/lmod.py
+++ b/lib/spack/spack/test/modules/lmod.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
import pytest
@@ -433,3 +434,87 @@ class TestLmod:
path = module.layout.filename
assert str(spec.os) not in path
+
+ def test_hide_implicits(self, module_configuration):
+ """Tests the addition and removal of hide command in modulerc."""
+ module_configuration("hide_implicits")
+
+ spec = spack.spec.Spec("mpileaks@2.3").concretized()
+
+ # mpileaks is defined as implicit, thus hide command should appear in modulerc
+ writer = writer_cls(spec, "default", False)
+ writer.write()
+ assert os.path.exists(writer.layout.modulerc)
+ with open(writer.layout.modulerc) as f:
+ content = f.readlines()
+ content = "".join(content).split("\n")
+ hide_cmd = 'hide_version("%s")' % writer.layout.use_name
+ assert len([x for x in content if hide_cmd == x]) == 1
+
+ # mpileaks becomes explicit, thus modulerc is removed
+ writer = writer_cls(spec, "default", True)
+ writer.write(overwrite=True)
+ assert not os.path.exists(writer.layout.modulerc)
+
+ # mpileaks is defined as explicit, no modulerc file should exist
+ writer = writer_cls(spec, "default", True)
+ writer.write()
+ assert not os.path.exists(writer.layout.modulerc)
+
+ # explicit module is removed
+ writer.remove()
+ assert not os.path.exists(writer.layout.modulerc)
+ assert not os.path.exists(writer.layout.filename)
+
+ # implicit module is removed
+ writer = writer_cls(spec, "default", False)
+ writer.write(overwrite=True)
+ assert os.path.exists(writer.layout.filename)
+ assert os.path.exists(writer.layout.modulerc)
+ writer.remove()
+ assert not os.path.exists(writer.layout.modulerc)
+ assert not os.path.exists(writer.layout.filename)
+
+ # three versions of mpileaks are implicit
+ writer = writer_cls(spec, "default", False)
+ writer.write(overwrite=True)
+ spec_alt1 = spack.spec.Spec("mpileaks@2.2").concretized()
+ spec_alt2 = spack.spec.Spec("mpileaks@2.1").concretized()
+ writer_alt1 = writer_cls(spec_alt1, "default", False)
+ writer_alt1.write(overwrite=True)
+ writer_alt2 = writer_cls(spec_alt2, "default", False)
+ writer_alt2.write(overwrite=True)
+ assert os.path.exists(writer.layout.modulerc)
+ with open(writer.layout.modulerc) as f:
+ content = f.readlines()
+ content = "".join(content).split("\n")
+ hide_cmd = 'hide_version("%s")' % writer.layout.use_name
+ hide_cmd_alt1 = 'hide_version("%s")' % writer_alt1.layout.use_name
+ hide_cmd_alt2 = 'hide_version("%s")' % writer_alt2.layout.use_name
+ assert len([x for x in content if hide_cmd == x]) == 1
+ assert len([x for x in content if hide_cmd_alt1 == x]) == 1
+ assert len([x for x in content if hide_cmd_alt2 == x]) == 1
+
+ # one version is removed, a second becomes explicit
+ writer_alt1.remove()
+ writer_alt2 = writer_cls(spec_alt2, "default", True)
+ writer_alt2.write(overwrite=True)
+ assert os.path.exists(writer.layout.modulerc)
+ with open(writer.layout.modulerc) as f:
+ content = f.readlines()
+ content = "".join(content).split("\n")
+ assert len([x for x in content if hide_cmd == x]) == 1
+ assert len([x for x in content if hide_cmd_alt1 == x]) == 0
+ assert len([x for x in content if hide_cmd_alt2 == x]) == 0
+
+ # disable hide_implicits configuration option
+ module_configuration("autoload_direct")
+ writer = writer_cls(spec, "default")
+ writer.write(overwrite=True)
+ assert not os.path.exists(writer.layout.modulerc)
+
+ # reenable hide_implicits configuration option
+ module_configuration("hide_implicits")
+ writer = writer_cls(spec, "default")
+ writer.write(overwrite=True)
+ assert os.path.exists(writer.layout.modulerc)
diff --git a/lib/spack/spack/test/modules/tcl.py b/lib/spack/spack/test/modules/tcl.py
index 3c5bb01b81..cc12a1eedc 100644
--- a/lib/spack/spack/test/modules/tcl.py
+++ b/lib/spack/spack/test/modules/tcl.py
@@ -3,6 +3,7 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import os
import pytest
@@ -438,38 +439,40 @@ class TestTcl:
@pytest.mark.regression("4400")
@pytest.mark.db
- def test_exclude_implicits(self, module_configuration, database):
- module_configuration("exclude_implicits")
+ @pytest.mark.parametrize("config_name", ["hide_implicits", "exclude_implicits"])
+ def test_hide_implicits_no_arg(self, module_configuration, database, config_name):
+ module_configuration(config_name)
# mpileaks has been installed explicitly when setting up
# the tests database
mpileaks_specs = database.query("mpileaks")
for item in mpileaks_specs:
writer = writer_cls(item, "default")
- assert not writer.conf.excluded
+ assert not writer.conf.hidden
# callpath is a dependency of mpileaks, and has been pulled
# in implicitly
callpath_specs = database.query("callpath")
for item in callpath_specs:
writer = writer_cls(item, "default")
- assert writer.conf.excluded
+ assert writer.conf.hidden
@pytest.mark.regression("12105")
- def test_exclude_implicits_with_arg(self, module_configuration):
- module_configuration("exclude_implicits")
+ @pytest.mark.parametrize("config_name", ["hide_implicits", "exclude_implicits"])
+ def test_hide_implicits_with_arg(self, module_configuration, config_name):
+ module_configuration(config_name)
# mpileaks is defined as explicit with explicit argument set on writer
mpileaks_spec = spack.spec.Spec("mpileaks")
mpileaks_spec.concretize()
writer = writer_cls(mpileaks_spec, "default", True)
- assert not writer.conf.excluded
+ assert not writer.conf.hidden
# callpath is defined as implicit with explicit argument set on writer
callpath_spec = spack.spec.Spec("callpath")
callpath_spec.concretize()
writer = writer_cls(callpath_spec, "default", False)
- assert writer.conf.excluded
+ assert writer.conf.hidden
@pytest.mark.regression("9624")
@pytest.mark.db
@@ -498,3 +501,87 @@ class TestTcl:
path = module.layout.filename
assert str(spec.os) not in path
+
+ def test_hide_implicits(self, module_configuration):
+ """Tests the addition and removal of hide command in modulerc."""
+ module_configuration("hide_implicits")
+
+ spec = spack.spec.Spec("mpileaks@2.3").concretized()
+
+ # mpileaks is defined as implicit, thus hide command should appear in modulerc
+ writer = writer_cls(spec, "default", False)
+ writer.write()
+ assert os.path.exists(writer.layout.modulerc)
+ with open(writer.layout.modulerc) as f:
+ content = f.readlines()
+ content = "".join(content).split("\n")
+ hide_cmd = "module-hide --soft --hidden-loaded %s" % writer.layout.use_name
+ assert len([x for x in content if hide_cmd == x]) == 1
+
+ # mpileaks becomes explicit, thus modulerc is removed
+ writer = writer_cls(spec, "default", True)
+ writer.write(overwrite=True)
+ assert not os.path.exists(writer.layout.modulerc)
+
+ # mpileaks is defined as explicit, no modulerc file should exist
+ writer = writer_cls(spec, "default", True)
+ writer.write()
+ assert not os.path.exists(writer.layout.modulerc)
+
+ # explicit module is removed
+ writer.remove()
+ assert not os.path.exists(writer.layout.modulerc)
+ assert not os.path.exists(writer.layout.filename)
+
+ # implicit module is removed
+ writer = writer_cls(spec, "default", False)
+ writer.write(overwrite=True)
+ assert os.path.exists(writer.layout.filename)
+ assert os.path.exists(writer.layout.modulerc)
+ writer.remove()
+ assert not os.path.exists(writer.layout.modulerc)
+ assert not os.path.exists(writer.layout.filename)
+
+ # three versions of mpileaks are implicit
+ writer = writer_cls(spec, "default", False)
+ writer.write(overwrite=True)
+ spec_alt1 = spack.spec.Spec("mpileaks@2.2").concretized()
+ spec_alt2 = spack.spec.Spec("mpileaks@2.1").concretized()
+ writer_alt1 = writer_cls(spec_alt1, "default", False)
+ writer_alt1.write(overwrite=True)
+ writer_alt2 = writer_cls(spec_alt2, "default", False)
+ writer_alt2.write(overwrite=True)
+ assert os.path.exists(writer.layout.modulerc)
+ with open(writer.layout.modulerc) as f:
+ content = f.readlines()
+ content = "".join(content).split("\n")
+ hide_cmd = "module-hide --soft --hidden-loaded %s" % writer.layout.use_name
+ hide_cmd_alt1 = "module-hide --soft --hidden-loaded %s" % writer_alt1.layout.use_name
+ hide_cmd_alt2 = "module-hide --soft --hidden-loaded %s" % writer_alt2.layout.use_name
+ assert len([x for x in content if hide_cmd == x]) == 1
+ assert len([x for x in content if hide_cmd_alt1 == x]) == 1
+ assert len([x for x in content if hide_cmd_alt2 == x]) == 1
+
+ # one version is removed, a second becomes explicit
+ writer_alt1.remove()
+ writer_alt2 = writer_cls(spec_alt2, "default", True)
+ writer_alt2.write(overwrite=True)
+ assert os.path.exists(writer.layout.modulerc)
+ with open(writer.layout.modulerc) as f:
+ content = f.readlines()
+ content = "".join(content).split("\n")
+ assert len([x for x in content if hide_cmd == x]) == 1
+ assert len([x for x in content if hide_cmd_alt1 == x]) == 0
+ assert len([x for x in content if hide_cmd_alt2 == x]) == 0
+
+ # disable hide_implicits configuration option
+ module_configuration("autoload_direct")
+ writer = writer_cls(spec, "default")
+ writer.write(overwrite=True)
+ assert not os.path.exists(writer.layout.modulerc)
+
+ # reenable hide_implicits configuration option
+ module_configuration("hide_implicits")
+ writer = writer_cls(spec, "default")
+ writer.write(overwrite=True)
+ assert os.path.exists(writer.layout.modulerc)