Repository: easybuilders/easybuild-easyblocks

Issue:
fix sanity checks for Clang 11 when built with NVPTX support
Clang creates the following `libomptarget*nvptx*.bc` according to its version (11, 12, 13). The current sanity check is handling wrongly 11.0.1 case.

```
# Clang 11.0.1
libomptarget-nvptx-sm_70.bc

libomptarget-nvptx.a
libomptarget.rtl.x86_64.so
libomptarget.so
libomptarget.rtl.cuda.so

# Clang 12.0.1
libomptarget-nvptx-cuda_80-sm_70.bc
libomptarget-nvptx-cuda_90-sm_70.bc
libomptarget-nvptx-cuda_91-sm_70.bc
libomptarget-nvptx-cuda_92-sm_70.bc
libomptarget-nvptx-cuda_100-sm_70.bc
libomptarget-nvptx-cuda_101-sm_70.bc
libomptarget-nvptx-cuda_102-sm_70.bc
libomptarget-nvptx-cuda_110-sm_70.bc
libomptarget-nvptx-cuda_111-sm_70.bc
libomptarget-nvptx-cuda_112-sm_70.bc

libomptarget.rtl.x86_64.so
libomptarget.so
libomptarget.rtl.cuda.so
libomptarget.so.12

# Clang 13.0.1
libomptarget-amdgcn-gfx700.bc
libomptarget-amdgcn-gfx701.bc
libomptarget-amdgcn-gfx801.bc
libomptarget-amdgcn-gfx803.bc
libomptarget-amdgcn-gfx900.bc
libomptarget-amdgcn-gfx902.bc
libomptarget-amdgcn-gfx906.bc
libomptarget-amdgcn-gfx908.bc

libomptarget-new-nvptx-sm_70.bc
libomptarget-nvptx-sm_70.bc

libomptarget.rtl.x86_64.so
libomptarget.so
libomptarget.rtl.cuda.so
libomptarget.rtl.amdgpu.so
```
I think the behavior when there is CUDA version included is typical only for v12 whereas in the current test it is applied on v11 too.

```Python
# clang.py (develop)

if LooseVersion('11.0') < LooseVersion(self.version) < LooseVersion('13.0'):
    custom_paths['files'].extend(["lib/libomptarget-nvptx-cuda_%s-sm_%s.bc" % (x, y)
                                 for x in CUDA_TOOLKIT_SUPPORT for y in cuda_cc])
else:
    custom_paths['files'].extend(["lib/libomptarget-nvptx-sm_%s.bc" % cc
                                 for cc in cuda_cc])
```

I was building on x86_64 with V100.


Discussion:

Resolving patch:
diff --git a/easybuild/easyblocks/c/clang.py b/easybuild/easyblocks/c/clang.py
--- a/easybuild/easyblocks/c/clang.py
+++ b/easybuild/easyblocks/c/clang.py
@@ -32,6 +32,7 @@
 @author: Dmitri Gribenko (National Technical University of Ukraine "KPI")
 @author: Ward Poelmans (Ghent University)
 @author: Alan O'Cais (Juelich Supercomputing Centre)
