summaryrefslogtreecommitdiffstats
path: root/build/build-clang/build-clang.py
diff options
context:
space:
mode:
Diffstat (limited to 'build/build-clang/build-clang.py')
-rwxr-xr-xbuild/build-clang/build-clang.py843
1 files changed, 843 insertions, 0 deletions
diff --git a/build/build-clang/build-clang.py b/build/build-clang/build-clang.py
new file mode 100755
index 0000000000..08e05483f5
--- /dev/null
+++ b/build/build-clang/build-clang.py
@@ -0,0 +1,843 @@
+#!/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
+
+import zstandard
+
+SUPPORTED_TARGETS = {
+ "x86_64-unknown-linux-gnu": ("Linux", "x86_64"),
+ "x86_64-pc-windows-msvc": ("Windows", "AMD64"),
+ "x86_64-apple-darwin": ("Darwin", "x86_64"),
+ "aarch64-apple-darwin": ("Darwin", "arm64"),
+}
+
+
+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
+
+ output_match = find_first_match(cmake_output_re)
+ error_match = find_first_match(cmake_error_re)
+
+ 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)
+
+
+@contextmanager
+def chdir(path):
+ d = os.getcwd()
+ print('cd "%s"' % path, file=sys.stderr)
+ os.chdir(path)
+ try:
+ yield
+ finally:
+ print('cd "%s"' % d, file=sys.stderr)
+ os.chdir(d)
+
+
+def patch(patch, srcdir):
+ patch = os.path.realpath(patch)
+ check_run(["patch", "-d", srcdir, "-p1", "-i", patch, "--fuzz=0", "-s"])
+
+
+def import_clang_tidy(source_dir, build_clang_tidy_alpha, build_clang_tidy_external):
+ clang_plugin_path = os.path.join(os.path.dirname(sys.argv[0]), "..", "clang-plugin")
+ clang_tidy_path = os.path.join(source_dir, "clang-tools-extra/clang-tidy")
+ sys.path.append(clang_plugin_path)
+ from import_mozilla_checks import do_import
+
+ import_options = {
+ "alpha": build_clang_tidy_alpha,
+ "external": build_clang_tidy_external,
+ }
+ do_import(clang_plugin_path, clang_tidy_path, import_options)
+
+
+def build_package(package_build_dir, cmake_args):
+ if not 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")
+
+ run_in(package_build_dir, ["cmake"] + cmake_args)
+ run_in(package_build_dir, ["ninja", "install", "-v"])
+
+
+@contextmanager
+def updated_env(env):
+ old_env = os.environ.copy()
+ os.environ.update(env)
+ yield
+ os.environ.clear()
+ os.environ.update(old_env)
+
+
+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 or not os.path.isdir(path):
+ raise
+
+
+def delete(path):
+ if os.path.isdir(path):
+ shutil.rmtree(path)
+ else:
+ try:
+ os.unlink(path)
+ except Exception:
+ pass
+
+
+def install_import_library(build_dir, clang_dir):
+ shutil.copy2(
+ os.path.join(build_dir, "lib", "clang.lib"), os.path.join(clang_dir, "lib")
+ )
+
+
+def is_darwin(target):
+ return "-apple-darwin" in target
+
+
+def is_linux(target):
+ return "-linux-gnu" in target
+
+
+def is_windows(target):
+ return "-windows-msvc" in target
+
+
+def is_cross_compile(target):
+ return SUPPORTED_TARGETS[target] != (platform.system(), platform.machine())
+
+
+def build_one_stage(
+ cc,
+ cxx,
+ asm,
+ ar,
+ ranlib,
+ ldflags,
+ src_dir,
+ stage_dir,
+ package_name,
+ build_type,
+ assertions,
+ target,
+ targets,
+ is_final_stage=False,
+ profile=None,
+):
+ if not os.path.exists(stage_dir):
+ os.mkdir(stage_dir)
+
+ build_dir = stage_dir + "/build"
+ inst_dir = stage_dir + "/" + package_name
+
+ # cmake doesn't deal well with backslashes in paths.
+ def slashify_path(path):
+ return path.replace("\\", "/")
+
+ def cmake_base_args(cc, cxx, asm, ar, ranlib, ldflags, inst_dir):
+ machine_targets = targets if is_final_stage and targets else "X86"
+
+ cmake_args = [
+ "-GNinja",
+ "-DCMAKE_C_COMPILER=%s" % slashify_path(cc[0]),
+ "-DCMAKE_CXX_COMPILER=%s" % slashify_path(cxx[0]),
+ "-DCMAKE_ASM_COMPILER=%s" % slashify_path(asm[0]),
+ "-DCMAKE_AR=%s" % slashify_path(ar),
+ "-DCMAKE_C_FLAGS_INIT=%s" % " ".join(cc[1:]),
+ "-DCMAKE_CXX_FLAGS_INIT=%s" % " ".join(cxx[1:]),
+ "-DCMAKE_ASM_FLAGS_INIT=%s" % " ".join(asm[1:]),
+ "-DCMAKE_EXE_LINKER_FLAGS_INIT=%s" % " ".join(ldflags),
+ "-DCMAKE_SHARED_LINKER_FLAGS_INIT=%s" % " ".join(ldflags),
+ "-DCMAKE_BUILD_TYPE=%s" % build_type,
+ "-DCMAKE_INSTALL_PREFIX=%s" % inst_dir,
+ "-DLLVM_TARGETS_TO_BUILD=%s" % machine_targets,
+ "-DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF",
+ "-DLLVM_ENABLE_ASSERTIONS=%s" % ("ON" if assertions else "OFF"),
+ "-DLLVM_ENABLE_BINDINGS=OFF",
+ "-DLLVM_ENABLE_CURL=OFF",
+ "-DLLVM_INCLUDE_TESTS=OFF",
+ ]
+ if is_llvm_toolchain(cc[0], cxx[0]):
+ cmake_args += ["-DLLVM_ENABLE_LLD=ON"]
+ elif is_windows(target) and is_cross_compile(target):
+ raise Exception(
+ "Cannot cross-compile for Windows with a compiler that is not clang"
+ )
+
+ if "TASK_ID" in os.environ:
+ cmake_args += [
+ "-DCLANG_REPOSITORY_STRING=taskcluster-%s" % os.environ["TASK_ID"],
+ ]
+ projects = ["clang", "lld"]
+ if is_final_stage:
+ projects.append("clang-tools-extra")
+ else:
+ cmake_args.append("-DLLVM_TOOL_LLI_BUILD=OFF")
+
+ cmake_args.append("-DLLVM_ENABLE_PROJECTS=%s" % ";".join(projects))
+
+ # There is no libxml2 on Windows except if we build one ourselves.
+ # libxml2 is only necessary for llvm-mt, but Windows can just use the
+ # native MT tool.
+ if not is_windows(target) and 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",
+ ]
+ else:
+ # libllvm as a shared library is not supported on Windows
+ cmake_args += ["-DLLVM_LINK_LLVM_DYLIB=ON"]
+ if ranlib is not None:
+ cmake_args += ["-DCMAKE_RANLIB=%s" % slashify_path(ranlib)]
+ if is_darwin(target) and is_cross_compile(target):
+ arch = "arm64" if target.startswith("aarch64") else "x86_64"
+ cmake_args += [
+ "-DCMAKE_SYSTEM_NAME=Darwin",
+ "-DCMAKE_SYSTEM_VERSION=%s" % os.environ["MACOSX_DEPLOYMENT_TARGET"],
+ "-DCMAKE_OSX_SYSROOT=%s" % slashify_path(os.getenv("CROSS_SYSROOT")),
+ "-DCMAKE_FIND_ROOT_PATH=%s" % slashify_path(os.getenv("CROSS_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("CROSS_SYSROOT")),
+ "-DLLVM_DEFAULT_TARGET_TRIPLE=%s" % target,
+ "-DCMAKE_C_COMPILER_TARGET=%s" % target,
+ "-DCMAKE_CXX_COMPILER_TARGET=%s" % target,
+ "-DCMAKE_ASM_COMPILER_TARGET=%s" % target,
+ ]
+ 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"],
+ ]
+
+ if profile == "gen":
+ # Per https://releases.llvm.org/10.0.0/docs/HowToBuildWithPGO.html
+ cmake_args += [
+ "-DLLVM_BUILD_INSTRUMENTED=IR",
+ "-DLLVM_BUILD_RUNTIME=No",
+ ]
+ elif profile:
+ cmake_args += [
+ "-DLLVM_PROFDATA_FILE=%s" % profile,
+ ]
+
+ # Using LTO for both profile generation and usage to avoid most
+ # "function control flow change detected (hash mismatch)" error.
+ if profile and not is_windows(target):
+ cmake_args.append("-DLLVM_ENABLE_LTO=Thin")
+ return cmake_args
+
+ cmake_args = []
+ cmake_args += cmake_base_args(cc, cxx, asm, ar, ranlib, ldflags, inst_dir)
+ cmake_args += [src_dir]
+ build_package(build_dir, 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):
+ if not os.path.exists(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)
+ if not 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) not in dirs:
+ raise Exception("Found unknown file %s in the final directory" % f)
+ if not 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)) is None:
+ 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)) is None:
+ 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)) is None:
+ delete(f)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "-c",
+ "--config",
+ action="append",
+ required=True,
+ type=argparse.FileType("r"),
+ help="Clang configuration file",
+ )
+ parser.add_argument(
+ "--clean", required=False, action="store_true", help="Clean the build directory"
+ )
+ parser.add_argument(
+ "--skip-tar",
+ required=False,
+ action="store_true",
+ help="Skip tar packaging stage",
+ )
+ parser.add_argument(
+ "--skip-patch",
+ required=False,
+ action="store_true",
+ help="Do not patch source",
+ )
+
+ args = parser.parse_args()
+
+ if not 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 is None:
+ config[key] = value
+ elif value is None:
+ if key in config:
+ del config[key]
+ elif type(old_value) != 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 not in 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 not in (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 not in (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 not in ("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 not in (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 not in (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 not in (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 not in (True, False):
+ raise ValueError("Only boolean values are accepted for assertions.")
+
+ for t in SUPPORTED_TARGETS:
+ if not 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 not in SUPPORTED_TARGETS:
+ raise ValueError(f"{target} is not a supported target.")
+
+ if is_cross_compile(target) and not 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"
+ )
+
+ if is_windows(target):
+ exe_ext = ".exe"
+ cc_name = "clang-cl"
+ cxx_name = "clang-cl"
+ 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 = None if is_windows(target) else get_tool(config, "ranlib")
+
+ if not os.path.exists(source_dir):
+ os.makedirs(source_dir)
+
+ if not args.skip_patch:
+ for p in config.get("patches", []):
+ patch(p, source_dir)
+
+ package_name = "clang"
+ if build_clang_tidy:
+ package_name = "clang-tidy"
+ if not args.skip_patch:
+ import_clang_tidy(
+ source_dir, build_clang_tidy_alpha, build_clang_tidy_external
+ )
+
+ if not os.path.exists(build_dir):
+ os.makedirs(build_dir)
+
+ stage1_dir = build_dir + "/stage1"
+ stage1_inst_dir = stage1_dir + "/" + package_name
+
+ final_stage_dir = stage1_dir
+
+ 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]
+
+ if skip_stages < 1:
+ build_one_stage(
+ [cc] + extra_cflags,
+ [cxx] + extra_cxxflags,
+ [asm] + extra_asmflags,
+ ar,
+ ranlib,
+ extra_ldflags,
+ llvm_source_dir,
+ stage1_dir,
+ package_name,
+ build_type,
+ assertions,
+ target,
+ targets,
+ is_final_stage=(stages == 1),
+ )
+
+ if stages >= 2 and skip_stages < 2:
+ stage2_dir = build_dir + "/stage2"
+ stage2_inst_dir = stage2_dir + "/" + package_name
+ final_stage_dir = stage2_dir
+ if skip_stages < 1:
+ cc = stage1_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)
+ cxx = stage1_inst_dir + "/bin/%s%s" % (cxx_name, exe_ext)
+ asm = stage1_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)
+ build_one_stage(
+ [cc] + extra_cflags2,
+ [cxx] + extra_cxxflags2,
+ [asm] + extra_asmflags,
+ ar,
+ ranlib,
+ extra_ldflags,
+ llvm_source_dir,
+ stage2_dir,
+ package_name,
+ build_type,
+ assertions,
+ target,
+ targets,
+ is_final_stage=(stages == 2),
+ profile="gen" if pgo else None,
+ )
+
+ if stages >= 3 and skip_stages < 3:
+ stage3_dir = build_dir + "/stage3"
+ stage3_inst_dir = stage3_dir + "/" + package_name
+ final_stage_dir = stage3_dir
+ if skip_stages < 2:
+ cc = stage2_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)
+ cxx = stage2_inst_dir + "/bin/%s%s" % (cxx_name, exe_ext)
+ asm = stage2_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)
+ build_one_stage(
+ [cc] + extra_cflags2,
+ [cxx] + extra_cxxflags2,
+ [asm] + extra_asmflags,
+ ar,
+ ranlib,
+ extra_ldflags,
+ llvm_source_dir,
+ stage3_dir,
+ package_name,
+ build_type,
+ assertions,
+ target,
+ targets,
+ (stages == 3),
+ )
+ if pgo:
+ llvm_profdata = stage2_inst_dir + "/bin/llvm-profdata%s" % exe_ext
+ merge_cmd = [llvm_profdata, "merge", "-o", "merged.profdata"]
+ profraw_files = glob.glob(
+ os.path.join(stage2_dir, "build", "profiles", "*.profraw")
+ )
+ run_in(stage3_dir, merge_cmd + profraw_files)
+ if stages == 3:
+ mkdir_p(upload_dir)
+ shutil.copy2(os.path.join(stage3_dir, "merged.profdata"), upload_dir)
+ return
+
+ if stages >= 4 and skip_stages < 4:
+ stage4_dir = build_dir + "/stage4"
+ final_stage_dir = stage4_dir
+ profile = None
+ if pgo:
+ if skip_stages == 3:
+ profile_dir = os.environ.get("MOZ_FETCHES_DIR", "")
+ else:
+ profile_dir = stage3_dir
+ profile = os.path.join(profile_dir, "merged.profdata")
+ if skip_stages < 3:
+ cc = stage3_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)
+ cxx = stage3_inst_dir + "/bin/%s%s" % (cxx_name, exe_ext)
+ asm = stage3_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)
+ build_one_stage(
+ [cc] + extra_cflags2,
+ [cxx] + extra_cxxflags2,
+ [asm] + extra_asmflags,
+ ar,
+ ranlib,
+ extra_ldflags,
+ llvm_source_dir,
+ stage4_dir,
+ package_name,
+ build_type,
+ assertions,
+ target,
+ targets,
+ (stages == 4),
+ profile=profile,
+ )
+
+ if build_clang_tidy:
+ prune_final_dir_for_clang_tidy(
+ os.path.join(final_stage_dir, package_name), target
+ )
+
+ if not args.skip_tar:
+ build_tar_package("%s.tar.zst" % package_name, final_stage_dir, package_name)
+
+
+if __name__ == "__main__":
+ main()