summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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)