968 lines
32 KiB
Python
968 lines
32 KiB
Python
|
#!/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)
|