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