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