xref: /llvm-project/lldb/packages/Python/lldbsuite/test/builders/builder.py (revision 058ede06c4ffd4e3c9f54d947e3bfb027c2d0557)
1import os
2import pathlib
3import platform
4import subprocess
5import sys
6import itertools
7
8import lldbsuite.test.lldbtest as lldbtest
9import lldbsuite.test.lldbplatformutil as lldbplatformutil
10import lldbsuite.test.lldbutil as lldbutil
11from lldbsuite.test import configuration
12from lldbsuite.test_event import build_exception
13from lldbsuite.support import seven
14
15
16class Builder:
17    def getArchitecture(self):
18        """Returns the architecture in effect the test suite is running with."""
19        return configuration.arch if configuration.arch else ""
20
21    def getCompiler(self):
22        """Returns the compiler in effect the test suite is running with."""
23        compiler = configuration.compiler if configuration.compiler else "clang"
24        compiler = lldbutil.which(compiler)
25        return os.path.abspath(compiler)
26
27    def getTriple(self, arch):
28        """Returns the triple for the given architecture or None."""
29        return None
30
31    def getExtraMakeArgs(self):
32        """
33        Helper function to return extra argumentsfor the make system. This
34        method is meant to be overridden by platform specific builders.
35        """
36        return []
37
38    def getArchCFlags(self, architecture):
39        """Returns the ARCH_CFLAGS for the make system."""
40        return []
41
42    def getMake(self, test_subdir, test_name):
43        """Returns the invocation for GNU make.
44        The first argument is a tuple of the relative path to the testcase
45        and its filename stem."""
46        # Construct the base make invocation.
47        lldb_test = os.environ["LLDB_TEST"]
48        if not (
49            lldb_test
50            and configuration.test_build_dir
51            and test_subdir
52            and test_name
53            and (not os.path.isabs(test_subdir))
54        ):
55            raise Exception("Could not derive test directories")
56        build_dir = os.path.join(configuration.test_build_dir, test_subdir, test_name)
57        src_dir = os.path.join(configuration.test_src_root, test_subdir)
58        # This is a bit of a hack to make inline testcases work.
59        makefile = os.path.join(src_dir, "Makefile")
60        if not os.path.isfile(makefile):
61            makefile = os.path.join(build_dir, "Makefile")
62        return [
63            configuration.make_path,
64            "VPATH=" + src_dir,
65            "-C",
66            build_dir,
67            "-I",
68            src_dir,
69            "-I",
70            os.path.join(lldb_test, "make"),
71            "-f",
72            makefile,
73        ]
74
75    def getCmdLine(self, d):
76        """
77        Helper function to return a command line argument string used for the
78        make system.
79        """
80
81        # If d is None or an empty mapping, just return an empty list.
82        if not d:
83            return []
84
85        def setOrAppendVariable(k, v):
86            append_vars = ["CFLAGS", "CFLAGS_EXTRAS", "LD_EXTRAS"]
87            if k in append_vars and k in os.environ:
88                v = os.environ[k] + " " + v
89            return "%s=%s" % (k, v)
90
91        cmdline = [setOrAppendVariable(k, v) for k, v in list(d.items())]
92
93        return cmdline
94
95    def getArchSpec(self, architecture):
96        """
97        Helper function to return the key-value string to specify the architecture
98        used for the make system.
99        """
100        return ["ARCH=" + architecture] if architecture else []
101
102    def getToolchainSpec(self, compiler):
103        """
104        Helper function to return the key-value strings to specify the toolchain
105        used for the make system.
106        """
107        cc = compiler if compiler else None
108        if not cc and configuration.compiler:
109            cc = configuration.compiler
110
111        if not cc:
112            return []
113
114        exe_ext = ""
115        if lldbplatformutil.getHostPlatform() == "windows":
116            exe_ext = ".exe"
117
118        cc = cc.strip()
119        cc_path = pathlib.Path(cc)
120
121        # We can get CC compiler string in the following formats:
122        #  [<tool>] <compiler>    - such as 'xrun clang', 'xrun /usr/bin/clang' & etc
123        #
124        # Where <compiler> could contain the following parts:
125        #   <simple-name>[.<exe-ext>]                           - sucn as 'clang', 'clang.exe' ('clang-cl.exe'?)
126        #   <target-triple>-<simple-name>[.<exe-ext>]           - such as 'armv7-linux-gnueabi-gcc'
127        #   <path>/<simple-name>[.<exe-ext>]                    - such as '/usr/bin/clang', 'c:\path\to\compiler\clang,exe'
128        #   <path>/<target-triple>-<simple-name>[.<exe-ext>]    - such as '/usr/bin/clang', 'c:\path\to\compiler\clang,exe'
129
130        cc_ext = cc_path.suffix
131        # Compiler name without extension
132        cc_name = cc_path.stem.split(" ")[-1]
133
134        # A kind of compiler (canonical name): clang, gcc, cc & etc.
135        cc_type = cc_name
136        # A triple prefix of compiler name: <armv7-none-linux-gnu->gcc
137        cc_prefix = ""
138        if not "clang-cl" in cc_name and not "llvm-gcc" in cc_name:
139            cc_name_parts = cc_name.split("-")
140            cc_type = cc_name_parts[-1]
141            if len(cc_name_parts) > 1:
142                cc_prefix = "-".join(cc_name_parts[:-1]) + "-"
143
144        # A kind of C++ compiler.
145        cxx_types = {
146            "icc": "icpc",
147            "llvm-gcc": "llvm-g++",
148            "gcc": "g++",
149            "cc": "c++",
150            "clang": "clang++",
151        }
152        cxx_type = cxx_types.get(cc_type, cc_type)
153
154        cc_dir = cc_path.parent
155
156        def getToolchainUtil(util_name):
157            return os.path.join(configuration.llvm_tools_dir, util_name + exe_ext)
158
159        cxx = cc_dir / (cc_prefix + cxx_type + cc_ext)
160
161        util_names = {
162            "OBJCOPY": "objcopy",
163            "STRIP": "strip",
164            "ARCHIVER": "ar",
165            "DWP": "dwp",
166        }
167        utils = []
168
169        # Required by API TestBSDArchives.py tests.
170        if not os.getenv("LLVM_AR"):
171            utils.extend(["LLVM_AR=%s" % getToolchainUtil("llvm-ar")])
172
173        if cc_type in ["clang", "cc", "gcc"]:
174            util_paths = {}
175            # Assembly a toolchain side tool cmd based on passed CC.
176            for var, name in util_names.items():
177                # Do not override explicity specified tool from the cmd line.
178                if not os.getenv(var):
179                    util_paths[var] = getToolchainUtil("llvm-" + name)
180                else:
181                    util_paths[var] = os.getenv(var)
182            utils.extend(["AR=%s" % util_paths["ARCHIVER"]])
183
184            # Look for llvm-dwp or gnu dwp
185            if not lldbutil.which(util_paths["DWP"]):
186                util_paths["DWP"] = getToolchainUtil("llvm-dwp")
187            if not lldbutil.which(util_paths["DWP"]):
188                util_paths["DWP"] = lldbutil.which("llvm-dwp")
189            if not util_paths["DWP"]:
190                util_paths["DWP"] = lldbutil.which("dwp")
191                if not util_paths["DWP"]:
192                    del util_paths["DWP"]
193
194            if lldbplatformutil.platformIsDarwin():
195                util_paths["STRIP"] = seven.get_command_output("xcrun -f strip")
196
197            for var, path in util_paths.items():
198                utils.append("%s=%s" % (var, path))
199
200        if lldbplatformutil.platformIsDarwin():
201            utils.extend(["AR=%slibtool" % os.getenv("CROSS_COMPILE", "")])
202
203        return [
204            "CC=%s" % cc,
205            "CC_TYPE=%s" % cc_type,
206            "CXX=%s" % cxx,
207        ] + utils
208
209    def getSDKRootSpec(self):
210        """
211        Helper function to return the key-value string to specify the SDK root
212        used for the make system.
213        """
214        if configuration.sdkroot:
215            return ["SDKROOT={}".format(configuration.sdkroot)]
216        return []
217
218    def getModuleCacheSpec(self):
219        """
220        Helper function to return the key-value string to specify the clang
221        module cache used for the make system.
222        """
223        if configuration.clang_module_cache_dir:
224            return [
225                "CLANG_MODULE_CACHE_DIR={}".format(configuration.clang_module_cache_dir)
226            ]
227        return []
228
229    def getLibCxxArgs(self):
230        if configuration.libcxx_include_dir and configuration.libcxx_library_dir:
231            libcpp_args = [
232                "LIBCPP_INCLUDE_DIR={}".format(configuration.libcxx_include_dir),
233                "LIBCPP_LIBRARY_DIR={}".format(configuration.libcxx_library_dir),
234            ]
235            if configuration.libcxx_include_target_dir:
236                libcpp_args.append(
237                    "LIBCPP_INCLUDE_TARGET_DIR={}".format(
238                        configuration.libcxx_include_target_dir
239                    )
240                )
241            return libcpp_args
242        return []
243
244    def getLLDBObjRoot(self):
245        return ["LLDB_OBJ_ROOT={}".format(configuration.lldb_obj_root)]
246
247    def _getDebugInfoArgs(self, debug_info):
248        if debug_info is None:
249            return []
250        if debug_info == "dwarf":
251            return ["MAKE_DSYM=NO"]
252        if debug_info == "dwo":
253            return ["MAKE_DSYM=NO", "MAKE_DWO=YES"]
254        if debug_info == "gmodules":
255            return ["MAKE_DSYM=NO", "MAKE_GMODULES=YES"]
256        return None
257
258    def getBuildCommand(
259        self,
260        debug_info,
261        architecture=None,
262        compiler=None,
263        dictionary=None,
264        testdir=None,
265        testname=None,
266        make_targets=None,
267    ):
268        debug_info_args = self._getDebugInfoArgs(debug_info)
269        if debug_info_args is None:
270            return None
271        if make_targets is None:
272            make_targets = ["all"]
273        command_parts = [
274            self.getMake(testdir, testname),
275            debug_info_args,
276            make_targets,
277            self.getArchCFlags(architecture),
278            self.getArchSpec(architecture),
279            self.getToolchainSpec(compiler),
280            self.getExtraMakeArgs(),
281            self.getSDKRootSpec(),
282            self.getModuleCacheSpec(),
283            self.getLibCxxArgs(),
284            self.getLLDBObjRoot(),
285            self.getCmdLine(dictionary),
286        ]
287        command = list(itertools.chain(*command_parts))
288
289        return command
290
291    def cleanup(self, dictionary=None):
292        """Perform a platform-specific cleanup after the test."""
293        return True
294