mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			847 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			847 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
#!/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))
 | 
						|
 | 
						|
        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 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()
 |