xref: /llvm-project/lldb/test/Shell/helper/build.py (revision 22ea97d7bfd65abf68a68b13bf96ad69be23df54)
1#!/usr/bin/env python
2
3import argparse
4import os
5import platform
6import shutil
7import signal
8import subprocess
9import sys
10
11if sys.platform == "win32":
12    # This module was renamed in Python 3.  Make sure to import it using a
13    # consistent name regardless of python version.
14    try:
15        import winreg
16    except:
17        import _winreg as winreg
18
19if __name__ != "__main__":
20    raise RuntimeError("Do not import this script, run it instead")
21
22
23parser = argparse.ArgumentParser(description="LLDB compilation wrapper")
24parser.add_argument(
25    "--arch",
26    metavar="arch",
27    dest="arch",
28    required=True,
29    default="host",
30    choices=["32", "64", "host"],
31    help="Specify the architecture to target.",
32)
33
34parser.add_argument(
35    "--compiler",
36    metavar="compiler",
37    dest="compiler",
38    required=True,
39    help="Path to a compiler executable, or one of the values [any, msvc, clang-cl, gcc, clang]",
40)
41
42parser.add_argument(
43    "--libs-dir",
44    metavar="directory",
45    dest="libs_dir",
46    required=False,
47    action="append",
48    help="If specified, a path to linked libraries to be passed via -L",
49)
50
51parser.add_argument(
52    "--tools-dir",
53    metavar="directory",
54    dest="tools_dir",
55    required=False,
56    action="append",
57    help="If specified, a path to search in addition to PATH when --compiler is not an exact path",
58)
59
60parser.add_argument(
61    "--objc-gnustep-dir",
62    metavar="directory",
63    dest="objc_gnustep_dir",
64    required=False,
65    help="If specified, a path to GNUstep libobjc2 runtime for use on Windows and Linux",
66)
67
68parser.add_argument(
69    "--objc-gnustep",
70    dest="objc_gnustep",
71    action="store_true",
72    default=False,
73    help="Include and link GNUstep libobjc2 (Windows and Linux only)",
74)
75
76parser.add_argument(
77    "--sysroot",
78    metavar="directory",
79    dest="sysroot",
80    required=False,
81    help="If specified, a sysroot to be passed via --sysroot",
82)
83
84if sys.platform == "darwin":
85    parser.add_argument(
86        "--apple-sdk",
87        metavar="apple_sdk",
88        dest="apple_sdk",
89        default="macosx",
90        help="Specify the name of the Apple SDK (macosx, macosx.internal, iphoneos, iphoneos.internal, or path to SDK) and use the appropriate tools from that SDK's toolchain.",
91    )
92
93parser.add_argument(
94    "--output",
95    "-o",
96    dest="output",
97    metavar="file",
98    required=False,
99    default="",
100    help="Path to output file",
101)
102
103parser.add_argument(
104    "--outdir",
105    "-d",
106    dest="outdir",
107    metavar="directory",
108    required=False,
109    help="Directory for output files",
110)
111
112parser.add_argument(
113    "--nodefaultlib",
114    dest="nodefaultlib",
115    action="store_true",
116    default=False,
117    help="When specified, the resulting image should not link against system libraries or include system headers.  Useful when writing cross-targeting tests.",
118)
119
120parser.add_argument(
121    "--opt",
122    dest="opt",
123    default="none",
124    choices=["none", "basic", "lto"],
125    help="Optimization level",
126)
127
128parser.add_argument(
129    "--mode",
130    dest="mode",
131    default="compile-and-link",
132    choices=["compile", "link", "compile-and-link"],
133    help="Specifies whether to compile, link, or both",
134)
135
136parser.add_argument(
137    "--noclean",
138    dest="clean",
139    action="store_false",
140    default=True,
141    help="Dont clean output file before building",
142)
143
144parser.add_argument(
145    "--verbose",
146    dest="verbose",
147    action="store_true",
148    default=False,
149    help="Print verbose output",
150)
151
152parser.add_argument(
153    "-n",
154    "--dry-run",
155    dest="dry",
156    action="store_true",
157    default=False,
158    help="Print the commands that would run, but dont actually run them",
159)
160
161parser.add_argument(
162    "inputs",
163    metavar="file",
164    nargs="+",
165    help="Source file(s) to compile / object file(s) to link",
166)
167
168parser.add_argument(
169    "--std",
170    metavar="std",
171    dest="std",
172    required=False,
173    help="Specify the C/C++ standard.",
174)
175
176
177args = parser.parse_args(args=sys.argv[1:])
178
179
180def to_string(b):
181    """Return the parameter as type 'str', possibly encoding it.
182
183    In Python2, the 'str' type is the same as 'bytes'. In Python3, the
184    'str' type is (essentially) Python2's 'unicode' type, and 'bytes' is
185    distinct.
186
187    This function is copied from llvm/utils/lit/lit/util.py
188    """
189    if isinstance(b, str):
190        # In Python2, this branch is taken for types 'str' and 'bytes'.
191        # In Python3, this branch is taken only for 'str'.
192        return b
193    if isinstance(b, bytes):
194        # In Python2, this branch is never taken ('bytes' is handled as 'str').
195        # In Python3, this is true only for 'bytes'.
196        try:
197            return b.decode("utf-8")
198        except UnicodeDecodeError:
199            # If the value is not valid Unicode, return the default
200            # repr-line encoding.
201            return str(b)
202
203    # By this point, here's what we *don't* have:
204    #
205    #  - In Python2:
206    #    - 'str' or 'bytes' (1st branch above)
207    #  - In Python3:
208    #    - 'str' (1st branch above)
209    #    - 'bytes' (2nd branch above)
210    #
211    # The last type we might expect is the Python2 'unicode' type. There is no
212    # 'unicode' type in Python3 (all the Python3 cases were already handled). In
213    # order to get a 'str' object, we need to encode the 'unicode' object.
214    try:
215        return b.encode("utf-8")
216    except AttributeError:
217        raise TypeError("not sure how to convert %s to %s" % (type(b), str))
218
219
220def format_text(lines, indent_0, indent_n):
221    result = " " * indent_0 + lines[0]
222    for next in lines[1:]:
223        result = result + "\n{0}{1}".format(" " * indent_n, next)
224    return result
225
226
227def print_environment(env):
228    if env is None:
229        print("    Inherited")
230        return
231    for e in env:
232        value = env[e]
233        lines = value.split(os.pathsep)
234        formatted_value = format_text(lines, 0, 7 + len(e))
235        print("    {0} = {1}".format(e, formatted_value))
236
237
238def find_executable(binary_name, search_paths):
239    # shutil.which will ignore PATH if given a path argument, we want to include it.
240    search_paths.append(os.environ.get("PATH", ""))
241    search_path = os.pathsep.join(search_paths)
242    binary_path = shutil.which(binary_name, path=search_path)
243    if binary_path is not None:
244        # So for example, we get '/bin/gcc' instead of '/usr/../bin/gcc'.
245        binary_path = os.path.normpath(binary_path)
246    return binary_path
247
248
249def find_toolchain(compiler, tools_dir):
250    if compiler == "msvc":
251        return ("msvc", find_executable("cl", tools_dir))
252    if compiler == "clang-cl":
253        return ("clang-cl", find_executable("clang-cl", tools_dir))
254    if compiler == "gcc":
255        return ("gcc", find_executable("g++", tools_dir))
256    if compiler == "clang":
257        return ("clang", find_executable("clang++", tools_dir))
258    if compiler == "any":
259        priorities = []
260        if sys.platform == "win32":
261            priorities = ["clang-cl", "msvc", "clang", "gcc"]
262        else:
263            priorities = ["clang", "gcc", "clang-cl"]
264        for toolchain in priorities:
265            (type, dir) = find_toolchain(toolchain, tools_dir)
266            if type and dir:
267                return (type, dir)
268        # Could not find any toolchain.
269        return (None, None)
270
271    # From here on, assume that |compiler| is a path to a file.
272    file = os.path.basename(compiler)
273    name, ext = os.path.splitext(file)
274    if file.lower() == "cl.exe":
275        return ("msvc", compiler)
276    if name == "clang-cl":
277        return ("clang-cl", compiler)
278    if name.startswith("clang"):
279        return ("clang", compiler)
280    if name.startswith("gcc") or name.startswith("g++"):
281        return ("gcc", compiler)
282    if name == "cc" or name == "c++":
283        return ("generic", compiler)
284    return ("unknown", compiler)
285
286
287class Builder(object):
288    def __init__(self, toolchain_type, args, obj_ext):
289        self.toolchain_type = toolchain_type
290        self.inputs = args.inputs
291        self.arch = args.arch
292        self.opt = args.opt
293        self.outdir = args.outdir
294        self.compiler = args.compiler
295        self.clean = args.clean
296        self.output = args.output
297        self.mode = args.mode
298        self.nodefaultlib = args.nodefaultlib
299        self.verbose = args.verbose
300        self.obj_ext = obj_ext
301        self.lib_paths = args.libs_dir
302        self.std = args.std
303        assert (
304            not args.objc_gnustep or args.objc_gnustep_dir
305        ), "--objc-gnustep specified without path to libobjc2"
306        self.objc_gnustep_inc = (
307            os.path.join(args.objc_gnustep_dir, "include")
308            if args.objc_gnustep_dir
309            else None
310        )
311        self.objc_gnustep_lib = (
312            os.path.join(args.objc_gnustep_dir, "lib")
313            if args.objc_gnustep_dir
314            else None
315        )
316        self.sysroot = args.sysroot
317
318    def _exe_file_name(self):
319        assert self.mode != "compile"
320        return self.output
321
322    def _output_name(self, input, extension, with_executable=False):
323        basename = os.path.splitext(os.path.basename(input))[0] + extension
324        if with_executable:
325            exe_basename = os.path.basename(self._exe_file_name())
326            basename = exe_basename + "-" + basename
327
328        output = os.path.join(self.outdir, basename)
329        return os.path.normpath(output)
330
331    def _obj_file_names(self):
332        if self.mode == "link":
333            return self.inputs
334
335        if self.mode == "compile-and-link":
336            # Object file names should factor in both the input file (source)
337            # name and output file (executable) name, to ensure that two tests
338            # which share a common source file don't race to write the same
339            # object file.
340            return [self._output_name(x, self.obj_ext, True) for x in self.inputs]
341
342        if self.mode == "compile" and self.output:
343            return [self.output]
344
345        return [self._output_name(x, self.obj_ext) for x in self.inputs]
346
347    def build_commands(self):
348        commands = []
349        if self.mode == "compile" or self.mode == "compile-and-link":
350            for input, output in zip(self.inputs, self._obj_file_names()):
351                commands.append(self._get_compilation_command(input, output))
352        if self.mode == "link" or self.mode == "compile-and-link":
353            commands.append(self._get_link_command())
354        return commands
355
356
357class MsvcBuilder(Builder):
358    def __init__(self, toolchain_type, args):
359        Builder.__init__(self, toolchain_type, args, ".obj")
360
361        if platform.uname().machine.lower() == "arm64":
362            self.msvc_arch_str = "arm" if self.arch == "32" else "arm64"
363        else:
364            self.msvc_arch_str = "x86" if self.arch == "32" else "x64"
365
366        if toolchain_type == "msvc":
367            # Make sure we're using the appropriate toolchain for the desired
368            # target type.
369            compiler_parent_dir = os.path.dirname(self.compiler)
370            selected_target_version = os.path.basename(compiler_parent_dir)
371            if selected_target_version != self.msvc_arch_str:
372                host_dir = os.path.dirname(compiler_parent_dir)
373                self.compiler = os.path.join(host_dir, self.msvc_arch_str, "cl.exe")
374                if self.verbose:
375                    print(
376                        'Using alternate compiler "{0}" to match selected target.'.format(
377                            self.compiler
378                        )
379                    )
380
381        if self.mode == "link" or self.mode == "compile-and-link":
382            self.linker = (
383                self._find_linker("link")
384                if toolchain_type == "msvc"
385                else self._find_linker("lld-link", args.tools_dir)
386            )
387            if not self.linker:
388                raise ValueError("Unable to find an appropriate linker.")
389
390        self.compile_env, self.link_env = self._get_visual_studio_environment()
391
392    def _find_linker(self, name, search_paths=[]):
393        compiler_dir = os.path.dirname(self.compiler)
394        linker_path = find_executable(name, [compiler_dir] + search_paths)
395        if linker_path is None:
396            raise ValueError("Could not find '{}'".format(name))
397        return linker_path
398
399    def _get_vc_install_dir(self):
400        dir = os.getenv("VCINSTALLDIR", None)
401        if dir:
402            if self.verbose:
403                print("Using %VCINSTALLDIR% {}".format(dir))
404            return dir
405
406        dir = os.getenv("VSINSTALLDIR", None)
407        if dir:
408            if self.verbose:
409                print("Using %VSINSTALLDIR% {}".format(dir))
410            return os.path.join(dir, "VC")
411
412        dir = os.getenv("VS2019INSTALLDIR", None)
413        if dir:
414            if self.verbose:
415                print("Using %VS2019INSTALLDIR% {}".format(dir))
416            return os.path.join(dir, "VC")
417
418        dir = os.getenv("VS2017INSTALLDIR", None)
419        if dir:
420            if self.verbose:
421                print("Using %VS2017INSTALLDIR% {}".format(dir))
422            return os.path.join(dir, "VC")
423
424        dir = os.getenv("VS2015INSTALLDIR", None)
425        if dir:
426            if self.verbose:
427                print("Using %VS2015INSTALLDIR% {}".format(dir))
428            return os.path.join(dir, "VC")
429        return None
430
431    def _get_vctools_version(self):
432        ver = os.getenv("VCToolsVersion", None)
433        if ver:
434            if self.verbose:
435                print("Using %VCToolsVersion% {}".format(ver))
436            return ver
437
438        vcinstalldir = self._get_vc_install_dir()
439        vcinstalldir = os.path.join(vcinstalldir, "Tools", "MSVC")
440        subdirs = next(os.walk(vcinstalldir))[1]
441        if not subdirs:
442            return None
443
444        from packaging import version
445
446        subdirs.sort(key=lambda x: version.parse(x))
447
448        if self.verbose:
449            full_path = os.path.join(vcinstalldir, subdirs[-1])
450            print(
451                "Using VC tools version directory {0} found by directory walk.".format(
452                    full_path
453                )
454            )
455        return subdirs[-1]
456
457    def _get_vctools_install_dir(self):
458        dir = os.getenv("VCToolsInstallDir", None)
459        if dir:
460            if self.verbose:
461                print("Using %VCToolsInstallDir% {}".format(dir))
462            return dir
463
464        vcinstalldir = self._get_vc_install_dir()
465        if not vcinstalldir:
466            return None
467        vctoolsver = self._get_vctools_version()
468        if not vctoolsver:
469            return None
470        result = os.path.join(vcinstalldir, "Tools", "MSVC", vctoolsver)
471        if not os.path.exists(result):
472            return None
473        if self.verbose:
474            print(
475                "Using VC tools install dir {} found by directory walk".format(result)
476            )
477        return result
478
479    def _find_windows_sdk_in_registry_view(self, view):
480        products_key = None
481        roots_key = None
482        installed_options_keys = []
483        try:
484            sam = view | winreg.KEY_READ
485            products_key = winreg.OpenKey(
486                winreg.HKEY_LOCAL_MACHINE,
487                r"Software\Microsoft\Windows Kits\Installed Products",
488                0,
489                sam,
490            )
491
492            # This is the GUID for the desktop component.  If this is present
493            # then the components required for the Desktop SDK are installed.
494            # If not it will throw an exception.
495            winreg.QueryValueEx(products_key, "{5A3D81EC-D870-9ECF-D997-24BDA6644752}")
496
497            roots_key = winreg.OpenKey(
498                winreg.HKEY_LOCAL_MACHINE,
499                r"Software\Microsoft\Windows Kits\Installed Roots",
500                0,
501                sam,
502            )
503            root_dir = winreg.QueryValueEx(roots_key, "KitsRoot10")
504            root_dir = to_string(root_dir[0])
505            sdk_versions = []
506            index = 0
507            while True:
508                # Installed SDK versions are stored as sub-keys of the
509                # 'Installed Roots' key.  Find all of their names, then sort
510                # them by version
511                try:
512                    ver_key = winreg.EnumKey(roots_key, index)
513                    sdk_versions.append(ver_key)
514                    index = index + 1
515                except WindowsError:
516                    break
517            if not sdk_versions:
518                return (None, None)
519
520            from packaging import version
521
522            sdk_versions.sort(key=lambda x: version.parse(x), reverse=True)
523            option_value_name = "OptionId.DesktopCPP" + self.msvc_arch_str
524            for v in sdk_versions:
525                try:
526                    version_subkey = v + r"\Installed Options"
527                    key = winreg.OpenKey(roots_key, version_subkey)
528                    installed_options_keys.append(key)
529                    (value, value_type) = winreg.QueryValueEx(key, option_value_name)
530                    if value == 1:
531                        # The proper architecture is installed.  Return the
532                        # associated paths.
533                        if self.verbose:
534                            print(
535                                "Found Installed Windows SDK v{0} at {1}".format(
536                                    v, root_dir
537                                )
538                            )
539                        return (root_dir, v)
540                except:
541                    continue
542        except:
543            return (None, None)
544        finally:
545            del products_key
546            del roots_key
547            for k in installed_options_keys:
548                del k
549        return (None, None)
550
551    def _find_windows_sdk_in_registry(self):
552        # This could be a clang-cl cross-compile.  If so, there's no registry
553        # so just exit.
554        if sys.platform != "win32":
555            return (None, None)
556        if self.verbose:
557            print("Looking for Windows SDK in 64-bit registry.")
558        dir, ver = self._find_windows_sdk_in_registry_view(winreg.KEY_WOW64_64KEY)
559        if not dir or not ver:
560            if self.verbose:
561                print("Looking for Windows SDK in 32-bit registry.")
562            dir, ver = self._find_windows_sdk_in_registry_view(winreg.KEY_WOW64_32KEY)
563
564        return (dir, ver)
565
566    def _get_winsdk_dir(self):
567        # If a Windows SDK is specified in the environment, use that.  Otherwise
568        # try to find one in the Windows registry.
569        dir = os.getenv("WindowsSdkDir", None)
570        if not dir or not os.path.exists(dir):
571            return self._find_windows_sdk_in_registry()
572        ver = os.getenv("WindowsSDKLibVersion", None)
573        if not ver:
574            return self._find_windows_sdk_in_registry()
575
576        ver = ver.rstrip("\\")
577        if self.verbose:
578            print("Using %WindowsSdkDir% {}".format(dir))
579            print("Using %WindowsSDKLibVersion% {}".format(ver))
580        return (dir, ver)
581
582    def _get_msvc_native_toolchain_dir(self):
583        assert self.toolchain_type == "msvc"
584        compiler_dir = os.path.dirname(self.compiler)
585        target_dir = os.path.dirname(compiler_dir)
586        host_name = os.path.basename(target_dir)
587        host_name = host_name[4:].lower()
588        return os.path.join(target_dir, host_name)
589
590    def _get_visual_studio_environment(self):
591        vctools = self._get_vctools_install_dir()
592        winsdk, winsdkver = self._get_winsdk_dir()
593
594        if not vctools and self.verbose:
595            print("Unable to find VC tools installation directory.")
596        if (not winsdk or not winsdkver) and self.verbose:
597            print("Unable to find Windows SDK directory.")
598
599        vcincludes = []
600        vclibs = []
601        sdkincludes = []
602        sdklibs = []
603        if vctools is not None:
604            includes = [["ATLMFC", "include"], ["include"]]
605            libs = [["ATLMFC", "lib"], ["lib"]]
606            vcincludes = [os.path.join(vctools, *y) for y in includes]
607            vclibs = [os.path.join(vctools, *y) for y in libs]
608        if winsdk is not None:
609            includes = [
610                ["include", winsdkver, "ucrt"],
611                ["include", winsdkver, "shared"],
612                ["include", winsdkver, "um"],
613                ["include", winsdkver, "winrt"],
614                ["include", winsdkver, "cppwinrt"],
615            ]
616            libs = [["lib", winsdkver, "ucrt"], ["lib", winsdkver, "um"]]
617            sdkincludes = [os.path.join(winsdk, *y) for y in includes]
618            sdklibs = [os.path.join(winsdk, *y) for y in libs]
619
620        includes = vcincludes + sdkincludes
621        libs = vclibs + sdklibs
622        libs = [os.path.join(x, self.msvc_arch_str) for x in libs]
623        compileenv = None
624        linkenv = None
625        defaultenv = {}
626        if sys.platform == "win32":
627            defaultenv = {
628                x: os.environ[x] for x in ["SystemDrive", "SystemRoot", "TMP", "TEMP"]
629            }
630            # The directory to mspdbcore.dll needs to be in PATH, but this is
631            # always in the native toolchain path, not the cross-toolchain
632            # path.  So, for example, if we're using HostX64\x86 then we need
633            # to add HostX64\x64 to the path, and if we're using HostX86\x64
634            # then we need to add HostX86\x86 to the path.
635            if self.toolchain_type == "msvc":
636                defaultenv["PATH"] = self._get_msvc_native_toolchain_dir()
637
638        if includes:
639            compileenv = {}
640            compileenv["INCLUDE"] = os.pathsep.join(includes)
641            compileenv.update(defaultenv)
642        if libs:
643            linkenv = {}
644            linkenv["LIB"] = os.pathsep.join(libs)
645            linkenv.update(defaultenv)
646        return (compileenv, linkenv)
647
648    def _ilk_file_names(self):
649        if self.mode == "link":
650            return []
651
652        return [self._output_name(x, ".ilk") for x in self.inputs]
653
654    def _pdb_file_name(self):
655        if self.mode == "compile":
656            return None
657        return os.path.splitext(self.output)[0] + ".pdb"
658
659    def _get_compilation_command(self, source, obj):
660        args = []
661
662        args.append(self.compiler)
663        if self.toolchain_type == "clang-cl":
664            args.append("-m" + self.arch)
665
666        if self.opt == "none":
667            args.append("/Od")
668        elif self.opt == "basic":
669            args.append("/O2")
670        elif self.opt == "lto":
671            if self.toolchain_type == "msvc":
672                args.append("/GL")
673                args.append("/Gw")
674            else:
675                args.append("-flto=thin")
676        if self.nodefaultlib:
677            args.append("/GS-")
678            args.append("/GR-")
679        args.append("/Z7")
680        if self.toolchain_type == "clang-cl":
681            args.append("-Xclang")
682            args.append("-fkeep-static-consts")
683            args.append("-fms-compatibility-version=19")
684        args.append("/c")
685
686        args.append("/Fo" + obj)
687        if self.toolchain_type == "clang-cl":
688            args.append("--")
689        args.append(source)
690
691        if self.std:
692            args.append("/std:" + self.std)
693
694        return ("compiling", [source], obj, self.compile_env, args)
695
696    def _get_link_command(self):
697        args = []
698        args.append(self.linker)
699        args.append("/DEBUG:FULL")
700        args.append("/INCREMENTAL:NO")
701        if self.nodefaultlib:
702            args.append("/nodefaultlib")
703            args.append("/entry:main")
704        args.append("/PDB:" + self._pdb_file_name())
705        args.append("/OUT:" + self._exe_file_name())
706        args.extend(self._obj_file_names())
707
708        return (
709            "linking",
710            self._obj_file_names(),
711            self._exe_file_name(),
712            self.link_env,
713            args,
714        )
715
716    def build_commands(self):
717        commands = []
718        if self.mode == "compile" or self.mode == "compile-and-link":
719            for input, output in zip(self.inputs, self._obj_file_names()):
720                commands.append(self._get_compilation_command(input, output))
721        if self.mode == "link" or self.mode == "compile-and-link":
722            commands.append(self._get_link_command())
723        return commands
724
725    def output_files(self):
726        outputs = []
727        if self.mode == "compile" or self.mode == "compile-and-link":
728            outputs.extend(self._ilk_file_names())
729            outputs.extend(self._obj_file_names())
730        if self.mode == "link" or self.mode == "compile-and-link":
731            outputs.append(self._pdb_file_name())
732            outputs.append(self._exe_file_name())
733
734        return [x for x in outputs if x is not None]
735
736
737class GccBuilder(Builder):
738    def __init__(self, toolchain_type, args):
739        Builder.__init__(self, toolchain_type, args, ".o")
740        if sys.platform == "darwin":
741            cmd = ["xcrun", "--sdk", args.apple_sdk, "--show-sdk-path"]
742            self.apple_sdk = subprocess.check_output(cmd).strip().decode("utf-8")
743
744    def _add_m_option_if_needed(self, args):
745        # clang allows -m(32|64) for any target, gcc does not.
746        uname = platform.uname().machine.lower()
747        if self.toolchain_type != "gcc" or (
748            not "arm" in uname and not "aarch64" in uname
749        ):
750            args.append("-m" + self.arch)
751
752        return args
753
754    def _get_compilation_command(self, source, obj):
755        args = []
756
757        args.append(self.compiler)
758        args = self._add_m_option_if_needed(args)
759
760        args.append("-g")
761        if self.opt == "none":
762            args.append("-O0")
763        elif self.opt == "basic":
764            args.append("-O2")
765        elif self.opt == "lto":
766            args.append("-flto=thin")
767        if self.nodefaultlib:
768            args.append("-nostdinc")
769            args.append("-static")
770        args.append("-c")
771
772        if sys.platform == "darwin":
773            args.extend(["-isysroot", self.apple_sdk])
774        elif self.objc_gnustep_inc:
775            if source.endswith(".m") or source.endswith(".mm"):
776                args.extend(["-fobjc-runtime=gnustep-2.0", "-I", self.objc_gnustep_inc])
777                if sys.platform == "win32":
778                    args.extend(
779                        ["-Xclang", "-gcodeview", "-Xclang", "--dependent-lib=msvcrtd"]
780                    )
781        elif self.sysroot:
782            args.extend(["--sysroot", self.sysroot])
783
784        if self.std:
785            args.append("-std={0}".format(self.std))
786
787        args.extend(["-o", obj])
788        args.append(source)
789
790        return ("compiling", [source], obj, None, args)
791
792    def _get_link_command(self):
793        args = []
794        args.append(self.compiler)
795        args = self._add_m_option_if_needed(args)
796
797        if self.nodefaultlib:
798            args.append("-nostdlib")
799            args.append("-static")
800            main_symbol = "main"
801            if sys.platform == "darwin":
802                main_symbol = "_main"
803            args.append("-Wl,-e," + main_symbol)
804        if sys.platform.startswith("netbsd"):
805            for x in self.lib_paths:
806                args += ["-L" + x, "-Wl,-rpath," + x]
807        args.extend(["-o", self._exe_file_name()])
808        args.extend(self._obj_file_names())
809
810        if sys.platform == "darwin":
811            args.extend(["-isysroot", self.apple_sdk])
812        elif self.objc_gnustep_lib:
813            args.extend(["-L", self.objc_gnustep_lib, "-lobjc"])
814            if sys.platform == "linux":
815                args.extend(["-Wl,-rpath," + self.objc_gnustep_lib])
816            elif sys.platform == "win32":
817                args.extend(
818                    ["-fuse-ld=lld-link", "-g", "-Xclang", "--dependent-lib=msvcrtd"]
819                )
820        elif self.sysroot:
821            args.extend(["--sysroot", self.sysroot])
822
823        return ("linking", self._obj_file_names(), self._exe_file_name(), None, args)
824
825    def output_files(self):
826        outputs = []
827        if self.mode == "compile" or self.mode == "compile-and-link":
828            outputs.extend(self._obj_file_names())
829        if self.mode == "link" or self.mode == "compile-and-link":
830            outputs.append(self._exe_file_name())
831
832        return outputs
833
834
835def indent(text, spaces):
836    def prefixed_lines():
837        prefix = " " * spaces
838        for line in text.splitlines(True):
839            yield prefix + line
840
841    return "".join(prefixed_lines())
842
843
844def build(commands):
845    global args
846    for status, inputs, output, env, child_args in commands:
847        print("\n\n")
848        inputs = [os.path.basename(x) for x in inputs]
849        output = os.path.basename(output)
850        print(status + " {0} -> {1}".format("+".join(inputs), output))
851
852        if args.verbose:
853            print("  Command Line: " + " ".join(child_args))
854            print("  Env:")
855            print_environment(env)
856        if args.dry:
857            continue
858
859        popen = subprocess.Popen(
860            child_args,
861            stdout=subprocess.PIPE,
862            stderr=subprocess.PIPE,
863            env=env,
864            universal_newlines=True,
865        )
866        stdout, stderr = popen.communicate()
867        res = popen.wait()
868        if res == -signal.SIGINT:
869            raise KeyboardInterrupt
870        print("  STDOUT:")
871        print(indent(stdout, 4))
872        if res != 0:
873            print("  STDERR:")
874            print(indent(stderr, 4))
875            sys.exit(res)
876
877
878def clean(files):
879    global args
880    if not files:
881        return
882    for o in files:
883        file = o if args.verbose else os.path.basename(o)
884        print("Cleaning {0}".format(file))
885        try:
886            if os.path.exists(o):
887                if not args.dry:
888                    os.remove(o)
889                if args.verbose:
890                    print("  The file was successfully cleaned.")
891            elif args.verbose:
892                print("  The file does not exist.")
893        except:
894            if args.verbose:
895                print("  The file could not be removed.")
896
897
898def fix_arguments(args):
899    if not args.inputs:
900        raise ValueError("No input files specified")
901
902    if args.output and args.mode == "compile" and len(args.inputs) > 1:
903        raise ValueError(
904            "Cannot specify -o with mode=compile and multiple source files.  Use --outdir instead."
905        )
906
907    if not args.dry:
908        args.inputs = [os.path.abspath(x) for x in args.inputs]
909
910    # If user didn't specify the outdir, use the directory of the first input.
911    if not args.outdir:
912        if args.output:
913            args.outdir = os.path.dirname(args.output)
914        else:
915            args.outdir = os.path.dirname(args.inputs[0])
916            args.outdir = os.path.abspath(args.outdir)
917        args.outdir = os.path.normpath(args.outdir)
918
919    # If user specified a non-absolute path for the output file, append the
920    # output directory to it.
921    if args.output:
922        if not os.path.isabs(args.output):
923            args.output = os.path.join(args.outdir, args.output)
924        args.output = os.path.normpath(args.output)
925
926
927fix_arguments(args)
928
929(toolchain_type, toolchain_path) = find_toolchain(args.compiler, args.tools_dir)
930if not toolchain_path or not toolchain_type:
931    print("Unable to find toolchain {0}".format(args.compiler))
932    sys.exit(1)
933
934if args.verbose:
935    print("Script Arguments:")
936    print("  Arch: " + args.arch)
937    print("  Compiler: " + args.compiler)
938    print("  Outdir: " + args.outdir)
939    print("  Output: " + args.output)
940    print("  Nodefaultlib: " + str(args.nodefaultlib))
941    print("  Opt: " + args.opt)
942    print("  Mode: " + args.mode)
943    print("  Clean: " + str(args.clean))
944    print("  Verbose: " + str(args.verbose))
945    print("  Dryrun: " + str(args.dry))
946    print("  Inputs: " + format_text(args.inputs, 0, 10))
947    print("  C/C++ Standard: " + str(args.std))
948    print("Script Environment:")
949    print_environment(os.environ)
950
951args.compiler = toolchain_path
952if not os.path.exists(args.compiler) and not args.dry:
953    raise ValueError("The toolchain {} does not exist.".format(args.compiler))
954
955if toolchain_type == "msvc" or toolchain_type == "clang-cl":
956    builder = MsvcBuilder(toolchain_type, args)
957else:
958    builder = GccBuilder(toolchain_type, args)
959
960if args.clean:
961    clean(builder.output_files())
962
963cmds = builder.build_commands()
964
965build(cmds)
966