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