lmod: allow core compiler to be specified with a version range (#37789)

Use CompilerSpec with satisfies instead of string equality tests

Co-authored-by: Harmen Stoppels <harmenstoppels@gmail.com>
This commit is contained in:
Massimiliano Culpo 2023-05-19 13:21:40 +02:00
parent 9694225b80
commit bb61ecb9b9
5 changed files with 48 additions and 22 deletions

View File

@ -7,7 +7,7 @@
import itertools import itertools
import os.path import os.path
import posixpath import posixpath
from typing import Any, Dict from typing import Any, Dict, List
import llnl.util.lang as lang import llnl.util.lang as lang
@ -56,7 +56,7 @@ def make_context(spec, module_set_name, explicit):
return LmodContext(conf) return LmodContext(conf)
def guess_core_compilers(name, store=False): def guess_core_compilers(name, store=False) -> List[spack.spec.CompilerSpec]:
"""Guesses the list of core compilers installed in the system. """Guesses the list of core compilers installed in the system.
Args: Args:
@ -64,21 +64,19 @@ def guess_core_compilers(name, store=False):
modules.yaml configuration file modules.yaml configuration file
Returns: Returns:
List of core compilers, if found, or None List of found core compilers
""" """
core_compilers = [] core_compilers = []
for compiler_config in spack.compilers.all_compilers_config(): for compiler in spack.compilers.all_compilers():
try: try:
compiler = compiler_config["compiler"]
# A compiler is considered to be a core compiler if any of the # A compiler is considered to be a core compiler if any of the
# C, C++ or Fortran compilers reside in a system directory # C, C++ or Fortran compilers reside in a system directory
is_system_compiler = any( is_system_compiler = any(
os.path.dirname(x) in spack.util.environment.SYSTEM_DIRS os.path.dirname(getattr(compiler, x, "")) in spack.util.environment.SYSTEM_DIRS
for x in compiler["paths"].values() for x in ("cc", "cxx", "f77", "fc")
if x is not None
) )
if is_system_compiler: if is_system_compiler:
core_compilers.append(str(compiler["spec"])) core_compilers.append(compiler.spec)
except (KeyError, TypeError, AttributeError): except (KeyError, TypeError, AttributeError):
continue continue
@ -89,10 +87,10 @@ def guess_core_compilers(name, store=False):
modules_cfg = spack.config.get( modules_cfg = spack.config.get(
"modules:" + name, {}, scope=spack.config.default_modify_scope() "modules:" + name, {}, scope=spack.config.default_modify_scope()
) )
modules_cfg.setdefault("lmod", {})["core_compilers"] = core_compilers modules_cfg.setdefault("lmod", {})["core_compilers"] = [str(x) for x in core_compilers]
spack.config.set("modules:" + name, modules_cfg, scope=spack.config.default_modify_scope()) spack.config.set("modules:" + name, modules_cfg, scope=spack.config.default_modify_scope())
return core_compilers or None return core_compilers
class LmodConfiguration(BaseConfiguration): class LmodConfiguration(BaseConfiguration):
@ -104,7 +102,7 @@ class LmodConfiguration(BaseConfiguration):
default_projections = {"all": posixpath.join("{name}", "{version}")} default_projections = {"all": posixpath.join("{name}", "{version}")}
@property @property
def core_compilers(self): def core_compilers(self) -> List[spack.spec.CompilerSpec]:
"""Returns the list of "Core" compilers """Returns the list of "Core" compilers
Raises: Raises:
@ -112,14 +110,18 @@ def core_compilers(self):
specified in the configuration file or the sequence specified in the configuration file or the sequence
is empty is empty
""" """
value = configuration(self.name).get("core_compilers") or guess_core_compilers( compilers = [
self.name, store=True spack.spec.CompilerSpec(c) for c in configuration(self.name).get("core_compilers", [])
) ]
if not value: if not compilers:
compilers = guess_core_compilers(self.name, store=True)
if not compilers:
msg = 'the key "core_compilers" must be set in modules.yaml' msg = 'the key "core_compilers" must be set in modules.yaml'
raise CoreCompilersNotFoundError(msg) raise CoreCompilersNotFoundError(msg)
return value
return compilers
@property @property
def core_specs(self): def core_specs(self):
@ -283,16 +285,18 @@ def token_to_path(self, name, value):
# If we are dealing with a core compiler, return 'Core' # If we are dealing with a core compiler, return 'Core'
core_compilers = self.conf.core_compilers core_compilers = self.conf.core_compilers
if name == "compiler" and str(value) in core_compilers: if name == "compiler" and any(
spack.spec.CompilerSpec(value).satisfies(c) for c in core_compilers
):
return "Core" return "Core"
# CompilerSpec does not have an hash, as we are not allowed to # CompilerSpec does not have a hash, as we are not allowed to
# use different flavors of the same compiler # use different flavors of the same compiler
if name == "compiler": if name == "compiler":
return path_part_fmt.format(token=value) return path_part_fmt.format(token=value)
# In case the hierarchy token refers to a virtual provider # In case the hierarchy token refers to a virtual provider
# we need to append an 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.format(token=value) path = path_part_fmt.format(token=value)

View File

@ -4,7 +4,7 @@ lmod:
hash_length: 0 hash_length: 0
core_compilers: core_compilers:
- 'clang@3.3' - 'clang@12.0.0'
core_specs: core_specs:
- 'mpich@3.0.1' - 'mpich@3.0.1'

View File

@ -0,0 +1,5 @@
enable:
- lmod
lmod:
core_compilers:
- 'clang@12.0.0'

View File

@ -0,0 +1,5 @@
enable:
- lmod
lmod:
core_compilers:
- 'clang@=12.0.0'

View File

@ -45,6 +45,18 @@ def provider(request):
@pytest.mark.usefixtures("config", "mock_packages") @pytest.mark.usefixtures("config", "mock_packages")
class TestLmod(object): class TestLmod(object):
@pytest.mark.regression("37788")
@pytest.mark.parametrize("modules_config", ["core_compilers", "core_compilers_at_equal"])
def test_layout_for_specs_compiled_with_core_compilers(
self, modules_config, module_configuration, factory
):
"""Tests that specs compiled with core compilers are in the 'Core' folder. Also tests that
we can use both ``compiler@version`` and ``compiler@=version`` to specify a core compiler.
"""
module_configuration(modules_config)
module, spec = factory("libelf%clang@12.0.0")
assert "Core" in module.layout.available_path_parts
def test_file_layout(self, compiler, provider, factory, module_configuration): def test_file_layout(self, compiler, provider, factory, module_configuration):
"""Tests the layout of files in the hierarchy is the one expected.""" """Tests the layout of files in the hierarchy is the one expected."""
module_configuration("complex_hierarchy") module_configuration("complex_hierarchy")
@ -61,7 +73,7 @@ def test_file_layout(self, compiler, provider, factory, module_configuration):
# is transformed to r"Core" if the compiler is listed among core # is transformed to r"Core" if the compiler is listed among core
# compilers # compilers
# Check that specs listed as core_specs are transformed to "Core" # Check that specs listed as core_specs are transformed to "Core"
if compiler == "clang@=3.3" or spec_string == "mpich@3.0.1": if compiler == "clang@=12.0.0" or spec_string == "mpich@3.0.1":
assert "Core" in layout.available_path_parts assert "Core" in layout.available_path_parts
else: else:
assert compiler.replace("@=", "/") in layout.available_path_parts assert compiler.replace("@=", "/") in layout.available_path_parts