| #!/usr/bin/env python |
| |
| import argparse |
| import os |
| import platform |
| import shutil |
| import signal |
| import subprocess |
| import sys |
| |
| if sys.platform == "win32": |
| # This module was renamed in Python 3. Make sure to import it using a |
| # consistent name regardless of python version. |
| try: |
| import winreg |
| except: |
| import _winreg as 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 == "msvc": |
| return ("msvc", find_executable("cl", tools_dir)) |
| if compiler == "clang-cl": |
| return ("clang-cl", find_executable("clang-cl", tools_dir)) |
| if compiler == "gcc": |
| return ("gcc", find_executable("g++", tools_dir)) |
| if compiler == "clang": |
| return ("clang", find_executable("clang++", 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, dir) = find_toolchain(toolchain, tools_dir) |
| if type and dir: |
| return (type, dir) |
| # Could not find any toolchain. |
| return (None, None) |
| |
| # 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) |
| if name == "clang-cl": |
| return ("clang-cl", compiler) |
| if name.startswith("clang"): |
| return ("clang", compiler) |
| if name.startswith("gcc") or name.startswith("g++"): |
| return ("gcc", compiler) |
| if name == "cc" or name == "c++": |
| return ("generic", compiler) |
| return ("unknown", 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.compiler = args.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 |
| |
| |
| 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.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.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.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.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 distutils.version import StrictVersion |
| |
| subdirs.sort(key=lambda x: StrictVersion(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) |
| |
| # Windows SDK version numbers consist of 4 dotted components, so we |
| # have to use LooseVersion, as StrictVersion supports 3 or fewer. |
| from distutils.version import LooseVersion |
| |
| sdk_versions.sort(key=lambda x: LooseVersion(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.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 = [] |
| |
| args.append(self.compiler) |
| 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") |
| |
| args.append("/Fo" + obj) |
| if self.toolchain_type == "clang-cl": |
| args.append("--") |
| args.append(source) |
| |
| if self.std: |
| args.append("/std:" + self.std) |
| |
| 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 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 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 = [] |
| |
| args.append(self.compiler) |
| 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 = [] |
| args.append(self.compiler) |
| 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": |
| args.extend(["-isysroot", self.apple_sdk]) |
| 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, toolchain_path) = find_toolchain(args.compiler, args.tools_dir) |
| if not toolchain_path or not toolchain_type: |
| print("Unable to find toolchain {0}".format(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.compiler = toolchain_path |
| if not os.path.exists(args.compiler) and not args.dry: |
| raise ValueError("The toolchain {} does not exist.".format(args.compiler)) |
| |
| 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) |