1import os
2import platform
3import subprocess
4import sys
5import itertools
6
7import lldbsuite.test.lldbtest as lldbtest
8import lldbsuite.test.lldbutil as lldbutil
9from lldbsuite.test import configuration
10from lldbsuite.test_event import build_exception
11
12
13class Builder:
14    def getArchitecture(self):
15        """Returns the architecture in effect the test suite is running with."""
16        return configuration.arch if configuration.arch else ""
17
18    def getCompiler(self):
19        """Returns the compiler in effect the test suite is running with."""
20        compiler = configuration.compiler if configuration.compiler else "clang"
21        compiler = lldbutil.which(compiler)
22        return os.path.abspath(compiler)
23
24    def getExtraMakeArgs(self):
25        """
26        Helper function to return extra argumentsfor the make system. This
27        method is meant to be overridden by platform specific builders.
28        """
29        return []
30
31    def getArchCFlags(self, architecture):
32        """Returns the ARCH_CFLAGS for the make system."""
33        return []
34
35    def getMake(self, test_subdir, test_name):
36        """Returns the invocation for GNU make.
37        The first argument is a tuple of the relative path to the testcase
38        and its filename stem."""
39        if platform.system() == "FreeBSD" or platform.system() == "NetBSD":
40            make = "gmake"
41        else:
42            make = "make"
43
44        # Construct the base make invocation.
45        lldb_test = os.environ["LLDB_TEST"]
46        if not (lldb_test and configuration.test_build_dir and test_subdir
47                and test_name and (not os.path.isabs(test_subdir))):
48            raise Exception("Could not derive test directories")
49        build_dir = os.path.join(configuration.test_build_dir, test_subdir,
50                                 test_name)
51        src_dir = os.path.join(configuration.test_src_root, test_subdir)
52        # This is a bit of a hack to make inline testcases work.
53        makefile = os.path.join(src_dir, "Makefile")
54        if not os.path.isfile(makefile):
55            makefile = os.path.join(build_dir, "Makefile")
56        return [
57            make, "VPATH=" + src_dir, "-C", build_dir, "-I", src_dir, "-I",
58            os.path.join(lldb_test, "make"), "-f", makefile
59        ]
60
61    def getCmdLine(self, d):
62        """
63        Helper function to return a command line argument string used for the
64        make system.
65        """
66
67        # If d is None or an empty mapping, just return an empty list.
68        if not d:
69            return []
70
71        def setOrAppendVariable(k, v):
72            append_vars = ["CFLAGS", "CFLAGS_EXTRAS", "LD_EXTRAS"]
73            if k in append_vars and k in os.environ:
74                v = os.environ[k] + " " + v
75            return '%s=%s' % (k, v)
76
77        cmdline = [setOrAppendVariable(k, v) for k, v in list(d.items())]
78
79        return cmdline
80
81    def runBuildCommands(self, commands):
82        try:
83            lldbtest.system(commands)
84        except subprocess.CalledProcessError as called_process_error:
85            # Convert to a build-specific error.
86            # We don't do that in lldbtest.system() since that
87            # is more general purpose.
88            raise build_exception.BuildError(called_process_error)
89
90    def getArchSpec(self, architecture):
91        """
92        Helper function to return the key-value string to specify the architecture
93        used for the make system.
94        """
95        return ["ARCH=" + architecture] if architecture else []
96
97    def getCCSpec(self, compiler):
98        """
99        Helper function to return the key-value string to specify the compiler
100        used for the make system.
101        """
102        cc = compiler if compiler else None
103        if not cc and configuration.compiler:
104            cc = configuration.compiler
105        if cc:
106            return ["CC=\"%s\"" % cc]
107        return []
108
109    def getSDKRootSpec(self):
110        """
111        Helper function to return the key-value string to specify the SDK root
112        used for the make system.
113        """
114        if configuration.sdkroot:
115            return ["SDKROOT={}".format(configuration.sdkroot)]
116        return []
117
118    def getModuleCacheSpec(self):
119        """
120        Helper function to return the key-value string to specify the clang
121        module cache used for the make system.
122        """
123        if configuration.clang_module_cache_dir:
124            return ["CLANG_MODULE_CACHE_DIR={}".format(
125                configuration.clang_module_cache_dir)]
126        return []
127
128    def _getDebugInfoArgs(self, debug_info):
129        if debug_info is None:
130            return []
131        if debug_info == "dwarf":
132            return ["MAKE_DSYM=NO"]
133        if debug_info == "dwo":
134            return ["MAKE_DSYM=NO", "MAKE_DWO=YES"]
135        if debug_info == "gmodules":
136            return ["MAKE_DSYM=NO", "MAKE_GMODULES=YES"]
137        return None
138
139    def build(self, debug_info, architecture=None, compiler=None,
140            dictionary=None, testdir=None, testname=None):
141        debug_info_args = self._getDebugInfoArgs(debug_info)
142        if debug_info_args is None:
143            return False
144
145        command_parts = [
146            self.getMake(testdir, testname), debug_info_args, ["all"],
147            self.getArchCFlags(architecture), self.getArchSpec(architecture),
148            self.getCCSpec(compiler), self.getExtraMakeArgs(),
149            self.getSDKRootSpec(), self.getModuleCacheSpec(),
150            self.getCmdLine(dictionary)]
151        command = list(itertools.chain(*command_parts))
152
153        self.runBuildCommands([command])
154        return True
155
156    def cleanup(self, dictionary=None):
157        """Perform a platform-specific cleanup after the test."""
158        return True
159