1#===----------------------------------------------------------------------===//
2#
3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4# See https://llvm.org/LICENSE.txt for license information.
5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6#
7#===----------------------------------------------------------------------===//
8
9import importlib
10import lit.util
11import os
12import platform
13import re
14import subprocess
15import shlex
16import sys
17
18from libcxx.util import executeCommand
19
20class DefaultTargetInfo(object):
21    def __init__(self, full_config):
22        self.full_config = full_config
23        self.executor = None
24
25    def platform(self):
26        return sys.platform.lower().strip()
27
28    def is_windows(self):
29        return self.platform() == 'win32'
30
31    def is_darwin(self):
32        return self.platform() == 'darwin'
33
34    def add_cxx_flags(self, flags): pass
35    def add_cxx_compile_flags(self, flags): pass
36    def add_cxx_link_flags(self, flags): pass
37    def allow_cxxabi_link(self): return True
38    def default_cxx_abi_library(self): raise NotImplementedError(self.__class__.__name__)
39
40    def add_path(self, dest_env, new_path):
41        if not new_path:
42            return
43        if 'PATH' not in dest_env:
44            dest_env['PATH'] = new_path
45        else:
46            split_char = ';' if self.is_windows() else ':'
47            dest_env['PATH'] = '%s%s%s' % (new_path, split_char,
48                                           dest_env['PATH'])
49
50
51class DarwinLocalTI(DefaultTargetInfo):
52    def __init__(self, full_config):
53        super(DarwinLocalTI, self).__init__(full_config)
54
55    def is_host_macosx(self):
56        name = lit.util.to_string(subprocess.check_output(['sw_vers', '-productName'])).strip()
57        return name == "Mac OS X"
58
59    def get_macosx_version(self):
60        assert self.is_host_macosx()
61        version = lit.util.to_string(subprocess.check_output(['sw_vers', '-productVersion'])).strip()
62        version = re.sub(r'([0-9]+\.[0-9]+)(\..*)?', r'\1', version)
63        return version
64
65    def get_sdk_version(self, name):
66        assert self.is_host_macosx()
67        cmd = ['xcrun', '--sdk', name, '--show-sdk-path']
68        try:
69            out = subprocess.check_output(cmd).strip()
70        except OSError:
71            pass
72
73        if not out:
74            self.full_config.lit_config.fatal(
75                    "cannot infer sdk version with: %r" % cmd)
76
77        return re.sub(r'.*/[^0-9]+([0-9.]+)\.sdk', r'\1', out)
78
79    def add_cxx_flags(self, flags):
80        out, err, exit_code = executeCommand(['xcrun', '--show-sdk-path'])
81        if exit_code != 0:
82            self.full_config.lit_config.warning("Could not determine macOS SDK path! stderr was " + err)
83        if exit_code == 0 and out:
84            sdk_path = out.strip()
85            self.full_config.lit_config.note('using SDKROOT: %r' % sdk_path)
86            assert isinstance(sdk_path, str)
87            flags += ["-isysroot", sdk_path]
88
89    def add_cxx_link_flags(self, flags):
90        flags += ['-lSystem']
91
92    def allow_cxxabi_link(self):
93        # Don't link libc++abi explicitly on OS X because the symbols
94        # should be available in libc++ directly.
95        return False
96
97    def default_cxx_abi_library(self):
98        return "libcxxabi"
99
100
101class FreeBSDLocalTI(DefaultTargetInfo):
102    def __init__(self, full_config):
103        super(FreeBSDLocalTI, self).__init__(full_config)
104
105    def add_cxx_link_flags(self, flags):
106        flags += ['-lc', '-lm', '-lpthread', '-lgcc_s', '-lcxxrt']
107
108    def default_cxx_abi_library(self):
109        return "libcxxrt"
110
111
112class CheriBSDRemoteTI(DefaultTargetInfo):
113    def __init__(self, full_config):
114        super(CheriBSDRemoteTI, self).__init__(full_config)
115        # TODO: support dynamically linked
116        self.static = True
117
118    def platform(self):
119        return 'freebsd'
120
121    def add_cxx_link_flags(self, flags):
122        explicit_flags = shlex.split(self.full_config.get_lit_conf('test_linker_flags'))
123        if self.full_config.link_shared is False:
124            # We also need to pull in compiler-rt and libunwind (gcc_eh) when building static tests
125            flags += ['-lcompiler_rt', '-lgcc_eh', '-static']
126            # FIXME: work around bug in libthr (or lld?) that doesn't pull in all symbols (if a weak symbol already exists)
127            #flags += ['-Wl,--whole-archive', '-lthr', '-Wl,--no-whole-archive']
128        # else:
129        flags += ['-lpthread']
130
131        flags += ['-lc', '-lm', '-fuse-ld=lld',
132                  '-B' + self.full_config.get_lit_conf('sysroot') + '/../bin']
133        if self.full_config.lit_config.run_with_debugger:
134            flags += ['-Wl,--gdb-index']
135        if self.full_config.get_lit_conf('target_triple').startswith("cheri-"):
136            assert '-mabi=purecap' in explicit_flags, explicit_flags
137
138    def add_cxx_compile_flags(self, flags):
139        explicit_flags = shlex.split(self.full_config.get_lit_conf('test_compiler_flags'))
140        if self.full_config.link_shared is False:
141            # we currently only support static linking so we need to add _LIBCPP_BUILD_STATIC
142            flags += ["-D_LIBCPP_BUILD_STATIC"]
143
144    # def configure_env(self, env): pass
145    def allow_cxxabi_link(self):
146        return False # should either be included or using libcxxrt
147    # def add_sanitizer_features(self, sanitizer_type, features): pass
148    # def use_lit_shell_default(self): return False
149
150    def default_cxx_abi_library(self):
151        return "libcxxrt"
152
153class NetBSDLocalTI(DefaultTargetInfo):
154    def __init__(self, full_config):
155        super(NetBSDLocalTI, self).__init__(full_config)
156
157    def add_cxx_link_flags(self, flags):
158        flags += ['-lc', '-lm', '-lpthread', '-lgcc_s', '-lc++abi',
159                  '-lunwind']
160
161
162class LinuxLocalTI(DefaultTargetInfo):
163    def __init__(self, full_config):
164        super(LinuxLocalTI, self).__init__(full_config)
165
166    def platform(self):
167        return 'linux'
168
169    def _distribution(self):
170        try:
171            # linux_distribution is not available since Python 3.8
172            # However, this function is only used to detect SLES 11,
173            # which is quite an old distribution that doesn't have
174            # Python 3.8.
175            return platform.linux_distribution()
176        except AttributeError:
177            return '', '', ''
178
179    def platform_name(self):
180        name, _, _ = self._distribution()
181        # Some distros have spaces, e.g. 'SUSE Linux Enterprise Server'
182        # lit features can't have spaces
183        name = name.lower().strip().replace(' ', '-')
184        return name # Permitted to be None
185
186    def platform_ver(self):
187        _, ver, _ = self._distribution()
188        ver = ver.lower().strip().replace(' ', '-')
189        return ver # Permitted to be None.
190
191    def add_cxx_compile_flags(self, flags):
192        flags += ['-D__STDC_FORMAT_MACROS',
193                  '-D__STDC_LIMIT_MACROS',
194                  '-D__STDC_CONSTANT_MACROS']
195
196    def add_cxx_link_flags(self, flags):
197        enable_threads = ('libcpp-has-no-threads' not in
198                          self.full_config.config.available_features)
199        llvm_unwinder = self.full_config.get_lit_bool('llvm_unwinder', False)
200        shared_libcxx = self.full_config.get_lit_bool('enable_shared', True)
201        flags += ['-lm']
202        if not llvm_unwinder:
203            flags += ['-lgcc_s', '-lgcc']
204        if enable_threads:
205            flags += ['-lpthread']
206            if not shared_libcxx:
207                flags += ['-lrt']
208        flags += ['-lc']
209        if llvm_unwinder:
210            flags += ['-lunwind', '-ldl']
211        else:
212            flags += ['-lgcc_s']
213        builtins_lib = self.full_config.get_lit_conf('builtins_library')
214        if builtins_lib:
215            flags += [builtins_lib]
216        else:
217            flags += ['-lgcc']
218        has_libatomic = self.full_config.get_lit_bool('has_libatomic', False)
219        if has_libatomic:
220            flags += ['-latomic']
221        san = self.full_config.get_lit_conf('use_sanitizer', '').strip()
222        if san:
223            # The libraries and their order are taken from the
224            # linkSanitizerRuntimeDeps function in
225            # clang/lib/Driver/Tools.cpp
226            flags += ['-lpthread', '-lrt', '-lm', '-ldl']
227
228    def default_cxx_abi_library(self):
229        return "libsupc++"
230
231class LinuxRemoteTI(LinuxLocalTI):
232    def __init__(self, full_config):
233        super(LinuxRemoteTI, self).__init__(full_config)
234
235class WindowsLocalTI(DefaultTargetInfo):
236    def __init__(self, full_config):
237        super(WindowsLocalTI, self).__init__(full_config)
238
239class BaremetalNewlibTI(DefaultTargetInfo):
240    def __init__(self, full_config):
241        super(BaremetalNewlibTI, self).__init__(full_config)
242
243    def platform(self):
244        return 'baremetal-' + self.full_config.config.target_triple
245
246    def add_locale_features(self, features):
247        add_common_locales(features, self.full_config.lit_config, unchecked_add=True)
248
249    def add_cxx_compile_flags(self, flags):
250        # I'm not sure the _LIBCPP_BUILD_STATIC should be passed when building
251        # against libcpp but it seems to be needed
252        flags += ['-D_GNU_SOURCE', '-D_LIBCPP_BUILD_STATIC']
253        # For now always build with debug info:
254        flags.append('-g')
255        pass
256
257    def add_cxx_link_flags(self, flags):
258        llvm_unwinder = self.full_config.get_lit_bool('llvm_unwinder', False)
259        use_exceptions = self.full_config.get_lit_bool('enable_exceptions', False)
260        # shared_libcxx = self.full_config.get_lit_bool('enable_shared', False)
261        flags += ['-static', '-lm', '-lc']
262        enable_threads = ('libcpp-has-no-threads' not in self.full_config.config.available_features)
263        if enable_threads:
264            pass
265            # flags += ['-lpthread']
266            # if not shared_libcxx:
267            #  flags += ['-lrt']
268        if use_exceptions:
269            flags += ['-lunwind', '-ldl'] if llvm_unwinder else ['-lgcc_s']
270        use_libatomic = self.full_config.get_lit_bool('use_libatomic', False)
271        if use_libatomic:
272            flags += ['-latomic']
273
274
275def make_target_info(full_config):
276    default = "libcxx.test.target_info.LocalTI"
277    info_str = full_config.get_lit_conf('target_info', default)
278    if info_str != default:
279        mod_path, _, info = info_str.rpartition('.')
280        mod = importlib.import_module(mod_path)
281        target_info = getattr(mod, info)(full_config)
282        full_config.lit_config.note("inferred target_info as: %r" % info_str)
283        return target_info
284    target_system = platform.system()
285    if target_system == 'Darwin':  return DarwinLocalTI(full_config)
286    if target_system == 'FreeBSD': return FreeBSDLocalTI(full_config)
287    if target_system == 'NetBSD':  return NetBSDLocalTI(full_config)
288    if target_system == 'Linux':   return LinuxLocalTI(full_config)
289    if target_system == 'Windows': return WindowsLocalTI(full_config)
290    return DefaultTargetInfo(full_config)
291