1import os
2import platform
3import subprocess
4import sys
5
6import lldbsuite.test.lldbtest as lldbtest
7import lldbsuite.test.lldbutil as lldbutil
8from lldbsuite.test import configuration
9from lldbsuite.test_event import build_exception
10
11
12class Builder:
13    def getArchitecture(self):
14        """Returns the architecture in effect the test suite is running with."""
15        return configuration.arch if configuration.arch else ""
16
17    def getCompiler(self):
18        """Returns the compiler in effect the test suite is running with."""
19        compiler = configuration.compiler if configuration.compiler else "clang"
20        compiler = lldbutil.which(compiler)
21        return os.path.abspath(compiler)
22
23    def getExtraMakeArgs(self):
24        """
25        Helper function to return extra argumentsfor the make system. This
26        method is meant to be overridden by platform specific builders.
27        """
28        return ""
29
30    def getArchCFlags(self, architecture):
31        """Returns the ARCH_CFLAGS for the make system."""
32        return ""
33
34    def getMake(self, test_subdir, test_name):
35        """Returns the invocation for GNU make.
36        The first argument is a tuple of the relative path to the testcase
37        and its filename stem."""
38        if platform.system() == "FreeBSD" or platform.system() == "NetBSD":
39            make = "gmake"
40        else:
41            make = "make"
42
43        # Construct the base make invocation.
44        lldb_test = os.environ["LLDB_TEST"]
45        if not (lldb_test and configuration.test_build_dir and test_subdir
46                and test_name and (not os.path.isabs(test_subdir))):
47            raise Exception("Could not derive test directories")
48        build_dir = os.path.join(configuration.test_build_dir, test_subdir,
49                                 test_name)
50        src_dir = os.path.join(configuration.test_src_root, test_subdir)
51        # This is a bit of a hack to make inline testcases work.
52        makefile = os.path.join(src_dir, "Makefile")
53        if not os.path.isfile(makefile):
54            makefile = os.path.join(build_dir, "Makefile")
55        return [
56            make, "VPATH=" + src_dir, "-C", build_dir, "-I", src_dir, "-I",
57            os.path.join(lldb_test, "make"), "-f", makefile
58        ]
59
60    def getCmdLine(self, d):
61        """
62        Helper function to return a properly formatted command line argument(s)
63        string used for the make system.
64        """
65
66        # If d is None or an empty mapping, just return an empty string.
67        if not d:
68            return ""
69        pattern = '%s="%s"' if "win32" in sys.platform else "%s='%s'"
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 pattern % (k, v)
76
77        cmdline = " ".join(
78            [setOrAppendVariable(k, v) for k, v in list(d.items())])
79
80        return cmdline
81
82    def runBuildCommands(self, commands, sender):
83        try:
84            lldbtest.system(commands, sender=sender)
85        except subprocess.CalledProcessError as called_process_error:
86            # Convert to a build-specific error.
87            # We don't do that in lldbtest.system() since that
88            # is more general purpose.
89            raise build_exception.BuildError(called_process_error)
90
91    def getArchSpec(self, architecture):
92        """
93        Helper function to return the key-value string to specify the architecture
94        used for the make system.
95        """
96        return ("ARCH=" + architecture) if architecture else ""
97
98    def getCCSpec(self, compiler):
99        """
100        Helper function to return the key-value string to specify the compiler
101        used for the make system.
102        """
103        cc = compiler if compiler else None
104        if not cc and configuration.compiler:
105            cc = configuration.compiler
106        if cc:
107            return "CC=\"%s\"" % cc
108        else:
109            return ""
110
111    def getSDKRootSpec(self):
112        """
113        Helper function to return the key-value string to specify the SDK root
114        used for the make system.
115        """
116        if configuration.sdkroot:
117            return "SDKROOT={}".format(configuration.sdkroot)
118        return ""
119
120    def getModuleCacheSpec(self):
121        """
122        Helper function to return the key-value string to specify the clang
123        module cache used for the make system.
124        """
125        if configuration.clang_module_cache_dir:
126            return "CLANG_MODULE_CACHE_DIR={}".format(
127                configuration.clang_module_cache_dir)
128        return ""
129
130    def buildDefault(self,
131                     sender=None,
132                     architecture=None,
133                     compiler=None,
134                     dictionary=None,
135                     testdir=None,
136                     testname=None):
137        """Build the binaries the default way."""
138        commands = []
139        commands.append(
140            self.getMake(testdir, testname) + [
141                "all",
142                self.getArchCFlags(architecture),
143                self.getArchSpec(architecture),
144                self.getCCSpec(compiler),
145                self.getExtraMakeArgs(),
146                self.getSDKRootSpec(),
147                self.getModuleCacheSpec(),
148                self.getCmdLine(dictionary)
149            ])
150
151        self.runBuildCommands(commands, sender=sender)
152
153        # True signifies that we can handle building default.
154        return True
155
156    def buildDwarf(self,
157                   sender=None,
158                   architecture=None,
159                   compiler=None,
160                   dictionary=None,
161                   testdir=None,
162                   testname=None):
163        """Build the binaries with dwarf debug info."""
164        commands = []
165        commands.append(
166            self.getMake(testdir, testname) + [
167                "MAKE_DSYM=NO",
168                self.getArchCFlags(architecture),
169                self.getArchSpec(architecture),
170                self.getCCSpec(compiler),
171                self.getExtraMakeArgs(),
172                self.getSDKRootSpec(),
173                self.getModuleCacheSpec(),
174                self.getCmdLine(dictionary)
175            ])
176
177        self.runBuildCommands(commands, sender=sender)
178        # True signifies that we can handle building dwarf.
179        return True
180
181    def buildDwo(self,
182                 sender=None,
183                 architecture=None,
184                 compiler=None,
185                 dictionary=None,
186                 testdir=None,
187                 testname=None):
188        """Build the binaries with dwarf debug info."""
189        commands = []
190        commands.append(
191            self.getMake(testdir, testname) + [
192                "MAKE_DSYM=NO", "MAKE_DWO=YES",
193                self.getArchCFlags(architecture),
194                self.getArchSpec(architecture),
195                self.getCCSpec(compiler),
196                self.getExtraMakeArgs(),
197                self.getSDKRootSpec(),
198                self.getModuleCacheSpec(),
199                self.getCmdLine(dictionary)
200            ])
201
202        self.runBuildCommands(commands, sender=sender)
203        # True signifies that we can handle building dwo.
204        return True
205
206    def buildGModules(self,
207                      sender=None,
208                      architecture=None,
209                      compiler=None,
210                      dictionary=None,
211                      testdir=None,
212                      testname=None):
213        """Build the binaries with dwarf debug info."""
214        commands = []
215        commands.append(
216            self.getMake(testdir, testname) + [
217                "MAKE_DSYM=NO", "MAKE_GMODULES=YES",
218                self.getArchCFlags(architecture),
219                self.getArchSpec(architecture),
220                self.getCCSpec(compiler),
221                self.getExtraMakeArgs(),
222                self.getSDKRootSpec(),
223                self.getModuleCacheSpec(),
224                self.getCmdLine(dictionary)
225            ])
226
227        self.runBuildCommands(commands, sender=sender)
228        # True signifies that we can handle building with gmodules.
229        return True
230
231    def buildDsym(self,
232                  sender=None,
233                  architecture=None,
234                  compiler=None,
235                  dictionary=None,
236                  testdir=None,
237                  testname=None):
238        # False signifies that we cannot handle building with dSYM.
239        return False
240
241    def cleanup(self, sender=None, dictionary=None):
242        """Perform a platform-specific cleanup after the test."""
243        return True
244