#!/usr/bin/python3 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Only necessary for flake8 to be happy... import argparse import errno import fnmatch import glob import json import os import os.path import platform import re import shutil import subprocess import sys import tarfile from contextlib import contextmanager from shutil import which
def is_llvm_toolchain(cc, cxx): return"clang"in cc and"clang"in cxx
def check_run(args):
print(" ".join(args), file=sys.stderr, flush=True) if args[0] == "cmake": # CMake `message(STATUS)` messages, as appearing in failed source code # compiles, appear on stdout, so we only capture that.
p = subprocess.Popen(args, stdout=subprocess.PIPE)
lines = [] for line in p.stdout:
lines.append(line)
sys.stdout.write(line.decode())
sys.stdout.flush()
r = p.wait() if r != 0 and os.environ.get("UPLOAD_DIR"):
cmake_output_re = re.compile(b'See also "(.*/CMakeOutput.log)"')
cmake_error_re = re.compile(b'See also "(.*/CMakeError.log)"')
def find_first_match(re): for l in lines:
match = re.search(l) if match: return match
upload_dir = os.environ["UPLOAD_DIR"].encode("utf-8") if output_match or error_match:
mkdir_p(upload_dir) if output_match:
shutil.copy2(output_match.group(1), upload_dir) if error_match:
shutil.copy2(error_match.group(1), upload_dir) else:
r = subprocess.call(args) assert r == 0
def run_in(path, args): with chdir(path):
check_run(args)
def build_package(package_build_dir, cmake_args): ifnot os.path.exists(package_build_dir):
os.mkdir(package_build_dir) # If CMake has already been run, it may have been run with different # arguments, so we need to re-run it. Make sure the cached copy of the # previous CMake run is cleared before running it again. if os.path.exists(package_build_dir + "/CMakeCache.txt"):
os.remove(package_build_dir + "/CMakeCache.txt") if os.path.exists(package_build_dir + "/CMakeFiles"):
shutil.rmtree(package_build_dir + "/CMakeFiles")
def build_tar_package(name, base, directory):
name = os.path.realpath(name)
print("tarring {} from {}/{}".format(name, base, directory), file=sys.stderr) assert name.endswith(".tar.zst")
cctx = zstandard.ZstdCompressor() with open(name, "wb") as f, cctx.stream_writer(f) as z: with tarfile.open(mode="w|", fileobj=z) as tf: with chdir(base):
tf.add(directory)
def mkdir_p(path): try:
os.makedirs(path) except OSError as e: if e.errno != errno.EEXIST ornot os.path.isdir(path): raise
def is_cross_compile(target):
target_system, target_machine = SUPPORTED_TARGETS[target]
system, machine = (platform.system(), platform.machine()) if system != target_system: returnTrue # Don't consider x86 mac on arm64 mac a cross-compile so that we # can build x86 mac clang on arm64 mac via Rosetta, as if they # were building on x86. if system == "Darwin"and machine == "arm64": returnFalse return machine != target_machine
if is_final_stage:
cmake_args += ["-DLLVM_ENABLE_LIBXML2=FORCE_ON"] if is_linux(target) and is_final_stage:
sysroot = os.path.join(os.environ.get("MOZ_FETCHES_DIR", ""), "sysroot") if os.path.exists(sysroot):
cmake_args += ["-DLLVM_BINUTILS_INCDIR=/usr/include"]
cmake_args += ["-DCMAKE_SYSROOT=%s" % sysroot] # Work around the LLVM build system not building the i386 compiler-rt # because it doesn't allow to use a sysroot for that during the cmake # checks.
cmake_args += ["-DCAN_TARGET_i386=1"]
cmake_args += ["-DLLVM_ENABLE_TERMINFO=OFF"] if is_windows(target):
cmake_args.insert(-1, "-DLLVM_EXPORT_SYMBOLS_FOR_PLUGINS=ON")
cmake_args.insert(-1, "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded") if is_cross_compile(target):
cmake_args += [
f"-DCMAKE_TOOLCHAIN_FILE={src_dir}/cmake/platforms/WinMsvc.cmake",
f"-DLLVM_NATIVE_TOOLCHAIN={os.path.dirname(os.path.dirname(cc[0]))}",
f"-DHOST_ARCH={target[: -len('-pc-windows-msvc')]}",
f"-DLLVM_WINSYSROOT={os.environ['VSINSTALLDIR']}", "-DLLVM_DISABLE_ASSEMBLY_FILES=ON",
] if is_final_stage:
fetches = os.environ["MOZ_FETCHES_DIR"]
cmake_args += [ "-DLIBXML2_DEFINITIONS=-DLIBXML_STATIC",
f"-DLIBXML2_INCLUDE_DIR={fetches}/libxml2/include/libxml2",
f"-DLIBXML2_LIBRARIES={fetches}/libxml2/lib/libxml2s.lib",
] else: # libllvm as a shared library is not supported on Windows
cmake_args += ["-DLLVM_LINK_LLVM_DYLIB=ON"] if ranlib isnotNone:
cmake_args += ["-DCMAKE_RANLIB=%s" % slashify_path(ranlib)] if libtool isnotNone:
cmake_args += ["-DCMAKE_LIBTOOL=%s" % slashify_path(libtool)] if is_darwin(target):
arch = "arm64"if target.startswith("aarch64") else"x86_64"
cmake_args += [ "-DCMAKE_SYSTEM_VERSION=%s" % os.environ["MACOSX_DEPLOYMENT_TARGET"], "-DCMAKE_OSX_SYSROOT=%s" % slashify_path(os.getenv("OSX_SYSROOT")), "-DCMAKE_FIND_ROOT_PATH=%s" % slashify_path(os.getenv("OSX_SYSROOT")), "-DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER", "-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY", "-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY", "-DCMAKE_MACOSX_RPATH=ON", "-DCMAKE_OSX_ARCHITECTURES=%s" % arch, "-DDARWIN_osx_ARCHS=%s" % arch, "-DDARWIN_osx_SYSROOT=%s" % slashify_path(os.getenv("OSX_SYSROOT")),
] if arch == "arm64":
cmake_args += [ "-DDARWIN_osx_BUILTIN_ARCHS=arm64",
] # Starting in LLVM 11 (which requires SDK 10.12) the build tries to # detect the SDK version by calling xcrun. Cross-compiles don't have # an xcrun, so we have to set the version explicitly.
cmake_args += [ "-DDARWIN_macosx_OVERRIDE_SDK_VERSION=%s"
% os.environ["MACOSX_DEPLOYMENT_TARGET"],
]
# Using LTO for both profile generation and usage to avoid most # "function control flow change detected (hash mismatch)" error. if profile andnot is_windows(target):
cmake_args.append("-DLLVM_ENABLE_LTO=Thin") return cmake_args
# For some reasons the import library clang.lib of clang.exe is not # installed, so we copy it by ourselves. if is_windows(target) and is_final_stage:
install_import_library(build_dir, inst_dir)
# Return the absolute path of a build tool. We first look to see if the # variable is defined in the config file, and if so we make sure it's an # absolute path to an existing tool, otherwise we look for a program in # $PATH named "key". # # This expects the name of the key in the config file to match the name of # the tool in the default toolchain on the system (for example, "ld" on Unix # and "link" on Windows). def get_tool(config, key):
f = None if key in config:
f = config[key].format(**os.environ) if os.path.isabs(f):
path, f = os.path.split(f) # Searches for .exes on windows too, even if the extension is # not given. which(absolute_path) doesn't do that until python 3.12.
f = which(f, path=path) ifnot f: raise ValueError("%s must point to an existing path" % key) return f
# Assume that we have the name of some program that should be on PATH.
tool = which(f) if f else which(key) ifnot tool: raise ValueError("%s not found on PATH" % (f or key)) return tool
# This function is intended to be called on the final build directory when # building clang-tidy. Also clang-format binaries are included that can be used # in conjunction with clang-tidy. # As a separate binary we also ship clangd for the language server protocol that # can be used as a plugin in `vscode`. # Its job is to remove all of the files which won't be used for clang-tidy or # clang-format to reduce the download size. Currently when this function # finishes its job, it will leave final_dir with a layout like this: # # clang/ # bin/ # clang-apply-replacements # clang-format # clang-tidy # clangd # run-clang-tidy # include/ # * (nothing will be deleted here) # lib/ # clang/ # 4.0.0/ # include/ # * (nothing will be deleted here) # share/ # clang/ # clang-format-diff.py # clang-tidy-diff.py # run-clang-tidy.py def prune_final_dir_for_clang_tidy(final_dir, target): # Make sure we only have what we expect.
dirs = [ "bin", "include", "lib", "lib32", "libexec", "msbuild-bin", "share", "tools",
] if is_linux(target):
dirs.append("x86_64-unknown-linux-gnu") for f in glob.glob("%s/*" % final_dir): if os.path.basename(f) notin dirs: raise Exception("Found unknown file %s in the final directory" % f) ifnot os.path.isdir(f): raise Exception("Expected %s to be a directory" % f)
kept_binaries = [ "clang-apply-replacements", "clang-format", "clang-tidy", "clangd", "clang-query", "run-clang-tidy",
]
re_clang_tidy = re.compile(r"^(" + "|".join(kept_binaries) + r")(\.exe)?$", re.I) for f in glob.glob("%s/bin/*" % final_dir): if re_clang_tidy.search(os.path.basename(f)) isNone:
delete(f)
# Keep include/ intact.
# Remove the target-specific files. if is_linux(target): if os.path.exists(os.path.join(final_dir, "x86_64-unknown-linux-gnu")):
shutil.rmtree(os.path.join(final_dir, "x86_64-unknown-linux-gnu"))
# In lib/, only keep lib/clang/N.M.O/include and the LLVM shared library.
re_ver_num = re.compile(r"^\d+(?:\.\d+\.\d+)?$", re.I) for f in glob.glob("%s/lib/*" % final_dir):
name = os.path.basename(f) if name == "clang": continue if is_darwin(target) and name in ["libLLVM.dylib", "libclang-cpp.dylib"]: continue if is_linux(target) and (
fnmatch.fnmatch(name, "libLLVM*.so*") or fnmatch.fnmatch(name, "libclang-cpp.so*")
): continue
delete(f) for f in glob.glob("%s/lib/clang/*" % final_dir): if re_ver_num.search(os.path.basename(f)) isNone:
delete(f) for f in glob.glob("%s/lib/clang/*/*" % final_dir): if os.path.basename(f) != "include":
delete(f)
# Completely remove libexec/, msbuild-bin and tools, if it exists.
shutil.rmtree(os.path.join(final_dir, "libexec")) for d in ("msbuild-bin", "tools"):
d = os.path.join(final_dir, d) if os.path.exists(d):
shutil.rmtree(d)
# In share/, only keep share/clang/*tidy*
re_clang_tidy = re.compile(r"format|tidy", re.I) for f in glob.glob("%s/share/*" % final_dir): if os.path.basename(f) != "clang":
delete(f) for f in glob.glob("%s/share/clang/*" % final_dir): if re_clang_tidy.search(os.path.basename(f)) isNone:
delete(f)
ifnot os.path.exists("llvm/README.txt"): raise Exception( "The script must be run from the root directory of the llvm-project tree"
)
source_dir = os.getcwd()
build_dir = source_dir + "/build"
if args.clean:
shutil.rmtree(build_dir)
os.sys.exit(0)
llvm_source_dir = source_dir + "/llvm"
config = {} # Merge all the configs we got from the command line. for c in args.config:
this_config_dir = os.path.dirname(c.name)
this_config = json.load(c)
patches = this_config.get("patches") if patches:
this_config["patches"] = [os.path.join(this_config_dir, p) for p in patches] for key, value in this_config.items():
old_value = config.get(key) if old_value isNone:
config[key] = value elif value isNone: if key in config: del config[key] elif type(old_value) isnot type(value): raise Exception( "{} is overriding `{}` with a value of the wrong type".format(
c.name, key
)
) elif isinstance(old_value, list): for v in value: if v notin old_value:
old_value.append(v) elif isinstance(old_value, dict): raise Exception("{} is setting `{}` to a dict?".format(c.name, key)) else:
config[key] = value
stages = 2 if"stages"in config:
stages = int(config["stages"]) if stages notin (1, 2, 3, 4): raise ValueError("We only know how to build 1, 2, 3, or 4 stages.")
skip_stages = 0 if"skip_stages"in config: # The assumption here is that the compiler given in `cc` and other configs # is the result of the last skip stage, built somewhere else.
skip_stages = int(config["skip_stages"]) if skip_stages >= stages: raise ValueError("Cannot skip more stages than are built.")
pgo = False if"pgo"in config:
pgo = config["pgo"] if pgo notin (True, False): raise ValueError("Only boolean values are accepted for pgo.")
build_type = "Release" if"build_type"in config:
build_type = config["build_type"] if build_type notin ("Release", "Debug", "RelWithDebInfo", "MinSizeRel"): raise ValueError( "We only know how to do Release, Debug, RelWithDebInfo or " "MinSizeRel builds"
)
targets = config.get("targets")
build_clang_tidy = False if"build_clang_tidy"in config:
build_clang_tidy = config["build_clang_tidy"] if build_clang_tidy notin (True, False): raise ValueError("Only boolean values are accepted for build_clang_tidy.")
build_clang_tidy_alpha = False # check for build_clang_tidy_alpha only if build_clang_tidy is true if build_clang_tidy and"build_clang_tidy_alpha"in config:
build_clang_tidy_alpha = config["build_clang_tidy_alpha"] if build_clang_tidy_alpha notin (True, False): raise ValueError( "Only boolean values are accepted for build_clang_tidy_alpha."
)
build_clang_tidy_external = False # check for build_clang_tidy_external only if build_clang_tidy is true if build_clang_tidy and"build_clang_tidy_external"in config:
build_clang_tidy_external = config["build_clang_tidy_external"] if build_clang_tidy_external notin (True, False): raise ValueError( "Only boolean values are accepted for build_clang_tidy_external."
)
assertions = False if"assertions"in config:
assertions = config["assertions"] if assertions notin (True, False): raise ValueError("Only boolean values are accepted for assertions.")
for t in SUPPORTED_TARGETS: ifnot is_cross_compile(t):
host = t break else: raise Exception(
f"Cannot use this script on {platform.system()} {platform.machine()}"
)
target = config.get("target", host) if target notin SUPPORTED_TARGETS: raise ValueError(f"{target} is not a supported target.")
if is_cross_compile(target) andnot is_linux(host): raise Exception("Cross-compilation is only supported on Linux")
if is_darwin(target):
os.environ["MACOSX_DEPLOYMENT_TARGET"] = ( "11.0"if target.startswith("aarch64") else"10.12"
)
# Used by llvm/lib/DebugInfo/PDB
os.environ["VSCMD_ARG_TGT_ARCH"] = SUPPORTED_TARGETS[target][1].lower() else:
exe_ext = ""
cc_name = "clang"
cxx_name = "clang++"
cc = get_tool(config, "cc")
cxx = get_tool(config, "cxx")
asm = get_tool(config, "ml"if is_windows(target) else"as") # Not using lld here as default here because it's not in PATH. But clang # knows how to find it when they are installed alongside each others.
ar = get_tool(config, "lib"if is_windows(target) else"ar")
ranlib = Noneif is_windows(target) else get_tool(config, "ranlib")
libtool = get_tool(config, "libtool") if is_darwin(target) elseNone
if is_darwin(target):
extra_cflags = []
extra_cxxflags = []
extra_cflags2 = []
extra_cxxflags2 = []
extra_asmflags = [] # It's unfortunately required to specify the linker used here because # the linker flags are used in LLVM's configure step before # -DLLVM_ENABLE_LLD is actually processed.
extra_ldflags = [ "-fuse-ld=lld", "-Wl,-dead_strip",
] elif is_linux(target):
extra_cflags = []
extra_cxxflags = []
extra_cflags2 = ["-fPIC"] # Silence clang's warnings about arguments not being used in compilation.
extra_cxxflags2 = [ "-fPIC", "-Qunused-arguments",
]
extra_asmflags = [] # Avoid libLLVM internal function calls going through the PLT.
extra_ldflags = ["-Wl,-Bsymbolic-functions"] # For whatever reason, LLVM's build system will set things up to turn # on -ffunction-sections and -fdata-sections, but won't turn on the # corresponding option to strip unused sections. We do it explicitly # here. LLVM's build system is also picky about turning on ICF, so # we do that explicitly here, too.
# It's unfortunately required to specify the linker used here because # the linker flags are used in LLVM's configure step before # -DLLVM_ENABLE_LLD is actually processed. if is_llvm_toolchain(cc, cxx):
extra_ldflags += ["-fuse-ld=lld", "-Wl,--icf=safe"]
extra_ldflags += ["-Wl,--gc-sections"] elif is_windows(target):
extra_cflags = []
extra_cxxflags = [] # clang-cl would like to figure out what it's supposed to be emulating # by looking at an MSVC install, but we don't really have that here. # Force things on based on WinMsvc.cmake. # Ideally, we'd just use WinMsvc.cmake as a toolchain file, but it only # really works for cross-compiles, which this is not. with open(os.path.join(llvm_source_dir, "cmake/platforms/WinMsvc.cmake")) as f:
compat = [
item for line in f for item in line.split() if"-fms-compatibility-version="in item
][0]
extra_cflags2 = [compat]
extra_cxxflags2 = [compat]
extra_asmflags = []
extra_ldflags = []
upload_dir = os.getenv("UPLOAD_DIR") if assertions and upload_dir:
extra_cflags2 += ["-fcrash-diagnostics-dir=%s" % upload_dir]
extra_cxxflags2 += ["-fcrash-diagnostics-dir=%s" % upload_dir]
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.