xref: /llvm-project/llvm/utils/lit/lit/llvm/config.py (revision 9d88543301f262e584a36ea969237a2cf054328b)
1import itertools
2import os
3import platform
4import re
5import subprocess
6import sys
7import errno
8
9import lit.util
10from lit.llvm.subst import FindTool
11from lit.llvm.subst import ToolSubst
12
13lit_path_displayed = False
14
15
16def user_is_root():
17    # os.getuid() is not available on all platforms
18    try:
19        if os.getuid() == 0:
20            return True
21    except:
22        pass
23
24    return False
25
26
27class LLVMConfig(object):
28    def __init__(self, lit_config, config):
29        self.lit_config = lit_config
30        self.config = config
31
32        features = config.available_features
33
34        self.use_lit_shell = False
35        # Tweak PATH for Win32 to decide to use bash.exe or not.
36        if sys.platform == "win32":
37            # Seek necessary tools in directories and set to $PATH.
38            path = None
39            lit_tools_dir = getattr(config, "lit_tools_dir", None)
40            required_tools = ["cmp.exe", "grep.exe", "sed.exe", "diff.exe", "echo.exe"]
41            path = self.lit_config.getToolsPath(
42                lit_tools_dir, config.environment["PATH"], required_tools
43            )
44            if path is None:
45                path = self._find_git_windows_unix_tools(required_tools)
46            if path is not None:
47                self.with_environment("PATH", path, append_path=True)
48            # Many tools behave strangely if these environment variables aren't
49            # set.
50            self.with_system_environment(
51                ["SystemDrive", "SystemRoot", "TEMP", "TMP", "PLATFORM"]
52            )
53            self.use_lit_shell = True
54
55            global lit_path_displayed
56            if not self.lit_config.quiet and lit_path_displayed is False:
57                self.lit_config.note("using lit tools: {}".format(path))
58                lit_path_displayed = True
59
60        if platform.system() == "OS/390":
61            self.with_environment("_BPXK_AUTOCVT", "ON")
62            self.with_environment("_TAG_REDIR_IN", "TXT")
63            self.with_environment("_TAG_REDIR_OUT", "TXT")
64            self.with_environment("_TAG_REDIR_ERR", "TXT")
65            self.with_environment("_CEE_RUNOPTS", "FILETAG(AUTOCVT,AUTOTAG) POSIX(ON)")
66
67        # Choose between lit's internal shell pipeline runner and a real shell.
68        # If LIT_USE_INTERNAL_SHELL is in the environment, we use that as an
69        # override.
70        lit_shell_env = os.environ.get("LIT_USE_INTERNAL_SHELL")
71        if lit_shell_env:
72            self.use_lit_shell = lit.util.pythonize_bool(lit_shell_env)
73
74        if not self.use_lit_shell:
75            features.add("shell")
76
77        self.with_system_environment(
78            [
79                "ASAN_SYMBOLIZER_PATH",
80                "HWASAN_SYMBOLIZER_PATH",
81                "MSAN_SYMBOLIZER_PATH",
82                "TSAN_SYMBOLIZER_PATH",
83                "UBSAN_SYMBOLIZER_PATH" "ASAN_OPTIONS",
84                "HWASAN_OPTIONS",
85                "MSAN_OPTIONS",
86                "RTSAN_OPTIONS",
87                "TSAN_OPTIONS",
88                "UBSAN_OPTIONS",
89            ]
90        )
91
92        # Running on Darwin OS
93        if platform.system() == "Darwin":
94            features.add("system-darwin")
95        elif platform.system() == "Windows":
96            # For tests that require Windows to run.
97            features.add("system-windows")
98        elif platform.system() == "Linux":
99            features.add("system-linux")
100        elif platform.system() in ["FreeBSD"]:
101            features.add("system-freebsd")
102        elif platform.system() == "NetBSD":
103            features.add("system-netbsd")
104        elif platform.system() == "AIX":
105            features.add("system-aix")
106        elif platform.system() == "SunOS":
107            features.add("system-solaris")
108        elif platform.system() == "OS/390":
109            features.add("system-zos")
110
111        # Native compilation: host arch == default triple arch
112        # Both of these values should probably be in every site config (e.g. as
113        # part of the standard header.  But currently they aren't)
114        host_triple = getattr(config, "host_triple", None)
115        target_triple = getattr(config, "target_triple", None)
116        features.add("host=%s" % host_triple)
117        features.add("target=%s" % target_triple)
118        if host_triple and host_triple == target_triple:
119            features.add("native")
120
121        # Sanitizers.
122        sanitizers = getattr(config, "llvm_use_sanitizer", "")
123        sanitizers = frozenset(x.lower() for x in sanitizers.split(";"))
124        if "address" in sanitizers:
125            features.add("asan")
126        if "hwaddress" in sanitizers:
127            features.add("hwasan")
128        if "memory" in sanitizers or "memorywithorigins" in sanitizers:
129            features.add("msan")
130        if "undefined" in sanitizers:
131            features.add("ubsan")
132        if "thread" in sanitizers:
133            features.add("tsan")
134
135        have_zlib = getattr(config, "have_zlib", None)
136        if have_zlib:
137            features.add("zlib")
138        have_zstd = getattr(config, "have_zstd", None)
139        if have_zstd:
140            features.add("zstd")
141
142        if getattr(config, "reverse_iteration", None):
143            features.add("reverse_iteration")
144
145        # Check if we should run long running tests.
146        long_tests = lit_config.params.get("run_long_tests", None)
147        if lit.util.pythonize_bool(long_tests):
148            features.add("long_tests")
149
150        if target_triple:
151            if re.match(r"^x86_64.*-apple", target_triple):
152                features.add("x86_64-apple")
153                host_cxx = getattr(config, "host_cxx", None)
154                if "address" in sanitizers and self.get_clang_has_lsan(
155                    host_cxx, target_triple
156                ):
157                    self.with_environment(
158                        "ASAN_OPTIONS", "detect_leaks=1", append_path=True
159                    )
160            if re.match(r"^x86_64.*-linux", target_triple):
161                features.add("x86_64-linux")
162            if re.match(r"^i.86.*", target_triple):
163                features.add("target-x86")
164            elif re.match(r"^x86_64.*", target_triple):
165                features.add("target-x86_64")
166            elif re.match(r"^aarch64.*", target_triple):
167                features.add("target-aarch64")
168            elif re.match(r"^arm64.*", target_triple):
169                features.add("target-aarch64")
170            elif re.match(r"^arm.*", target_triple):
171                features.add("target-arm")
172            if re.match(r'^ppc64le.*-linux', target_triple):
173                features.add('target=powerpc64le-linux')
174
175        if not user_is_root():
176            features.add("non-root-user")
177
178        use_gmalloc = lit_config.params.get("use_gmalloc", None)
179        if lit.util.pythonize_bool(use_gmalloc):
180            # Allow use of an explicit path for gmalloc library.
181            # Will default to '/usr/lib/libgmalloc.dylib' if not set.
182            gmalloc_path_str = lit_config.params.get(
183                "gmalloc_path", "/usr/lib/libgmalloc.dylib"
184            )
185            if gmalloc_path_str is not None:
186                self.with_environment("DYLD_INSERT_LIBRARIES", gmalloc_path_str)
187
188    def _find_git_windows_unix_tools(self, tools_needed):
189        assert sys.platform == "win32"
190        import winreg
191
192        # Search both the 64 and 32-bit hives, as well as HKLM + HKCU
193        masks = [0, winreg.KEY_WOW64_64KEY]
194        hives = [winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER]
195        for mask, hive in itertools.product(masks, hives):
196            try:
197                with winreg.OpenKey(
198                    hive, r"SOFTWARE\GitForWindows", 0, winreg.KEY_READ | mask
199                ) as key:
200                    install_root, _ = winreg.QueryValueEx(key, "InstallPath")
201
202                    if not install_root:
203                        continue
204                    candidate_path = os.path.join(install_root, "usr", "bin")
205                    if not lit.util.checkToolsPath(candidate_path, tools_needed):
206                        continue
207
208                    # We found it, stop enumerating.
209                    return lit.util.to_string(candidate_path)
210            except:
211                continue
212
213        return None
214
215    def with_environment(self, variable, value, append_path=False):
216        if append_path:
217            # For paths, we should be able to take a list of them and process
218            # all of them.
219            paths_to_add = value
220            if lit.util.is_string(paths_to_add):
221                paths_to_add = [paths_to_add]
222
223            def norm(x):
224                return os.path.normcase(os.path.normpath(x))
225
226            current_paths = self.config.environment.get(variable, None)
227            if current_paths:
228                current_paths = current_paths.split(os.path.pathsep)
229                paths = [norm(p) for p in current_paths]
230            else:
231                paths = []
232
233            # If we are passed a list [a b c], then iterating this list forwards
234            # and adding each to the beginning would result in c b a.  So we
235            # need to iterate in reverse to end up with the original ordering.
236            for p in reversed(paths_to_add):
237                # Move it to the front if it already exists, otherwise insert
238                # it at the beginning.
239                p = norm(p)
240                try:
241                    paths.remove(p)
242                except ValueError:
243                    pass
244                paths = [p] + paths
245            value = os.pathsep.join(paths)
246        self.config.environment[variable] = value
247
248    def with_system_environment(self, variables, append_path=False):
249        if lit.util.is_string(variables):
250            variables = [variables]
251        for v in variables:
252            value = os.environ.get(v)
253            if value:
254                self.with_environment(v, value, append_path)
255
256    def clear_environment(self, variables):
257        for name in variables:
258            if name in self.config.environment:
259                del self.config.environment[name]
260
261    def get_process_output(self, command):
262        try:
263            cmd = subprocess.Popen(
264                command,
265                stdout=subprocess.PIPE,
266                stderr=subprocess.PIPE,
267                env=self.config.environment,
268            )
269            stdout, stderr = cmd.communicate()
270            stdout = lit.util.to_string(stdout)
271            stderr = lit.util.to_string(stderr)
272            return (stdout, stderr)
273        except OSError:
274            self.lit_config.fatal("Could not run process %s" % command)
275
276    def feature_config(self, features):
277        # Ask llvm-config about the specified feature.
278        arguments = [x for (x, _) in features]
279        config_path = os.path.join(self.config.llvm_tools_dir, "llvm-config")
280
281        output, _ = self.get_process_output([config_path] + arguments)
282        lines = output.split("\n")
283
284        for (feature_line, (_, patterns)) in zip(lines, features):
285            # We should have either a callable or a dictionary.  If it's a
286            # dictionary, grep each key against the output and use the value if
287            # it matches.  If it's a callable, it does the entire translation.
288            if callable(patterns):
289                features_to_add = patterns(feature_line)
290                self.config.available_features.update(features_to_add)
291            else:
292                for (re_pattern, feature) in patterns.items():
293                    if re.search(re_pattern, feature_line):
294                        self.config.available_features.add(feature)
295
296    # Note that when substituting %clang_cc1 also fill in the include directory
297    # of the builtin headers. Those are part of even a freestanding
298    # environment, but Clang relies on the driver to locate them.
299    def get_clang_builtin_include_dir(self, clang):
300        # FIXME: Rather than just getting the version, we should have clang
301        # print out its resource dir here in an easy to scrape form.
302        clang_dir, _ = self.get_process_output([clang, "-print-file-name=include"])
303
304        if not clang_dir:
305            print(clang)
306            self.lit_config.fatal(
307                "Couldn't find the include dir for Clang ('%s')" % clang
308            )
309
310        clang_dir = clang_dir.strip()
311        if sys.platform in ["win32"] and not self.use_lit_shell:
312            # Don't pass dosish path separator to msys bash.exe.
313            clang_dir = clang_dir.replace("\\", "/")
314        # Ensure the result is an ascii string, across Python2.5+ - Python3.
315        return clang_dir
316
317    # On macOS, LSan is only supported on clang versions 5 and higher
318    def get_clang_has_lsan(self, clang, triple):
319        if not clang:
320            self.lit_config.warning(
321                "config.host_cxx is unset but test suite is configured "
322                "to use sanitizers."
323            )
324            return False
325
326        clang_binary = clang.split()[0]
327        version_string, _ = self.get_process_output([clang_binary, "--version"])
328        if not "clang" in version_string:
329            self.lit_config.warning(
330                "compiler '%s' does not appear to be clang, " % clang_binary
331                + "but test suite is configured to use sanitizers."
332            )
333            return False
334
335        if re.match(r".*-linux", triple):
336            return True
337
338        if re.match(r"^x86_64.*-apple", triple):
339            version_regex = re.search(
340                r"version ([0-9]+)\.([0-9]+).([0-9]+)", version_string
341            )
342            major_version_number = int(version_regex.group(1))
343            minor_version_number = int(version_regex.group(2))
344            patch_version_number = int(version_regex.group(3))
345            if "Apple LLVM" in version_string or "Apple clang" in version_string:
346                # Apple clang doesn't yet support LSan
347                return False
348            return major_version_number >= 5
349
350        return False
351
352    def make_itanium_abi_triple(self, triple):
353        m = re.match(r"(\w+)-(\w+)-(\w+)", triple)
354        if not m:
355            self.lit_config.fatal(
356                "Could not turn '%s' into Itanium ABI triple" % triple
357            )
358        if m.group(3).lower() != "windows":
359            # All non-windows triples use the Itanium ABI.
360            return triple
361        return m.group(1) + "-" + m.group(2) + "-" + m.group(3) + "-gnu"
362
363    def make_msabi_triple(self, triple):
364        m = re.match(r"(\w+)-(\w+)-(\w+)", triple)
365        if not m:
366            self.lit_config.fatal("Could not turn '%s' into MS ABI triple" % triple)
367        isa = m.group(1).lower()
368        vendor = m.group(2).lower()
369        os = m.group(3).lower()
370        if os == "windows" and re.match(r".*-msvc$", triple):
371            # If the OS is windows and environment is msvc, we're done.
372            return triple
373        if isa.startswith("x86") or isa == "amd64" or re.match(r"i\d86", isa):
374            # For x86 ISAs, adjust the OS.
375            return isa + "-" + vendor + "-windows-msvc"
376        # -msvc is not supported for non-x86 targets; use a default.
377        return "i686-pc-windows-msvc"
378
379    def add_tool_substitutions(self, tools, search_dirs=None):
380        if not search_dirs:
381            search_dirs = [self.config.llvm_tools_dir]
382
383        if lit.util.is_string(search_dirs):
384            search_dirs = [search_dirs]
385
386        tools = [x if isinstance(x, ToolSubst) else ToolSubst(x) for x in tools]
387
388        search_dirs = os.pathsep.join(search_dirs)
389        substitutions = []
390
391        for tool in tools:
392            match = tool.resolve(self, search_dirs)
393
394            # Either no match occurred, or there was an unresolved match that
395            # is ignored.
396            if not match:
397                continue
398
399            subst_key, tool_pipe, command = match
400
401            # An unresolved match occurred that can't be ignored.  Fail without
402            # adding any of the previously-discovered substitutions.
403            if not command:
404                return False
405
406            substitutions.append((subst_key, tool_pipe + command))
407
408        self.config.substitutions.extend(substitutions)
409        return True
410
411    def add_err_msg_substitutions(self):
412        # Python's strerror may not supply the same message
413        # as C++ std::error_code. One example of such a platform is
414        # Visual Studio. errc_messages may be supplied which contains the error
415        # messages for ENOENT, EISDIR, EINVAL and EACCES as a semi colon
416        # separated string. LLVM testsuites can use get_errc_messages in cmake
417        # to automatically get the messages and pass them into lit.
418        errc_messages = getattr(self.config, "errc_messages", "")
419        if len(errc_messages) != 0:
420            (errc_enoent, errc_eisdir, errc_einval, errc_eacces) = errc_messages.split(
421                ";"
422            )
423            self.config.substitutions.append(("%errc_ENOENT", "'" + errc_enoent + "'"))
424            self.config.substitutions.append(("%errc_EISDIR", "'" + errc_eisdir + "'"))
425            self.config.substitutions.append(("%errc_EINVAL", "'" + errc_einval + "'"))
426            self.config.substitutions.append(("%errc_EACCES", "'" + errc_eacces + "'"))
427        else:
428            self.config.substitutions.append(
429                ("%errc_ENOENT", "'" + os.strerror(errno.ENOENT) + "'")
430            )
431            self.config.substitutions.append(
432                ("%errc_EISDIR", "'" + os.strerror(errno.EISDIR) + "'")
433            )
434            self.config.substitutions.append(
435                ("%errc_EINVAL", "'" + os.strerror(errno.EINVAL) + "'")
436            )
437            self.config.substitutions.append(
438                ("%errc_EACCES", "'" + os.strerror(errno.EACCES) + "'")
439            )
440
441    def use_default_substitutions(self):
442        tool_patterns = [
443            ToolSubst("FileCheck", unresolved="fatal"),
444            # Handle these specially as they are strings searched for during
445            # testing.
446            ToolSubst(
447                r"\| \bcount\b",
448                command=FindTool("count"),
449                verbatim=True,
450                unresolved="fatal",
451            ),
452            ToolSubst(
453                r"\| \bnot\b",
454                command=FindTool("not"),
455                verbatim=True,
456                unresolved="fatal",
457            ),
458        ]
459
460        self.config.substitutions.append(("%python", '"%s"' % (sys.executable)))
461
462        self.add_tool_substitutions(tool_patterns, [self.config.llvm_tools_dir])
463
464        self.add_err_msg_substitutions()
465
466    def use_llvm_tool(
467        self,
468        name,
469        search_env=None,
470        required=False,
471        quiet=False,
472        search_paths=None,
473        use_installed=False,
474    ):
475        """Find the executable program 'name', optionally using the specified
476        environment variable as an override before searching the build directory
477        and then optionally the configuration's PATH."""
478        # If the override is specified in the environment, use it without
479        # validation.
480        tool = None
481        if search_env:
482            tool = self.config.environment.get(search_env)
483
484        if not tool:
485            if search_paths is None:
486                search_paths = [self.config.llvm_tools_dir]
487            # Use the specified search paths.
488            path = os.pathsep.join(search_paths)
489            tool = lit.util.which(name, path)
490
491        if not tool and use_installed:
492            # Otherwise look in the path, if enabled.
493            tool = lit.util.which(name, self.config.environment["PATH"])
494
495        if required and not tool:
496            message = "couldn't find '{}' program".format(name)
497            if search_env:
498                message = message + ", try setting {} in your environment".format(
499                    search_env
500                )
501            self.lit_config.fatal(message)
502
503        if tool:
504            tool = os.path.normpath(tool)
505            if not self.lit_config.quiet and not quiet:
506                self.lit_config.note("using {}: {}".format(name, tool))
507        return tool
508
509    def use_clang(
510        self,
511        additional_tool_dirs=[],
512        additional_flags=[],
513        required=True,
514        use_installed=False,
515    ):
516        """Configure the test suite to be able to invoke clang.
517
518        Sets up some environment variables important to clang, locates a
519        just-built or optionally an installed clang, and add a set of standard
520        substitutions useful to any test suite that makes use of clang.
521
522        """
523        # Clear some environment variables that might affect Clang.
524        #
525        # This first set of vars are read by Clang, but shouldn't affect tests
526        # that aren't specifically looking for these features, or are required
527        # simply to run the tests at all.
528        #
529        # FIXME: Should we have a tool that enforces this?
530
531        # safe_env_vars = (
532        #     'TMPDIR', 'TEMP', 'TMP', 'USERPROFILE', 'PWD',
533        #     'MACOSX_DEPLOYMENT_TARGET', 'IPHONEOS_DEPLOYMENT_TARGET',
534        #     'VCINSTALLDIR', 'VC100COMNTOOLS', 'VC90COMNTOOLS',
535        #     'VC80COMNTOOLS')
536        possibly_dangerous_env_vars = [
537            "COMPILER_PATH",
538            "RC_DEBUG_OPTIONS",
539            "CINDEXTEST_PREAMBLE_FILE",
540            "LIBRARY_PATH",
541            "CPATH",
542            "C_INCLUDE_PATH",
543            "CPLUS_INCLUDE_PATH",
544            "OBJC_INCLUDE_PATH",
545            "OBJCPLUS_INCLUDE_PATH",
546            "LIBCLANG_TIMING",
547            "LIBCLANG_OBJTRACKING",
548            "LIBCLANG_LOGGING",
549            "LIBCLANG_BGPRIO_INDEX",
550            "LIBCLANG_BGPRIO_EDIT",
551            "LIBCLANG_NOTHREADS",
552            "LIBCLANG_RESOURCE_USAGE",
553            "LIBCLANG_CODE_COMPLETION_LOGGING",
554        ]
555        # Clang/Win32 may refer to %INCLUDE%. vsvarsall.bat sets it.
556        if platform.system() != "Windows":
557            possibly_dangerous_env_vars.append("INCLUDE")
558
559        self.clear_environment(possibly_dangerous_env_vars)
560
561        # Tweak the PATH to include the tools dir and the scripts dir.
562        # Put Clang first to avoid LLVM from overriding out-of-tree clang
563        # builds.
564        exe_dir_props = [
565            self.config.name.lower() + "_tools_dir",
566            "clang_tools_dir",
567            "llvm_tools_dir",
568        ]
569        paths = [
570            getattr(self.config, pp)
571            for pp in exe_dir_props
572            if getattr(self.config, pp, None)
573        ]
574        paths = additional_tool_dirs + paths
575        self.with_environment("PATH", paths, append_path=True)
576
577        lib_dir_props = [
578            self.config.name.lower() + "_libs_dir",
579            "llvm_shlib_dir",
580            "llvm_libs_dir",
581        ]
582        lib_paths = [
583            getattr(self.config, pp)
584            for pp in lib_dir_props
585            if getattr(self.config, pp, None)
586        ]
587
588        if platform.system() == "AIX":
589            self.with_environment("LIBPATH", lib_paths, append_path=True)
590        else:
591            self.with_environment("LD_LIBRARY_PATH", lib_paths, append_path=True)
592
593        shl = getattr(self.config, "llvm_shlib_dir", None)
594        pext = getattr(self.config, "llvm_plugin_ext", None)
595        if shl:
596            self.config.substitutions.append(("%llvmshlibdir", shl))
597        if pext:
598            self.config.substitutions.append(("%pluginext", pext))
599
600        # Discover the 'clang' and 'clangcc' to use.
601        self.config.clang = self.use_llvm_tool(
602            "clang",
603            search_env="CLANG",
604            required=required,
605            search_paths=paths,
606            use_installed=use_installed,
607        )
608        if self.config.clang:
609            self.config.available_features.add("clang")
610            builtin_include_dir = self.get_clang_builtin_include_dir(self.config.clang)
611            tool_substitutions = [
612                ToolSubst(
613                    "%clang", command=self.config.clang, extra_args=additional_flags
614                ),
615                ToolSubst(
616                    "%clang_analyze_cc1",
617                    command="%clang_cc1",
618                    extra_args=["-analyze", "%analyze", "-setup-static-analyzer"]
619                    + additional_flags,
620                ),
621                ToolSubst(
622                    "%clang_cc1",
623                    command=self.config.clang,
624                    extra_args=[
625                        "-cc1",
626                        "-internal-isystem",
627                        builtin_include_dir,
628                        "-nostdsysteminc",
629                    ]
630                    + additional_flags,
631                ),
632                ToolSubst(
633                    "%clang_cpp",
634                    command=self.config.clang,
635                    extra_args=["--driver-mode=cpp"] + additional_flags,
636                ),
637                ToolSubst(
638                    "%clang_cl",
639                    command=self.config.clang,
640                    extra_args=["--driver-mode=cl"] + additional_flags,
641                ),
642                ToolSubst(
643                    "%clang_dxc",
644                    command=self.config.clang,
645                    extra_args=["--driver-mode=dxc"] + additional_flags,
646                ),
647                ToolSubst(
648                    "%clangxx",
649                    command=self.config.clang,
650                    extra_args=["--driver-mode=g++"] + additional_flags,
651                ),
652            ]
653            self.add_tool_substitutions(tool_substitutions)
654            self.config.substitutions.append(("%resource_dir", builtin_include_dir))
655
656        # There will be no default target triple if one was not specifically
657        # set, and the host's architecture is not an enabled target.
658        if self.config.target_triple:
659            self.config.substitutions.append(
660                (
661                    "%itanium_abi_triple",
662                    self.make_itanium_abi_triple(self.config.target_triple),
663                )
664            )
665            self.config.substitutions.append(
666                ("%ms_abi_triple", self.make_msabi_triple(self.config.target_triple))
667            )
668        else:
669            if not self.lit_config.quiet:
670                self.lit_config.note(
671                    "No default target triple was found, some tests may fail as a result."
672                )
673            self.config.substitutions.append(("%itanium_abi_triple", ""))
674            self.config.substitutions.append(("%ms_abi_triple", ""))
675
676        # The host triple might not be set, at least if we're compiling clang
677        # from an already installed llvm.
678        if self.config.host_triple and self.config.host_triple != "@LLVM_HOST_TRIPLE@":
679            self.config.substitutions.append(
680                (
681                    "%target_itanium_abi_host_triple",
682                    "--target=" + self.make_itanium_abi_triple(self.config.host_triple),
683                )
684            )
685        else:
686            self.config.substitutions.append(("%target_itanium_abi_host_triple", ""))
687
688        # TODO: Many tests work across many language standards. Before
689        # https://discourse.llvm.org/t/lit-run-a-run-line-multiple-times-with-different-replacements/64932
690        # has a solution, provide substitutions to conveniently try every standard with LIT_CLANG_STD_GROUP.
691        clang_std_group = int(os.environ.get("LIT_CLANG_STD_GROUP", "0"))
692        clang_std_values = ("98", "11", "14", "17", "20", "2b")
693
694        def add_std_cxx(s):
695            t = s[8:]
696            if t.endswith("-"):
697                t += clang_std_values[-1]
698            l = clang_std_values.index(t[0:2] if t[0:2] != "23" else "2b")
699            h = clang_std_values.index(t[3:5])
700            # Let LIT_CLANG_STD_GROUP=0 pick the highest value (likely the most relevant
701            # standard).
702            l = h - clang_std_group % (h - l + 1)
703            self.config.substitutions.append((s, "-std=c++" + clang_std_values[l]))
704
705        add_std_cxx("%std_cxx98-14")
706        add_std_cxx("%std_cxx98-")
707        add_std_cxx("%std_cxx11-14")
708        add_std_cxx("%std_cxx11-")
709        add_std_cxx("%std_cxx14-")
710        add_std_cxx("%std_cxx17-20")
711        add_std_cxx("%std_cxx17-")
712        add_std_cxx("%std_cxx20-")
713        add_std_cxx("%std_cxx23-")
714
715        # FIXME: Find nicer way to prohibit this.
716        def prefer(this, to):
717            return '''\"*** Do not use '%s' in tests, use '%s'. ***\"''' % (to, this)
718
719        self.config.substitutions.append((" clang ", prefer("%clang", "clang")))
720        self.config.substitutions.append(
721            (r" clang\+\+ ", prefer("%clangxx", "clang++"))
722        )
723        self.config.substitutions.append(
724            (" clang-cc ", prefer("%clang_cc1", "clang-cc"))
725        )
726        self.config.substitutions.append(
727            (" clang-cl ", prefer("%clang_cl", "clang-cl"))
728        )
729        self.config.substitutions.append(
730            (
731                " clang -cc1 -analyze ",
732                prefer("%clang_analyze_cc1", "clang -cc1 -analyze"),
733            )
734        )
735        self.config.substitutions.append(
736            (" clang -cc1 ", prefer("%clang_cc1", "clang -cc1"))
737        )
738        self.config.substitutions.append(
739            (" %clang-cc1 ", '''\"*** invalid substitution, use '%clang_cc1'. ***\"''')
740        )
741        self.config.substitutions.append(
742            (" %clang-cpp ", '''\"*** invalid substitution, use '%clang_cpp'. ***\"''')
743        )
744        self.config.substitutions.append(
745            (" %clang-cl ", '''\"*** invalid substitution, use '%clang_cl'. ***\"''')
746        )
747
748    def use_lld(self, additional_tool_dirs=[], required=True, use_installed=False):
749        """Configure the test suite to be able to invoke lld.
750
751        Sets up some environment variables important to lld, locates a
752        just-built or optionally an installed lld, and add a set of standard
753        substitutions useful to any test suite that makes use of lld.
754
755        """
756
757        # Tweak the PATH to include the tools dir and the scripts dir.
758        exe_dir_props = [
759            self.config.name.lower() + "_tools_dir",
760            "lld_tools_dir",
761            "llvm_tools_dir",
762        ]
763        paths = [
764            getattr(self.config, pp)
765            for pp in exe_dir_props
766            if getattr(self.config, pp, None)
767        ]
768        paths = additional_tool_dirs + paths
769        self.with_environment("PATH", paths, append_path=True)
770
771        lib_dir_props = [
772            self.config.name.lower() + "_libs_dir",
773            "lld_libs_dir",
774            "llvm_shlib_dir",
775            "llvm_libs_dir",
776        ]
777        lib_paths = [
778            getattr(self.config, pp)
779            for pp in lib_dir_props
780            if getattr(self.config, pp, None)
781        ]
782
783        self.with_environment("LD_LIBRARY_PATH", lib_paths, append_path=True)
784
785        # Discover the LLD executables to use.
786
787        ld_lld = self.use_llvm_tool(
788            "ld.lld", required=required, search_paths=paths, use_installed=use_installed
789        )
790        lld_link = self.use_llvm_tool(
791            "lld-link",
792            required=required,
793            search_paths=paths,
794            use_installed=use_installed,
795        )
796        ld64_lld = self.use_llvm_tool(
797            "ld64.lld",
798            required=required,
799            search_paths=paths,
800            use_installed=use_installed,
801        )
802        wasm_ld = self.use_llvm_tool(
803            "wasm-ld",
804            required=required,
805            search_paths=paths,
806            use_installed=use_installed,
807        )
808
809        was_found = ld_lld and lld_link and ld64_lld and wasm_ld
810        tool_substitutions = []
811        if ld_lld:
812            tool_substitutions.append(ToolSubst(r"ld\.lld", command=ld_lld))
813            self.config.available_features.add("ld.lld")
814        if lld_link:
815            tool_substitutions.append(ToolSubst("lld-link", command=lld_link))
816            self.config.available_features.add("lld-link")
817        if ld64_lld:
818            tool_substitutions.append(ToolSubst(r"ld64\.lld", command=ld64_lld))
819            self.config.available_features.add("ld64.lld")
820        if wasm_ld:
821            tool_substitutions.append(ToolSubst("wasm-ld", command=wasm_ld))
822            self.config.available_features.add("wasm-ld")
823        self.add_tool_substitutions(tool_substitutions)
824
825        return was_found
826