From a277d17dafdd1800ef178ad1e4f336845bbc9cef Mon Sep 17 00:00:00 2001
From: Damian Alvarez <swmanage@jwlogin04.juwels>
Date: Tue, 25 May 2021 19:01:19 +0200
Subject: [PATCH] To support the new oneAPI-based toolchains, and other
 preparations for the 2020 stage update

---
 Custom_Hooks/eb_hooks.py                      | 41 ++++++++++++--
 .../flexible_custom_hierarchical_mns.py       | 53 +++++++++++++++----
 2 files changed, 80 insertions(+), 14 deletions(-)

diff --git a/Custom_Hooks/eb_hooks.py b/Custom_Hooks/eb_hooks.py
index da66022ca..b96dfd0b7 100644
--- a/Custom_Hooks/eb_hooks.py
+++ b/Custom_Hooks/eb_hooks.py
@@ -8,7 +8,7 @@ from easybuild.tools.build_log import EasyBuildError, print_warning
 from easybuild.tools.toolchain.toolchain import SYSTEM_TOOLCHAIN_NAME
 from easybuild.toolchains.compiler.systemcompiler import TC_CONSTANT_SYSTEM
 
-SUPPORTED_COMPILERS = ["GCC", "iccifort", "PGI", "GCCcore"]
+SUPPORTED_COMPILERS = ["GCC", "iccifort", "intel-compilers", "NVHPC", "PGI", "GCCcore"]
 SUPPORTED_MPIS = ["impi", "psmpi", "OpenMPI", "MVAPICH2"]
 # Maintain toplevel list for easy use of --try-toolchain
 SUPPORTED_TOPLEVEL_TOOLCHAIN_FAMILIES = [
@@ -17,14 +17,28 @@ SUPPORTED_TOPLEVEL_TOOLCHAIN_FAMILIES = [
     "iomkl",
     "gpsmkl",
     "gomkl",
+    "npsmkl",
+    "nomkl",
     "pmvmklc",
     "gmvmklc",
 ]
+SUPPORTED_MPI_TOOLCHAIN_FAMILIES = [
+    "iimpi",
+    "ipsmpi",
+    "iompi",
+    "gpsmpi",
+    "gompi",
+    "npsmpi",
+    "nompi",
+    "gmvapich2c",
+    "pmvapich2c",
+]
 # Could potentially make a dictionary of names and supported versions here but that is
 # probably overkill
 SUPPORTED_TOOLCHAIN_FAMILIES = (
     SUPPORTED_COMPILERS
-    + ["gcccoremkl", "gpsmpi", "ipsmpi", "iimpi", "iompi", "gmvapich2c", "pmvapich2c"]
+    + ["gcccoremkl"]
+    + SUPPORTED_MPI_TOOLCHAIN_FAMILIES
     + SUPPORTED_TOPLEVEL_TOOLCHAIN_FAMILIES
 )
 # Also maintain a list of CUDA enabled compilers
@@ -37,6 +51,7 @@ REQUIRE_MODALTSOFTNAME = {
     "impi": "IntelMPI",
     "psmpi": "ParaStationMPI",
     "iccifort": "Intel",
+    "intel-compilers": "Intel",
 }
 
 
@@ -61,7 +76,8 @@ def parse_hook(ec, *args, **kwargs):
             ec.log.info(
                 "[parse hook] Injecting recursive module unloading for supported compiler"
             )
-    # MPI are are a family (in the Lmod sense)
+
+    # MPIs are a family (in the Lmod sense)
     if ec.name in SUPPORTED_MPIS:
         key = "modluafooter"
         value = 'family("mpi")'
@@ -72,6 +88,21 @@ def parse_hook(ec, *args, **kwargs):
             ec[key] = value
         ec.log.info("[parse hook] Injecting Lmod mpi family")
 
+    # MPIs require to load mpi-settings
+    if ec.name in SUPPORTED_MPIS:
+        key = "modluafooter"
+        value = '''
+if not ( isloaded("mpi-settings") ) then
+    load("mpi-settings")
+end
+        '''
+        if key in ec_dict:
+            if not value in ec_dict[key]:
+                ec[key] = "\n".join([ec[key], value])
+        else:
+            ec[key] = value
+        ec.log.info("[parse hook] Injecting mpi-settings loading")
+
     # Check if we need to use 'modaltsoftname'
     if ec.name in REQUIRE_MODALTSOFTNAME:
         key = "modaltsoftname"
@@ -113,7 +144,7 @@ def parse_hook(ec, *args, **kwargs):
     site_contacts = None
     if "stages" in install_path().lower():
         users_groups = [grp.getgrgid(g).gr_name for g in os.getgroups()]
-        if any(group in user_groups for group in ["swmanage", "software"]):
+        if any(group in users_groups for group in ["swmanage", "software"]):
             site_contacts = "sc@fz-juelich.de"
     if site_contacts is None:
         # Inject the user
@@ -229,7 +260,7 @@ def pre_ready_hook(self, *args, **kwargs):
 
 
 def end_hook(*args, **kwargs):
-    # If the user is part of the development group and the installation is systemwide, 
+    # If the user is part of the development group and the installation is systemwide,
     # rebuild the system cache
     "Refresh Lmod's cache"
 
diff --git a/Custom_MNS/flexible_custom_hierarchical_mns.py b/Custom_MNS/flexible_custom_hierarchical_mns.py
index 640e87e73..eaf5a8e67 100644
--- a/Custom_MNS/flexible_custom_hierarchical_mns.py
+++ b/Custom_MNS/flexible_custom_hierarchical_mns.py
@@ -36,6 +36,7 @@ COMP_NAME_VERSION_TEMPLATES = {
 # Compiler relevant version numbers
 comp_relevant_versions = {
     'intel': 1,
+    'intel-compilers': 1,
     'PGI': 1,
     'NVHPC': 1,
 # The compilers load GCCcore/version. So GCC and GCCcore can't really be flexible, since GCCcore will always be loaded
@@ -64,7 +65,7 @@ class FlexibleCustomHierarchicalMNS(HierarchicalMNS):
             <name>/<version>[-<toolchain>]
         """
         # We rename our iccifort compiler to INTEL and this needs a hard fix because it is a toolchain
-        if name == 'iccifort':
+        if name == 'iccifort' or name == 'intel-compilers':
             modname_regex = re.compile('^%s/\S+$' % re.escape('Intel'))
         elif name == 'psmpi':
             modname_regex = re.compile('^%s/\S+$' % re.escape('ParaStationMPI'))
@@ -97,20 +98,22 @@ class FlexibleCustomHierarchicalMNS(HierarchicalMNS):
         mpi_ver = self.det_full_version(mpi_info)
         mpi_name = mpi_info['name']
 
+        # We'll start ignoring suffixes in MPI toolchains. But let's keep the code around for a bit, in case it needs
+        # to be readded. Not elegant, I know, but digging up in git is always extra effort.
+
         # Find suffix, if any, to be appended. Try to be clever, since the suffix is embedded in the version
         # and sometimes the version might include a string that looks like a suffix (ie: psmpi-5.4.0-1)
         if mpi_name in mpi_relevant_versions:
             # Find possible suffixes
             possible_suffixes = mpi_ver.split('-')[1:]
+            suffix = ''
             # Match the '-1' that is a typical part of psmpi's version
-            if possible_suffixes:
-                if re.match('^\d$', possible_suffixes[0]):
-                    suffix_index = 2
-                else:
-                    suffix_index = 1
-                suffix = '-'.join(mpi_ver.split('-')[suffix_index:])
-            else:
-                suffix = ''
+            #if possible_suffixes:
+            #    if re.match('^\d$', possible_suffixes[0]):
+            #        suffix_index = 2
+            #    else:
+            #        suffix_index = 1
+            #    suffix = '-'.join(mpi_ver.split('-')[suffix_index:])
 
             mpi_ver = '.'.join(mpi_ver.split('.')[:mpi_relevant_versions[mpi_name]])
             if suffix:
@@ -118,6 +121,38 @@ class FlexibleCustomHierarchicalMNS(HierarchicalMNS):
 
         return mpi_name, mpi_ver
 
+    def det_toolchain_compilers_name_version(self, tc_comps):
+        """
+        Determine toolchain compiler tag, for given list of compilers.
+        """
+        if tc_comps is None:
+            # no compiler in toolchain, system toolchain
+            res = None
+        elif len(tc_comps) == 1:
+            tc_comp = tc_comps[0]
+            if tc_comp is None:
+                res = None
+            else:
+                # Rename intel-compilers to intel, just to keep it consistent with installations pre oneAPI
+                if tc_comp['name'] == 'intel-compilers':
+                    tc_comp['name'] = 'intel'
+                res = (tc_comp['name'], self.det_full_version(tc_comp))
+        else:
+            comp_versions = dict([(comp['name'], self.det_full_version(comp)) for comp in tc_comps])
+            comp_names = comp_versions.keys()
+            key = ','.join(sorted(comp_names))
+            if key in COMP_NAME_VERSION_TEMPLATES:
+                tc_comp_name, tc_comp_ver_tmpl = COMP_NAME_VERSION_TEMPLATES[key]
+                tc_comp_ver = tc_comp_ver_tmpl % comp_versions
+                # make sure that icc/ifort versions match (unless not existing as separate modules)
+                if tc_comp_name == 'intel' and comp_versions.get('icc') != comp_versions.get('ifort'):
+                    raise EasyBuildError("Bumped into different versions for Intel compilers: %s", comp_versions)
+            else:
+                raise EasyBuildError("Unknown set of toolchain compilers, module naming scheme needs work: %s",
+                                     comp_names)
+            res = (tc_comp_name, tc_comp_ver)
+        return res
+
     def det_module_subdir(self, ec):
         """
         Determine module subdirectory, relative to the top of the module path.
-- 
GitLab