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.py1067
1 files changed, 1067 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..c935e3dfc8
--- /dev/null
+++ b/build/build-clang/build-clang.py
@@ -0,0 +1,1067 @@
+#!/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...
+from __future__ import print_function
+
+import os
+import os.path
+import shutil
+import subprocess
+import platform
+import json
+import argparse
+import fnmatch
+import glob
+import errno
+import re
+import sys
+import tarfile
+from contextlib import contextmanager
+from distutils.dir_util import copy_tree
+
+from shutil import which
+
+import zstandard
+
+
+def symlink(source, link_name):
+ os_symlink = getattr(os, "symlink", None)
+ if callable(os_symlink):
+ os_symlink(source, link_name)
+ else:
+ if os.path.isdir(source):
+ # Fall back to copying the directory :(
+ copy_tree(source, link_name)
+
+
+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:
+ 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)
+
+ def dump_file(log):
+ with open(log, "rb") as f:
+ print("\nContents of", log, "follow\n", file=sys.stderr)
+ print(f.read(), file=sys.stderr)
+
+ if output_match:
+ dump_file(output_match.group(1))
+ if error_match:
+ dump_file(error_match.group(1))
+ 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_libgcc(gcc_dir, clang_dir, is_final_stage):
+ gcc_bin_dir = os.path.join(gcc_dir, "bin")
+
+ # Copy over gcc toolchain bits that clang looks for, to ensure that
+ # clang is using a consistent version of ld, since the system ld may
+ # be incompatible with the output clang produces. But copy it to a
+ # target-specific directory so a cross-compiler to Mac doesn't pick
+ # up the (Linux-specific) ld with disastrous results.
+ #
+ # Only install this for the bootstrap process; we expect any consumers of
+ # the newly-built toolchain to provide an appropriate ld themselves.
+ if not is_final_stage:
+ x64_bin_dir = os.path.join(clang_dir, "x86_64-unknown-linux-gnu", "bin")
+ mkdir_p(x64_bin_dir)
+ shutil.copy2(os.path.join(gcc_bin_dir, "ld"), x64_bin_dir)
+
+ out = subprocess.check_output(
+ [os.path.join(gcc_bin_dir, "gcc"), "-print-libgcc-file-name"]
+ )
+
+ libgcc_dir = os.path.dirname(out.decode().rstrip())
+ clang_lib_dir = os.path.join(
+ clang_dir,
+ "lib",
+ "gcc",
+ "x86_64-unknown-linux-gnu",
+ os.path.basename(libgcc_dir),
+ )
+ mkdir_p(clang_lib_dir)
+ copy_tree(libgcc_dir, clang_lib_dir, preserve_symlinks=True)
+ libgcc_dir = os.path.join(gcc_dir, "lib64")
+ clang_lib_dir = os.path.join(clang_dir, "lib")
+ copy_tree(libgcc_dir, clang_lib_dir, preserve_symlinks=True)
+ libgcc_dir = os.path.join(gcc_dir, "lib32")
+ clang_lib_dir = os.path.join(clang_dir, "lib32")
+ copy_tree(libgcc_dir, clang_lib_dir, preserve_symlinks=True)
+ include_dir = os.path.join(gcc_dir, "include")
+ clang_include_dir = os.path.join(clang_dir, "include")
+ copy_tree(include_dir, clang_include_dir, preserve_symlinks=True)
+
+
+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 install_asan_symbols(build_dir, clang_dir):
+ lib_path_pattern = os.path.join("lib", "clang", "*.*.*", "lib", "windows")
+ src_path = glob.glob(
+ os.path.join(build_dir, lib_path_pattern, "clang_rt.asan_dynamic-*.pdb")
+ )
+ dst_path = glob.glob(os.path.join(clang_dir, lib_path_pattern))
+
+ if len(src_path) != 1:
+ raise Exception("Source path pattern did not resolve uniquely")
+
+ if len(src_path) != 1:
+ raise Exception("Destination path pattern did not resolve uniquely")
+
+ shutil.copy2(src_path[0], dst_path[0])
+
+
+def is_darwin():
+ return platform.system() == "Darwin"
+
+
+def is_linux():
+ return platform.system() == "Linux"
+
+
+def is_windows():
+ return platform.system() == "Windows"
+
+
+def build_one_stage(
+ cc,
+ cxx,
+ asm,
+ ld,
+ ar,
+ ranlib,
+ libtool,
+ src_dir,
+ stage_dir,
+ package_name,
+ build_libcxx,
+ osx_cross_compile,
+ build_type,
+ assertions,
+ python_path,
+ gcc_dir,
+ libcxx_include_dir,
+ build_wasm,
+ compiler_rt_source_dir=None,
+ runtimes_source_link=None,
+ compiler_rt_source_link=None,
+ is_final_stage=False,
+ android_targets=None,
+ extra_targets=None,
+ pgo_phase=None,
+):
+ if is_final_stage and (android_targets or extra_targets):
+ # Linking compiler-rt under "runtimes" activates LLVM_RUNTIME_TARGETS
+ # and related arguments.
+ symlink(compiler_rt_source_dir, runtimes_source_link)
+ try:
+ os.unlink(compiler_rt_source_link)
+ except Exception:
+ pass
+
+ 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, ld, ar, ranlib, libtool, inst_dir):
+ machine_targets = "X86;ARM;AArch64" if is_final_stage 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_LINKER=%s" % slashify_path(ld[0]),
+ "-DCMAKE_AR=%s" % slashify_path(ar),
+ "-DCMAKE_C_FLAGS=%s" % " ".join(cc[1:]),
+ "-DCMAKE_CXX_FLAGS=%s" % " ".join(cxx[1:]),
+ "-DCMAKE_ASM_FLAGS=%s" % " ".join(asm[1:]),
+ "-DCMAKE_EXE_LINKER_FLAGS=%s" % " ".join(ld[1:]),
+ "-DCMAKE_SHARED_LINKER_FLAGS=%s" % " ".join(ld[1:]),
+ "-DCMAKE_BUILD_TYPE=%s" % build_type,
+ "-DCMAKE_INSTALL_PREFIX=%s" % inst_dir,
+ "-DLLVM_TARGETS_TO_BUILD=%s" % machine_targets,
+ "-DLLVM_ENABLE_ASSERTIONS=%s" % ("ON" if assertions else "OFF"),
+ "-DPYTHON_EXECUTABLE=%s" % slashify_path(python_path),
+ "-DLLVM_TOOL_LIBCXX_BUILD=%s" % ("ON" if build_libcxx else "OFF"),
+ "-DLLVM_ENABLE_BINDINGS=OFF",
+ ]
+ if "TASK_ID" in os.environ:
+ cmake_args += [
+ "-DCLANG_REPOSITORY_STRING=taskcluster-%s" % os.environ["TASK_ID"],
+ ]
+ if not is_final_stage:
+ cmake_args += ["-DLLVM_ENABLE_PROJECTS=clang;compiler-rt"]
+ if build_wasm:
+ cmake_args += ["-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly"]
+ if is_linux():
+ cmake_args += ["-DLLVM_BINUTILS_INCDIR=%s/include" % gcc_dir]
+ cmake_args += ["-DLLVM_ENABLE_LIBXML2=FORCE_ON"]
+ if is_windows():
+ cmake_args.insert(-1, "-DLLVM_EXPORT_SYMBOLS_FOR_PLUGINS=ON")
+ cmake_args.insert(-1, "-DLLVM_USE_CRT_RELEASE=MT")
+ 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 libtool is not None:
+ cmake_args += ["-DCMAKE_LIBTOOL=%s" % slashify_path(libtool)]
+ if osx_cross_compile:
+ cmake_args += [
+ "-DCMAKE_SYSTEM_NAME=Darwin",
+ "-DCMAKE_SYSTEM_VERSION=10.10",
+ # Xray requires a OSX 10.12 SDK (https://bugs.llvm.org/show_bug.cgi?id=38959)
+ "-DCOMPILER_RT_BUILD_XRAY=OFF",
+ "-DLIBCXXABI_LIBCXX_INCLUDES=%s" % libcxx_include_dir,
+ "-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=x86_64",
+ "-DDARWIN_osx_ARCHS=x86_64",
+ "-DDARWIN_osx_SYSROOT=%s" % slashify_path(os.getenv("CROSS_SYSROOT")),
+ "-DLLVM_DEFAULT_TARGET_TRIPLE=x86_64-apple-darwin",
+ ]
+ # 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.
+ if "MacOSX10.12.sdk" in os.getenv("CROSS_SYSROOT"):
+ cmake_args += [
+ "-DDARWIN_macosx_OVERRIDE_SDK_VERSION=10.12",
+ ]
+ if pgo_phase == "gen":
+ # Per https://releases.llvm.org/10.0.0/docs/HowToBuildWithPGO.html
+ cmake_args += [
+ "-DLLVM_BUILD_INSTRUMENTED=IR",
+ "-DLLVM_BUILD_RUNTIME=No",
+ ]
+ if pgo_phase == "use":
+ cmake_args += [
+ "-DLLVM_PROFDATA_FILE=%s/merged.profdata" % stage_dir,
+ ]
+ return cmake_args
+
+ cmake_args = []
+
+ runtime_targets = []
+ if is_final_stage:
+ if android_targets:
+ runtime_targets = list(sorted(android_targets.keys()))
+ if extra_targets:
+ runtime_targets.extend(sorted(extra_targets))
+
+ if runtime_targets:
+ cmake_args += [
+ "-DLLVM_BUILTIN_TARGETS=%s" % ";".join(runtime_targets),
+ "-DLLVM_RUNTIME_TARGETS=%s" % ";".join(runtime_targets),
+ ]
+
+ for target in runtime_targets:
+ cmake_args += [
+ "-DRUNTIMES_%s_COMPILER_RT_BUILD_PROFILE=ON" % target,
+ "-DRUNTIMES_%s_COMPILER_RT_BUILD_SANITIZERS=ON" % target,
+ "-DRUNTIMES_%s_COMPILER_RT_BUILD_XRAY=OFF" % target,
+ "-DRUNTIMES_%s_SANITIZER_ALLOW_CXXABI=OFF" % target,
+ "-DRUNTIMES_%s_COMPILER_RT_BUILD_LIBFUZZER=OFF" % target,
+ "-DRUNTIMES_%s_COMPILER_RT_INCLUDE_TESTS=OFF" % target,
+ "-DRUNTIMES_%s_LLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF" % target,
+ "-DRUNTIMES_%s_LLVM_INCLUDE_TESTS=OFF" % target,
+ ]
+
+ # The above code flipped switches to build various runtime libraries on
+ # Android; we now have to provide all the necessary compiler switches to
+ # make that work.
+ if is_final_stage and android_targets:
+ cmake_args += [
+ "-DLLVM_LIBDIR_SUFFIX=64",
+ ]
+
+ android_link_flags = "-fuse-ld=lld"
+
+ for target, cfg in android_targets.items():
+ sysroot_dir = cfg["ndk_sysroot"].format(**os.environ)
+ android_gcc_dir = cfg["ndk_toolchain"].format(**os.environ)
+ android_include_dirs = cfg["ndk_includes"]
+ api_level = cfg["api_level"]
+
+ android_flags = [
+ "-isystem %s" % d.format(**os.environ) for d in android_include_dirs
+ ]
+ android_flags += ["--gcc-toolchain=%s" % android_gcc_dir]
+ android_flags += ["-D__ANDROID_API__=%s" % api_level]
+
+ # Our flags go last to override any --gcc-toolchain that may have
+ # been set earlier.
+ rt_c_flags = " ".join(cc[1:] + android_flags)
+ rt_cxx_flags = " ".join(cxx[1:] + android_flags)
+ rt_asm_flags = " ".join(asm[1:] + android_flags)
+
+ for kind in ("BUILTINS", "RUNTIMES"):
+ for var, arg in (
+ ("ANDROID", "1"),
+ ("CMAKE_ASM_FLAGS", rt_asm_flags),
+ ("CMAKE_CXX_FLAGS", rt_cxx_flags),
+ ("CMAKE_C_FLAGS", rt_c_flags),
+ ("CMAKE_EXE_LINKER_FLAGS", android_link_flags),
+ ("CMAKE_SHARED_LINKER_FLAGS", android_link_flags),
+ ("CMAKE_SYSROOT", sysroot_dir),
+ ("ANDROID_NATIVE_API_LEVEL", api_level),
+ ):
+ cmake_args += ["-D%s_%s_%s=%s" % (kind, target, var, arg)]
+
+ cmake_args += cmake_base_args(cc, cxx, asm, ld, ar, ranlib, libtool, inst_dir)
+ cmake_args += [src_dir]
+ build_package(build_dir, cmake_args)
+
+ if is_linux():
+ install_libgcc(gcc_dir, inst_dir, is_final_stage)
+ # For some reasons the import library clang.lib of clang.exe is not
+ # installed, so we copy it by ourselves.
+ if is_windows():
+ # The compiler-rt cmake scripts don't allow to build it for multiple
+ # targets at once on Windows, so manually build the 32-bits compiler-rt
+ # during the final stage.
+ build_32_bit = False
+ if is_final_stage:
+ # Only build the 32-bits compiler-rt when we originally built for
+ # 64-bits, which we detect through the contents of the LIB
+ # environment variable, which we also adjust for a 32-bits build
+ # at the same time.
+ old_lib = os.environ["LIB"]
+ new_lib = []
+ for l in old_lib.split(os.pathsep):
+ if l.endswith("x64"):
+ l = l[:-3] + "x86"
+ build_32_bit = True
+ elif l.endswith("amd64"):
+ l = l[:-5]
+ build_32_bit = True
+ new_lib.append(l)
+ if build_32_bit:
+ os.environ["LIB"] = os.pathsep.join(new_lib)
+ compiler_rt_build_dir = stage_dir + "/compiler-rt"
+ compiler_rt_inst_dir = inst_dir + "/lib/clang/"
+ subdirs = os.listdir(compiler_rt_inst_dir)
+ assert len(subdirs) == 1
+ compiler_rt_inst_dir += subdirs[0]
+ cmake_args = cmake_base_args(
+ [os.path.join(inst_dir, "bin", "clang-cl.exe"), "-m32"] + cc[1:],
+ [os.path.join(inst_dir, "bin", "clang-cl.exe"), "-m32"] + cxx[1:],
+ [os.path.join(inst_dir, "bin", "clang-cl.exe"), "-m32"] + asm[1:],
+ ld,
+ ar,
+ ranlib,
+ libtool,
+ compiler_rt_inst_dir,
+ )
+ cmake_args += [
+ "-DLLVM_CONFIG_PATH=%s"
+ % slashify_path(os.path.join(inst_dir, "bin", "llvm-config")),
+ os.path.join(src_dir, "projects", "compiler-rt"),
+ ]
+ build_package(compiler_rt_build_dir, cmake_args)
+ os.environ["LIB"] = old_lib
+ if is_final_stage:
+ install_import_library(build_dir, inst_dir)
+ install_asan_symbols(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
+# 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, osx_cross_compile):
+ # Make sure we only have what we expect.
+ dirs = [
+ "bin",
+ "include",
+ "lib",
+ "lib32",
+ "libexec",
+ "msbuild-bin",
+ "share",
+ "tools",
+ ]
+ if is_linux():
+ 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"]
+ 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():
+ 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 osx_cross_compile and name in ["libLLVM.dylib", "libclang-cpp.dylib"]:
+ continue
+ if is_linux() 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)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "-c",
+ "--config",
+ 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-checkout",
+ required=False,
+ action="store_true",
+ help="Do not checkout/revert 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"
+ extra_source_dir = source_dir + "/clang-tools-extra"
+ clang_source_dir = source_dir + "/clang"
+ lld_source_dir = source_dir + "/lld"
+ compiler_rt_source_dir = source_dir + "/compiler-rt"
+ libcxx_source_dir = source_dir + "/libcxx"
+ libcxxabi_source_dir = source_dir + "/libcxxabi"
+
+ exe_ext = ""
+ if is_windows():
+ exe_ext = ".exe"
+
+ cc_name = "clang"
+ cxx_name = "clang++"
+ if is_windows():
+ cc_name = "clang-cl"
+ cxx_name = "clang-cl"
+
+ config_dir = os.path.dirname(args.config.name)
+ config = json.load(args.config)
+
+ stages = 3
+ 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.")
+ pgo = False
+ if "pgo" in config:
+ pgo = config["pgo"]
+ if pgo not in (True, False):
+ raise ValueError("Only boolean values are accepted for pgo.")
+ if pgo and stages != 4:
+ raise ValueError("PGO is only supported in 4-stage builds.")
+ 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"
+ )
+ build_libcxx = False
+ if "build_libcxx" in config:
+ build_libcxx = config["build_libcxx"]
+ if build_libcxx not in (True, False):
+ raise ValueError("Only boolean values are accepted for build_libcxx.")
+ build_wasm = False
+ if "build_wasm" in config:
+ build_wasm = config["build_wasm"]
+ if build_wasm not in (True, False):
+ raise ValueError("Only boolean values are accepted for build_wasm.")
+ 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."
+ )
+ osx_cross_compile = False
+ if "osx_cross_compile" in config:
+ osx_cross_compile = config["osx_cross_compile"]
+ if osx_cross_compile not in (True, False):
+ raise ValueError("Only boolean values are accepted for osx_cross_compile.")
+ if osx_cross_compile and not is_linux():
+ raise ValueError("osx_cross_compile can only be used on Linux.")
+ assertions = False
+ if "assertions" in config:
+ assertions = config["assertions"]
+ if assertions not in (True, False):
+ raise ValueError("Only boolean values are accepted for assertions.")
+ python_path = None
+ if "python_path" not in config:
+ raise ValueError("Config file needs to set python_path")
+ python_path = config["python_path"]
+ gcc_dir = None
+ if "gcc_dir" in config:
+ gcc_dir = config["gcc_dir"].format(**os.environ)
+ if not os.path.exists(gcc_dir):
+ raise ValueError("gcc_dir must point to an existing path")
+ ndk_dir = None
+ android_targets = None
+ if "android_targets" in config:
+ android_targets = config["android_targets"]
+ for attr in ("ndk_toolchain", "ndk_sysroot", "ndk_includes", "api_level"):
+ for target, cfg in android_targets.items():
+ if attr not in cfg:
+ raise ValueError(
+ "must specify '%s' as a key for android target: %s"
+ % (attr, target)
+ )
+ extra_targets = None
+ if "extra_targets" in config:
+ extra_targets = config["extra_targets"]
+ if not isinstance(extra_targets, list):
+ raise ValueError("extra_targets must be a list")
+ if not all(isinstance(t, str) for t in extra_targets):
+ raise ValueError("members of extra_targets should be strings")
+
+ if is_linux() and gcc_dir is None:
+ raise ValueError("Config file needs to set gcc_dir")
+
+ if is_darwin() or osx_cross_compile:
+ os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.12"
+
+ cc = get_tool(config, "cc")
+ cxx = get_tool(config, "cxx")
+ asm = get_tool(config, "ml" if is_windows() else "as")
+ ld = get_tool(config, "link" if is_windows() else "ld")
+ ar = get_tool(config, "lib" if is_windows() else "ar")
+ ranlib = None if is_windows() else get_tool(config, "ranlib")
+ libtool = None
+ if "libtool" in config:
+ libtool = get_tool(config, "libtool")
+
+ if not os.path.exists(source_dir):
+ os.makedirs(source_dir)
+
+ for p in config.get("patches", []):
+ patch(os.path.join(config_dir, p), source_dir)
+
+ compiler_rt_source_link = llvm_source_dir + "/projects/compiler-rt"
+
+ symlinks = [
+ (clang_source_dir, llvm_source_dir + "/tools/clang"),
+ (extra_source_dir, llvm_source_dir + "/tools/clang/tools/extra"),
+ (lld_source_dir, llvm_source_dir + "/tools/lld"),
+ (compiler_rt_source_dir, compiler_rt_source_link),
+ (libcxx_source_dir, llvm_source_dir + "/projects/libcxx"),
+ (libcxxabi_source_dir, llvm_source_dir + "/projects/libcxxabi"),
+ ]
+ for l in symlinks:
+ # On Windows, we have to re-copy the whole directory every time.
+ if not is_windows() and os.path.islink(l[1]):
+ continue
+ delete(l[1])
+ if os.path.exists(l[0]):
+ symlink(l[0], l[1])
+
+ package_name = "clang"
+ if build_clang_tidy:
+ package_name = "clang-tidy"
+ import_clang_tidy(source_dir, build_clang_tidy_alpha, build_clang_tidy_external)
+
+ if not os.path.exists(build_dir):
+ os.makedirs(build_dir)
+
+ libcxx_include_dir = os.path.join(llvm_source_dir, "projects", "libcxx", "include")
+
+ stage1_dir = build_dir + "/stage1"
+ stage1_inst_dir = stage1_dir + "/" + package_name
+
+ final_stage_dir = stage1_dir
+ final_inst_dir = stage1_inst_dir
+
+ if is_darwin():
+ extra_cflags = []
+ extra_cxxflags = ["-stdlib=libc++"]
+ extra_cflags2 = []
+ extra_cxxflags2 = ["-stdlib=libc++"]
+ extra_asmflags = []
+ extra_ldflags = []
+ elif is_linux():
+ extra_cflags = []
+ extra_cxxflags = []
+ # When building stage2 and stage3, we want the newly-built clang to pick
+ # up whatever headers were installed from the gcc we used to build stage1,
+ # always, rather than the system headers. Providing -gcc-toolchain
+ # encourages clang to do that.
+ extra_cflags2 = ["-fPIC", "-gcc-toolchain", stage1_inst_dir]
+ # Silence clang's warnings about arguments not being used in compilation.
+ extra_cxxflags2 = [
+ "-fPIC",
+ "-Qunused-arguments",
+ "-gcc-toolchain",
+ stage1_inst_dir,
+ ]
+ 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.
+ extra_ldflags += ["-fuse-ld=gold", "-Wl,--gc-sections", "-Wl,--icf=safe"]
+
+ if "LD_LIBRARY_PATH" in os.environ:
+ os.environ["LD_LIBRARY_PATH"] = "%s/lib64/:%s" % (
+ gcc_dir,
+ os.environ["LD_LIBRARY_PATH"],
+ )
+ else:
+ os.environ["LD_LIBRARY_PATH"] = "%s/lib64/" % gcc_dir
+ elif is_windows():
+ 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.
+ extra_cflags2 = []
+ extra_cxxflags2 = [
+ "-fms-compatibility-version=19.15.26726",
+ "-Xclang",
+ "-std=c++14",
+ ]
+ extra_asmflags = []
+ extra_ldflags = []
+
+ if osx_cross_compile:
+ # undo the damage done in the is_linux() block above, and also simulate
+ # the is_darwin() block above.
+ extra_cflags = []
+ extra_cxxflags = ["-stdlib=libc++"]
+ extra_cxxflags2 = ["-stdlib=libc++"]
+
+ extra_flags = [
+ "-target",
+ "x86_64-apple-darwin",
+ "-mlinker-version=137",
+ "-B",
+ "%s/bin" % os.getenv("CROSS_CCTOOLS_PATH"),
+ "-isysroot",
+ os.getenv("CROSS_SYSROOT"),
+ # technically the sysroot flag there should be enough to deduce this,
+ # but clang needs some help to figure this out.
+ "-I%s/usr/include" % os.getenv("CROSS_SYSROOT"),
+ "-iframework",
+ "%s/System/Library/Frameworks" % os.getenv("CROSS_SYSROOT"),
+ ]
+ extra_cflags += extra_flags
+ extra_cxxflags += extra_flags
+ extra_cflags2 += extra_flags
+ extra_cxxflags2 += extra_flags
+ extra_asmflags += extra_flags
+ extra_ldflags = [
+ "-Wl,-syslibroot,%s" % os.getenv("CROSS_SYSROOT"),
+ "-Wl,-dead_strip",
+ ]
+
+ 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]
+
+ build_one_stage(
+ [cc] + extra_cflags,
+ [cxx] + extra_cxxflags,
+ [asm] + extra_asmflags,
+ [ld] + extra_ldflags,
+ ar,
+ ranlib,
+ libtool,
+ llvm_source_dir,
+ stage1_dir,
+ package_name,
+ build_libcxx,
+ osx_cross_compile,
+ build_type,
+ assertions,
+ python_path,
+ gcc_dir,
+ libcxx_include_dir,
+ build_wasm,
+ is_final_stage=(stages == 1),
+ )
+
+ runtimes_source_link = llvm_source_dir + "/runtimes/compiler-rt"
+
+ if stages >= 2:
+ stage2_dir = build_dir + "/stage2"
+ stage2_inst_dir = stage2_dir + "/" + package_name
+ final_stage_dir = stage2_dir
+ final_inst_dir = stage2_inst_dir
+ pgo_phase = "gen" if pgo else None
+ build_one_stage(
+ [stage1_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_cflags2,
+ [stage1_inst_dir + "/bin/%s%s" % (cxx_name, exe_ext)] + extra_cxxflags2,
+ [stage1_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_asmflags,
+ [ld] + extra_ldflags,
+ ar,
+ ranlib,
+ libtool,
+ llvm_source_dir,
+ stage2_dir,
+ package_name,
+ build_libcxx,
+ osx_cross_compile,
+ build_type,
+ assertions,
+ python_path,
+ gcc_dir,
+ libcxx_include_dir,
+ build_wasm,
+ compiler_rt_source_dir,
+ runtimes_source_link,
+ compiler_rt_source_link,
+ is_final_stage=(stages == 2),
+ android_targets=android_targets,
+ extra_targets=extra_targets,
+ pgo_phase=pgo_phase,
+ )
+
+ if stages >= 3:
+ stage3_dir = build_dir + "/stage3"
+ stage3_inst_dir = stage3_dir + "/" + package_name
+ final_stage_dir = stage3_dir
+ final_inst_dir = stage3_inst_dir
+ build_one_stage(
+ [stage2_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_cflags2,
+ [stage2_inst_dir + "/bin/%s%s" % (cxx_name, exe_ext)] + extra_cxxflags2,
+ [stage2_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_asmflags,
+ [ld] + extra_ldflags,
+ ar,
+ ranlib,
+ libtool,
+ llvm_source_dir,
+ stage3_dir,
+ package_name,
+ build_libcxx,
+ osx_cross_compile,
+ build_type,
+ assertions,
+ python_path,
+ gcc_dir,
+ libcxx_include_dir,
+ build_wasm,
+ compiler_rt_source_dir,
+ runtimes_source_link,
+ compiler_rt_source_link,
+ (stages == 3),
+ extra_targets=extra_targets,
+ )
+
+ if stages >= 4:
+ stage4_dir = build_dir + "/stage4"
+ stage4_inst_dir = stage4_dir + "/" + package_name
+ final_stage_dir = stage4_dir
+ final_inst_dir = stage4_inst_dir
+ pgo_phase = None
+ if pgo:
+ pgo_phase = "use"
+ llvm_profdata = stage3_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")
+ )
+ if not os.path.exists(stage4_dir):
+ os.mkdir(stage4_dir)
+ run_in(stage4_dir, merge_cmd + profraw_files)
+ build_one_stage(
+ [stage3_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_cflags2,
+ [stage3_inst_dir + "/bin/%s%s" % (cxx_name, exe_ext)] + extra_cxxflags2,
+ [stage3_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_asmflags,
+ [ld] + extra_ldflags,
+ ar,
+ ranlib,
+ libtool,
+ llvm_source_dir,
+ stage4_dir,
+ package_name,
+ build_libcxx,
+ osx_cross_compile,
+ build_type,
+ assertions,
+ python_path,
+ gcc_dir,
+ libcxx_include_dir,
+ build_wasm,
+ compiler_rt_source_dir,
+ runtimes_source_link,
+ compiler_rt_source_link,
+ (stages == 4),
+ extra_targets=extra_targets,
+ pgo_phase=pgo_phase,
+ )
+
+ if build_clang_tidy:
+ prune_final_dir_for_clang_tidy(
+ os.path.join(final_stage_dir, package_name), osx_cross_compile
+ )
+
+ # Copy the wasm32 builtins to the final_inst_dir if the archive is present.
+ if "wasi-sysroot" in config:
+ sysroot = config["wasi-sysroot"].format(**os.environ)
+ if os.path.isdir(sysroot):
+ for srcdir in glob.glob(
+ os.path.join(sysroot, "lib", "clang", "*", "lib", "wasi")
+ ):
+ print("Copying from wasi-sysroot srcdir %s" % srcdir)
+ # Copy the contents of the "lib/wasi" subdirectory to the
+ # appropriate location in final_inst_dir.
+ version = os.path.basename(os.path.dirname(os.path.dirname(srcdir)))
+ destdir = os.path.join(
+ final_inst_dir, "lib", "clang", version, "lib", "wasi"
+ )
+ mkdir_p(destdir)
+ copy_tree(srcdir, destdir)
+
+ if not args.skip_tar:
+ build_tar_package("%s.tar.zst" % package_name, final_stage_dir, package_name)