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