+@author: Maxime Boissonneault (Digital Research Alliance of Canada, Universite Laval)
 """
 
 import glob
@@ -74,6 +75,13 @@
 CUDA_TOOLKIT_SUPPORT = ['80', '90', '91', '92', '100', '101', '102', '110', '111', '112']
 
 
+# When extending the lists below, make sure to add additional sanity checks!
+# List of the known LLVM projects
+KNOWN_LLVM_PROJECTS = ['llvm', 'clang', 'polly', 'lld', 'lldb', 'clang-tools-extra', 'flang']
+# List of the known LLVM runtimes
+KNOWN_LLVM_RUNTIMES = ['compiler-rt', 'libunwind', 'libcxx', 'libcxxabi', 'openmp']
+
+
 class EB_Clang(CMakeMake):
     """Support for bootstrapping Clang."""
 
@@ -87,6 +95,7 @@ def extra_options():
             'bootstrap': [True, "Bootstrap Clang using GCC", CUSTOM],
             'build_extra_clang_tools': [False, "Build extra Clang tools", CUSTOM],
             'build_lld': [False, "Build the LLVM lld linker", CUSTOM],
+            'build_lldb': [False, "Build the LLVM lldb debugger", CUSTOM],
             'build_targets': [None, "Build targets for LLVM (host architecture if None). Possible values: " +
                                     ', '.join(CLANG_TARGETS), CUSTOM],
             'default_cuda_capability': [None, "Default CUDA capability specified for clang, e.g. '7.5'", CUSTOM],
@@ -99,6 +108,8 @@ def extra_options():
             # The sanitizer tests often fail on HPC systems due to the 'weird' environment.
             'skip_sanitizer_tests': [True, "Do not run the sanitizer tests", CUSTOM],
             'usepolly': [False, "Build Clang with polly", CUSTOM],
+            'llvm_projects': [[], "LLVM projects to install", CUSTOM],
+            'llvm_runtimes': [[], "LLVM runtimes to install", CUSTOM],
         })
         # disable regular out-of-source build, too simplistic for Clang to work
         extra_vars['separate_build_dir'][0] = False
@@ -113,6 +124,91 @@ def __init__(self, *args, **kwargs):
         self.llvm_obj_dir_stage2 = None
         self.llvm_obj_dir_stage3 = None
         self.make_parallel_opts = ""
+        self.runtime_lib_path = "lib"
+
+        if not self.cfg['llvm_projects']:
+            self.cfg['llvm_projects'] = []
+        if not self.cfg['llvm_runtimes']:
+            self.cfg['llvm_runtimes'] = []
+
+        # Be forgiving if someone places a runtime under projects since it is pretty new
+        for project in [p for p in self.cfg['llvm_projects'] if p in KNOWN_LLVM_RUNTIMES]:
+            msg = "LLVM project %s included but this should be a runtime, moving to runtime list!" % project
+            self.log.warning(msg)
+            self.cfg.update('llvm_runtimes', [project], allow_duplicate=False)
+            # No cleaner way to remove an element from the list
+            self.cfg['llvm_projects'] = [p for p in self.cfg['llvm_projects'] if p != project]
+            print_warning(msg)
+
+        for project in [p for p in self.cfg['llvm_projects'] if p not in KNOWN_LLVM_PROJECTS]:
+            msg = "LLVM project %s included but not recognised, this project will NOT be sanity checked!" % project
+            self.log.warning(msg)
+            print_warning(msg)
+
+        for runtime in [r for r in self.cfg['llvm_runtimes'] if r not in KNOWN_LLVM_RUNTIMES]:
+            msg = "LLVM runtime %s included but not recognised, this runtime will NOT be sanity checked!" % runtime
+            self.log.warning(msg)
+            print_warning(msg)
+
+        # keep compatibility between using llvm_projects/llvm_runtimes vs using flags
+        if LooseVersion(self.version) >= LooseVersion('14'):
+            self.cfg.update('llvm_projects', ['llvm', 'clang'], allow_duplicate=False)
+            self.cfg.update('llvm_runtimes', ['compiler-rt', 'openmp'], allow_duplicate=False)
+            if self.cfg['usepolly']:
+                self.cfg.update('llvm_projects', 'polly', allow_duplicate=False)
+            if self.cfg['build_lld']:
+                self.cfg.update('llvm_projects', ['lld'], allow_duplicate=False)
+                self.cfg.update('llvm_runtimes', ['libunwind'], allow_duplicate=False)
+            if self.cfg['build_lldb']:
+                self.cfg.update('llvm_projects', 'lldb', allow_duplicate=False)
+            if self.cfg['libcxx']:
+                self.cfg.update('llvm_runtimes', ['libcxx', 'libcxxabi'], allow_duplicate=False)
+            if self.cfg['build_extra_clang_tools']:
+                self.cfg.update('llvm_projects', 'clang-tools-extra', allow_duplicate=False)
+
+        # ensure libunwind is there if lld is there
+        if 'lld' in self.cfg['llvm_projects']:
+            self.cfg.update('llvm_runtimes', 'libunwind', allow_duplicate=False)
+
+        # ensure libcxxabi is there if libcxx is there
+        if 'libcxx' in self.cfg['llvm_runtimes']:
+            self.cfg.update('llvm_runtimes', 'libcxxabi', allow_duplicate=False)
+
+        build_targets = self.cfg['build_targets']
+        # define build_targets if not set
+        if build_targets is None:
+            arch = get_cpu_architecture()
+            try:
+                default_targets = DEFAULT_TARGETS_MAP[arch][:]
+                # If CUDA is included as a dep, add NVPTX as a target
+                if get_software_root("CUDA"):
+                    default_targets += ["NVPTX"]
+                # For AMDGPU support we need ROCR-Runtime and
+                # ROCT-Thunk-Interface, however, since ROCT is a dependency of
+                # ROCR we only check for the ROCR-Runtime here
+                # https://openmp.llvm.org/SupportAndFAQ.html#q-how-to-build-an-openmp-amdgpu-offload-capable-compiler
+                if get_software_root("ROCR-Runtime"):
+                    default_targets += ["AMDGPU"]
+                self.cfg['build_targets'] = build_targets = default_targets
+                self.log.debug("Using %s as default build targets for CPU/GPU architecture %s.", default_targets, arch)
+            except KeyError:
+                raise EasyBuildError("No default build targets defined for CPU architecture %s.", arch)
+
+        # carry on with empty list from this point forward if no build targets are specified
+        if build_targets is None:
+            self.cfg['build_targets'] = build_targets = []
+
+        unknown_targets = [target for target in build_targets if target not in CLANG_TARGETS]
+
+        if unknown_targets:
+            raise EasyBuildError("Some of the chosen build targets (%s) are not in %s.",
+                                 ', '.join(unknown_targets), ', '.join(CLANG_TARGETS))
+
+        if LooseVersion(self.version) < LooseVersion('3.4') and "R600" in build_targets:
+            raise EasyBuildError("Build target R600 not supported in < Clang-3.4")
+
+        if LooseVersion(self.version) > LooseVersion('3.3') and "MBlaze" in build_targets:
+            raise EasyBuildError("Build target MBlaze is not supported anymore in > Clang-3.3")
 
     def check_readiness_step(self):
         """Fail early on RHEL 5.x and derivatives because of known bug in libc."""
@@ -124,20 +220,7 @@ def check_readiness_step(self):
 
     def extract_step(self):
         """
