Hidden modules: always append hash (#40868)

This commit is contained in:
Harmen Stoppels 2023-11-05 08:56:11 +01:00 committed by GitHub
parent c9dfb9b0fd
commit 4755b28398
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 147 additions and 144 deletions

View file

@ -3,17 +3,22 @@
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
from typing import Optional, Set
from llnl.util import tty from llnl.util import tty
import spack.config import spack.config
import spack.modules import spack.modules
import spack.spec
def _for_each_enabled(spec, method_name, explicit=None): def _for_each_enabled(
spec: spack.spec.Spec, method_name: str, explicit: Optional[bool] = None
) -> None:
"""Calls a method for each enabled module""" """Calls a method for each enabled module"""
set_names = set(spack.config.get("modules", {}).keys()) set_names: Set[str] = set(spack.config.get("modules", {}).keys())
for name in set_names: for name in set_names:
enabled = spack.config.get("modules:%s:enable" % name) enabled = spack.config.get(f"modules:{name}:enable")
if not enabled: if not enabled:
tty.debug("NO MODULE WRITTEN: list of enabled module files is empty") tty.debug("NO MODULE WRITTEN: list of enabled module files is empty")
continue continue
@ -28,7 +33,7 @@ def _for_each_enabled(spec, method_name, explicit=None):
tty.warn(msg.format(method_name, str(e))) tty.warn(msg.format(method_name, str(e)))
def post_install(spec, explicit): def post_install(spec, explicit: bool):
import spack.environment as ev # break import cycle import spack.environment as ev # break import cycle
if ev.active_environment(): if ev.active_environment():

View file

@ -7,10 +7,15 @@
include Tcl non-hierarchical modules, Lua hierarchical modules, and others. include Tcl non-hierarchical modules, Lua hierarchical modules, and others.
""" """
from .common import disable_modules from typing import Dict, Type
from .common import BaseModuleFileWriter, disable_modules
from .lmod import LmodModulefileWriter from .lmod import LmodModulefileWriter
from .tcl import TclModulefileWriter from .tcl import TclModulefileWriter
__all__ = ["TclModulefileWriter", "LmodModulefileWriter", "disable_modules"] __all__ = ["TclModulefileWriter", "LmodModulefileWriter", "disable_modules"]
module_types = {"tcl": TclModulefileWriter, "lmod": LmodModulefileWriter} module_types: Dict[str, Type[BaseModuleFileWriter]] = {
"tcl": TclModulefileWriter,
"lmod": LmodModulefileWriter,
}

View file

@ -35,7 +35,7 @@
import os.path import os.path
import re import re
import string import string
from typing import Optional from typing import List, Optional
import llnl.util.filesystem import llnl.util.filesystem
import llnl.util.tty as tty import llnl.util.tty as tty
@ -50,6 +50,7 @@
import spack.projections as proj import spack.projections as proj
import spack.repo import spack.repo
import spack.schema.environment import spack.schema.environment
import spack.spec
import spack.store import spack.store
import spack.tengine as tengine import spack.tengine as tengine
import spack.util.environment import spack.util.environment
@ -395,16 +396,14 @@ class BaseConfiguration:
default_projections = {"all": "{name}/{version}-{compiler.name}-{compiler.version}"} default_projections = {"all": "{name}/{version}-{compiler.name}-{compiler.version}"}
def __init__(self, spec, module_set_name, explicit=None): def __init__(self, spec: spack.spec.Spec, module_set_name: str, explicit: bool) -> None:
# Module where type(self) is defined # Module where type(self) is defined
self.module = inspect.getmodule(self) m = inspect.getmodule(self)
assert m is not None # make mypy happy
self.module = m
# Spec for which we want to generate a module file # Spec for which we want to generate a module file
self.spec = spec self.spec = spec
self.name = module_set_name self.name = module_set_name
# Software installation has been explicitly asked (get this information from
# db when querying an existing module, like during a refresh or rm operations)
if explicit is None:
explicit = spec._installed_explicitly()
self.explicit = explicit self.explicit = explicit
# Dictionary of configuration options that should be applied # Dictionary of configuration options that should be applied
# to the spec # to the spec
@ -458,7 +457,11 @@ def suffixes(self):
if constraint in self.spec: if constraint in self.spec:
suffixes.append(suffix) suffixes.append(suffix)
suffixes = list(dedupe(suffixes)) suffixes = list(dedupe(suffixes))
if self.hash: # For hidden modules we can always add a fixed length hash as suffix, since it guards
# against file name clashes, and the module is not exposed to the user anyways.
if self.hidden:
suffixes.append(self.spec.dag_hash(length=7))
elif self.hash:
suffixes.append(self.hash) suffixes.append(self.hash)
return suffixes return suffixes
@ -551,8 +554,7 @@ def exclude_env_vars(self):
def _create_list_for(self, what): def _create_list_for(self, what):
include = [] include = []
for item in self.conf[what]: for item in self.conf[what]:
conf = type(self)(item, self.name) if not self.module.make_configuration(item, self.name).excluded:
if not conf.excluded:
include.append(item) include.append(item)
return include return include
@ -826,8 +828,7 @@ def autoload(self):
def _create_module_list_of(self, what): def _create_module_list_of(self, what):
m = self.conf.module m = self.conf.module
name = self.conf.name name = self.conf.name
explicit = self.conf.explicit return [m.make_layout(x, name).use_name for x in getattr(self.conf, what)]
return [m.make_layout(x, name, explicit).use_name for x in getattr(self.conf, what)]
@tengine.context_property @tengine.context_property
def verbose(self): def verbose(self):
@ -836,12 +837,19 @@ def verbose(self):
class BaseModuleFileWriter: class BaseModuleFileWriter:
def __init__(self, spec, module_set_name, explicit=None): default_template: str
hide_cmd_format: str
modulerc_header: List[str]
def __init__(
self, spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
) -> None:
self.spec = spec self.spec = spec
# This class is meant to be derived. Get the module of the # This class is meant to be derived. Get the module of the
# actual writer. # actual writer.
self.module = inspect.getmodule(self) self.module = inspect.getmodule(self)
assert self.module is not None # make mypy happy
m = self.module m = self.module
# Create the triplet of configuration/layout/context # Create the triplet of configuration/layout/context

View file

@ -6,8 +6,7 @@
import collections import collections
import itertools import itertools
import os.path import os.path
import posixpath from typing import Dict, List, Optional, Tuple
from typing import Any, Dict, List
import llnl.util.filesystem as fs import llnl.util.filesystem as fs
import llnl.util.lang as lang import llnl.util.lang as lang
@ -24,18 +23,19 @@
#: lmod specific part of the configuration #: lmod specific part of the configuration
def configuration(module_set_name): def configuration(module_set_name: str) -> dict:
config_path = "modules:%s:lmod" % module_set_name return spack.config.get(f"modules:{module_set_name}:lmod", {})
config = spack.config.get(config_path, {})
return config
# Caches the configuration {spec_hash: configuration} # Caches the configuration {spec_hash: configuration}
configuration_registry: Dict[str, Any] = {} configuration_registry: Dict[Tuple[str, str, bool], BaseConfiguration] = {}
def make_configuration(spec, module_set_name, explicit): def make_configuration(
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
) -> BaseConfiguration:
"""Returns the lmod configuration for spec""" """Returns the lmod configuration for spec"""
explicit = bool(spec._installed_explicitly()) if explicit is None else explicit
key = (spec.dag_hash(), module_set_name, explicit) key = (spec.dag_hash(), module_set_name, explicit)
try: try:
return configuration_registry[key] return configuration_registry[key]
@ -45,16 +45,18 @@ def make_configuration(spec, module_set_name, explicit):
) )
def make_layout(spec, module_set_name, explicit): def make_layout(
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
) -> BaseFileLayout:
"""Returns the layout information for spec""" """Returns the layout information for spec"""
conf = make_configuration(spec, module_set_name, explicit) return LmodFileLayout(make_configuration(spec, module_set_name, explicit))
return LmodFileLayout(conf)
def make_context(spec, module_set_name, explicit): def make_context(
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
) -> BaseContext:
"""Returns the context information for spec""" """Returns the context information for spec"""
conf = make_configuration(spec, module_set_name, explicit) return LmodContext(make_configuration(spec, module_set_name, explicit))
return LmodContext(conf)
def guess_core_compilers(name, store=False) -> List[spack.spec.CompilerSpec]: def guess_core_compilers(name, store=False) -> List[spack.spec.CompilerSpec]:
@ -97,10 +99,7 @@ def guess_core_compilers(name, store=False) -> List[spack.spec.CompilerSpec]:
class LmodConfiguration(BaseConfiguration): class LmodConfiguration(BaseConfiguration):
"""Configuration class for lmod module files.""" """Configuration class for lmod module files."""
# Note: Posixpath is used here as well as below as opposed to default_projections = {"all": "{name}/{version}"}
# os.path.join due to spack.spec.Spec.format
# requiring forward slash path seperators at this stage
default_projections = {"all": posixpath.join("{name}", "{version}")}
@property @property
def core_compilers(self) -> List[spack.spec.CompilerSpec]: def core_compilers(self) -> List[spack.spec.CompilerSpec]:
@ -274,19 +273,16 @@ def filename(self):
hierarchy_name = os.path.join(*parts) hierarchy_name = os.path.join(*parts)
# Compute the absolute path # Compute the absolute path
fullname = os.path.join( return os.path.join(
self.arch_dirname, # root for lmod files on this architecture self.arch_dirname, # root for lmod files on this architecture
hierarchy_name, # relative path hierarchy_name, # relative path
".".join([self.use_name, self.extension]), # file name f"{self.use_name}.{self.extension}", # file name
) )
return fullname
@property @property
def modulerc(self): def modulerc(self):
"""Returns the modulerc file associated with current module file""" """Returns the modulerc file associated with current module file"""
return os.path.join( return os.path.join(os.path.dirname(self.filename), f".modulerc.{self.extension}")
os.path.dirname(self.filename), ".".join([".modulerc", self.extension])
)
def token_to_path(self, name, value): def token_to_path(self, name, value):
"""Transforms a hierarchy token into the corresponding path part. """Transforms a hierarchy token into the corresponding path part.
@ -319,9 +315,7 @@ def path_part_fmt(token):
# we need to append a hash to the version to distinguish # we need to append a hash to the version to distinguish
# among flavors of the same library (e.g. openblas~openmp vs. # among flavors of the same library (e.g. openblas~openmp vs.
# openblas+openmp) # openblas+openmp)
path = path_part_fmt(token=value) return f"{path_part_fmt(token=value)}-{value.dag_hash(length=7)}"
path = "-".join([path, value.dag_hash(length=7)])
return path
@property @property
def available_path_parts(self): def available_path_parts(self):
@ -333,8 +327,7 @@ def available_path_parts(self):
# List of services that are part of the hierarchy # List of services that are part of the hierarchy
hierarchy = self.conf.hierarchy_tokens hierarchy = self.conf.hierarchy_tokens
# Tokenize each part that is both in the hierarchy and available # Tokenize each part that is both in the hierarchy and available
parts = [self.token_to_path(x, available[x]) for x in hierarchy if x in available] return [self.token_to_path(x, available[x]) for x in hierarchy if x in available]
return parts
@property @property
@lang.memoized @lang.memoized
@ -452,7 +445,7 @@ def missing(self):
@lang.memoized @lang.memoized
def unlocked_paths(self): def unlocked_paths(self):
"""Returns the list of paths that are unlocked unconditionally.""" """Returns the list of paths that are unlocked unconditionally."""
layout = make_layout(self.spec, self.conf.name, self.conf.explicit) layout = make_layout(self.spec, self.conf.name)
return [os.path.join(*parts) for parts in layout.unlocked_paths[None]] return [os.path.join(*parts) for parts in layout.unlocked_paths[None]]
@tengine.context_property @tengine.context_property
@ -460,7 +453,7 @@ def conditionally_unlocked_paths(self):
"""Returns the list of paths that are unlocked conditionally. """Returns the list of paths that are unlocked conditionally.
Each item in the list is a tuple with the structure (condition, path). Each item in the list is a tuple with the structure (condition, path).
""" """
layout = make_layout(self.spec, self.conf.name, self.conf.explicit) layout = make_layout(self.spec, self.conf.name)
value = [] value = []
conditional_paths = layout.unlocked_paths conditional_paths = layout.unlocked_paths
conditional_paths.pop(None) conditional_paths.pop(None)
@ -482,9 +475,9 @@ def manipulate_path(token):
class LmodModulefileWriter(BaseModuleFileWriter): class LmodModulefileWriter(BaseModuleFileWriter):
"""Writer class for lmod module files.""" """Writer class for lmod module files."""
default_template = posixpath.join("modules", "modulefile.lua") default_template = "modules/modulefile.lua"
modulerc_header: list = [] modulerc_header = []
hide_cmd_format = 'hide_version("%s")' hide_cmd_format = 'hide_version("%s")'

