xref: /llvm-project/lldb/packages/Python/lldbsuite/test/builders/builder.py (revision aea06684992873f70c5834e2f455f913e5b8d671)
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        exe_ext = ""
114        if lldbplatformutil.getHostPlatform() == "windows":
115            exe_ext = ".exe"
116
117        cc = cc.strip()
118        cc_path = pathlib.Path(cc)
119
120        # We can get CC compiler string in the following formats:
121        #  [<tool>] <compiler>    - such as 'xrun clang', 'xrun /usr/bin/clang' & etc
122        #
123        # Where <compiler> could contain the following parts:
124        #   <simple-name>[.<exe-ext>]                           - sucn as 'clang', 'clang.exe' ('clang-cl.exe'?)
125        #   <target-triple>-<simple-name>[.<exe-ext>]           - such as 'armv7-linux-gnueabi-gcc'
126        #   <path>/<simple-name>[.<exe-ext>]                    - such as '/usr/bin/clang', 'c:\path\to\compiler\clang,exe'
127        #   <path>/<target-triple>-<simple-name>[.<exe-ext>]    - such as '/usr/bin/clang', 'c:\path\to\compiler\clang,exe'
128
129        cc_ext = cc_path.suffix
130        # Compiler name without extension
131        cc_name = cc_path.stem.split(" ")[-1]
132
133        # A kind of compiler (canonical name): clang, gcc, cc & etc.
134        cc_type = cc_name
135        # A triple prefix of compiler name: <armv7-none-linux-gnu->gcc
136        cc_prefix = ""
137        if not "clang-cl" in cc_name and not "llvm-gcc" in cc_name:
138            cc_name_parts = cc_name.split("-")
139            cc_type = cc_name_parts[-1]
140            if len(cc_name_parts) > 1:
141                cc_prefix = "-".join(cc_name_parts[:-1]) + "-"
142
143        # A kind of C++ compiler.
144        cxx_types = {
145            "icc": "icpc",
146            "llvm-gcc": "llvm-g++",
147            "gcc": "g++",
148            "cc": "c++",
149            "clang": "clang++",
150        }
151        cxx_type = cxx_types.get(cc_type, cc_type)
152
153        cc_dir = cc_path.parent
154
155        def getToolchainUtil(util_name):
156            return os.path.join(configuration.llvm_tools_dir, util_name + exe_ext)
157
158        cxx = cc_dir / (cc_prefix + cxx_type + cc_ext)
159
160        util_names = {
161            "OBJCOPY": "objcopy",
162            "STRIP": "strip",
163            "ARCHIVER": "ar",
164            "DWP": "dwp",
165        }
166        utils = []
167
168        # Required by API TestBSDArchives.py tests.
169        if not os.getenv("LLVM_AR"):
170            utils.extend(["LLVM_AR=%s" % getToolchainUtil("llvm-ar")])
171
172        if not lldbplatformutil.platformIsDarwin():
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(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                for var, path in util_paths.items():
195                    utils.append("%s=%s" % (var, path))
196        else:
197            utils.extend(["AR=%slibtool" % os.getenv("CROSS_COMPILE", "")])
198
199        return [
200            "CC=%s" % cc,
201            "CC_TYPE=%s" % cc_type,
202            "CXX=%s" % cxx,
203        ] + utils
204
205    def getSDKRootSpec(self):
206        """
207        Helper function to return the key-value string to specify the SDK root
208        used for the make system.
209        """
210        if configuration.sdkroot:
211            return ["SDKROOT={}".format(configuration.sdkroot)]
212        return []
213
214    def getModuleCacheSpec(self):
215        """
216        Helper function to return the key-value string to specify the clang
217        module cache used for the make system.
218        """
219        if configuration.clang_module_cache_dir:
220            return [
221                "CLANG_MODULE_CACHE_DIR={}".format(configuration.clang_module_cache_dir)
222            ]
223        return []
224
225    def getLibCxxArgs(self):
226        if configuration.libcxx_include_dir and configuration.libcxx_library_dir:
227            libcpp_args = [
228                "LIBCPP_INCLUDE_DIR={}".format(configuration.libcxx_include_dir),
229                "LIBCPP_LIBRARY_DIR={}".format(configuration.libcxx_library_dir),
230            ]
231            if configuration.libcxx_include_target_dir:
232                libcpp_args.append(
233                    "LIBCPP_INCLUDE_TARGET_DIR={}".format(
234                        configuration.libcxx_include_target_dir
235                    )
236                )
237            return libcpp_args
238        return []
239
240    def getLLDBObjRoot(self):
241        return ["LLDB_OBJ_ROOT={}".format(configuration.lldb_obj_root)]
242
243    def _getDebugInfoArgs(self, debug_info):
244        if debug_info is None:
245            return []
246        if debug_info == "dwarf":
247            return ["MAKE_DSYM=NO"]
248        if debug_info == "dwo":
249            return ["MAKE_DSYM=NO", "MAKE_DWO=YES"]
250        if debug_info == "gmodules":
251            return ["MAKE_DSYM=NO", "MAKE_GMODULES=YES"]
252        return None
253
254    def getBuildCommand(
255        self,
256        debug_info,
257        architecture=None,
258        compiler=None,
259        dictionary=None,
260        testdir=None,
261        testname=None,
262        make_targets=None,
263    ):
264        debug_info_args = self._getDebugInfoArgs(debug_info)
265        if debug_info_args is None:
266            return None
267        if make_targets is None:
268            make_targets = ["all"]
269        command_parts = [
270            self.getMake(testdir, testname),
271            debug_info_args,
272            make_targets,
273            self.getArchCFlags(architecture),
274            self.getArchSpec(architecture),
275            self.getToolchainSpec(compiler),
276            self.getExtraMakeArgs(),
277            self.getSDKRootSpec(),
278            self.getModuleCacheSpec(),
279            self.getLibCxxArgs(),
280            self.getLLDBObjRoot(),
281            self.getCmdLine(dictionary),
282        ]
283        command = list(itertools.chain(*command_parts))
284
285        return command
286
287    def cleanup(self, dictionary=None):
288        """Perform a platform-specific cleanup after the test."""
289        return True
290