-        Prepare a combined LLVM source tree.  The layout is:
-        llvm/             Unpack llvm-*.tar.gz here
-          projects/
-            compiler-rt/  Unpack compiler-rt-*.tar.gz here
-            openmp/       Unpack openmp-*.tar.xz here
-          tools/
-            clang/        Unpack clang-*.tar.gz here
-              tools/
-                extra/    Unpack clang-tools-extra-*.tar.gz here
-            polly/        Unpack polly-*.tar.gz here
-            libcxx/       Unpack libcxx-*.tar.gz here
-            libcxxabi/    Unpack libcxxabi-*.tar.gz here
-            lld/          Unpack lld-*.tar.gz here
-        libunwind/        Unpack libunwind-*.tar.gz here
+        Prepare a combined LLVM source tree.  The layout is different for versions earlier and later than 14.
         """
 
         # Extract everything into separate directories.
@@ -165,27 +248,52 @@ def find_source_dir(globpatterns, targetdir):
                                      glob_src_dirs)
             src_dirs[glob_src_dirs[0]] = targetdir
 
-        find_source_dir('compiler-rt-*', os.path.join(self.llvm_src_dir, 'projects', 'compiler-rt'))
-
-        if self.cfg["usepolly"]:
-            find_source_dir('polly-*', os.path.join(self.llvm_src_dir, 'tools', 'polly'))
-
-        if self.cfg["build_lld"]:
-            find_source_dir('lld-*', os.path.join(self.llvm_src_dir, 'tools', 'lld'))
-            if LooseVersion(self.version) >= LooseVersion('12.0.1'):
-                find_source_dir('libunwind-*', os.path.normpath(os.path.join(self.llvm_src_dir, '..', 'libunwind')))
-
-        if self.cfg["libcxx"]:
-            find_source_dir('libcxx-*', os.path.join(self.llvm_src_dir, 'projects', 'libcxx'))
-            find_source_dir('libcxxabi-*', os.path.join(self.llvm_src_dir, 'projects', 'libcxxabi'))
-
-        find_source_dir(['clang-[1-9]*', 'cfe-*'], os.path.join(self.llvm_src_dir, 'tools', 'clang'))
-
-        if self.cfg["build_extra_clang_tools"]:
-            find_source_dir('clang-tools-extra-*', os.path.join(self.llvm_src_dir, 'tools', 'clang', 'tools', 'extra'))
-
-        if LooseVersion(self.version) >= LooseVersion('3.8'):
-            find_source_dir('openmp-*', os.path.join(self.llvm_src_dir, 'projects', 'openmp'))
+        if any([x['name'].startswith('llvm-project') for x in self.src]):
+            # if sources contain 'llvm-project*', we use the full tarball
+            find_source_dir("../llvm-project-*", os.path.join(self.llvm_src_dir, "llvm-project-%s" % self.version))
+            self.cfg.update('configopts', '-DLLVM_ENABLE_PROJECTS="%s"' % ';'.join(self.cfg['llvm_projects']))
+            self.cfg.update('configopts', '-DLLVM_ENABLE_RUNTIMES="%s"' % ';'.join(self.cfg['llvm_runtimes']))
+        else:
+            # Layout for previous versions
+            #        llvm/             Unpack llvm-*.tar.gz here
+            #          projects/
+            #            compiler-rt/  Unpack compiler-rt-*.tar.gz here
+            #            openmp/       Unpack openmp-*.tar.xz here
+            #          tools/
+            #            clang/        Unpack clang-*.tar.gz here
+            #              tools/
+            #                extra/    Unpack clang-tools-extra-*.tar.gz here
+            #            polly/        Unpack polly-*.tar.gz here
+            #            libcxx/       Unpack libcxx-*.tar.gz here
+            #            libcxxabi/    Unpack libcxxabi-*.tar.gz here
+            #            lld/          Unpack lld-*.tar.gz here
+            #            lldb/         Unpack lldb-*.tar.gz here
+            #        libunwind/        Unpack libunwind-*.tar.gz here
+            find_source_dir('compiler-rt-*', os.path.join(self.llvm_src_dir, 'projects', 'compiler-rt'))
+
+            if 'polly' in self.cfg['llvm_projects']:
+                find_source_dir('polly-*', os.path.join(self.llvm_src_dir, 'tools', 'polly'))
+
+            if 'lld' in self.cfg['llvm_projects']:
+                find_source_dir('lld-*', os.path.join(self.llvm_src_dir, 'tools', 'lld'))
+                if LooseVersion(self.version) >= LooseVersion('12.0.1'):
+                    find_source_dir('libunwind-*', os.path.normpath(os.path.join(self.llvm_src_dir, '..', 'libunwind')))
+
+            if 'lldb' in self.cfg['llvm_projects']:
+                find_source_dir('lldb-*', os.path.join(self.llvm_src_dir, 'tools', 'lldb'))
+
+            if 'libcxx' in self.cfg['llvm_runtimes']:
+                find_source_dir('libcxx-*', os.path.join(self.llvm_src_dir, 'projects', 'libcxx'))
+                find_source_dir('libcxxabi-*', os.path.join(self.llvm_src_dir, 'projects', 'libcxxabi'))
+
+            find_source_dir(['clang-[1-9]*', 'cfe-*'], os.path.join(self.llvm_src_dir, 'tools', 'clang'))
+
+            if 'clang-tools-extra' in self.cfg['llvm_projects']:
+                find_source_dir('clang-tools-extra-*',
+                                os.path.join(self.llvm_src_dir, 'tools', 'clang', 'tools', 'extra'))
+
+            if LooseVersion(self.version) >= LooseVersion('3.8'):
+                find_source_dir('openmp-*', os.path.join(self.llvm_src_dir, 'projects', 'openmp'))
 
         for src in self.src:
             for (dirname, new_path) in src_dirs.items():
@@ -198,45 +306,6 @@ def find_source_dir(globpatterns, targetdir):
                     src['finalpath'] = new_path
                     break
 
-    def prepare_step(self, *args, **kwargs):
-        """Prepare build environment."""
-        super(EB_Clang, self).prepare_step(*args, **kwargs)
-
-        build_targets = self.cfg['build_targets']
-        if build_targets is None:
-            arch = get_cpu_architecture()
-            try:
-                default_targets = DEFAULT_TARGETS_MAP[arch][:]
-                # If CUDA is included as a dep, add NVPTX as a target
-                if get_software_root("CUDA"):
-                    default_targets += ["NVPTX"]
-                # For AMDGPU support we need ROCR-Runtime and
-                # ROCT-Thunk-Interface, however, since ROCT is a dependency of
-                # ROCR we only check for the ROCR-Runtime here
-                # https://openmp.llvm.org/SupportAndFAQ.html#q-how-to-build-an-openmp-amdgpu-offload-capable-compiler
-                if get_software_root("ROCR-Runtime"):
-                    default_targets += ["AMDGPU"]
-                self.cfg['build_targets'] = build_targets = default_targets
-                self.log.debug("Using %s as default build targets for CPU/GPU architecture %s.", default_targets, arch)
-            except KeyError:
-                raise EasyBuildError("No default build targets defined for CPU architecture %s.", arch)
-
-        # carry on with empty list from this point forward if no build targets are specified
-        if build_targets is None:
-            self.cfg['build_targets'] = build_targets = []
-
-        unknown_targets = [target for target in build_targets if target not in CLANG_TARGETS]
-
-        if unknown_targets:
-            raise EasyBuildError("Some of the chosen build targets (%s) are not in %s.",
-                                 ', '.join(unknown_targets), ', '.join(CLANG_TARGETS))
-
-        if LooseVersion(self.version) < LooseVersion('3.4') and "R600" in build_targets:
-            raise EasyBuildError("Build target R600 not supported in < Clang-3.4")
-
-        if LooseVersion(self.version) > LooseVersion('3.3') and "MBlaze" in build_targets:
-            raise EasyBuildError("Build target MBlaze is not supported anymore in > Clang-3.3")
-
     def configure_step(self):
         """Run CMake for stage 1 Clang."""
 
@@ -307,8 +376,12 @@ def configure_step(self):
         else:
             self.cfg.update('configopts', "-DLLVM_ENABLE_ASSERTIONS=OFF")
 
-        if self.cfg["usepolly"]:
-            self.cfg.update('configopts', "-DLINK_POLLY_INTO_TOOLS=ON")
+        if 'polly' in self.cfg['llvm_projects']:
+            # Not exactly sure when this change took place, educated guess
+            if LooseVersion(self.version) >= LooseVersion('14'):
+                self.cfg.update('configopts', "-DLLVM_POLLY_LINK_INTO_TOOLS=ON")
+            else:
+                self.cfg.update('configopts', "-DLINK_POLLY_INTO_TOOLS=ON")
 
         # If Z3 is included as a dep, enable support in static analyzer (if enabled)
         if self.cfg["static_analyzer"] and LooseVersion(self.version) >= LooseVersion('9.0.0'):
@@ -319,7 +392,7 @@ def configure_step(self):
 
         build_targets = self.cfg['build_targets']
 
-        if self.cfg["usepolly"] and "NVPTX" in build_targets:
+        if 'polly' in self.cfg['llvm_projects'] and "NVPTX" in build_targets:
             self.cfg.update('configopts', "-DPOLLY_ENABLE_GPGPU_CODEGEN=ON")
 
         self.cfg.update('configopts', '-DLLVM_TARGETS_TO_BUILD="%s"' % ';'.join(build_targets))
@@ -369,7 +442,12 @@ def configure_step(self):
             self.cfg.update('configopts', '-DLIBOMPTARGET_AMDGCN_GFXLIST=%s' % ' '.join(ec_amdgfx))
 
         self.log.info("Configuring")
-        super(EB_Clang, self).configure_step(srcdir=self.llvm_src_dir)
+
+        # directory structure has changed in version 14.x, cmake must start in llvm sub directory
+        if LooseVersion(self.version) >= LooseVersion('14'):
+            super(EB_Clang, self).configure_step(srcdir=os.path.join(self.llvm_src_dir, "llvm"))
+        else:
+            super(EB_Clang, self).configure_step(srcdir=self.llvm_src_dir)
 
     def disable_sanitizer_tests(self):
         """Disable the tests of all the sanitizers by removing the test directories from the build system"""
@@ -384,7 +462,7 @@ def disable_sanitizer_tests(self):
                     regex_subs = [(r'.*add_subdirectory\(lit_tests\).*', '')]
                     apply_regex_substitutions(cmakelists, regex_subs)
 
-            # There is a common part seperate for the specific saniters, we disable all the common tests
+            # There is a common part seperate for the specific sanitizers, we disable all the common tests
             cmakelists = os.path.join('projects', 'compiler-rt', 'lib', 'sanitizer_common', 'CMakeLists.txt')
             regex_subs = [(r'.*add_subdirectory\(tests\).*', '')]
             apply_regex_substitutions(cmakelists, regex_subs)
@@ -392,7 +470,10 @@ def disable_sanitizer_tests(self):
         else:
             # In Clang 3.6, the sanitizer tests are grouped together in one CMakeLists
             # We patch out adding the subdirectories with the sanitizer tests
-            cmakelists_tests = os.path.join(self.llvm_src_dir, 'projects', 'compiler-rt', 'test', 'CMakeLists.txt')
+            if LooseVersion(self.version) >= LooseVersion('14'):
+                cmakelists_tests = os.path.join(self.llvm_src_dir, 'compiler-rt', 'test', 'CMakeLists.txt')
+            else:
+                cmakelists_tests = os.path.join(self.llvm_src_dir, 'projects', 'compiler-rt', 'test', 'CMakeLists.txt')
             regex_subs = []
             if LooseVersion(self.version) >= LooseVersion('5.0'):
                 regex_subs.append((r'compiler_rt_test_runtime.*san.*', ''))
@@ -455,7 +536,10 @@ def build_with_prev_stage(self, prev_obj, next_obj):
             options.append("-DLIBOMPTARGET_NVPTX_BC_LINKER=%s" % llvm_link)
 
         self.log.info("Configuring")
-        run_cmd("cmake %s %s" % (' '.join(options), self.llvm_src_dir), log_all=True)
+        if LooseVersion(self.version) >= LooseVersion('14'):
+            run_cmd("cmake %s %s" % (' '.join(options), os.path.join(self.llvm_src_dir, "llvm")), log_all=True)
+        else:
+            run_cmd("cmake %s %s" % (' '.join(options), self.llvm_src_dir), log_all=True)
 
         self.log.info("Building")
         run_cmd("make %s VERBOSE=1" % self.make_parallel_opts, log_all=True)
@@ -533,14 +617,44 @@ def post_install_step(self):
 
         # copy Python bindings here in post-install step so that it is not done more than once in multi_deps context
         if self.cfg['python_bindings']:
-            python_bindings_source_dir = os.path.join(self.llvm_src_dir, "tools", "clang", "bindings", "python")
+            if LooseVersion(self.version) >= LooseVersion('14'):
+                python_bindings_source_dir = os.path.join(self.llvm_src_dir, "clang", "bindings", "python")
+            else:
+                python_bindings_source_dir = os.path.join(self.llvm_src_dir, "tools", "clang", "bindings", "python")
             python_bindins_target_dir = os.path.join(self.installdir, 'lib', 'python')
 
             shutil.copytree(python_bindings_source_dir, python_bindins_target_dir)
 
     def sanity_check_step(self):
         """Custom sanity check for Clang."""
+        custom_commands = ['clang --help', 'clang++ --help', 'llvm-config --cxxflags']
         shlib_ext = get_shared_lib_ext()
+
+        # Detect OpenMP support for CPU architecture
+        arch = get_cpu_architecture()
+        # Check architecture explicitly since Clang uses potentially
+        # different names
+        if arch == X86_64:
+            arch = 'x86_64'
+        elif arch == POWER:
+            arch = 'ppc64'
+        elif arch == AARCH64:
+            arch = 'aarch64'
+        else:
+            print_warning("Unknown CPU architecture (%s) for OpenMP and runtime libraries check!" % arch)
+
+        if LooseVersion(self.version) >= LooseVersion('14'):
+            glob_pattern = os.path.join(self.installdir, 'lib', '%s-*' % arch)
+            matches = glob.glob(glob_pattern)
+            if matches:
+                directory = os.path.basename(matches[0])
+                self.runtime_lib_path = os.path.join("lib", directory)
+            else:
+                print_warning("Could not find runtime library directory")
+                self.runtime_lib_path = "lib"
+        else:
+            self.runtime_lib_path = "lib"
+
         custom_paths = {
             'files': [
                 "bin/clang", "bin/clang++", "bin/llvm-ar", "bin/llvm-nm", "bin/llvm-as", "bin/opt", "bin/llvm-link",
@@ -552,37 +666,42 @@ def sanity_check_step(self):
         if self.cfg['static_analyzer']:
             custom_paths['files'].extend(["bin/scan-build", "bin/scan-view"])
 
-        if self.cfg['build_extra_clang_tools'] and LooseVersion(self.version) >= LooseVersion('3.4'):
+        if 'clang-tools-extra' in self.cfg['llvm_projects'] and LooseVersion(self.version) >= LooseVersion('3.4'):
             custom_paths['files'].extend(["bin/clang-tidy"])
 
-        if self.cfg["usepolly"]:
+        if 'polly' in self.cfg['llvm_projects']:
             custom_paths['files'].extend(["lib/LLVMPolly.%s" % shlib_ext])
             custom_paths['dirs'].extend(["include/polly"])
 
-        if self.cfg["build_lld"]:
+        if 'lld' in self.cfg['llvm_projects']:
             custom_paths['files'].extend(["bin/lld"])
 
-        if self.cfg["libcxx"]:
-            custom_paths['files'].extend(["lib/libc++.%s" % shlib_ext])
-            custom_paths['files'].extend(["lib/libc++abi.%s" % shlib_ext])
+        if 'lldb' in self.cfg['llvm_projects']:
+            custom_paths['files'].extend(["bin/lldb"])
+
+        if 'libunwind' in self.cfg['llvm_runtimes']:
+            custom_paths['files'].extend([os.path.join(self.runtime_lib_path, "libunwind.%s" % shlib_ext)])
+
+        if 'libcxx' in self.cfg['llvm_runtimes']:
+            custom_paths['files'].extend([os.path.join(self.runtime_lib_path, "libc++.%s" % shlib_ext)])
+
+        if 'libcxxabi' in self.cfg['llvm_runtimes']:
+            custom_paths['files'].extend([os.path.join(self.runtime_lib_path, "libc++abi.%s" % shlib_ext)])
+
+        if 'flang' in self.cfg['llvm_projects'] and LooseVersion(self.version) >= LooseVersion('15'):
+            flang_compiler = 'flang-new'
+            custom_paths['files'].extend(["bin/%s" % flang_compiler])
+            custom_commands.extend(["%s --help" % flang_compiler])
 
         if LooseVersion(self.version) >= LooseVersion('3.8'):
             custom_paths['files'].extend(["lib/libomp.%s" % shlib_ext, "lib/clang/%s/include/omp.h" % self.version])
 
-        # Detect OpenMP support for CPU architecture
-        arch = get_cpu_architecture()
-        # Check architecture explicitly since Clang uses potentially
-        # different names
-        if arch == X86_64:
-            arch = 'x86_64'
-        elif arch == POWER:
-            arch = 'ppc64'
-        elif arch == AARCH64:
-            arch = 'aarch64'
+        if LooseVersion(self.version) >= LooseVersion('12'):
+            omp_target_libs = ["lib/libomptarget.%s" % shlib_ext, "lib/libomptarget.rtl.%s.%s" % (arch, shlib_ext)]
         else:
-            print_warning("Unknown CPU architecture (%s) for OpenMP library check!" % arch)
-        custom_paths['files'].extend(["lib/libomptarget.%s" % shlib_ext,
-                                      "lib/libomptarget.rtl.%s.%s" % (arch, shlib_ext)])
+            omp_target_libs = ["lib/libomptarget.%s" % shlib_ext]
+        custom_paths['files'].extend(omp_target_libs)
+
         # If building for CUDA check that OpenMP target library was created
         if 'NVPTX' in self.cfg['build_targets']:
             custom_paths['files'].append("lib/libomptarget.rtl.cuda.%s" % shlib_ext)
@@ -622,7 +741,6 @@ def sanity_check_step(self):
                 custom_paths['files'].extend(["lib/libomptarget-new-amdgpu-%s.bc" % gfx
                                               for gfx in self.cfg['amd_gfx_list']])
 
-        custom_commands = ['clang --help', 'clang++ --help', 'llvm-config --cxxflags']
         if self.cfg['python_bindings']:
             custom_paths['files'].extend([os.path.join("lib", "python", "clang", "cindex.py")])
             custom_commands.extend(["python -c 'import clang'"])
@@ -638,3 +756,15 @@ def make_module_extra(self):
         if self.cfg['python_bindings']:
             txt += self.module_generator.prepend_paths('PYTHONPATH', os.path.join("lib", "python"))
         return txt
+
+    def make_module_req_guess(self):
+        """
+        Clang can find its own headers and libraries but the .so's need to be in LD_LIBRARY_PATH
+        """
+        guesses = super(EB_Clang, self).make_module_req_guess()
+        guesses.update({
+            'CPATH': [],
+            'LIBRARY_PATH': [],
+            'LD_LIBRARY_PATH': ['lib', 'lib64', self.runtime_lib_path],
+        })
+        return guesses


Test patch: