Allow users to remove items from hierarchy per-path (#31351)

* lmod modules: allow users to remove items from hierarchy per-spec

This allows MPI wrappers that depend on MPI to be removed from the MPI portion of
the hierarchy and be made available when the appropriate compiler is loaded.

module load gcc
module load mpi-wrapper  # implicitly loads mpi
module load hdf5

This allows users to treat an mpi wrapper like an mpi program
This commit is contained in:
Greg Becker 2023-04-17 22:17:11 -07:00 committed by GitHub
parent 2bc1779a71
commit 480b7f397e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 2 deletions

View file

@ -126,6 +126,11 @@ def core_specs(self):
"""Returns the list of "Core" specs"""
return configuration(self.name).get("core_specs", [])
@property
def filter_hierarchy_specs(self):
"""Returns the dict of specs with modified hierarchies"""
return configuration(self.name).get("filter_hierarchy_specs", {})
@property
def hierarchy_tokens(self):
"""Returns the list of tokens that are part of the modulefile
@ -160,11 +165,21 @@ def requires(self):
if any(self.spec.satisfies(core_spec) for core_spec in self.core_specs):
return {"compiler": self.core_compilers[0]}
hierarchy_filter_list = []
for spec, filter_list in self.filter_hierarchy_specs.items():
if self.spec.satisfies(spec):
hierarchy_filter_list = filter_list
break
# Keep track of the requirements that this package has in terms
# of virtual packages that participate in the hierarchical structure
requirements = {"compiler": self.spec.compiler}
# For each virtual dependency in the hierarchy
for x in self.hierarchy_tokens:
# Skip anything filtered for this spec
if x in hierarchy_filter_list:
continue
# If I depend on it
if x in self.spec and not self.spec.package.provides(x):
requirements[x] = self.spec[x] # record the actual provider

View file

@ -17,7 +17,7 @@
#: 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|"
r"(?!hierarchy|core_specs|verbose|hash_length|defaults|filter_hierarchy_specs|"
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-]*)"
@ -127,6 +127,10 @@
"core_compilers": array_of_strings,
"hierarchy": array_of_strings,
"core_specs": array_of_strings,
"filter_hierarchy_specs": {
"type": "object",
"patternProperties": {spec_regex: array_of_strings},
},
},
}, # Specific lmod extensions
]

View file

@ -14,6 +14,9 @@ lmod:
- blas
- mpi
filter_hierarchy_specs:
'mpileaks@:2.1': [mpi]
verbose: false
all:

View file

@ -35,6 +35,8 @@ def compiler(request):
("mpich@3.0.1", []),
("openblas@0.2.15", ("blas",)),
("openblas-with-lapack@0.2.15", ("blas", "lapack")),
("mpileaks@2.3", ("mpi",)),
("mpileaks@2.1", []),
]
)
def provider(request):
@ -69,12 +71,20 @@ def test_file_layout(self, compiler, provider, factory, module_configuration):
path_parts = layout.available_path_parts
service_part = spec_string.replace("@", "/")
service_part = "-".join([service_part, layout.spec.dag_hash(length=7)])
assert service_part in path_parts
if "mpileaks" in spec_string:
# It's a user, not a provider, so create the provider string
service_part = layout.spec["mpi"].format("{name}/{version}-{hash:7}")
else:
# Only relevant for providers, not users, of virtuals
assert service_part in path_parts
# Check that multi-providers have repetitions in path parts
repetitions = len([x for x in path_parts if service_part == x])
if spec_string == "openblas-with-lapack@0.2.15":
assert repetitions == 2
elif spec_string == "mpileaks@2.1":
assert repetitions == 0
else:
assert repetitions == 1