View file

@ -7,28 +7,29 @@
non-hierarchical modules. non-hierarchical modules.
""" """
import os.path import os.path
import posixpath from typing import Dict, Optional, Tuple
from typing import Any, Dict
import spack.config import spack.config
import spack.spec
import spack.tengine as tengine import spack.tengine as tengine
from .common import BaseConfiguration, BaseContext, BaseFileLayout, BaseModuleFileWriter from .common import BaseConfiguration, BaseContext, BaseFileLayout, BaseModuleFileWriter
#: Tcl specific part of the configuration #: Tcl specific part of the configuration
def configuration(module_set_name): def configuration(module_set_name: str) -> dict:
config_path = "modules:%s:tcl" % module_set_name return spack.config.get(f"modules:{module_set_name}:tcl", {})
config = spack.config.get(config_path, {})
return config
# Caches the configuration {spec_hash: configuration} # Caches the configuration {spec_hash: configuration}
configuration_registry: Dict[str, Any] = {} configuration_registry: Dict[Tuple[str, str, bool], BaseConfiguration] = {}
def make_configuration(spec, module_set_name, explicit): def make_configuration(
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
) -> BaseConfiguration:
"""Returns the tcl configuration for spec""" """Returns the tcl configuration for spec"""
explicit = bool(spec._installed_explicitly()) if explicit is None else explicit
key = (spec.dag_hash(), module_set_name, explicit) key = (spec.dag_hash(), module_set_name, explicit)
try: try:
return configuration_registry[key] return configuration_registry[key]
@ -38,16 +39,18 @@ def make_configuration(spec, module_set_name, explicit):
) )
def make_layout(spec, module_set_name, explicit): def make_layout(
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
) -> BaseFileLayout:
"""Returns the layout information for spec""" """Returns the layout information for spec"""
conf = make_configuration(spec, module_set_name, explicit) return TclFileLayout(make_configuration(spec, module_set_name, explicit))
return TclFileLayout(conf)
def make_context(spec, module_set_name, explicit): def make_context(
spec: spack.spec.Spec, module_set_name: str, explicit: Optional[bool] = None
) -> BaseContext:
"""Returns the context information for spec""" """Returns the context information for spec"""
conf = make_configuration(spec, module_set_name, explicit) return TclContext(make_configuration(spec, module_set_name, explicit))
return TclContext(conf)
class TclConfiguration(BaseConfiguration): class TclConfiguration(BaseConfiguration):
@ -75,10 +78,7 @@ def prerequisites(self):
class TclModulefileWriter(BaseModuleFileWriter): class TclModulefileWriter(BaseModuleFileWriter):
"""Writer class for tcl module files.""" """Writer class for tcl module files."""
# Note: Posixpath is used here as opposed to default_template = "modules/modulefile.tcl"
# 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"] modulerc_header = ["#%Module4.7"]

View file

@ -2,6 +2,7 @@ enable:
- lmod - lmod
lmod: lmod:
hide_implicits: true hide_implicits: true
hash_length: 0
core_compilers: core_compilers:
- 'clang@3.3' - 'clang@3.3'
hierarchy: hierarchy:

View file

@ -4,5 +4,6 @@ enable:
- tcl - tcl
tcl: tcl:
exclude_implicits: true exclude_implicits: true
hash_length: 0
all: all:
autoload: direct autoload: direct

View file

@ -2,5 +2,6 @@ enable:
- tcl - tcl
tcl: tcl:
hide_implicits: true hide_implicits: true
hash_length: 0
all: all:
autoload: direct autoload: direct

View file

@ -435,7 +435,7 @@ def test_modules_no_arch(self, factory, module_configuration):
assert str(spec.os) not in path assert str(spec.os) not in path
def test_hide_implicits(self, module_configuration): def test_hide_implicits(self, module_configuration, temporary_store):
"""Tests the addition and removal of hide command in modulerc.""" """Tests the addition and removal of hide command in modulerc."""
module_configuration("hide_implicits") module_configuration("hide_implicits")
@ -446,29 +446,42 @@ def test_hide_implicits(self, module_configuration):
writer.write() writer.write()
assert os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
with open(writer.layout.modulerc) as f: with open(writer.layout.modulerc) as f:
content = f.readlines() content = [line.strip() for line in f.readlines()]
content = "".join(content).split("\n") hide_implicit_mpileaks = f'hide_version("{writer.layout.use_name}")'
hide_cmd = 'hide_version("%s")' % writer.layout.use_name assert len([x for x in content if hide_implicit_mpileaks == x]) == 1
assert len([x for x in content if hide_cmd == x]) == 1
# mpileaks becomes explicit, thus modulerc is removed # The direct dependencies are all implicitly installed, and they should all be hidden,
writer = writer_cls(spec, "default", True) # except for mpich, which is provider for mpi, which is in the hierarchy, and therefore
writer.write(overwrite=True) # can't be hidden. All other hidden modules should have a 7 character hash (the config
assert not os.path.exists(writer.layout.modulerc) # hash_length = 0 only applies to exposed modules).
with open(writer.layout.filename) as f:
depends_statements = [line.strip() for line in f.readlines() if "depends_on" in line]
for dep in spec.dependencies(deptype=("link", "run")):
if dep.satisfies("mpi"):
assert not any(dep.dag_hash(7) in line for line in depends_statements)
else:
assert any(dep.dag_hash(7) in line for line in depends_statements)
# mpileaks is defined as explicit, no modulerc file should exist # when mpileaks becomes explicit, its file name changes (hash_length = 0), meaning an
# extra module file is created; the old one still exists and remains hidden.
writer = writer_cls(spec, "default", True) writer = writer_cls(spec, "default", True)
writer.write() writer.write()
assert not os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
with open(writer.layout.modulerc) as f:
content = [line.strip() for line in f.readlines()]
assert hide_implicit_mpileaks in content # old, implicit mpileaks is still hidden
assert f'hide_version("{writer.layout.use_name}")' not in content
# explicit module is removed # after removing both the implicit and explicit module, the modulerc file would be empty
writer.remove() # and should be removed.
writer_cls(spec, "default", False).remove()
writer_cls(spec, "default", True).remove()
assert not os.path.exists(writer.layout.modulerc) assert not os.path.exists(writer.layout.modulerc)
assert not os.path.exists(writer.layout.filename) assert not os.path.exists(writer.layout.filename)
# implicit module is removed # implicit module is removed
writer = writer_cls(spec, "default", False) writer = writer_cls(spec, "default", False)
writer.write(overwrite=True) writer.write()
assert os.path.exists(writer.layout.filename) assert os.path.exists(writer.layout.filename)
assert os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
writer.remove() writer.remove()
@ -486,35 +499,19 @@ def test_hide_implicits(self, module_configuration):
writer_alt2.write(overwrite=True) writer_alt2.write(overwrite=True)
assert os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
with open(writer.layout.modulerc) as f: with open(writer.layout.modulerc) as f:
content = f.readlines() content = [line.strip() for line in f.readlines()]
content = "".join(content).split("\n") hide_cmd = f'hide_version("{writer.layout.use_name}")'
hide_cmd = 'hide_version("%s")' % writer.layout.use_name hide_cmd_alt1 = f'hide_version("{writer_alt1.layout.use_name}")'
hide_cmd_alt1 = 'hide_version("%s")' % writer_alt1.layout.use_name hide_cmd_alt2 = f'hide_version("{writer_alt2.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 == 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_alt1 == x]) == 1
assert len([x for x in content if hide_cmd_alt2 == x]) == 1 assert len([x for x in content if hide_cmd_alt2 == x]) == 1
# one version is removed, a second becomes explicit # one version is removed
writer_alt1.remove() writer_alt1.remove()
writer_alt2 = writer_cls(spec_alt2, "default", True)
writer_alt2.write(overwrite=True)
assert os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
with open(writer.layout.modulerc) as f: with open(writer.layout.modulerc) as f:
content = f.readlines() content = [line.strip() for line in 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 == 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_alt1 == x]) == 0
assert len([x for x in content if hide_cmd_alt2 == x]) == 0 assert len([x for x in content if hide_cmd_alt2 == x]) == 1
# 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)

