diff --git a/var/spack/repos/builtin/packages/llvm/package.py b/var/spack/repos/builtin/packages/llvm/package.py
index 9573c5d7150260bd48ad84eb9be3f8c5062958c6..95ffcc51fd33dd28eb705b4472324cf3a73806d6 100644
--- a/var/spack/repos/builtin/packages/llvm/package.py
+++ b/var/spack/repos/builtin/packages/llvm/package.py
@@ -2,10 +2,13 @@
 # Spack Project Developers. See the top-level COPYRIGHT file for details.
 #
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
-
-from spack import *
+import os.path
+import re
 import sys
 
+import llnl.util.tty as tty
+import spack.util.executable
+
 
 class Llvm(CMakePackage, CudaPackage):
     """The LLVM Project is a collection of modular and reusable compiler and
@@ -204,6 +207,108 @@ class Llvm(CMakePackage, CudaPackage):
     # https://bugs.llvm.org/show_bug.cgi?id=39696
     patch("thread-p9.patch", when="@develop+libcxx")
 
+    # The functions and attributes below implement external package
+    # detection for LLVM. See:
+    #
+    # https://spack.readthedocs.io/en/latest/packaging_guide.html#making-a-package-discoverable-with-spack-external-find
+    executables = ['clang', 'ld.lld', 'lldb']
+
+    @classmethod
+    def filter_detected_exes(cls, prefix, exes_in_prefix):
+        result = []
+        for exe in exes_in_prefix:
+            # Executables like lldb-vscode-X are daemon listening
+            # on some port and would hang Spack during detection.
+            # clang-cl and clang-cpp are dev tools that we don't
+            # need to test
+            if any(x in exe for x in ('vscode', 'cpp', '-cl', '-gpu')):
+                continue
+            result.append(exe)
+        return result
+
+    @classmethod
+    def determine_version(cls, exe):
+        version_regex = re.compile(
+            # Normal clang compiler versions are left as-is
+            r'clang version ([^ )]+)-svn[~.\w\d-]*|'
+            # Don't include hyphenated patch numbers in the version
+            # (see https://github.com/spack/spack/pull/14365 for details)
+            r'clang version ([^ )]+?)-[~.\w\d-]*|'
+            r'clang version ([^ )]+)|'
+            # LLDB
+            r'lldb version ([^ )\n]+)|'
+            # LLD
+            r'LLD ([^ )\n]+) \(compatible with GNU linkers\)'
+        )
+        try:
+            compiler = Executable(exe)
+            output = compiler('--version', output=str, error=str)
+            if 'Apple' in output:
+                return None
+            match = version_regex.search(output)
+            if match:
+                return match.group(match.lastindex)
+        except spack.util.executable.ProcessError:
+            pass
+        except Exception as e:
+            tty.debug(e)
+
+        return None
+
+    @classmethod
+    def determine_variants(cls, exes, version_str):
+        variants, compilers = ['+clang'], {}
+        lld_found, lldb_found = False, False
+        for exe in exes:
+            if 'clang++' in exe:
+                compilers['cxx'] = exe
+            elif 'clang' in exe:
+                compilers['c'] = exe
+            elif 'ld.lld' in exe:
+                lld_found = True
+                compilers['ld'] = exe
+            elif 'lldb' in exe:
+                lldb_found = True
+                compilers['lldb'] = exe
+
+        variants.append('+lld' if lld_found else '~lld')
+        variants.append('+lldb' if lldb_found else '~lldb')
+
+        return ''.join(variants), {'compilers': compilers}
+
+    @classmethod
+    def validate_detected_spec(cls, spec, extra_attributes):
+        # For LLVM 'compilers' is a mandatory attribute
+        msg = ('the extra attribute "compilers" must be set for '
+               'the detected spec "{0}"'.format(spec))
+        assert 'compilers' in extra_attributes, msg
+        compilers = extra_attributes['compilers']
+        for key in ('c', 'cxx'):
+            msg = '{0} compiler not found for {1}'
+            assert key in compilers, msg.format(key, spec)
+
+    @property
+    def cc(self):
+        msg = "cannot retrieve C compiler [spec is not concrete]"
+        assert self.spec.concrete, msg
+        if self.spec.external:
+            return self.spec.extra_attributes['compilers'].get('c', None)
+        result = None
+        if '+clang' in self.spec:
+            result = os.path.join(self.spec.prefix.bin, 'clang')
+        return result
+
+    @property
+    def cxx(self):
+        msg = "cannot retrieve C++ compiler [spec is not concrete]"
+        assert self.spec.concrete, msg
+        if self.spec.external:
+            return self.spec.extra_attributes['compilers'].get('cxx', None)
+        result = None
+        if '+clang' in self.spec:
+            result = os.path.join(self.spec.prefix.bin, 'clang++')
+        return result
+
     @run_before('cmake')
     def codesign_check(self):
         if self.spec.satisfies("+code_signing"):