| #!/usr/bin/env python3 | |
| import argparse | |
| import os | |
| import platform | |
| import shutil | |
| import signal | |
| import subprocess | |
| import sys | |
| if sys.platform == "win32": | |
| import winreg | |
| if __name__ != "__main__": | |
| raise RuntimeError("Do not import this script, run it instead") | |
| parser = argparse.ArgumentParser(description="LLDB compilation wrapper") | |
| parser.add_argument( | |
| "--arch", | |
| metavar="arch", | |
| dest="arch", | |
| required=True, | |
| default="host", | |
| choices=["32", "64", "host"], | |
| help="Specify the architecture to target.", | |
| ) | |
| parser.add_argument( | |
| "--compiler", | |
| metavar="compiler", | |
| dest="compiler", | |
| required=True, | |
| help="Path to a compiler executable, or one of the values [any, msvc, clang-cl, gcc, clang]", | |
| ) | |
| parser.add_argument( | |
| "--libs-dir", | |
| metavar="directory", | |
| dest="libs_dir", | |
| required=False, | |
| action="append", | |
| help="If specified, a path to linked libraries to be passed via -L", | |
| ) | |
| parser.add_argument( | |
| "--tools-dir", | |
| metavar="directory", | |
| dest="tools_dir", | |
| required=False, | |
| action="append", | |
| help="If specified, a path to search in addition to PATH when --compiler is not an exact path", | |
| ) | |
| parser.add_argument( | |
| "--objc-gnustep-dir", | |
| metavar="directory", | |
| dest="objc_gnustep_dir", | |
| required=False, | |
| help="If specified, a path to GNUstep libobjc2 runtime for use on Windows and Linux", | |
| ) | |
| parser.add_argument( | |
| "--objc-gnustep", | |
| dest="objc_gnustep", | |
| action="store_true", | |
| default=False, | |
| help="Include and link GNUstep libobjc2 (Windows and Linux only)", | |
| ) | |
| parser.add_argument( | |
| "--sysroot", | |
| metavar="directory", | |
| dest="sysroot", | |
| required=False, | |
| help="If specified, a sysroot to be passed via --sysroot", | |
| ) | |
| if sys.platform == "darwin": | |
| parser.add_argument( | |
| "--apple-sdk", | |
| metavar="apple_sdk", | |
| dest="apple_sdk", | |
| default="macosx", | |
| 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.", | |
| ) | |
| parser.add_argument( | |
| "--output", | |
| "-o", | |
| dest="output", | |
| metavar="file", | |
| required=False, | |
| default="", | |
| help="Path to output file", | |
| ) | |
| parser.add_argument( | |
| "--outdir", | |
| "-d", | |
| dest="outdir", | |
| metavar="directory", | |
| required=False, | |
| help="Directory for output files", | |
| ) | |
| parser.add_argument( | |
| "--nodefaultlib", | |
| dest="nodefaultlib", | |
| action="store_true", | |
| default=False, | |
| help="When specified, the resulting image should not link against system libraries or include system headers. Useful when writing cross-targeting tests.", | |
| ) | |
| parser.add_argument( | |
| "--opt", | |
| dest="opt", | |
| default="none", | |
| choices=["none", "basic", "lto"], | |
| help="Optimization level", | |
| ) | |
| parser.add_argument( | |
| "--mode", | |
| dest="mode", | |
| default="compile-and-link", | |
| choices=["compile", "link", "compile-and-link"], | |
| help="Specifies whether to compile, link, or both", | |
| ) | |
| parser.add_argument( | |
| "--noclean", | |
| dest="clean", | |
| action="store_false", | |
| default=True, | |
| help="Dont clean output file before building", | |
| ) | |
| parser.add_argument( | |
| "--verbose", | |
| dest="verbose", | |
| action="store_true", | |
| default=False, | |
| help="Print verbose output", | |
| ) | |
| parser.add_argument( | |
| "-n", | |
| "--dry-run", | |
| dest="dry", | |
| action="store_true", | |
| default=False, | |
| help="Print the commands that would run, but dont actually run them", | |
| ) | |
| parser.add_argument( | |
| "inputs", | |
| metavar="file", | |
| nargs="+", | |
| help="Source file(s) to compile / object file(s) to link", | |
| ) | |
| parser.add_argument( | |
| "--std", | |
| metavar="std", | |
| dest="std", | |
| required=False, | |
| help="Specify the C/C++ standard.", | |
| ) | |
| args = parser.parse_args(args=sys.argv[1:]) | |
| def to_string(b): | |
| """Return the parameter as type 'str', possibly encoding it. | |
| In Python2, the 'str' type is the same as 'bytes'. In Python3, the | |
| 'str' type is (essentially) Python2's 'unicode' type, and 'bytes' is | |
| distinct. | |
| This function is copied from llvm/utils/lit/lit/util.py | |
| """ | |
| if isinstance(b, str): | |
| # In Python2, this branch is taken for types 'str' and 'bytes'. | |
| # In Python3, this branch is taken only for 'str'. | |
| return b | |
| if isinstance(b, bytes): | |
| # In Python2, this branch is never taken ('bytes' is handled as 'str'). | |
| # In Python3, this is true only for 'bytes'. | |
| try: | |
| return b.decode("utf-8") | |
| except UnicodeDecodeError: | |
| # If the value is not valid Unicode, return the default | |
| # repr-line encoding. | |
| return str(b) | |
| # By this point, here's what we *don't* have: | |
| # | |
| # - In Python2: | |
| # - 'str' or 'bytes' (1st branch above) | |
| # - In Python3: | |
| # - 'str' (1st branch above) | |
| # - 'bytes' (2nd branch above) | |
| # | |
| # The last type we might expect is the Python2 'unicode' type. There is no | |
| # 'unicode' type in Python3 (all the Python3 cases were already handled). In | |
| # order to get a 'str' object, we need to encode the 'unicode' object. | |
| try: | |
| return b.encode("utf-8") | |
| except AttributeError: | |
| raise TypeError("not sure how to convert %s to %s" % (type(b), str)) | |
| def format_text(lines, indent_0, indent_n): | |
| result = " " * indent_0 + lines[0] | |
| for next in lines[1:]: | |
| result = result + "\n{0}{1}".format(" " * indent_n, next) | |
| return result | |
| def print_environment(env): | |
| if env is None: | |
| print(" Inherited") | |
| return | |
| for e in env: | |
| value = env[e] | |
| lines = value.split(os.pathsep) | |
| formatted_value = format_text(lines, 0, 7 + len(e)) | |
| print(" {0} = {1}".format(e, formatted_value)) | |
| def find_executable(binary_name, search_paths): | |
| # shutil.which will ignore PATH if given a path argument, we want to include it. | |
| search_paths.append(os.environ.get("PATH", "")) | |
| search_path = os.pathsep.join(search_paths) | |
| binary_path = shutil.which(binary_name, path=search_path) | |
| if binary_path is not None: | |
| # So for example, we get '/bin/gcc' instead of '/usr/../bin/gcc'. | |
| binary_path = os.path.normpath(binary_path) | |
| return binary_path | |
| def find_toolchain(compiler, tools_dir): | |
| if compiler == "any": | |
| priorities = [] | |
| if sys.platform == "win32": | |
| priorities = ["clang-cl", "msvc", "clang", "gcc"] | |
| else: | |
| priorities = ["clang", "gcc", "clang-cl"] | |
| for toolchain in priorities: | |
| (type, c_compiler, cxx_compiler) = find_toolchain(toolchain, tools_dir) | |
| if type and c_compiler and cxx_compiler: | |
| return (type, c_compiler, cxx_compiler) | |
| # Could not find a toolchain. | |
| return (None, None, None) | |
| c_compiler = cxx_compiler = None | |
| if compiler == "msvc": | |
| c_compiler = "cl" | |
| if compiler == "clang-cl": | |
| c_compiler = "clang-cl" | |
| if compiler == "gcc": | |
| c_compiler = "gcc" | |
| cxx_compiler = "g++" | |
| if compiler == "clang": | |
| c_compiler = "clang" | |
| cxx_compiler = "clang++" | |
| if not cxx_compiler: | |
| cxx_compiler = c_compiler | |
| if c_compiler: | |
| return ( | |
| compiler, | |
| find_executable(c_compiler, tools_dir), | |
| find_executable(cxx_compiler, tools_dir), | |
| ) | |
| # From here on, assume that |compiler| is a path to a file. | |
| file = os.path.basename(compiler) | |
| name, ext = os.path.splitext(file) | |
| if file.lower() == "cl.exe": | |
| return ("msvc", compiler, compiler) | |
| if name == "clang-cl": | |
| return ("clang-cl", compiler, compiler) | |
| if name.startswith("clang"): | |
| return ("clang", compiler, compiler) | |
| if name.startswith("gcc") or name.startswith("g++"): | |
| return ("gcc", compiler, compiler) | |
| if name == "cc" or name == "c++": | |
| return ("generic", compiler, compiler) | |
| return ("unknown", compiler, compiler) | |
| class Builder(object): | |
| def __init__(self, toolchain_type, args, obj_ext): | |
| self.toolchain_type = toolchain_type | |
| self.inputs = args.inputs | |
| self.arch = args.arch | |
| self.opt = args.opt | |
| self.outdir = args.outdir | |
| self.c_compiler = args.c_compiler | |
| self.cxx_compiler = self.linker = args.cxx_compiler | |
| self.clean = args.clean | |
| self.output = args.output | |
| self.mode = args.mode | |
| self.nodefaultlib = args.nodefaultlib | |
| self.verbose = args.verbose | |
| self.obj_ext = obj_ext | |
| self.lib_paths = args.libs_dir | |
| self.std = args.std | |
| assert ( | |
| not args.objc_gnustep or args.objc_gnustep_dir | |
| ), "--objc-gnustep specified without path to libobjc2" | |
| self.objc_gnustep_inc = ( | |
| os.path.join(args.objc_gnustep_dir, "include") | |
| if args.objc_gnustep_dir | |
| else None | |
| ) | |
| self.objc_gnustep_lib = ( | |
| os.path.join(args.objc_gnustep_dir, "lib") | |
| if args.objc_gnustep_dir | |
| else None | |
| ) | |
| self.sysroot = args.sysroot | |
| def _exe_file_name(self): | |
| assert self.mode != "compile" | |
| return self.output | |
| def _output_name(self, input, extension, with_executable=False): | |
| basename = os.path.splitext(os.path.basename(input))[0] + extension | |
| if with_executable: | |
| exe_basename = os.path.basename(self._exe_file_name()) | |
| basename = exe_basename + "-" + basename | |
| output = os.path.join(self.outdir, basename) | |
| return os.path.normpath(output) | |
| def _obj_file_names(self): | |
| if self.mode == "link": | |
| return self.inputs | |
| if self.mode == "compile-and-link": | |
| # Object file names should factor in both the input file (source) | |
| # name and output file (executable) name, to ensure that two tests | |
| # which share a common source file don't race to write the same | |
| # object file. | |
| return [self._output_name(x, self.obj_ext, True) for x in self.inputs] | |
| if self.mode == "compile" and self.output: | |
| return [self.output] | |
| return [self._output_name(x, self.obj_ext) for x in self.inputs] | |
| def build_commands(self): | |
| commands = [] | |
| if self.mode == "compile" or self.mode == "compile-and-link": | |
| for input, output in zip(self.inputs, self._obj_file_names()): | |
| commands.append(self._get_compilation_command(input, output)) | |
| if self.mode == "link" or self.mode == "compile-and-link": | |
| commands.append(self._get_link_command()) | |
| return commands | |
| def is_c_file(self, filename): | |
| return filename.endswith((".c", ".m")) | |
| def compiler_for_file(self, filename): | |
| return self.c_compiler if self.is_c_file(filename) else self.cxx_compiler | |
| class MsvcBuilder(Builder): | |
| def __init__(self, toolchain_type, args): | |
| Builder.__init__(self, toolchain_type, args, ".obj") | |
| if platform.uname().machine.lower() == "arm64": | |
| self.msvc_arch_str = "arm" if self.arch == "32" else "arm64" | |
| else: | |
| self.msvc_arch_str = "x86" if self.arch == "32" else "x64" | |
| if toolchain_type == "msvc": | |
| # Make sure we're using the appropriate toolchain for the desired | |
| # target type. | |
| compiler_parent_dir = os.path.dirname(self.cxx_compiler) | |
| selected_target_version = os.path.basename(compiler_parent_dir) | |
| if selected_target_version != self.msvc_arch_str: | |
| host_dir = os.path.dirname(compiler_parent_dir) | |
| self.c_compiler = self.cxx_compiler = os.path.join( | |
| host_dir, self.msvc_arch_str, "cl.exe" | |
| ) | |
| if self.verbose: | |
| print( | |
| 'Using alternate compiler "{0}" to match selected target.'.format( | |
| self.cxx_compiler | |
| ) | |
| ) | |
| if self.mode == "link" or self.mode == "compile-and-link": | |
| self.linker = ( | |
| self._find_linker("link") | |
| if toolchain_type == "msvc" | |
| else self._find_linker("lld-link", args.tools_dir) | |
| ) | |
| if not self.linker: | |
| raise ValueError("Unable to find an appropriate linker.") | |
| self.compile_env, self.link_env = self._get_visual_studio_environment() | |
| def _find_linker(self, name, search_paths=[]): | |
| compiler_dir = os.path.dirname(self.cxx_compiler) | |
| linker_path = find_executable(name, [compiler_dir] + search_paths) | |
| if linker_path is None: | |
| raise ValueError("Could not find '{}'".format(name)) | |
| return linker_path | |
| def _get_vc_install_dir(self): | |
| dir = os.getenv("VCINSTALLDIR", None) | |
| if dir: | |
| if self.verbose: | |
| print("Using %VCINSTALLDIR% {}".format(dir)) | |
| return dir | |
| dir = os.getenv("VSINSTALLDIR", None) | |
| if dir: | |
| if self.verbose: | |
| print("Using %VSINSTALLDIR% {}".format(dir)) | |
| return os.path.join(dir, "VC") | |
| dir = os.getenv("VS2019INSTALLDIR", None) | |
| if dir: | |
| if self.verbose: | |
| print("Using %VS2019INSTALLDIR% {}".format(dir)) | |
| return os.path.join(dir, "VC") | |
| dir = os.getenv("VS2017INSTALLDIR", None) | |
| if dir: | |
| if self.verbose: | |
| print("Using %VS2017INSTALLDIR% {}".format(dir)) | |
| return os.path.join(dir, "VC") | |
| dir = os.getenv("VS2015INSTALLDIR", None) | |
| if dir: | |
| if self.verbose: | |
| print("Using %VS2015INSTALLDIR% {}".format(dir)) | |
| return os.path.join(dir, "VC") | |
| return None | |
| def _get_vctools_version(self): | |
| ver = os.getenv("VCToolsVersion", None) | |
| if ver: | |
| if self.verbose: | |
| print("Using %VCToolsVersion% {}".format(ver)) | |
| return ver | |
| vcinstalldir = self._get_vc_install_dir() | |
| vcinstalldir = os.path.join(vcinstalldir, "Tools", "MSVC") | |
| subdirs = next(os.walk(vcinstalldir))[1] | |
| if not subdirs: | |
| return None | |
| from packaging import version | |
| subdirs.sort(key=lambda x: version.parse(x)) | |
| if self.verbose: | |
| full_path = os.path.join(vcinstalldir, subdirs[-1]) | |
| print( | |
| "Using VC tools version directory {0} found by directory walk.".format( | |
| full_path | |
| ) | |
| ) | |
| return subdirs[-1] | |
| def _get_vctools_install_dir(self): | |
| dir = os.getenv("VCToolsInstallDir", None) | |
| if dir: | |
| if self.verbose: | |
| print("Using %VCToolsInstallDir% {}".format(dir)) | |
| return dir | |
| vcinstalldir = self._get_vc_install_dir() | |
| if not vcinstalldir: | |
| return None | |
| vctoolsver = self._get_vctools_version() | |
| if not vctoolsver: | |
| return None | |
| result = os.path.join(vcinstalldir, "Tools", "MSVC", vctoolsver) | |
| if not os.path.exists(result): | |
| return None | |
| if self.verbose: | |
| print( | |
| "Using VC tools install dir {} found by directory walk".format(result) | |
| ) | |
| return result | |
| def _find_windows_sdk_in_registry_view(self, view): | |
| products_key = None | |
| roots_key = None | |
| installed_options_keys = [] | |
| try: | |
| sam = view | winreg.KEY_READ | |
| products_key = winreg.OpenKey( | |
| winreg.HKEY_LOCAL_MACHINE, | |
| r"Software\Microsoft\Windows Kits\Installed Products", | |
| 0, | |
| sam, | |
| ) | |
| # This is the GUID for the desktop component. If this is present | |
| # then the components required for the Desktop SDK are installed. | |
| # If not it will throw an exception. | |
| winreg.QueryValueEx(products_key, "{5A3D81EC-D870-9ECF-D997-24BDA6644752}") | |
| roots_key = winreg.OpenKey( | |
| winreg.HKEY_LOCAL_MACHINE, | |
| r"Software\Microsoft\Windows Kits\Installed Roots", | |
| 0, | |
| sam, | |
| ) | |
| root_dir = winreg.QueryValueEx(roots_key, "KitsRoot10") | |
| root_dir = to_string(root_dir[0]) | |
| sdk_versions = [] | |
| index = 0 | |
| while True: | |
| # Installed SDK versions are stored as sub-keys of the | |
| # 'Installed Roots' key. Find all of their names, then sort | |
| # them by version | |
| try: | |
| ver_key = winreg.EnumKey(roots_key, index) | |
| sdk_versions.append(ver_key) | |
| index = index + 1 | |
| except WindowsError: | |
| break | |
| if not sdk_versions: | |
| return (None, None) | |
| from packaging import version | |
| sdk_versions.sort(key=lambda x: version.parse(x), reverse=True) | |
| option_value_name = "OptionId.DesktopCPP" + self.msvc_arch_str | |
| for v in sdk_versions: | |
| try: | |
| version_subkey = v + r"\Installed Options" | |
| key = winreg.OpenKey(roots_key, version_subkey) | |
| installed_options_keys.append(key) | |
| (value, value_type) = winreg.QueryValueEx(key, option_value_name) | |
| if value == 1: | |
| # The proper architecture is installed. Return the | |
| # associated paths. | |
| if self.verbose: | |
| print( | |
| "Found Installed Windows SDK v{0} at {1}".format( | |
| v, root_dir | |
| ) | |
| ) | |
| return (root_dir, v) | |
| except: | |
| continue | |
| except: | |
| return (None, None) | |
| finally: | |
| del products_key | |
| del roots_key | |
| for k in installed_options_keys: | |
| del k | |
| return (None, None) | |
| def _find_windows_sdk_in_registry(self): | |
| # This could be a clang-cl cross-compile. If so, there's no registry | |
| # so just exit. | |
| if sys.platform != "win32": | |
| return (None, None) | |
| if self.verbose: | |
| print("Looking for Windows SDK in 64-bit registry.") | |
| dir, ver = self._find_windows_sdk_in_registry_view(winreg.KEY_WOW64_64KEY) | |
| if not dir or not ver: | |
| if self.verbose: | |
| print("Looking for Windows SDK in 32-bit registry.") | |
| dir, ver = self._find_windows_sdk_in_registry_view(winreg.KEY_WOW64_32KEY) | |
| return (dir, ver) | |
| def _get_winsdk_dir(self): | |
| # If a Windows SDK is specified in the environment, use that. Otherwise | |
| # try to find one in the Windows registry. | |
| dir = os.getenv("WindowsSdkDir", None) | |
| if not dir or not os.path.exists(dir): | |
| return self._find_windows_sdk_in_registry() | |
| ver = os.getenv("WindowsSDKLibVersion", None) | |
| if not ver: | |
| return self._find_windows_sdk_in_registry() | |
| ver = ver.rstrip("\\") | |
| if self.verbose: | |
| print("Using %WindowsSdkDir% {}".format(dir)) | |
| print("Using %WindowsSDKLibVersion% {}".format(ver)) | |
| return (dir, ver) | |
| def _get_msvc_native_toolchain_dir(self): | |
| assert self.toolchain_type == "msvc" | |
| compiler_dir = os.path.dirname(self.cxx_compiler) | |
| target_dir = os.path.dirname(compiler_dir) | |
| host_name = os.path.basename(target_dir) | |
| host_name = host_name[4:].lower() | |
| return os.path.join(target_dir, host_name) | |
| def _get_visual_studio_environment(self): | |
| vctools = self._get_vctools_install_dir() | |
| winsdk, winsdkver = self._get_winsdk_dir() | |
| if not vctools and self.verbose: | |
| print("Unable to find VC tools installation directory.") | |
| if (not winsdk or not winsdkver) and self.verbose: | |
| print("Unable to find Windows SDK directory.") | |
| vcincludes = [] | |
| vclibs = [] | |
| sdkincludes = [] | |
| sdklibs = [] | |
| if vctools is not None: | |
| includes = [["ATLMFC", "include"], ["include"]] | |
| libs = [["ATLMFC", "lib"], ["lib"]] | |
| vcincludes = [os.path.join(vctools, *y) for y in includes] | |
| vclibs = [os.path.join(vctools, *y) for y in libs] | |
| if winsdk is not None: | |
| includes = [ | |
| ["include", winsdkver, "ucrt"], | |
| ["include", winsdkver, "shared"], | |
| ["include", winsdkver, "um"], | |
| ["include", winsdkver, "winrt"], | |
| ["include", winsdkver, "cppwinrt"], | |
| ] | |
| libs = [["lib", winsdkver, "ucrt"], ["lib", winsdkver, "um"]] | |
| sdkincludes = [os.path.join(winsdk, *y) for y in includes] | |
| sdklibs = [os.path.join(winsdk, *y) for y in libs] | |
| includes = vcincludes + sdkincludes | |
| libs = vclibs + sdklibs | |
| libs = [os.path.join(x, self.msvc_arch_str) for x in libs] | |
| compileenv = None | |
| linkenv = None | |
| defaultenv = {} | |
| if sys.platform == "win32": | |
| defaultenv = { | |
| x: os.environ[x] for x in ["SystemDrive", "SystemRoot", "TMP", "TEMP"] | |
| } | |
| # The directory to mspdbcore.dll needs to be in PATH, but this is | |
| # always in the native toolchain path, not the cross-toolchain | |
| # path. So, for example, if we're using HostX64\x86 then we need | |
| # to add HostX64\x64 to the path, and if we're using HostX86\x64 | |
| # then we need to add HostX86\x86 to the path. | |
| if self.toolchain_type == "msvc": | |
| defaultenv["PATH"] = self._get_msvc_native_toolchain_dir() | |
| if includes: | |
| compileenv = {} | |
| compileenv["INCLUDE"] = os.pathsep.join(includes) | |
| compileenv.update(defaultenv) | |
| if libs: | |
| linkenv = {} | |
| linkenv["LIB"] = os.pathsep.join(libs) | |
| linkenv.update(defaultenv) | |
| return (compileenv, linkenv) | |
| def _ilk_file_names(self): | |
| if self.mode == "link": | |
| return [] | |
| return [self._output_name(x, ".ilk") for x in self.inputs] | |
| def _pdb_file_name(self): | |
| if self.mode == "compile": | |
| return None | |
| return os.path.splitext(self.output)[0] + ".pdb" | |
| def _get_compilation_command(self, source, obj): | |
| args = [self.compiler_for_file(source)] | |
| if self.toolchain_type == "clang-cl": | |
| args.append("-m" + self.arch) | |
| if self.opt == "none": | |
| args.append("/Od") | |
| elif self.opt == "basic": | |
| args.append("/O2") | |
| elif self.opt == "lto": | |
| if self.toolchain_type == "msvc": | |
| args.append("/GL") | |
| args.append("/Gw") | |
| else: | |
| args.append("-flto=thin") | |
| if self.nodefaultlib: | |
| args.append("/GS-") | |
| args.append("/GR-") | |
| args.append("/Z7") | |
| if self.toolchain_type == "clang-cl": | |
| args.append("-Xclang") | |
| args.append("-fkeep-static-consts") | |
| args.append("-fms-compatibility-version=19") | |
| args.append("/c") | |
| if self.std: | |
| args.append("/std:" + self.std) | |
| args.append("/Fo" + obj) | |
| if self.toolchain_type == "clang-cl": | |
| args.append("--") | |
| args.append(source) | |
| return ("compiling", [source], obj, self.compile_env, args) | |
| def _get_link_command(self): | |
| args = [] | |
| args.append(self.linker) | |
| args.append("/DEBUG:FULL") | |
| args.append("/INCREMENTAL:NO") | |
| if self.nodefaultlib: | |
| args.append("/nodefaultlib") | |
| args.append("/entry:main") | |
| args.append("/PDB:" + self._pdb_file_name()) | |
| args.append("/OUT:" + self._exe_file_name()) | |
| args.extend(self._obj_file_names()) | |
| return ( | |
| "linking", | |
| self._obj_file_names(), | |
| self._exe_file_name(), | |
| self.link_env, | |
| args, | |
| ) | |
| def output_files(self): | |
| outputs = [] | |
| if self.mode == "compile" or self.mode == "compile-and-link": | |
| outputs.extend(self._ilk_file_names()) | |
| outputs.extend(self._obj_file_names()) | |
| if self.mode == "link" or self.mode == "compile-and-link": | |
| outputs.append(self._pdb_file_name()) | |
| outputs.append(self._exe_file_name()) | |
| return [x for x in outputs if x is not None] | |
| class GccBuilder(Builder): | |
| def __init__(self, toolchain_type, args): | |
| Builder.__init__(self, toolchain_type, args, ".o") | |
| if sys.platform == "darwin": | |
| cmd = ["xcrun", "--sdk", args.apple_sdk, "--show-sdk-path"] | |
| self.apple_sdk = subprocess.check_output(cmd).strip().decode("utf-8") | |
| def _add_m_option_if_needed(self, args): | |
| # clang allows -m(32|64) for any target, gcc does not. | |
| uname = platform.uname().machine.lower() | |
| if self.toolchain_type != "gcc" or ( | |
| not "arm" in uname and not "aarch64" in uname | |
| ): | |
| args.append("-m" + self.arch) | |
| return args | |
| def _get_compilation_command(self, source, obj): | |
| args = [self.compiler_for_file(source)] | |
| args = self._add_m_option_if_needed(args) | |
| args.append("-g") | |
| if self.opt == "none": | |
| args.append("-O0") | |
| elif self.opt == "basic": | |
| args.append("-O2") | |
| elif self.opt == "lto": | |
| args.append("-flto=thin") | |
| if self.nodefaultlib: | |
| args.append("-nostdinc") | |
| args.append("-static") | |
| args.append("-c") | |
| if sys.platform == "darwin": | |
| args.extend(["-isysroot", self.apple_sdk]) | |
| elif self.objc_gnustep_inc: | |
| if source.endswith(".m") or source.endswith(".mm"): | |
| args.extend(["-fobjc-runtime=gnustep-2.0", "-I", self.objc_gnustep_inc]) | |
| if sys.platform == "win32": | |
| args.extend( | |
| ["-Xclang", "-gcodeview", "-Xclang", "--dependent-lib=msvcrtd"] | |
| ) | |
| elif self.sysroot: | |
| args.extend(["--sysroot", self.sysroot]) | |
| if self.std: | |
| args.append("-std={0}".format(self.std)) | |
| args.extend(["-o", obj]) | |
| args.append(source) | |
| return ("compiling", [source], obj, None, args) | |
| def _get_link_command(self): | |
| args = [self.linker] | |
| args = self._add_m_option_if_needed(args) | |
| if self.nodefaultlib: | |
| args.append("-nostdlib") | |
| args.append("-static") | |
| main_symbol = "main" | |
| if sys.platform == "darwin": | |
| main_symbol = "_main" | |
| args.append("-Wl,-e," + main_symbol) | |
| if sys.platform.startswith("netbsd"): | |
| for x in self.lib_paths: | |
| args += ["-L" + x, "-Wl,-rpath," + x] | |
| args.extend(["-o", self._exe_file_name()]) | |
| args.extend(self._obj_file_names()) | |
| if sys.platform == "darwin": | |
| # By default, macOS doesn't allow injecting the ASAN | |
| # runtime into system processes. | |
| system_clang = ( | |
| subprocess.check_output(["xcrun", "-find", "clang"]) | |
| .strip() | |
| .decode("utf-8") | |
| ) | |
| system_liblto = os.path.join( | |
| os.path.dirname(os.path.dirname(system_clang)), "lib", "libLTO.dylib" | |
| ) | |
| args.extend(["-isysroot", self.apple_sdk]) | |
| args.extend(["-Wl,-lto_library", "-Wl," + system_liblto]) | |
| elif self.objc_gnustep_lib: | |
| args.extend(["-L", self.objc_gnustep_lib, "-lobjc"]) | |
| if sys.platform == "linux": | |
| args.extend(["-Wl,-rpath," + self.objc_gnustep_lib]) | |
| elif sys.platform == "win32": | |
| args.extend( | |
| ["-fuse-ld=lld-link", "-g", "-Xclang", "--dependent-lib=msvcrtd"] | |
| ) | |
| elif self.sysroot: | |
| args.extend(["--sysroot", self.sysroot]) | |
| return ("linking", self._obj_file_names(), self._exe_file_name(), None, args) | |
| def output_files(self): | |
| outputs = [] | |
| if self.mode == "compile" or self.mode == "compile-and-link": | |
| outputs.extend(self._obj_file_names()) | |
| if self.mode == "link" or self.mode == "compile-and-link": | |
| outputs.append(self._exe_file_name()) | |
| return outputs | |
| def indent(text, spaces): | |
| def prefixed_lines(): | |
| prefix = " " * spaces | |
| for line in text.splitlines(True): | |
| yield prefix + line | |
| return "".join(prefixed_lines()) | |
| def build(commands): | |
| global args | |
| for status, inputs, output, env, child_args in commands: | |
| print("\n\n") | |
| inputs = [os.path.basename(x) for x in inputs] | |
| output = os.path.basename(output) | |
| print(status + " {0} -> {1}".format("+".join(inputs), output)) | |
| if args.verbose: | |
| print(" Command Line: " + " ".join(child_args)) | |
| print(" Env:") | |
| print_environment(env) | |
| if args.dry: | |
| continue | |
| popen = subprocess.Popen( | |
| child_args, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| env=env, | |
| universal_newlines=True, | |
| ) | |
| stdout, stderr = popen.communicate() | |
| res = popen.wait() | |
| if res == -signal.SIGINT: | |
| raise KeyboardInterrupt | |
| print(" STDOUT:") | |
| print(indent(stdout, 4)) | |
| if res != 0: | |
| print(" STDERR:") | |
| print(indent(stderr, 4)) | |
| sys.exit(res) | |
| def clean(files): | |
| global args | |
| if not files: | |
| return | |
| for o in files: | |
| file = o if args.verbose else os.path.basename(o) | |
| print("Cleaning {0}".format(file)) | |
| try: | |
| if os.path.exists(o): | |
| if not args.dry: | |
| os.remove(o) | |
| if args.verbose: | |
| print(" The file was successfully cleaned.") | |
| elif args.verbose: | |
| print(" The file does not exist.") | |
| except: | |
| if args.verbose: | |
| print(" The file could not be removed.") | |
| def fix_arguments(args): | |
| if not args.inputs: | |
| raise ValueError("No input files specified") | |
| if args.output and args.mode == "compile" and len(args.inputs) > 1: | |
| raise ValueError( | |
| "Cannot specify -o with mode=compile and multiple source files. Use --outdir instead." | |
| ) | |
| if not args.dry: | |
| args.inputs = [os.path.abspath(x) for x in args.inputs] | |
| # If user didn't specify the outdir, use the directory of the first input. | |
| if not args.outdir: | |
| if args.output: | |
| args.outdir = os.path.dirname(args.output) | |
| else: | |
| args.outdir = os.path.dirname(args.inputs[0]) | |
| args.outdir = os.path.abspath(args.outdir) | |
| args.outdir = os.path.normpath(args.outdir) | |
| # If user specified a non-absolute path for the output file, append the | |
| # output directory to it. | |
| if args.output: | |
| if not os.path.isabs(args.output): | |
| args.output = os.path.join(args.outdir, args.output) | |
| args.output = os.path.normpath(args.output) | |
| fix_arguments(args) | |
| (toolchain_type, c_compiler, cxx_compiler) = find_toolchain( | |
| args.compiler, args.tools_dir | |
| ) | |
| if not toolchain_type: | |
| print(f"Unable to find toolchain {args.compiler}") | |
| sys.exit(1) | |
| if args.verbose: | |
| print("Script Arguments:") | |
| print(" Arch: " + args.arch) | |
| print(" Compiler: " + args.compiler) | |
| print(" Outdir: " + args.outdir) | |
| print(" Output: " + args.output) | |
| print(" Nodefaultlib: " + str(args.nodefaultlib)) | |
| print(" Opt: " + args.opt) | |
| print(" Mode: " + args.mode) | |
| print(" Clean: " + str(args.clean)) | |
| print(" Verbose: " + str(args.verbose)) | |
| print(" Dryrun: " + str(args.dry)) | |
| print(" Inputs: " + format_text(args.inputs, 0, 10)) | |
| print(" C/C++ Standard: " + str(args.std)) | |
| print("Script Environment:") | |
| print_environment(os.environ) | |
| args.c_compiler = c_compiler | |
| args.cxx_compiler = cxx_compiler | |
| if not args.dry and not (os.path.exists(c_compiler) and os.path.exists(cxx_compiler)): | |
| raise ValueError( | |
| f"The toolchain {toolchain_type} compilers {c_compiler} and/or {cxx_compiler} do not exist" | |
| ) | |
| if toolchain_type == "msvc" or toolchain_type == "clang-cl": | |
| builder = MsvcBuilder(toolchain_type, args) | |
| else: | |
| builder = GccBuilder(toolchain_type, args) | |
| if args.clean: | |
| clean(builder.output_files()) | |
| cmds = builder.build_commands() | |
| build(cmds) |