View file

@ -488,7 +488,7 @@ def test_modules_no_arch(self, factory, module_configuration):
assert str(spec.os) not in path assert str(spec.os) not in path
def test_hide_implicits(self, module_configuration): def test_hide_implicits(self, module_configuration, temporary_store):
"""Tests the addition and removal of hide command in modulerc.""" """Tests the addition and removal of hide command in modulerc."""
module_configuration("hide_implicits") module_configuration("hide_implicits")
@ -499,29 +499,37 @@ def test_hide_implicits(self, module_configuration):
writer.write() writer.write()
assert os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
with open(writer.layout.modulerc) as f: with open(writer.layout.modulerc) as f:
content = f.readlines() content = [line.strip() for line in f.readlines()]
content = "".join(content).split("\n") hide_implicit_mpileaks = f"module-hide --soft --hidden-loaded {writer.layout.use_name}"
hide_cmd = "module-hide --soft --hidden-loaded %s" % writer.layout.use_name assert len([x for x in content if hide_implicit_mpileaks == x]) == 1
assert len([x for x in content if hide_cmd == x]) == 1
# mpileaks becomes explicit, thus modulerc is removed # The direct dependencies are all implicit, and they should have depends-on with fixed
writer = writer_cls(spec, "default", True) # 7 character hash, even though the config is set to hash_length = 0.
writer.write(overwrite=True) with open(writer.layout.filename) as f:
assert not os.path.exists(writer.layout.modulerc) depends_statements = [line.strip() for line in f.readlines() if "depends-on" in line]
for dep in spec.dependencies(deptype=("link", "run")):
assert any(dep.dag_hash(7) in line for line in depends_statements)
# mpileaks is defined as explicit, no modulerc file should exist # when mpileaks becomes explicit, its file name changes (hash_length = 0), meaning an
# extra module file is created; the old one still exists and remains hidden.
writer = writer_cls(spec, "default", True) writer = writer_cls(spec, "default", True)
writer.write() writer.write()
assert not os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
with open(writer.layout.modulerc) as f:
content = [line.strip() for line in f.readlines()]
assert hide_implicit_mpileaks in content # old, implicit mpileaks is still hidden
assert f"module-hide --soft --hidden-loaded {writer.layout.use_name}" not in content
# explicit module is removed # after removing both the implicit and explicit module, the modulerc file would be empty
writer.remove() # and should be removed.
writer_cls(spec, "default", False).remove()
writer_cls(spec, "default", True).remove()
assert not os.path.exists(writer.layout.modulerc) assert not os.path.exists(writer.layout.modulerc)
assert not os.path.exists(writer.layout.filename) assert not os.path.exists(writer.layout.filename)
# implicit module is removed # implicit module is removed
writer = writer_cls(spec, "default", False) writer = writer_cls(spec, "default", False)
writer.write(overwrite=True) writer.write()
assert os.path.exists(writer.layout.filename) assert os.path.exists(writer.layout.filename)
assert os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
writer.remove() writer.remove()
@ -539,35 +547,19 @@ def test_hide_implicits(self, module_configuration):
writer_alt2.write(overwrite=True) writer_alt2.write(overwrite=True)
assert os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
with open(writer.layout.modulerc) as f: with open(writer.layout.modulerc) as f:
content = f.readlines() content = [line.strip() for line in f.readlines()]
content = "".join(content).split("\n") hide_cmd = f"module-hide --soft --hidden-loaded {writer.layout.use_name}"
hide_cmd = "module-hide --soft --hidden-loaded %s" % writer.layout.use_name hide_cmd_alt1 = f"module-hide --soft --hidden-loaded {writer_alt1.layout.use_name}"
hide_cmd_alt1 = "module-hide --soft --hidden-loaded %s" % writer_alt1.layout.use_name hide_cmd_alt2 = f"module-hide --soft --hidden-loaded {writer_alt2.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 == 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_alt1 == x]) == 1
assert len([x for x in content if hide_cmd_alt2 == x]) == 1 assert len([x for x in content if hide_cmd_alt2 == x]) == 1
# one version is removed, a second becomes explicit # one version is removed
writer_alt1.remove() writer_alt1.remove()
writer_alt2 = writer_cls(spec_alt2, "default", True)
writer_alt2.write(overwrite=True)
assert os.path.exists(writer.layout.modulerc) assert os.path.exists(writer.layout.modulerc)
with open(writer.layout.modulerc) as f: with open(writer.layout.modulerc) as f:
content = f.readlines() content = [line.strip() for line in 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 == 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_alt1 == x]) == 0
assert len([x for x in content if hide_cmd_alt2 == x]) == 0 assert len([x for x in content if hide_cmd_alt2 == x]) == 1
# 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)