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