We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Hierarchical Module Naming Scheme with Cray toolchains
I have tried following the tutorial https://easybuilders.github.io/easybuild-tutorial/hmns to implement the Hierarchical Module Naming Scheme (MNS) with a modified Cray toolchain using Lmod 8.3.1, but the compiler modulefile created under the `Core` folder does not update the `MODULEPATH` once loaded, therefore the modules created cannot be searched (not even with `module spider`).

I was wondering if it is at all possible to use the Hierarchical MNS with the way Cray toolchains are configured: for instance I cannot prepare the environment as suggested in the tutorial, using the commands below, or I would unload essential modules:
```
module purge
module unuse $MODULEPATH
```

Thanks for your advice!

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `easybuild/tools/module_naming_scheme/hierarchical_mns.py`
Content:
```
1 ##
2 # Copyright 2013-2021 Ghent University
3 #
4 # This file is part of EasyBuild,
5 # originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
6 # with support of Ghent University (http://ugent.be/hpc),
7 # the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
8 # Flemish Research Foundation (FWO) (http://www.fwo.be/en)
9 # and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
10 #
11 # https://github.com/easybuilders/easybuild
12 #
13 # EasyBuild is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation v2.
16 #
17 # EasyBuild is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.
24 ##
25 """
26 Implementation of an example hierarchical module naming scheme.
27 
28 :author: Kenneth Hoste (Ghent University)
29 :author: Markus Geimer (Forschungszentrum Juelich GmbH)
30 """
31 
32 import os
33 import re
34 
35 from easybuild.toolchains.gcccore import GCCcore
36 from easybuild.tools.build_log import EasyBuildError
37 from easybuild.tools.module_naming_scheme.mns import ModuleNamingScheme
38 from easybuild.tools.module_naming_scheme.toolchain import det_toolchain_compilers, det_toolchain_mpi
39 
40 
41 CORE = 'Core'
42 COMPILER = 'Compiler'
43 MPI = 'MPI'
44 TOOLCHAIN = 'Toolchain'
45 
46 MODULECLASS_COMPILER = 'compiler'
47 MODULECLASS_MPI = 'mpi'
48 MODULECLASS_TOOLCHAIN = 'toolchain'
49 
50 GCCCORE = GCCcore.NAME
51 
52 # note: names in keys are ordered alphabetically
53 COMP_NAME_VERSION_TEMPLATES = {
54     # required for use of iccifort toolchain
55     'icc,ifort': ('intel', '%(icc)s'),
56     'iccifort': ('intel', '%(iccifort)s'),
57     # required for use of intel-compilers toolchain (OneAPI compilers)
58     'intel-compilers': ('intel', '%(intel-compilers)s'),
59     # required for use of ClangGCC toolchain
60     'Clang,GCC': ('Clang-GCC', '%(Clang)s-%(GCC)s'),
61     # required for use of gcccuda toolchain, and for CUDA installed with GCC toolchain
62     'CUDA,GCC': ('GCC-CUDA', '%(GCC)s-%(CUDA)s'),
63     # required for use of iccifortcuda toolchain
64     'CUDA,icc,ifort': ('intel-CUDA', '%(icc)s-%(CUDA)s'),
65     'CUDA,iccifort': ('intel-CUDA', '%(iccifort)s-%(CUDA)s'),
66     # required for CUDA installed with iccifort toolchain
67     # need to use 'intel' here because 'iccifort' toolchain maps to 'intel' (see above)
68     'CUDA,intel': ('intel-CUDA', '%(intel)s-%(CUDA)s'),
69     # required for use of xlcxlf toolchain
70     'xlc,xlf': ('xlcxlf', '%(xlc)s'),
71 }
72 
73 
74 class HierarchicalMNS(ModuleNamingScheme):
75     """Class implementing an example hierarchical module naming scheme."""
76 
77     REQUIRED_KEYS = ['name', 'versionprefix', 'version', 'versionsuffix', 'toolchain', 'moduleclass']
78 
79     def requires_toolchain_details(self):
80         """
81         Determine whether toolchain details are required by this module naming scheme,
82         e.g. whether one of det_toolchain_* functions are relied upon.
83         """
84         return True
85 
86     def det_full_module_name(self, ec):
87         """
88         Determine full module name, relative to the top of the module path.
89         Examples: Core/GCC/4.8.3, Compiler/GCC/4.8.3/OpenMPI/1.6.5, MPI/GCC/4.8.3/OpenMPI/1.6.5/HPL/2.1
90         """
91         return os.path.join(self.det_module_subdir(ec), self.det_short_module_name(ec))
92 
93     def det_short_module_name(self, ec):
94         """
95         Determine short module name, i.e. the name under which modules will be exposed to users.
96         Examples: GCC/4.8.3, OpenMPI/1.6.5, OpenBLAS/0.2.9, HPL/2.1, Python/2.7.5
97         """
98         return os.path.join(ec['name'], self.det_full_version(ec))
99 
100     def det_full_version(self, ec):
101         """Determine full version, taking into account version prefix/suffix."""
102         # versionprefix is not always available (e.g., for toolchains)
103         versionprefix = ec.get('versionprefix', '')
104         return versionprefix + ec['version'] + ec['versionsuffix']
105 
106     def det_toolchain_compilers_name_version(self, tc_comps):
107         """
108         Determine toolchain compiler tag, for given list of compilers.
109         """
110         if tc_comps is None:
111             # no compiler in toolchain, system toolchain
112             res = None
113         elif len(tc_comps) == 1:
114             tc_comp = tc_comps[0]
115             if tc_comp is None:
116                 res = None
117             else:
118                 res = (tc_comp['name'], self.det_full_version(tc_comp))
119         else:
120             comp_versions = dict([(comp['name'], self.det_full_version(comp)) for comp in tc_comps])
121             comp_names = comp_versions.keys()
122             key = ','.join(sorted(comp_names))
123             if key in COMP_NAME_VERSION_TEMPLATES:
124                 tc_comp_name, tc_comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key]
125                 tc_comp_ver = tc_comp_ver_tmpl % comp_versions
126                 # make sure that icc/ifort versions match (unless not existing as separate modules)
127                 if tc_comp_name == 'intel' and comp_versions.get('icc') != comp_versions.get('ifort'):
128                     raise EasyBuildError("Bumped into different versions for Intel compilers: %s", comp_versions)
129             else:
130                 raise EasyBuildError("Unknown set of toolchain compilers, module naming scheme needs work: %s",
131                                      comp_names)
132             res = (tc_comp_name, tc_comp_ver)
133         return res
134 
135     def det_module_subdir(self, ec):
136         """
137         Determine module subdirectory, relative to the top of the module path.
138         This determines the separation between module names exposed to users, and what's part of the $MODULEPATH.
139         Examples: Core, Compiler/GCC/4.8.3, MPI/GCC/4.8.3/OpenMPI/1.6.5
140         """
141         tc_comps = det_toolchain_compilers(ec)
142         # determine prefix based on type of toolchain used
143         if tc_comps is None:
144             # no compiler in toolchain, system toolchain => Core module
145             subdir = CORE
146         elif tc_comps == [None]:
147             # no info on toolchain compiler (cfr. Cray toolchains),
148             # then use toolchain name/version
149             subdir = os.path.join(TOOLCHAIN, ec.toolchain.name, ec.toolchain.version)
150         else:
151             tc_comp_name, tc_comp_ver = self.det_toolchain_compilers_name_version(tc_comps)
152             tc_mpi = det_toolchain_mpi(ec)
153             if tc_mpi is None:
154                 # compiler-only toolchain => Compiler/<compiler_name>/<compiler_version> namespace
155                 subdir = os.path.join(COMPILER, tc_comp_name, tc_comp_ver)
156             else:
157                 # compiler-MPI toolchain => MPI/<comp_name>/<comp_version>/<MPI_name>/<MPI_version> namespace
158                 tc_mpi_fullver = self.det_full_version(tc_mpi)
159                 subdir = os.path.join(MPI, tc_comp_name, tc_comp_ver, tc_mpi['name'], tc_mpi_fullver)
160 
161         return subdir
162 
163     def det_module_symlink_paths(self, ec):
164         """
165         Determine list of paths in which symlinks to module files must be created.
166         """
167         # symlinks are not very useful in the context of a hierarchical MNS
168         return []
169 
170     def det_modpath_extensions(self, ec):
171         """
172         Determine module path extensions, if any.
173         Examples: Compiler/GCC/4.8.3 (for GCC/4.8.3 module), MPI/GCC/4.8.3/OpenMPI/1.6.5 (for OpenMPI/1.6.5 module)
174         """
175         modclass = ec['moduleclass']
176         tc_comps = det_toolchain_compilers(ec)
177         tc_comp_info = self.det_toolchain_compilers_name_version(tc_comps)
178 
179         # we consider the following to be compilers:
180         # * has 'compiler' specified as moduleclass
181         is_compiler = modclass == MODULECLASS_COMPILER
182         # * CUDA, but only when not installed with 'system' toolchain (i.e. one or more toolchain compilers found)
183         non_system_tc = tc_comps is not None
184         non_system_cuda = ec['name'] == 'CUDA' and non_system_tc
185 
186         paths = []
187         if is_compiler or non_system_cuda:
188             # obtain list of compilers based on that extend $MODULEPATH in some way other than <name>/<version>
189             extend_comps = []
190             # exclude GCC for which <name>/<version> is used as $MODULEPATH extension
191             excluded_comps = ['GCC']
192             for comps in COMP_NAME_VERSION_TEMPLATES.keys():
193                 extend_comps.extend([comp for comp in comps.split(',') if comp not in excluded_comps])
194 
195             comp_name_ver = None
196             if ec['name'] in extend_comps:
197                 for key in COMP_NAME_VERSION_TEMPLATES:
198                     comp_names = key.split(',')
199                     if ec['name'] in comp_names:
200                         comp_name, comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key]
201                         comp_versions = {ec['name']: self.det_full_version(ec)}
202                         if ec['name'] == 'ifort':
203                             # 'icc' key should be provided since it's the only one used in the template
204                             comp_versions.update({'icc': self.det_full_version(ec)})
205 
206                         if non_system_tc:
207                             tc_comp_name, tc_comp_ver = tc_comp_info
208                             # Stick to name GCC for GCCcore
209                             if tc_comp_name == GCCCORE:
210                                 tc_comp_name = 'GCC'
211                             if tc_comp_name in comp_names:
212                                 # also provide toolchain version for non-system toolchains
213                                 comp_versions.update({tc_comp_name: tc_comp_ver})
214 
215                         comp_ver_keys = re.findall(r'%\((\w+)\)s', comp_ver_tmpl)
216                         if all(comp_ver_key in comp_versions for comp_ver_key in comp_ver_keys):
217                             comp_name_ver = [comp_name, comp_ver_tmpl % comp_versions]
218                             break
219             else:
220                 comp_name_ver = [ec['name'], self.det_full_version(ec)]
221 
222             if comp_name_ver is None:
223                 raise EasyBuildError("Required compilers not available in toolchain %s for %s v%s",
224                                      ec['toolchain'], ec['name'], ec['version'])
225 
226             paths.append(os.path.join(COMPILER, *comp_name_ver))
227 
228         elif modclass == MODULECLASS_MPI:
229             if tc_comp_info is None:
230                 raise EasyBuildError("No compiler available in toolchain %s used to install MPI library %s v%s, "
231                                      "which is required by the active module naming scheme.",
232                                      ec['toolchain'], ec['name'], ec['version'])
233             else:
234                 tc_comp_name, tc_comp_ver = tc_comp_info
235                 fullver = self.det_full_version(ec)
236                 paths.append(os.path.join(MPI, tc_comp_name, tc_comp_ver, ec['name'], fullver))
237 
238         # special case for Cray toolchains
239         elif modclass == MODULECLASS_TOOLCHAIN and tc_comp_info is None and ec.name.startswith('Cray'):
240             paths.append(os.path.join(TOOLCHAIN, ec.name, ec.version))
241 
242         return paths
243 
244     def expand_toolchain_load(self, ec=None):
245         """
246         Determine whether load statements for a toolchain should be expanded to load statements for its dependencies.
247         This is useful when toolchains are not exposed to users.
248         """
249         return True
250 
251     def det_init_modulepaths(self, ec):
252         """
253         Determine list of initial module paths (i.e. top of the hierarchy).
254         """
255         return [CORE]
256
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py
--- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py
+++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py
@@ -70,6 +70,12 @@
     'xlc,xlf': ('xlcxlf', '%(xlc)s'),
 }
 
+# possible prefixes for Cray toolchain names
+# example: CrayGNU, CrayCCE, cpeGNU, cpeCCE, ...;
+# important for determining $MODULEPATH extensions in det_modpath_extensions,
+# cfr. https://github.com/easybuilders/easybuild-framework/issues/3575
+CRAY_TOOLCHAIN_NAME_PREFIXES = ('Cray', 'cpe')
+
 
 class HierarchicalMNS(ModuleNamingScheme):
     """Class implementing an example hierarchical module naming scheme."""
@@ -236,8 +242,9 @@
                 paths.append(os.path.join(MPI, tc_comp_name, tc_comp_ver, ec['name'], fullver))
 
         # special case for Cray toolchains
-        elif modclass == MODULECLASS_TOOLCHAIN and tc_comp_info is None and ec.name.startswith('Cray'):
-            paths.append(os.path.join(TOOLCHAIN, ec.name, ec.version))
+        elif modclass == MODULECLASS_TOOLCHAIN and tc_comp_info is None:
+            if any(ec.name.startswith(x) for x in CRAY_TOOLCHAIN_NAME_PREFIXES):
+                paths.append(os.path.join(TOOLCHAIN, ec.name, ec.version))
 
         return paths

{"golden_diff": "diff --git a/easybuild/tools/module_naming_scheme/hierarchical_mns.py b/easybuild/tools/module_naming_scheme/hierarchical_mns.py\n--- a/easybuild/tools/module_naming_scheme/hierarchical_mns.py\n+++ b/easybuild/tools/module_naming_scheme/hierarchical_mns.py\n@@ -70,6 +70,12 @@\n     'xlc,xlf': ('xlcxlf', '%(xlc)s'),\n }\n \n+# possible prefixes for Cray toolchain names\n+# example: CrayGNU, CrayCCE, cpeGNU, cpeCCE, ...;\n+# important for determining $MODULEPATH extensions in det_modpath_extensions,\n+# cfr. https://github.com/easybuilders/easybuild-framework/issues/3575\n+CRAY_TOOLCHAIN_NAME_PREFIXES = ('Cray', 'cpe')\n+\n \n class HierarchicalMNS(ModuleNamingScheme):\n     \"\"\"Class implementing an example hierarchical module naming scheme.\"\"\"\n@@ -236,8 +242,9 @@\n                 paths.append(os.path.join(MPI, tc_comp_name, tc_comp_ver, ec['name'], fullver))\n \n         # special case for Cray toolchains\n-        elif modclass == MODULECLASS_TOOLCHAIN and tc_comp_info is None and ec.name.startswith('Cray'):\n-            paths.append(os.path.join(TOOLCHAIN, ec.name, ec.version))\n+        elif modclass == MODULECLASS_TOOLCHAIN and tc_comp_info is None:\n+            if any(ec.name.startswith(x) for x in CRAY_TOOLCHAIN_NAME_PREFIXES):\n+                paths.append(os.path.join(TOOLCHAIN, ec.name, ec.version))\n \n         return paths\n", "issue": "Hierarchical Module Naming Scheme with Cray toolchains\nI have tried following the tutorial https://easybuilders.github.io/easybuild-tutorial/hmns to implement the Hierarchical Module Naming Scheme (MNS) with a modified Cray toolchain using Lmod 8.3.1, but the compiler modulefile created under the `Core` folder does not update the `MODULEPATH` once loaded, therefore the modules created cannot be searched (not even with `module spider`).\r\n\r\nI was wondering if it is at all possible to use the Hierarchical MNS with the way Cray toolchains are configured: for instance I cannot prepare the environment as suggested in the tutorial, using the commands below, or I would unload essential modules:\r\n```\r\nmodule purge\r\nmodule unuse $MODULEPATH\r\n```\r\n\r\nThanks for your advice!\n", "before_files": [{"content": "##\n# Copyright 2013-2021 Ghent University\n#\n# This file is part of EasyBuild,\n# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),\n# with support of Ghent University (http://ugent.be/hpc),\n# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),\n# Flemish Research Foundation (FWO) (http://www.fwo.be/en)\n# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).\n#\n# https://github.com/easybuilders/easybuild\n#\n# EasyBuild is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation v2.\n#\n# EasyBuild is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.\n##\n\"\"\"\nImplementation of an example hierarchical module naming scheme.\n\n:author: Kenneth Hoste (Ghent University)\n:author: Markus Geimer (Forschungszentrum Juelich GmbH)\n\"\"\"\n\nimport os\nimport re\n\nfrom easybuild.toolchains.gcccore import GCCcore\nfrom easybuild.tools.build_log import EasyBuildError\nfrom easybuild.tools.module_naming_scheme.mns import ModuleNamingScheme\nfrom easybuild.tools.module_naming_scheme.toolchain import det_toolchain_compilers, det_toolchain_mpi\n\n\nCORE = 'Core'\nCOMPILER = 'Compiler'\nMPI = 'MPI'\nTOOLCHAIN = 'Toolchain'\n\nMODULECLASS_COMPILER = 'compiler'\nMODULECLASS_MPI = 'mpi'\nMODULECLASS_TOOLCHAIN = 'toolchain'\n\nGCCCORE = GCCcore.NAME\n\n# note: names in keys are ordered alphabetically\nCOMP_NAME_VERSION_TEMPLATES = {\n    # required for use of iccifort toolchain\n    'icc,ifort': ('intel', '%(icc)s'),\n    'iccifort': ('intel', '%(iccifort)s'),\n    # required for use of intel-compilers toolchain (OneAPI compilers)\n    'intel-compilers': ('intel', '%(intel-compilers)s'),\n    # required for use of ClangGCC toolchain\n    'Clang,GCC': ('Clang-GCC', '%(Clang)s-%(GCC)s'),\n    # required for use of gcccuda toolchain, and for CUDA installed with GCC toolchain\n    'CUDA,GCC': ('GCC-CUDA', '%(GCC)s-%(CUDA)s'),\n    # required for use of iccifortcuda toolchain\n    'CUDA,icc,ifort': ('intel-CUDA', '%(icc)s-%(CUDA)s'),\n    'CUDA,iccifort': ('intel-CUDA', '%(iccifort)s-%(CUDA)s'),\n    # required for CUDA installed with iccifort toolchain\n    # need to use 'intel' here because 'iccifort' toolchain maps to 'intel' (see above)\n    'CUDA,intel': ('intel-CUDA', '%(intel)s-%(CUDA)s'),\n    # required for use of xlcxlf toolchain\n    'xlc,xlf': ('xlcxlf', '%(xlc)s'),\n}\n\n\nclass HierarchicalMNS(ModuleNamingScheme):\n    \"\"\"Class implementing an example hierarchical module naming scheme.\"\"\"\n\n    REQUIRED_KEYS = ['name', 'versionprefix', 'version', 'versionsuffix', 'toolchain', 'moduleclass']\n\n    def requires_toolchain_details(self):\n        \"\"\"\n        Determine whether toolchain details are required by this module naming scheme,\n        e.g. whether one of det_toolchain_* functions are relied upon.\n        \"\"\"\n        return True\n\n    def det_full_module_name(self, ec):\n        \"\"\"\n        Determine full module name, relative to the top of the module path.\n        Examples: Core/GCC/4.8.3, Compiler/GCC/4.8.3/OpenMPI/1.6.5, MPI/GCC/4.8.3/OpenMPI/1.6.5/HPL/2.1\n        \"\"\"\n        return os.path.join(self.det_module_subdir(ec), self.det_short_module_name(ec))\n\n    def det_short_module_name(self, ec):\n        \"\"\"\n        Determine short module name, i.e. the name under which modules will be exposed to users.\n        Examples: GCC/4.8.3, OpenMPI/1.6.5, OpenBLAS/0.2.9, HPL/2.1, Python/2.7.5\n        \"\"\"\n        return os.path.join(ec['name'], self.det_full_version(ec))\n\n    def det_full_version(self, ec):\n        \"\"\"Determine full version, taking into account version prefix/suffix.\"\"\"\n        # versionprefix is not always available (e.g., for toolchains)\n        versionprefix = ec.get('versionprefix', '')\n        return versionprefix + ec['version'] + ec['versionsuffix']\n\n    def det_toolchain_compilers_name_version(self, tc_comps):\n        \"\"\"\n        Determine toolchain compiler tag, for given list of compilers.\n        \"\"\"\n        if tc_comps is None:\n            # no compiler in toolchain, system toolchain\n            res = None\n        elif len(tc_comps) == 1:\n            tc_comp = tc_comps[0]\n            if tc_comp is None:\n                res = None\n            else:\n                res = (tc_comp['name'], self.det_full_version(tc_comp))\n        else:\n            comp_versions = dict([(comp['name'], self.det_full_version(comp)) for comp in tc_comps])\n            comp_names = comp_versions.keys()\n            key = ','.join(sorted(comp_names))\n            if key in COMP_NAME_VERSION_TEMPLATES:\n                tc_comp_name, tc_comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key]\n                tc_comp_ver = tc_comp_ver_tmpl % comp_versions\n                # make sure that icc/ifort versions match (unless not existing as separate modules)\n                if tc_comp_name == 'intel' and comp_versions.get('icc') != comp_versions.get('ifort'):\n                    raise EasyBuildError(\"Bumped into different versions for Intel compilers: %s\", comp_versions)\n            else:\n                raise EasyBuildError(\"Unknown set of toolchain compilers, module naming scheme needs work: %s\",\n                                     comp_names)\n            res = (tc_comp_name, tc_comp_ver)\n        return res\n\n    def det_module_subdir(self, ec):\n        \"\"\"\n        Determine module subdirectory, relative to the top of the module path.\n        This determines the separation between module names exposed to users, and what's part of the $MODULEPATH.\n        Examples: Core, Compiler/GCC/4.8.3, MPI/GCC/4.8.3/OpenMPI/1.6.5\n        \"\"\"\n        tc_comps = det_toolchain_compilers(ec)\n        # determine prefix based on type of toolchain used\n        if tc_comps is None:\n            # no compiler in toolchain, system toolchain => Core module\n            subdir = CORE\n        elif tc_comps == [None]:\n            # no info on toolchain compiler (cfr. Cray toolchains),\n            # then use toolchain name/version\n            subdir = os.path.join(TOOLCHAIN, ec.toolchain.name, ec.toolchain.version)\n        else:\n            tc_comp_name, tc_comp_ver = self.det_toolchain_compilers_name_version(tc_comps)\n            tc_mpi = det_toolchain_mpi(ec)\n            if tc_mpi is None:\n                # compiler-only toolchain => Compiler/<compiler_name>/<compiler_version> namespace\n                subdir = os.path.join(COMPILER, tc_comp_name, tc_comp_ver)\n            else:\n                # compiler-MPI toolchain => MPI/<comp_name>/<comp_version>/<MPI_name>/<MPI_version> namespace\n                tc_mpi_fullver = self.det_full_version(tc_mpi)\n                subdir = os.path.join(MPI, tc_comp_name, tc_comp_ver, tc_mpi['name'], tc_mpi_fullver)\n\n        return subdir\n\n    def det_module_symlink_paths(self, ec):\n        \"\"\"\n        Determine list of paths in which symlinks to module files must be created.\n        \"\"\"\n        # symlinks are not very useful in the context of a hierarchical MNS\n        return []\n\n    def det_modpath_extensions(self, ec):\n        \"\"\"\n        Determine module path extensions, if any.\n        Examples: Compiler/GCC/4.8.3 (for GCC/4.8.3 module), MPI/GCC/4.8.3/OpenMPI/1.6.5 (for OpenMPI/1.6.5 module)\n        \"\"\"\n        modclass = ec['moduleclass']\n        tc_comps = det_toolchain_compilers(ec)\n        tc_comp_info = self.det_toolchain_compilers_name_version(tc_comps)\n\n        # we consider the following to be compilers:\n        # * has 'compiler' specified as moduleclass\n        is_compiler = modclass == MODULECLASS_COMPILER\n        # * CUDA, but only when not installed with 'system' toolchain (i.e. one or more toolchain compilers found)\n        non_system_tc = tc_comps is not None\n        non_system_cuda = ec['name'] == 'CUDA' and non_system_tc\n\n        paths = []\n        if is_compiler or non_system_cuda:\n            # obtain list of compilers based on that extend $MODULEPATH in some way other than <name>/<version>\n            extend_comps = []\n            # exclude GCC for which <name>/<version> is used as $MODULEPATH extension\n            excluded_comps = ['GCC']\n            for comps in COMP_NAME_VERSION_TEMPLATES.keys():\n                extend_comps.extend([comp for comp in comps.split(',') if comp not in excluded_comps])\n\n            comp_name_ver = None\n            if ec['name'] in extend_comps:\n                for key in COMP_NAME_VERSION_TEMPLATES:\n                    comp_names = key.split(',')\n                    if ec['name'] in comp_names:\n                        comp_name, comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key]\n                        comp_versions = {ec['name']: self.det_full_version(ec)}\n                        if ec['name'] == 'ifort':\n                            # 'icc' key should be provided since it's the only one used in the template\n                            comp_versions.update({'icc': self.det_full_version(ec)})\n\n                        if non_system_tc:\n                            tc_comp_name, tc_comp_ver = tc_comp_info\n                            # Stick to name GCC for GCCcore\n                            if tc_comp_name == GCCCORE:\n                                tc_comp_name = 'GCC'\n                            if tc_comp_name in comp_names:\n                                # also provide toolchain version for non-system toolchains\n                                comp_versions.update({tc_comp_name: tc_comp_ver})\n\n                        comp_ver_keys = re.findall(r'%\\((\\w+)\\)s', comp_ver_tmpl)\n                        if all(comp_ver_key in comp_versions for comp_ver_key in comp_ver_keys):\n                            comp_name_ver = [comp_name, comp_ver_tmpl % comp_versions]\n                            break\n            else:\n                comp_name_ver = [ec['name'], self.det_full_version(ec)]\n\n            if comp_name_ver is None:\n                raise EasyBuildError(\"Required compilers not available in toolchain %s for %s v%s\",\n                                     ec['toolchain'], ec['name'], ec['version'])\n\n            paths.append(os.path.join(COMPILER, *comp_name_ver))\n\n        elif modclass == MODULECLASS_MPI:\n            if tc_comp_info is None:\n                raise EasyBuildError(\"No compiler available in toolchain %s used to install MPI library %s v%s, \"\n                                     \"which is required by the active module naming scheme.\",\n                                     ec['toolchain'], ec['name'], ec['version'])\n            else:\n                tc_comp_name, tc_comp_ver = tc_comp_info\n                fullver = self.det_full_version(ec)\n                paths.append(os.path.join(MPI, tc_comp_name, tc_comp_ver, ec['name'], fullver))\n\n        # special case for Cray toolchains\n        elif modclass == MODULECLASS_TOOLCHAIN and tc_comp_info is None and ec.name.startswith('Cray'):\n            paths.append(os.path.join(TOOLCHAIN, ec.name, ec.version))\n\n        return paths\n\n    def expand_toolchain_load(self, ec=None):\n        \"\"\"\n        Determine whether load statements for a toolchain should be expanded to load statements for its dependencies.\n        This is useful when toolchains are not exposed to users.\n        \"\"\"\n        return True\n\n    def det_init_modulepaths(self, ec):\n        \"\"\"\n        Determine list of initial module paths (i.e. top of the hierarchy).\n        \"\"\"\n        return [CORE]\n", "path": "easybuild/tools/module_naming_scheme/hierarchical_mns.py"}], "after_files": [{"content": "##\n# Copyright 2013-2021 Ghent University\n#\n# This file is part of EasyBuild,\n# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),\n# with support of Ghent University (http://ugent.be/hpc),\n# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),\n# Flemish Research Foundation (FWO) (http://www.fwo.be/en)\n# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).\n#\n# https://github.com/easybuilders/easybuild\n#\n# EasyBuild is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation v2.\n#\n# EasyBuild is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with EasyBuild.  If not, see <http://www.gnu.org/licenses/>.\n##\n\"\"\"\nImplementation of an example hierarchical module naming scheme.\n\n:author: Kenneth Hoste (Ghent University)\n:author: Markus Geimer (Forschungszentrum Juelich GmbH)\n\"\"\"\n\nimport os\nimport re\n\nfrom easybuild.toolchains.gcccore import GCCcore\nfrom easybuild.tools.build_log import EasyBuildError\nfrom easybuild.tools.module_naming_scheme.mns import ModuleNamingScheme\nfrom easybuild.tools.module_naming_scheme.toolchain import det_toolchain_compilers, det_toolchain_mpi\n\n\nCORE = 'Core'\nCOMPILER = 'Compiler'\nMPI = 'MPI'\nTOOLCHAIN = 'Toolchain'\n\nMODULECLASS_COMPILER = 'compiler'\nMODULECLASS_MPI = 'mpi'\nMODULECLASS_TOOLCHAIN = 'toolchain'\n\nGCCCORE = GCCcore.NAME\n\n# note: names in keys are ordered alphabetically\nCOMP_NAME_VERSION_TEMPLATES = {\n    # required for use of iccifort toolchain\n    'icc,ifort': ('intel', '%(icc)s'),\n    'iccifort': ('intel', '%(iccifort)s'),\n    # required for use of intel-compilers toolchain (OneAPI compilers)\n    'intel-compilers': ('intel', '%(intel-compilers)s'),\n    # required for use of ClangGCC toolchain\n    'Clang,GCC': ('Clang-GCC', '%(Clang)s-%(GCC)s'),\n    # required for use of gcccuda toolchain, and for CUDA installed with GCC toolchain\n    'CUDA,GCC': ('GCC-CUDA', '%(GCC)s-%(CUDA)s'),\n    # required for use of iccifortcuda toolchain\n    'CUDA,icc,ifort': ('intel-CUDA', '%(icc)s-%(CUDA)s'),\n    'CUDA,iccifort': ('intel-CUDA', '%(iccifort)s-%(CUDA)s'),\n    # required for CUDA installed with iccifort toolchain\n    # need to use 'intel' here because 'iccifort' toolchain maps to 'intel' (see above)\n    'CUDA,intel': ('intel-CUDA', '%(intel)s-%(CUDA)s'),\n    # required for use of xlcxlf toolchain\n    'xlc,xlf': ('xlcxlf', '%(xlc)s'),\n}\n\n# possible prefixes for Cray toolchain names\n# example: CrayGNU, CrayCCE, cpeGNU, cpeCCE, ...;\n# important for determining $MODULEPATH extensions in det_modpath_extensions,\n# cfr. https://github.com/easybuilders/easybuild-framework/issues/3575\nCRAY_TOOLCHAIN_NAME_PREFIXES = ('Cray', 'cpe')\n\n\nclass HierarchicalMNS(ModuleNamingScheme):\n    \"\"\"Class implementing an example hierarchical module naming scheme.\"\"\"\n\n    REQUIRED_KEYS = ['name', 'versionprefix', 'version', 'versionsuffix', 'toolchain', 'moduleclass']\n\n    def requires_toolchain_details(self):\n        \"\"\"\n        Determine whether toolchain details are required by this module naming scheme,\n        e.g. whether one of det_toolchain_* functions are relied upon.\n        \"\"\"\n        return True\n\n    def det_full_module_name(self, ec):\n        \"\"\"\n        Determine full module name, relative to the top of the module path.\n        Examples: Core/GCC/4.8.3, Compiler/GCC/4.8.3/OpenMPI/1.6.5, MPI/GCC/4.8.3/OpenMPI/1.6.5/HPL/2.1\n        \"\"\"\n        return os.path.join(self.det_module_subdir(ec), self.det_short_module_name(ec))\n\n    def det_short_module_name(self, ec):\n        \"\"\"\n        Determine short module name, i.e. the name under which modules will be exposed to users.\n        Examples: GCC/4.8.3, OpenMPI/1.6.5, OpenBLAS/0.2.9, HPL/2.1, Python/2.7.5\n        \"\"\"\n        return os.path.join(ec['name'], self.det_full_version(ec))\n\n    def det_full_version(self, ec):\n        \"\"\"Determine full version, taking into account version prefix/suffix.\"\"\"\n        # versionprefix is not always available (e.g., for toolchains)\n        versionprefix = ec.get('versionprefix', '')\n        return versionprefix + ec['version'] + ec['versionsuffix']\n\n    def det_toolchain_compilers_name_version(self, tc_comps):\n        \"\"\"\n        Determine toolchain compiler tag, for given list of compilers.\n        \"\"\"\n        if tc_comps is None:\n            # no compiler in toolchain, system toolchain\n            res = None\n        elif len(tc_comps) == 1:\n            tc_comp = tc_comps[0]\n            if tc_comp is None:\n                res = None\n            else:\n                res = (tc_comp['name'], self.det_full_version(tc_comp))\n        else:\n            comp_versions = dict([(comp['name'], self.det_full_version(comp)) for comp in tc_comps])\n            comp_names = comp_versions.keys()\n            key = ','.join(sorted(comp_names))\n            if key in COMP_NAME_VERSION_TEMPLATES:\n                tc_comp_name, tc_comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key]\n                tc_comp_ver = tc_comp_ver_tmpl % comp_versions\n                # make sure that icc/ifort versions match (unless not existing as separate modules)\n                if tc_comp_name == 'intel' and comp_versions.get('icc') != comp_versions.get('ifort'):\n                    raise EasyBuildError(\"Bumped into different versions for Intel compilers: %s\", comp_versions)\n            else:\n                raise EasyBuildError(\"Unknown set of toolchain compilers, module naming scheme needs work: %s\",\n                                     comp_names)\n            res = (tc_comp_name, tc_comp_ver)\n        return res\n\n    def det_module_subdir(self, ec):\n        \"\"\"\n        Determine module subdirectory, relative to the top of the module path.\n        This determines the separation between module names exposed to users, and what's part of the $MODULEPATH.\n        Examples: Core, Compiler/GCC/4.8.3, MPI/GCC/4.8.3/OpenMPI/1.6.5\n        \"\"\"\n        tc_comps = det_toolchain_compilers(ec)\n        # determine prefix based on type of toolchain used\n        if tc_comps is None:\n            # no compiler in toolchain, system toolchain => Core module\n            subdir = CORE\n        elif tc_comps == [None]:\n            # no info on toolchain compiler (cfr. Cray toolchains),\n            # then use toolchain name/version\n            subdir = os.path.join(TOOLCHAIN, ec.toolchain.name, ec.toolchain.version)\n        else:\n            tc_comp_name, tc_comp_ver = self.det_toolchain_compilers_name_version(tc_comps)\n            tc_mpi = det_toolchain_mpi(ec)\n            if tc_mpi is None:\n                # compiler-only toolchain => Compiler/<compiler_name>/<compiler_version> namespace\n                subdir = os.path.join(COMPILER, tc_comp_name, tc_comp_ver)\n            else:\n                # compiler-MPI toolchain => MPI/<comp_name>/<comp_version>/<MPI_name>/<MPI_version> namespace\n                tc_mpi_fullver = self.det_full_version(tc_mpi)\n                subdir = os.path.join(MPI, tc_comp_name, tc_comp_ver, tc_mpi['name'], tc_mpi_fullver)\n\n        return subdir\n\n    def det_module_symlink_paths(self, ec):\n        \"\"\"\n        Determine list of paths in which symlinks to module files must be created.\n        \"\"\"\n        # symlinks are not very useful in the context of a hierarchical MNS\n        return []\n\n    def det_modpath_extensions(self, ec):\n        \"\"\"\n        Determine module path extensions, if any.\n        Examples: Compiler/GCC/4.8.3 (for GCC/4.8.3 module), MPI/GCC/4.8.3/OpenMPI/1.6.5 (for OpenMPI/1.6.5 module)\n        \"\"\"\n        modclass = ec['moduleclass']\n        tc_comps = det_toolchain_compilers(ec)\n        tc_comp_info = self.det_toolchain_compilers_name_version(tc_comps)\n\n        # we consider the following to be compilers:\n        # * has 'compiler' specified as moduleclass\n        is_compiler = modclass == MODULECLASS_COMPILER\n        # * CUDA, but only when not installed with 'system' toolchain (i.e. one or more toolchain compilers found)\n        non_system_tc = tc_comps is not None\n        non_system_cuda = ec['name'] == 'CUDA' and non_system_tc\n\n        paths = []\n        if is_compiler or non_system_cuda:\n            # obtain list of compilers based on that extend $MODULEPATH in some way other than <name>/<version>\n            extend_comps = []\n            # exclude GCC for which <name>/<version> is used as $MODULEPATH extension\n            excluded_comps = ['GCC']\n            for comps in COMP_NAME_VERSION_TEMPLATES.keys():\n                extend_comps.extend([comp for comp in comps.split(',') if comp not in excluded_comps])\n\n            comp_name_ver = None\n            if ec['name'] in extend_comps:\n                for key in COMP_NAME_VERSION_TEMPLATES:\n                    comp_names = key.split(',')\n                    if ec['name'] in comp_names:\n                        comp_name, comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key]\n                        comp_versions = {ec['name']: self.det_full_version(ec)}\n                        if ec['name'] == 'ifort':\n                            # 'icc' key should be provided since it's the only one used in the template\n                            comp_versions.update({'icc': self.det_full_version(ec)})\n\n                        if non_system_tc:\n                            tc_comp_name, tc_comp_ver = tc_comp_info\n                            # Stick to name GCC for GCCcore\n                            if tc_comp_name == GCCCORE:\n                                tc_comp_name = 'GCC'\n                            if tc_comp_name in comp_names:\n                                # also provide toolchain version for non-system toolchains\n                                comp_versions.update({tc_comp_name: tc_comp_ver})\n\n                        comp_ver_keys = re.findall(r'%\\((\\w+)\\)s', comp_ver_tmpl)\n                        if all(comp_ver_key in comp_versions for comp_ver_key in comp_ver_keys):\n                            comp_name_ver = [comp_name, comp_ver_tmpl % comp_versions]\n                            break\n            else:\n                comp_name_ver = [ec['name'], self.det_full_version(ec)]\n\n            if comp_name_ver is None:\n                raise EasyBuildError(\"Required compilers not available in toolchain %s for %s v%s\",\n                                     ec['toolchain'], ec['name'], ec['version'])\n\n            paths.append(os.path.join(COMPILER, *comp_name_ver))\n\n        elif modclass == MODULECLASS_MPI:\n            if tc_comp_info is None:\n                raise EasyBuildError(\"No compiler available in toolchain %s used to install MPI library %s v%s, \"\n                                     \"which is required by the active module naming scheme.\",\n                                     ec['toolchain'], ec['name'], ec['version'])\n            else:\n                tc_comp_name, tc_comp_ver = tc_comp_info\n                fullver = self.det_full_version(ec)\n                paths.append(os.path.join(MPI, tc_comp_name, tc_comp_ver, ec['name'], fullver))\n\n        # special case for Cray toolchains\n        elif modclass == MODULECLASS_TOOLCHAIN and tc_comp_info is None:\n            if any(ec.name.startswith(x) for x in CRAY_TOOLCHAIN_NAME_PREFIXES):\n                paths.append(os.path.join(TOOLCHAIN, ec.name, ec.version))\n\n        return paths\n\n    def expand_toolchain_load(self, ec=None):\n        \"\"\"\n        Determine whether load statements for a toolchain should be expanded to load statements for its dependencies.\n        This is useful when toolchains are not exposed to users.\n        \"\"\"\n        return True\n\n    def det_init_modulepaths(self, ec):\n        \"\"\"\n        Determine list of initial module paths (i.e. top of the hierarchy).\n        \"\"\"\n        return [CORE]\n", "path": "easybuild/tools/module_naming_scheme/hierarchical_mns.py"}]}