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        self.msvc_arch_str = 'x86' if self.arch == '32' else 'x64'
281
282        if toolchain_type == 'msvc':
283            # Make sure we're using the appropriate toolchain for the desired
284            # target type.
285            compiler_parent_dir = os.path.dirname(self.compiler)
286            selected_target_version = os.path.basename(compiler_parent_dir)
287            if selected_target_version != self.msvc_arch_str:
288                host_dir = os.path.dirname(compiler_parent_dir)
289                self.compiler = os.path.join(host_dir, self.msvc_arch_str, 'cl.exe')
290                if self.verbose:
291                    print('Using alternate compiler "{0}" to match selected target.'.format(self.compiler))
292
293        if self.mode == 'link' or self.mode == 'compile-and-link':
294            self.linker = self._find_linker('link') if toolchain_type == 'msvc' else self._find_linker('lld-link', args.tools_dir)
295            if not self.linker:
296                raise ValueError('Unable to find an appropriate linker.')
297
298        self.compile_env, self.link_env = self._get_visual_studio_environment()
299
300    def _find_linker(self, name, search_paths=[]):
301        compiler_dir = os.path.dirname(self.compiler)
302        linker_path = find_executable(name, [compiler_dir] + search_paths)
303        if linker_path is None:
304            raise ValueError('Could not find \'{}\''.format(name))
305        return linker_path
306
307    def _get_vc_install_dir(self):
308        dir = os.getenv('VCINSTALLDIR', None)
309        if dir:
310            if self.verbose:
311                print('Using %VCINSTALLDIR% {}'.format(dir))
312            return dir
313
314        dir = os.getenv('VSINSTALLDIR', None)
315        if dir:
316            if self.verbose:
317                print('Using %VSINSTALLDIR% {}'.format(dir))
318            return os.path.join(dir, 'VC')
319
320        dir = os.getenv('VS2019INSTALLDIR', None)
321        if dir:
322            if self.verbose:
323                print('Using %VS2019INSTALLDIR% {}'.format(dir))
324            return os.path.join(dir, 'VC')
325
326        dir = os.getenv('VS2017INSTALLDIR', None)
327        if dir:
328            if self.verbose:
329                print('Using %VS2017INSTALLDIR% {}'.format(dir))
330            return os.path.join(dir, 'VC')
331
332        dir = os.getenv('VS2015INSTALLDIR', None)
333        if dir:
334            if self.verbose:
335                print('Using %VS2015INSTALLDIR% {}'.format(dir))
336            return os.path.join(dir, 'VC')
337        return None
338
339    def _get_vctools_version(self):
340        ver = os.getenv('VCToolsVersion', None)
341        if ver:
342            if self.verbose:
343                print('Using %VCToolsVersion% {}'.format(ver))
344            return ver
345
346        vcinstalldir = self._get_vc_install_dir()
347        vcinstalldir = os.path.join(vcinstalldir, 'Tools', 'MSVC')
348        subdirs = next(os.walk(vcinstalldir))[1]
349        if not subdirs:
350            return None
351
352        from distutils.version import StrictVersion
353        subdirs.sort(key=lambda x : StrictVersion(x))
354
355        if self.verbose:
356            full_path = os.path.join(vcinstalldir, subdirs[-1])
357            print('Using VC tools version directory {0} found by directory walk.'.format(full_path))
358        return subdirs[-1]
359
360    def _get_vctools_install_dir(self):
361        dir = os.getenv('VCToolsInstallDir', None)
362        if dir:
363            if self.verbose:
364                print('Using %VCToolsInstallDir% {}'.format(dir))
365            return dir
366
367        vcinstalldir = self._get_vc_install_dir()
368        if not vcinstalldir:
369            return None
370        vctoolsver = self._get_vctools_version()
371        if not vctoolsver:
372            return None
373        result = os.path.join(vcinstalldir, 'Tools', 'MSVC', vctoolsver)
374        if not os.path.exists(result):
375            return None
376        if self.verbose:
377            print('Using VC tools install dir {} found by directory walk'.format(result))
378        return result
379
380    def _find_windows_sdk_in_registry_view(self, view):
381        products_key = None
382        roots_key = None
383        installed_options_keys = []
384        try:
385            sam = view | winreg.KEY_READ
386            products_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
387                                          r'Software\Microsoft\Windows Kits\Installed Products',
388                                          0,
389                                          sam)
390
391            # This is the GUID for the desktop component.  If this is present
392            # then the components required for the Desktop SDK are installed.
393            # If not it will throw an exception.
394            winreg.QueryValueEx(products_key, '{5A3D81EC-D870-9ECF-D997-24BDA6644752}')
395
396            roots_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
397                                       r'Software\Microsoft\Windows Kits\Installed Roots',
398                                       0,
399                                       sam)
400            root_dir = winreg.QueryValueEx(roots_key, 'KitsRoot10')
401            root_dir = to_string(root_dir[0])
402            sdk_versions = []
403            index = 0
404            while True:
405                # Installed SDK versions are stored as sub-keys of the
406                # 'Installed Roots' key.  Find all of their names, then sort
407                # them by version
408                try:
409                    ver_key = winreg.EnumKey(roots_key, index)
410                    sdk_versions.append(ver_key)
411                    index = index + 1
412                except WindowsError:
413                    break
414            if not sdk_versions:
415                return (None, None)
416
417            # Windows SDK version numbers consist of 4 dotted components, so we
418            # have to use LooseVersion, as StrictVersion supports 3 or fewer.
419            from distutils.version import LooseVersion
420            sdk_versions.sort(key=lambda x : LooseVersion(x), reverse=True)
421            option_value_name = 'OptionId.DesktopCPP' + self.msvc_arch_str
422            for v in sdk_versions:
423                try:
424                    version_subkey = v + r'\Installed Options'
425                    key = winreg.OpenKey(roots_key, version_subkey)
426                    installed_options_keys.append(key)
427                    (value, value_type) = winreg.QueryValueEx(key, option_value_name)
428                    if value == 1:
429                        # The proper architecture is installed.  Return the
430                        # associated paths.
431                        if self.verbose:
432                            print('Found Installed Windows SDK v{0} at {1}'.format(v, root_dir))
433                        return (root_dir, v)
434                except:
435                    continue
436        except:
437            return (None, None)
438        finally:
439            del products_key
440            del roots_key
441            for k in installed_options_keys:
442                del k
443        return (None, None)
444
445    def _find_windows_sdk_in_registry(self):
446        # This could be a clang-cl cross-compile.  If so, there's no registry
447        # so just exit.
448        if sys.platform != 'win32':
449            return (None, None)
450        if self.verbose:
451            print('Looking for Windows SDK in 64-bit registry.')
452        dir, ver = self._find_windows_sdk_in_registry_view(winreg.KEY_WOW64_64KEY)
453        if not dir or not ver:
454            if self.verbose:
455                print('Looking for Windows SDK in 32-bit registry.')
456            dir, ver = self._find_windows_sdk_in_registry_view(winreg.KEY_WOW64_32KEY)
457
458        return (dir, ver)
459
460    def _get_winsdk_dir(self):
461        # If a Windows SDK is specified in the environment, use that.  Otherwise
462        # try to find one in the Windows registry.
463        dir = os.getenv('WindowsSdkDir', None)
464        if not dir or not os.path.exists(dir):
465            return self._find_windows_sdk_in_registry()
466        ver = os.getenv('WindowsSDKLibVersion', None)
467        if not ver:
468            return self._find_windows_sdk_in_registry()
469
470        ver = ver.rstrip('\\')
471        if self.verbose:
472            print('Using %WindowsSdkDir% {}'.format(dir))
473            print('Using %WindowsSDKLibVersion% {}'.format(ver))
474        return (dir, ver)
475
476    def _get_msvc_native_toolchain_dir(self):
477        assert self.toolchain_type == 'msvc'
478        compiler_dir = os.path.dirname(self.compiler)
479        target_dir = os.path.dirname(compiler_dir)
480        host_name = os.path.basename(target_dir)
481        host_name = host_name[4:].lower()
482        return os.path.join(target_dir, host_name)
483
484    def _get_visual_studio_environment(self):
485        vctools = self._get_vctools_install_dir()
486        winsdk, winsdkver = self._get_winsdk_dir()
487
488        if not vctools and self.verbose:
489            print('Unable to find VC tools installation directory.')
490        if (not winsdk or not winsdkver) and self.verbose:
491            print('Unable to find Windows SDK directory.')
492
493        vcincludes = []
494        vclibs = []
495        sdkincludes = []
496        sdklibs = []
497        if vctools is not None:
498            includes = [['ATLMFC', 'include'], ['include']]
499            libs = [['ATLMFC', 'lib'], ['lib']]
500            vcincludes = [os.path.join(vctools, *y) for y in includes]
501            vclibs = [os.path.join(vctools, *y) for y in libs]
502        if winsdk is not None:
503            includes = [['include', winsdkver, 'ucrt'],
504                        ['include', winsdkver, 'shared'],
505                        ['include', winsdkver, 'um'],
506                        ['include', winsdkver, 'winrt'],
507                        ['include', winsdkver, 'cppwinrt']]
508            libs = [['lib', winsdkver, 'ucrt'],
509                    ['lib', winsdkver, 'um']]
510            sdkincludes = [os.path.join(winsdk, *y) for y in includes]
511            sdklibs = [os.path.join(winsdk, *y) for y in libs]
512
513        includes = vcincludes + sdkincludes
514        libs = vclibs + sdklibs
515        libs = [os.path.join(x, self.msvc_arch_str) for x in libs]
516        compileenv = None
517        linkenv = None
518        defaultenv = {}
519        if sys.platform == 'win32':
520            defaultenv = { x : os.environ[x] for x in
521                          ['SystemDrive', 'SystemRoot', 'TMP', 'TEMP'] }
522            # The directory to mspdbcore.dll needs to be in PATH, but this is
523            # always in the native toolchain path, not the cross-toolchain
524            # path.  So, for example, if we're using HostX64\x86 then we need
525            # to add HostX64\x64 to the path, and if we're using HostX86\x64
526            # then we need to add HostX86\x86 to the path.
527            if self.toolchain_type == 'msvc':
528                defaultenv['PATH'] = self._get_msvc_native_toolchain_dir()
529
530        if includes:
531            compileenv = {}
532            compileenv['INCLUDE'] = os.pathsep.join(includes)
533            compileenv.update(defaultenv)
534        if libs:
535            linkenv = {}
536            linkenv['LIB'] = os.pathsep.join(libs)
537            linkenv.update(defaultenv)
538        return (compileenv, linkenv)
539
540    def _ilk_file_names(self):
541        if self.mode == 'link':
542            return []
543
544        return [self._output_name(x, '.ilk') for x in self.inputs]
545
546    def _pdb_file_name(self):
547        if self.mode == 'compile':
548            return None
549        return os.path.splitext(self.output)[0] + '.pdb'
550
551    def _get_compilation_command(self, source, obj):
552        args = []
553
554        args.append(self.compiler)
555        if self.toolchain_type == 'clang-cl':
556            args.append('-m' + self.arch)
557
558        if self.opt == 'none':
559            args.append('/Od')
560        elif self.opt == 'basic':
561            args.append('/O2')
562        elif self.opt == 'lto':
563            if self.toolchain_type == 'msvc':
564                args.append('/GL')
565                args.append('/Gw')
566            else:
567                args.append('-flto=thin')
568        if self.nodefaultlib:
569            args.append('/GS-')
570            args.append('/GR-')
571        args.append('/Z7')
572        if self.toolchain_type == 'clang-cl':
573            args.append('-Xclang')
574            args.append('-fkeep-static-consts')
575            args.append('-fms-compatibility-version=19')
576        args.append('/c')
577
578        args.append('/Fo' + obj)
579        if self.toolchain_type == 'clang-cl':
580            args.append('--')
581        args.append(source)
582
583        return ('compiling', [source], obj,
584                self.compile_env,
585                args)
586
587    def _get_link_command(self):
588        args = []
589        args.append(self.linker)
590        args.append('/DEBUG:FULL')
591        args.append('/INCREMENTAL:NO')
592        if self.nodefaultlib:
593            args.append('/nodefaultlib')
594            args.append('/entry:main')
595        args.append('/PDB:' + self._pdb_file_name())
596        args.append('/OUT:' + self._exe_file_name())
597        args.extend(self._obj_file_names())
598
599        return ('linking', self._obj_file_names(), self._exe_file_name(),
600                self.link_env,
601                args)
602
603    def build_commands(self):
604        commands = []
605        if self.mode == 'compile' or self.mode == 'compile-and-link':
606            for input, output in zip(self.inputs, self._obj_file_names()):
607                commands.append(self._get_compilation_command(input, output))
608        if self.mode == 'link' or self.mode == 'compile-and-link':
609            commands.append(self._get_link_command())
610        return commands
611
612    def output_files(self):
613        outputs = []
614        if self.mode == 'compile' or self.mode == 'compile-and-link':
615            outputs.extend(self._ilk_file_names())
616            outputs.extend(self._obj_file_names())
617        if self.mode == 'link' or self.mode == 'compile-and-link':
618            outputs.append(self._pdb_file_name())
619            outputs.append(self._exe_file_name())
620
621        return [x for x in outputs if x is not None]
622
623class GccBuilder(Builder):
624    def __init__(self, toolchain_type, args):
625        Builder.__init__(self, toolchain_type, args, '.o')
626        if sys.platform == 'darwin':
627            cmd = ['xcrun', '--sdk', args.apple_sdk, '--show-sdk-path']
628            self.apple_sdk = subprocess.check_output(cmd).strip().decode('utf-8')
629
630    def _get_compilation_command(self, source, obj):
631        args = []
632
633        args.append(self.compiler)
634        args.append('-m' + self.arch)
635
636        args.append('-g')
637        if self.opt == 'none':
638            args.append('-O0')
639        elif self.opt == 'basic':
640            args.append('-O2')
641        elif self.opt == 'lto':
642            args.append('-flto=thin')
643        if self.nodefaultlib:
644            args.append('-nostdinc')
645            args.append('-static')
646        args.append('-c')
647
648        args.extend(['-o', obj])
649        args.append(source)
650
651        if sys.platform == 'darwin':
652            args.extend(['-isysroot', self.apple_sdk])
653
654        return ('compiling', [source], obj, None, args)
655
656    def _get_link_command(self):
657        args = []
658        args.append(self.compiler)
659        args.append('-m' + self.arch)
660        if self.nodefaultlib:
661            args.append('-nostdlib')
662            args.append('-static')
663            main_symbol = 'main'
664            if sys.platform == 'darwin':
665                main_symbol = '_main'
666            args.append('-Wl,-e,' + main_symbol)
667        if sys.platform.startswith('netbsd'):
668            for x in self.lib_paths:
669                args += ['-L' + x, '-Wl,-rpath,' + x]
670        args.extend(['-o', self._exe_file_name()])
671        args.extend(self._obj_file_names())
672
673        if sys.platform == 'darwin':
674            args.extend(['-isysroot', self.apple_sdk])
675
676        return ('linking', self._obj_file_names(), self._exe_file_name(), None, args)
677
678
679    def output_files(self):
680        outputs = []
681        if self.mode == 'compile' or self.mode == 'compile-and-link':
682            outputs.extend(self._obj_file_names())
683        if self.mode == 'link' or self.mode == 'compile-and-link':
684            outputs.append(self._exe_file_name())
685
686        return outputs
687
688def indent(text, spaces):
689    def prefixed_lines():
690        prefix = ' ' * spaces
691        for line in text.splitlines(True):
692            yield prefix + line
693    return ''.join(prefixed_lines())
694
695def build(commands):
696    global args
697    for (status, inputs, output, env, child_args) in commands:
698        print('\n\n')
699        inputs = [os.path.basename(x) for x in inputs]
700        output = os.path.basename(output)
701        print(status + ' {0} -> {1}'.format('+'.join(inputs), output))
702
703        if args.verbose:
704            print('  Command Line: ' + ' '.join(child_args))
705            print('  Env:')
706            print_environment(env)
707        if args.dry:
708            continue
709
710        popen = subprocess.Popen(child_args,
711                                 stdout=subprocess.PIPE,
712                                 stderr=subprocess.PIPE,
713                                 env=env,
714                                 universal_newlines=True)
715        stdout, stderr = popen.communicate()
716        res = popen.wait()
717        if res == -signal.SIGINT:
718            raise KeyboardInterrupt
719        print('  STDOUT:')
720        print(indent(stdout, 4))
721        if res != 0:
722            print('  STDERR:')
723            print(indent(stderr, 4))
724            sys.exit(res)
725
726def clean(files):
727    global args
728    if not files:
729        return
730    for o in files:
731        file = o if args.verbose else os.path.basename(o)
732        print('Cleaning {0}'.format(file))
733        try:
734            if os.path.exists(o):
735                if not args.dry:
736                    os.remove(o)
737                if args.verbose:
738                    print('  The file was successfully cleaned.')
739            elif args.verbose:
740                print('  The file does not exist.')
741        except:
742            if args.verbose:
743                print('  The file could not be removed.')
744
745def fix_arguments(args):
746    if not args.inputs:
747        raise ValueError('No input files specified')
748
749    if args.output and args.mode == 'compile' and len(args.inputs) > 1:
750        raise ValueError('Cannot specify -o with mode=compile and multiple source files.  Use --outdir instead.')
751
752    if not args.dry:
753        args.inputs = [os.path.abspath(x) for x in args.inputs]
754
755    # If user didn't specify the outdir, use the directory of the first input.
756    if not args.outdir:
757        if args.output:
758            args.outdir = os.path.dirname(args.output)
759        else:
760            args.outdir = os.path.dirname(args.inputs[0])
761            args.outdir = os.path.abspath(args.outdir)
762        args.outdir = os.path.normpath(args.outdir)
763
764    # If user specified a non-absolute path for the output file, append the
765    # output directory to it.
766    if args.output:
767        if not os.path.isabs(args.output):
768            args.output = os.path.join(args.outdir, args.output)
769        args.output = os.path.normpath(args.output)
770
771fix_arguments(args)
772
773(toolchain_type, toolchain_path) = find_toolchain(args.compiler, args.tools_dir)
774if not toolchain_path or not toolchain_type:
775    print('Unable to find toolchain {0}'.format(args.compiler))
776    sys.exit(1)
777
778if args.verbose:
779    print('Script Arguments:')
780    print('  Arch: ' + args.arch)
781    print('  Compiler: ' + args.compiler)
782    print('  Outdir: ' + args.outdir)
783    print('  Output: ' + args.output)
784    print('  Nodefaultlib: ' + str(args.nodefaultlib))
785    print('  Opt: ' + args.opt)
786    print('  Mode: ' + args.mode)
787    print('  Clean: ' + str(args.clean))
788    print('  Verbose: ' + str(args.verbose))
789    print('  Dryrun: ' + str(args.dry))
790    print('  Inputs: ' + format_text(args.inputs, 0, 10))
791    print('Script Environment:')
792    print_environment(os.environ)
793
794args.compiler = toolchain_path
795if not os.path.exists(args.compiler) and not args.dry:
796    raise ValueError('The toolchain {} does not exist.'.format(args.compiler))
797
798if toolchain_type == 'msvc' or toolchain_type=='clang-cl':
799    builder = MsvcBuilder(toolchain_type, args)
800else:
801    builder = GccBuilder(toolchain_type, args)
802
803if args.clean:
804    clean(builder.output_files())
805
806cmds = builder.build_commands()
807
808build(cmds)
809