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