1#!/usr/bin/python3
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this
4# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
6# Only necessary for flake8 to be happy...
7from __future__ import print_function
8
9import os
10import os.path
11import shutil
12import subprocess
13import platform
14import json
15import argparse
16import fnmatch
17import glob
18import errno
19import re
20import sys
21import tarfile
22from contextlib import contextmanager
23from distutils.dir_util import copy_tree
24from distutils.file_util import copy_file
25
26from shutil import which
27
28import zstandard
29
30
31def symlink(source, link_name):
32    os_symlink = getattr(os, "symlink", None)
33    if callable(os_symlink):
34        os_symlink(source, link_name)
35    else:
36        if os.path.isdir(source):
37            # Fall back to copying the directory :(
38            copy_tree(source, link_name)
39
40
41def check_run(args):
42    print(" ".join(args), file=sys.stderr, flush=True)
43    if args[0] == "cmake":
44        # CMake `message(STATUS)` messages, as appearing in failed source code
45        # compiles, appear on stdout, so we only capture that.
46        p = subprocess.Popen(args, stdout=subprocess.PIPE)
47        lines = []
48        for line in p.stdout:
49            lines.append(line)
50            sys.stdout.write(line.decode())
51            sys.stdout.flush()
52        r = p.wait()
53        if r != 0:
54            cmake_output_re = re.compile(b'See also "(.*/CMakeOutput.log)"')
55            cmake_error_re = re.compile(b'See also "(.*/CMakeError.log)"')
56
57            def find_first_match(re):
58                for l in lines:
59                    match = re.search(l)
60                    if match:
61                        return match
62
63            output_match = find_first_match(cmake_output_re)
64            error_match = find_first_match(cmake_error_re)
65
66            def dump_file(log):
67                with open(log, "r", errors="replace") as f:
68                    print("\nContents of", log, "follow\n", file=sys.stderr)
69                    for line in f:
70                        print(line, file=sys.stderr)
71
72            if output_match:
73                dump_file(output_match.group(1))
74            if error_match:
75                dump_file(error_match.group(1))
76    else:
77        r = subprocess.call(args)
78    assert r == 0
79
80
81def run_in(path, args):
82    with chdir(path):
83        check_run(args)
84
85
86@contextmanager
87def chdir(path):
88    d = os.getcwd()
89    print('cd "%s"' % path, file=sys.stderr)
90    os.chdir(path)
91    try:
92        yield
93    finally:
94        print('cd "%s"' % d, file=sys.stderr)
95        os.chdir(d)
96
97
98def patch(patch, srcdir):
99    patch = os.path.realpath(patch)
100    check_run(["patch", "-d", srcdir, "-p1", "-i", patch, "--fuzz=0", "-s"])
101
102
103def import_clang_tidy(source_dir, build_clang_tidy_alpha, build_clang_tidy_external):
104    clang_plugin_path = os.path.join(os.path.dirname(sys.argv[0]), "..", "clang-plugin")
105    clang_tidy_path = os.path.join(source_dir, "clang-tools-extra/clang-tidy")
106    sys.path.append(clang_plugin_path)
107    from import_mozilla_checks import do_import
108
109    import_options = {
110        "alpha": build_clang_tidy_alpha,
111        "external": build_clang_tidy_external,
112    }
113    do_import(clang_plugin_path, clang_tidy_path, import_options)
114
115
116def build_package(package_build_dir, cmake_args):
117    if not os.path.exists(package_build_dir):
118        os.mkdir(package_build_dir)
119    # If CMake has already been run, it may have been run with different
120    # arguments, so we need to re-run it.  Make sure the cached copy of the
121    # previous CMake run is cleared before running it again.
122    if os.path.exists(package_build_dir + "/CMakeCache.txt"):
123        os.remove(package_build_dir + "/CMakeCache.txt")
124    if os.path.exists(package_build_dir + "/CMakeFiles"):
125        shutil.rmtree(package_build_dir + "/CMakeFiles")
126
127    run_in(package_build_dir, ["cmake"] + cmake_args)
128    run_in(package_build_dir, ["ninja", "install", "-v"])
129
130
131@contextmanager
132def updated_env(env):
133    old_env = os.environ.copy()
134    os.environ.update(env)
135    yield
136    os.environ.clear()
137    os.environ.update(old_env)
138
139
140def build_tar_package(name, base, directory):
141    name = os.path.realpath(name)
142    print("tarring {} from {}/{}".format(name, base, directory), file=sys.stderr)
143    assert name.endswith(".tar.zst")
144
145    cctx = zstandard.ZstdCompressor()
146    with open(name, "wb") as f, cctx.stream_writer(f) as z:
147        with tarfile.open(mode="w|", fileobj=z) as tf:
148            with chdir(base):
149                tf.add(directory)
150
151
152def mkdir_p(path):
153    try:
154        os.makedirs(path)
155    except OSError as e:
156        if e.errno != errno.EEXIST or not os.path.isdir(path):
157            raise
158
159
160def delete(path):
161    if os.path.isdir(path):
162        shutil.rmtree(path)
163    else:
164        try:
165            os.unlink(path)
166        except Exception:
167            pass
168
169
170def install_libgcc(gcc_dir, clang_dir, is_final_stage):
171    gcc_bin_dir = os.path.join(gcc_dir, "bin")
172
173    out = subprocess.check_output(
174        [os.path.join(gcc_bin_dir, "gcc"), "-print-libgcc-file-name"]
175    )
176
177    libgcc_dir = os.path.dirname(out.decode().rstrip())
178    clang_lib_dir = os.path.join(
179        clang_dir,
180        "lib",
181        "gcc",
182        "x86_64-unknown-linux-gnu",
183        os.path.basename(libgcc_dir),
184    )
185    mkdir_p(clang_lib_dir)
186    copy_tree(libgcc_dir, clang_lib_dir, preserve_symlinks=True)
187    libgcc_dir = os.path.join(gcc_dir, "lib64")
188    # This is necessary as long as CI runs on debian8 docker images.
189    copy_file(
190        os.path.join(libgcc_dir, "libstdc++.so.6"), os.path.join(clang_dir, "lib")
191    )
192    copy_tree(libgcc_dir, clang_lib_dir, preserve_symlinks=True)
193    libgcc_dir = os.path.join(gcc_dir, "lib32")
194    clang_lib_dir = os.path.join(clang_lib_dir, "32")
195    copy_tree(libgcc_dir, clang_lib_dir, preserve_symlinks=True)
196    include_dir = os.path.join(gcc_dir, "include")
197    clang_include_dir = os.path.join(clang_dir, "include")
198    copy_tree(include_dir, clang_include_dir, preserve_symlinks=True)
199
200
201def install_import_library(build_dir, clang_dir):
202    shutil.copy2(
203        os.path.join(build_dir, "lib", "clang.lib"), os.path.join(clang_dir, "lib")
204    )
205
206
207def install_asan_symbols(build_dir, clang_dir):
208    lib_path_pattern = os.path.join("lib", "clang", "*.*.*", "lib", "windows")
209    src_path = glob.glob(
210        os.path.join(build_dir, lib_path_pattern, "clang_rt.asan_dynamic-*.pdb")
211    )
212    dst_path = glob.glob(os.path.join(clang_dir, lib_path_pattern))
213
214    if len(src_path) != 1:
215        raise Exception("Source path pattern did not resolve uniquely")
216
217    if len(src_path) != 1:
218        raise Exception("Destination path pattern did not resolve uniquely")
219
220    shutil.copy2(src_path[0], dst_path[0])
221
222
223def is_darwin():
224    return platform.system() == "Darwin"
225
226
227def is_linux():
228    return platform.system() == "Linux"
229
230
231def is_windows():
232    return platform.system() == "Windows"
233
234
235def build_one_stage(
236    cc,
237    cxx,
238    asm,
239    ld,
240    ar,
241    ranlib,
242    libtool,
243    src_dir,
244    stage_dir,
245    package_name,
246    build_libcxx,
247    osx_cross_compile,
248    build_type,
249    assertions,
250    python_path,
251    gcc_dir,
252    libcxx_include_dir,
253    build_wasm,
254    compiler_rt_source_dir=None,
255    runtimes_source_link=None,
256    compiler_rt_source_link=None,
257    is_final_stage=False,
258    android_targets=None,
259    extra_targets=None,
260    pgo_phase=None,
261):
262    if is_final_stage and (android_targets or extra_targets):
263        # Linking compiler-rt under "runtimes" activates LLVM_RUNTIME_TARGETS
264        # and related arguments.
265        symlink(compiler_rt_source_dir, runtimes_source_link)
266        try:
267            os.unlink(compiler_rt_source_link)
268        except Exception:
269            pass
270
271    if not os.path.exists(stage_dir):
272        os.mkdir(stage_dir)
273
274    build_dir = stage_dir + "/build"
275    inst_dir = stage_dir + "/" + package_name
276
277    # cmake doesn't deal well with backslashes in paths.
278    def slashify_path(path):
279        return path.replace("\\", "/")
280
281    def cmake_base_args(cc, cxx, asm, ld, ar, ranlib, libtool, inst_dir):
282        machine_targets = "X86;ARM;AArch64" if is_final_stage else "X86"
283        cmake_args = [
284            "-GNinja",
285            "-DCMAKE_C_COMPILER=%s" % slashify_path(cc[0]),
286            "-DCMAKE_CXX_COMPILER=%s" % slashify_path(cxx[0]),
287            "-DCMAKE_ASM_COMPILER=%s" % slashify_path(asm[0]),
288            "-DCMAKE_LINKER=%s" % slashify_path(ld[0]),
289            "-DCMAKE_AR=%s" % slashify_path(ar),
290            "-DCMAKE_C_FLAGS=%s" % " ".join(cc[1:]),
291            "-DCMAKE_CXX_FLAGS=%s" % " ".join(cxx[1:]),
292            "-DCMAKE_ASM_FLAGS=%s" % " ".join(asm[1:]),
293            "-DCMAKE_EXE_LINKER_FLAGS=%s" % " ".join(ld[1:]),
294            "-DCMAKE_SHARED_LINKER_FLAGS=%s" % " ".join(ld[1:]),
295            "-DCMAKE_BUILD_TYPE=%s" % build_type,
296            "-DCMAKE_INSTALL_PREFIX=%s" % inst_dir,
297            "-DLLVM_TARGETS_TO_BUILD=%s" % machine_targets,
298            "-DLLVM_ENABLE_ASSERTIONS=%s" % ("ON" if assertions else "OFF"),
299            "-DPYTHON_EXECUTABLE=%s" % slashify_path(python_path),
300            "-DLLVM_TOOL_LIBCXX_BUILD=%s" % ("ON" if build_libcxx else "OFF"),
301            "-DLLVM_ENABLE_BINDINGS=OFF",
302        ]
303        if "TASK_ID" in os.environ:
304            cmake_args += [
305                "-DCLANG_REPOSITORY_STRING=taskcluster-%s" % os.environ["TASK_ID"],
306            ]
307        if not is_final_stage:
308            cmake_args += ["-DLLVM_ENABLE_PROJECTS=clang;compiler-rt"]
309        if build_wasm:
310            cmake_args += ["-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly"]
311        if is_linux() and not osx_cross_compile:
312            cmake_args += ["-DLLVM_BINUTILS_INCDIR=/usr/include"]
313            cmake_args += ["-DLLVM_ENABLE_LIBXML2=FORCE_ON"]
314            sysroot = os.path.join(os.environ.get("MOZ_FETCHES_DIR", ""), "sysroot")
315            if os.path.exists(sysroot):
316                cmake_args += ["-DCMAKE_SYSROOT=%s" % sysroot]
317        if is_windows():
318            cmake_args.insert(-1, "-DLLVM_EXPORT_SYMBOLS_FOR_PLUGINS=ON")
319            cmake_args.insert(-1, "-DLLVM_USE_CRT_RELEASE=MT")
320        else:
321            # libllvm as a shared library is not supported on Windows
322            cmake_args += ["-DLLVM_LINK_LLVM_DYLIB=ON"]
323        if ranlib is not None:
324            cmake_args += ["-DCMAKE_RANLIB=%s" % slashify_path(ranlib)]
325        if libtool is not None:
326            cmake_args += ["-DCMAKE_LIBTOOL=%s" % slashify_path(libtool)]
327        if osx_cross_compile:
328            arch = "arm64" if os.environ.get("OSX_ARCH") == "arm64" else "x86_64"
329            target_cpu = (
330                "aarch64" if os.environ.get("OSX_ARCH") == "arm64" else "x86_64"
331            )
332            cmake_args += [
333                "-DCMAKE_SYSTEM_NAME=Darwin",
334                "-DCMAKE_SYSTEM_VERSION=%s" % os.environ["MACOSX_DEPLOYMENT_TARGET"],
335                "-DLIBCXXABI_LIBCXX_INCLUDES=%s" % libcxx_include_dir,
336                "-DCMAKE_OSX_SYSROOT=%s" % slashify_path(os.getenv("CROSS_SYSROOT")),
337                "-DCMAKE_FIND_ROOT_PATH=%s" % slashify_path(os.getenv("CROSS_SYSROOT")),
338                "-DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER",
339                "-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY",
340                "-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY",
341                "-DCMAKE_MACOSX_RPATH=ON",
342                "-DCMAKE_OSX_ARCHITECTURES=%s" % arch,
343                "-DDARWIN_osx_ARCHS=%s" % arch,
344                "-DDARWIN_osx_SYSROOT=%s" % slashify_path(os.getenv("CROSS_SYSROOT")),
345                "-DLLVM_DEFAULT_TARGET_TRIPLE=%s-apple-darwin" % target_cpu,
346            ]
347            if os.environ.get("OSX_ARCH") == "arm64":
348                cmake_args += [
349                    "-DDARWIN_osx_BUILTIN_ARCHS=arm64",
350                ]
351            # Starting in LLVM 11 (which requires SDK 10.12) the build tries to
352            # detect the SDK version by calling xcrun. Cross-compiles don't have
353            # an xcrun, so we have to set the version explicitly.
354            cmake_args += [
355                "-DDARWIN_macosx_OVERRIDE_SDK_VERSION=%s"
356                % os.environ["MACOSX_DEPLOYMENT_TARGET"],
357            ]
358        if pgo_phase == "gen":
359            # Per https://releases.llvm.org/10.0.0/docs/HowToBuildWithPGO.html
360            cmake_args += [
361                "-DLLVM_BUILD_INSTRUMENTED=IR",
362                "-DLLVM_BUILD_RUNTIME=No",
363            ]
364        if pgo_phase == "use":
365            cmake_args += [
366                "-DLLVM_PROFDATA_FILE=%s/merged.profdata" % stage_dir,
367            ]
368        return cmake_args
369
370    cmake_args = []
371
372    runtime_targets = []
373    if is_final_stage:
374        if android_targets:
375            runtime_targets = list(sorted(android_targets.keys()))
376        if extra_targets:
377            runtime_targets.extend(sorted(extra_targets))
378
379    if runtime_targets:
380        cmake_args += [
381            "-DLLVM_BUILTIN_TARGETS=%s" % ";".join(runtime_targets),
382            "-DLLVM_RUNTIME_TARGETS=%s" % ";".join(runtime_targets),
383        ]
384
385        for target in runtime_targets:
386            cmake_args += [
387                "-DRUNTIMES_%s_COMPILER_RT_BUILD_PROFILE=ON" % target,
388                "-DRUNTIMES_%s_COMPILER_RT_BUILD_SANITIZERS=ON" % target,
389                "-DRUNTIMES_%s_COMPILER_RT_BUILD_XRAY=OFF" % target,
390                "-DRUNTIMES_%s_SANITIZER_ALLOW_CXXABI=OFF" % target,
391                "-DRUNTIMES_%s_COMPILER_RT_BUILD_LIBFUZZER=OFF" % target,
392                "-DRUNTIMES_%s_COMPILER_RT_INCLUDE_TESTS=OFF" % target,
393                "-DRUNTIMES_%s_LLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF" % target,
394                "-DRUNTIMES_%s_LLVM_INCLUDE_TESTS=OFF" % target,
395            ]
396
397    # The above code flipped switches to build various runtime libraries on
398    # Android; we now have to provide all the necessary compiler switches to
399    # make that work.
400    if is_final_stage and android_targets:
401        cmake_args += [
402            "-DLLVM_LIBDIR_SUFFIX=64",
403        ]
404
405        android_link_flags = "-fuse-ld=lld"
406
407        for target, cfg in android_targets.items():
408            sysroot_dir = cfg["ndk_sysroot"].format(**os.environ)
409            android_gcc_dir = cfg["ndk_toolchain"].format(**os.environ)
410            android_include_dirs = cfg["ndk_includes"]
411            api_level = cfg["api_level"]
412
413            android_flags = [
414                "-isystem %s" % d.format(**os.environ) for d in android_include_dirs
415            ]
416            android_flags += ["--gcc-toolchain=%s" % android_gcc_dir]
417            android_flags += ["-D__ANDROID_API__=%s" % api_level]
418
419            # Our flags go last to override any --gcc-toolchain that may have
420            # been set earlier.
421            rt_c_flags = " ".join(cc[1:] + android_flags)
422            rt_cxx_flags = " ".join(cxx[1:] + android_flags)
423            rt_asm_flags = " ".join(asm[1:] + android_flags)
424
425            for kind in ("BUILTINS", "RUNTIMES"):
426                for var, arg in (
427                    ("ANDROID", "1"),
428                    ("CMAKE_ASM_FLAGS", rt_asm_flags),
429                    ("CMAKE_CXX_FLAGS", rt_cxx_flags),
430                    ("CMAKE_C_FLAGS", rt_c_flags),
431                    ("CMAKE_EXE_LINKER_FLAGS", android_link_flags),
432                    ("CMAKE_SHARED_LINKER_FLAGS", android_link_flags),
433                    ("CMAKE_SYSROOT", sysroot_dir),
434                    ("ANDROID_NATIVE_API_LEVEL", api_level),
435                ):
436                    cmake_args += ["-D%s_%s_%s=%s" % (kind, target, var, arg)]
437
438    cmake_args += cmake_base_args(cc, cxx, asm, ld, ar, ranlib, libtool, inst_dir)
439    cmake_args += [src_dir]
440    build_package(build_dir, cmake_args)
441
442    if is_linux():
443        install_libgcc(gcc_dir, inst_dir, is_final_stage)
444    # For some reasons the import library clang.lib of clang.exe is not
445    # installed, so we copy it by ourselves.
446    if is_windows():
447        # The compiler-rt cmake scripts don't allow to build it for multiple
448        # targets at once on Windows, so manually build the 32-bits compiler-rt
449        # during the final stage.
450        build_32_bit = False
451        if is_final_stage:
452            # Only build the 32-bits compiler-rt when we originally built for
453            # 64-bits, which we detect through the contents of the LIB
454            # environment variable, which we also adjust for a 32-bits build
455            # at the same time.
456            old_lib = os.environ["LIB"]
457            new_lib = []
458            for l in old_lib.split(os.pathsep):
459                if l.endswith("x64"):
460                    l = l[:-3] + "x86"
461                    build_32_bit = True
462                elif l.endswith("amd64"):
463                    l = l[:-5]
464                    build_32_bit = True
465                new_lib.append(l)
466        if build_32_bit:
467            os.environ["LIB"] = os.pathsep.join(new_lib)
468            compiler_rt_build_dir = stage_dir + "/compiler-rt"
469            compiler_rt_inst_dir = inst_dir + "/lib/clang/"
470            subdirs = os.listdir(compiler_rt_inst_dir)
471            assert len(subdirs) == 1
472            compiler_rt_inst_dir += subdirs[0]
473            cmake_args = cmake_base_args(
474                [os.path.join(inst_dir, "bin", "clang-cl.exe"), "-m32"] + cc[1:],
475                [os.path.join(inst_dir, "bin", "clang-cl.exe"), "-m32"] + cxx[1:],
476                [os.path.join(inst_dir, "bin", "clang-cl.exe"), "-m32"] + asm[1:],
477                ld,
478                ar,
479                ranlib,
480                libtool,
481                compiler_rt_inst_dir,
482            )
483            cmake_args += [
484                "-DLLVM_CONFIG_PATH=%s"
485                % slashify_path(os.path.join(inst_dir, "bin", "llvm-config")),
486                os.path.join(src_dir, "projects", "compiler-rt"),
487            ]
488            build_package(compiler_rt_build_dir, cmake_args)
489            os.environ["LIB"] = old_lib
490        if is_final_stage:
491            install_import_library(build_dir, inst_dir)
492            install_asan_symbols(build_dir, inst_dir)
493
494
495# Return the absolute path of a build tool.  We first look to see if the
496# variable is defined in the config file, and if so we make sure it's an
497# absolute path to an existing tool, otherwise we look for a program in
498# $PATH named "key".
499#
500# This expects the name of the key in the config file to match the name of
501# the tool in the default toolchain on the system (for example, "ld" on Unix
502# and "link" on Windows).
503def get_tool(config, key):
504    f = None
505    if key in config:
506        f = config[key].format(**os.environ)
507        if os.path.isabs(f):
508            if not os.path.exists(f):
509                raise ValueError("%s must point to an existing path" % key)
510            return f
511
512    # Assume that we have the name of some program that should be on PATH.
513    tool = which(f) if f else which(key)
514    if not tool:
515        raise ValueError("%s not found on PATH" % (f or key))
516    return tool
517
518
519# This function is intended to be called on the final build directory when
520# building clang-tidy. Also clang-format binaries are included that can be used
521# in conjunction with clang-tidy.
522# As a separate binary we also ship clangd for the language server protocol that
523# can be used as a plugin in `vscode`.
524# Its job is to remove all of the files which won't be used for clang-tidy or
525# clang-format to reduce the download size.  Currently when this function
526# finishes its job, it will leave final_dir with a layout like this:
527#
528# clang/
529#   bin/
530#     clang-apply-replacements
531#     clang-format
532#     clang-tidy
533#     clangd
534#   include/
535#     * (nothing will be deleted here)
536#   lib/
537#     clang/
538#       4.0.0/
539#         include/
540#           * (nothing will be deleted here)
541#   share/
542#     clang/
543#       clang-format-diff.py
544#       clang-tidy-diff.py
545#       run-clang-tidy.py
546def prune_final_dir_for_clang_tidy(final_dir, osx_cross_compile):
547    # Make sure we only have what we expect.
548    dirs = [
549        "bin",
550        "include",
551        "lib",
552        "lib32",
553        "libexec",
554        "msbuild-bin",
555        "share",
556        "tools",
557    ]
558    if is_linux():
559        dirs.append("x86_64-unknown-linux-gnu")
560    for f in glob.glob("%s/*" % final_dir):
561        if os.path.basename(f) not in dirs:
562            raise Exception("Found unknown file %s in the final directory" % f)
563        if not os.path.isdir(f):
564            raise Exception("Expected %s to be a directory" % f)
565
566    kept_binaries = [
567        "clang-apply-replacements",
568        "clang-format",
569        "clang-tidy",
570        "clangd",
571        "clang-query",
572    ]
573    re_clang_tidy = re.compile(r"^(" + "|".join(kept_binaries) + r")(\.exe)?$", re.I)
574    for f in glob.glob("%s/bin/*" % final_dir):
575        if re_clang_tidy.search(os.path.basename(f)) is None:
576            delete(f)
577
578    # Keep include/ intact.
579
580    # Remove the target-specific files.
581    if is_linux():
582        if os.path.exists(os.path.join(final_dir, "x86_64-unknown-linux-gnu")):
583            shutil.rmtree(os.path.join(final_dir, "x86_64-unknown-linux-gnu"))
584
585    # In lib/, only keep lib/clang/N.M.O/include and the LLVM shared library.
586    re_ver_num = re.compile(r"^\d+\.\d+\.\d+$", re.I)
587    for f in glob.glob("%s/lib/*" % final_dir):
588        name = os.path.basename(f)
589        if name == "clang":
590            continue
591        if osx_cross_compile and name in ["libLLVM.dylib", "libclang-cpp.dylib"]:
592            continue
593        if is_linux() and (
594            fnmatch.fnmatch(name, "libLLVM*.so")
595            or fnmatch.fnmatch(name, "libclang-cpp.so*")
596        ):
597            continue
598        delete(f)
599    for f in glob.glob("%s/lib/clang/*" % final_dir):
600        if re_ver_num.search(os.path.basename(f)) is None:
601            delete(f)
602    for f in glob.glob("%s/lib/clang/*/*" % final_dir):
603        if os.path.basename(f) != "include":
604            delete(f)
605
606    # Completely remove libexec/, msbuild-bin and tools, if it exists.
607    shutil.rmtree(os.path.join(final_dir, "libexec"))
608    for d in ("msbuild-bin", "tools"):
609        d = os.path.join(final_dir, d)
610        if os.path.exists(d):
611            shutil.rmtree(d)
612
613    # In share/, only keep share/clang/*tidy*
614    re_clang_tidy = re.compile(r"format|tidy", re.I)
615    for f in glob.glob("%s/share/*" % final_dir):
616        if os.path.basename(f) != "clang":
617            delete(f)
618    for f in glob.glob("%s/share/clang/*" % final_dir):
619        if re_clang_tidy.search(os.path.basename(f)) is None:
620            delete(f)
621
622
623if __name__ == "__main__":
624    parser = argparse.ArgumentParser()
625    parser.add_argument(
626        "-c",
627        "--config",
628        required=True,
629        type=argparse.FileType("r"),
630        help="Clang configuration file",
631    )
632    parser.add_argument(
633        "--clean", required=False, action="store_true", help="Clean the build directory"
634    )
635    parser.add_argument(
636        "--skip-tar",
637        required=False,
638        action="store_true",
639        help="Skip tar packaging stage",
640    )
641    parser.add_argument(
642        "--skip-patch",
643        required=False,
644        action="store_true",
645        help="Do not patch source",
646    )
647
648    args = parser.parse_args()
649
650    if not os.path.exists("llvm/README.txt"):
651        raise Exception(
652            "The script must be run from the root directory of the llvm-project tree"
653        )
654    source_dir = os.getcwd()
655    build_dir = source_dir + "/build"
656
657    if args.clean:
658        shutil.rmtree(build_dir)
659        os.sys.exit(0)
660
661    llvm_source_dir = source_dir + "/llvm"
662    extra_source_dir = source_dir + "/clang-tools-extra"
663    clang_source_dir = source_dir + "/clang"
664    lld_source_dir = source_dir + "/lld"
665    compiler_rt_source_dir = source_dir + "/compiler-rt"
666    libcxx_source_dir = source_dir + "/libcxx"
667    libcxxabi_source_dir = source_dir + "/libcxxabi"
668
669    exe_ext = ""
670    if is_windows():
671        exe_ext = ".exe"
672
673    cc_name = "clang"
674    cxx_name = "clang++"
675    if is_windows():
676        cc_name = "clang-cl"
677        cxx_name = "clang-cl"
678
679    config_dir = os.path.dirname(args.config.name)
680    config = json.load(args.config)
681
682    stages = 3
683    if "stages" in config:
684        stages = int(config["stages"])
685        if stages not in (1, 2, 3, 4):
686            raise ValueError("We only know how to build 1, 2, 3, or 4 stages.")
687    pgo = False
688    if "pgo" in config:
689        pgo = config["pgo"]
690        if pgo not in (True, False):
691            raise ValueError("Only boolean values are accepted for pgo.")
692        if pgo and stages != 4:
693            raise ValueError("PGO is only supported in 4-stage builds.")
694    build_type = "Release"
695    if "build_type" in config:
696        build_type = config["build_type"]
697        if build_type not in ("Release", "Debug", "RelWithDebInfo", "MinSizeRel"):
698            raise ValueError(
699                "We only know how to do Release, Debug, RelWithDebInfo or "
700                "MinSizeRel builds"
701            )
702    build_libcxx = False
703    if "build_libcxx" in config:
704        build_libcxx = config["build_libcxx"]
705        if build_libcxx not in (True, False):
706            raise ValueError("Only boolean values are accepted for build_libcxx.")
707    build_wasm = False
708    if "build_wasm" in config:
709        build_wasm = config["build_wasm"]
710        if build_wasm not in (True, False):
711            raise ValueError("Only boolean values are accepted for build_wasm.")
712    build_clang_tidy = False
713    if "build_clang_tidy" in config:
714        build_clang_tidy = config["build_clang_tidy"]
715        if build_clang_tidy not in (True, False):
716            raise ValueError("Only boolean values are accepted for build_clang_tidy.")
717    build_clang_tidy_alpha = False
718    # check for build_clang_tidy_alpha only if build_clang_tidy is true
719    if build_clang_tidy and "build_clang_tidy_alpha" in config:
720        build_clang_tidy_alpha = config["build_clang_tidy_alpha"]
721        if build_clang_tidy_alpha not in (True, False):
722            raise ValueError(
723                "Only boolean values are accepted for build_clang_tidy_alpha."
724            )
725    build_clang_tidy_external = False
726    # check for build_clang_tidy_external only if build_clang_tidy is true
727    if build_clang_tidy and "build_clang_tidy_external" in config:
728        build_clang_tidy_external = config["build_clang_tidy_external"]
729        if build_clang_tidy_external not in (True, False):
730            raise ValueError(
731                "Only boolean values are accepted for build_clang_tidy_external."
732            )
733    osx_cross_compile = False
734    if "osx_cross_compile" in config:
735        osx_cross_compile = config["osx_cross_compile"]
736        if osx_cross_compile not in (True, False):
737            raise ValueError("Only boolean values are accepted for osx_cross_compile.")
738        if osx_cross_compile and not is_linux():
739            raise ValueError("osx_cross_compile can only be used on Linux.")
740    assertions = False
741    if "assertions" in config:
742        assertions = config["assertions"]
743        if assertions not in (True, False):
744            raise ValueError("Only boolean values are accepted for assertions.")
745    python_path = None
746    if "python_path" not in config:
747        raise ValueError("Config file needs to set python_path")
748    python_path = config["python_path"]
749    gcc_dir = None
750    if "gcc_dir" in config:
751        gcc_dir = config["gcc_dir"].format(**os.environ)
752        if not os.path.exists(gcc_dir):
753            raise ValueError("gcc_dir must point to an existing path")
754    ndk_dir = None
755    android_targets = None
756    if "android_targets" in config:
757        android_targets = config["android_targets"]
758        for attr in ("ndk_toolchain", "ndk_sysroot", "ndk_includes", "api_level"):
759            for target, cfg in android_targets.items():
760                if attr not in cfg:
761                    raise ValueError(
762                        "must specify '%s' as a key for android target: %s"
763                        % (attr, target)
764                    )
765    extra_targets = None
766    if "extra_targets" in config:
767        extra_targets = config["extra_targets"]
768        if not isinstance(extra_targets, list):
769            raise ValueError("extra_targets must be a list")
770        if not all(isinstance(t, str) for t in extra_targets):
771            raise ValueError("members of extra_targets should be strings")
772
773    if is_linux() and gcc_dir is None:
774        raise ValueError("Config file needs to set gcc_dir")
775
776    if is_darwin() or osx_cross_compile:
777        os.environ["MACOSX_DEPLOYMENT_TARGET"] = (
778            "11.0" if os.environ.get("OSX_ARCH") == "arm64" else "10.12"
779        )
780
781    cc = get_tool(config, "cc")
782    cxx = get_tool(config, "cxx")
783    asm = get_tool(config, "ml" if is_windows() else "as")
784    ld = get_tool(config, "link" if is_windows() else "ld")
785    ar = get_tool(config, "lib" if is_windows() else "ar")
786    ranlib = None if is_windows() else get_tool(config, "ranlib")
787    libtool = None
788    if "libtool" in config:
789        libtool = get_tool(config, "libtool")
790
791    if not os.path.exists(source_dir):
792        os.makedirs(source_dir)
793
794    if not args.skip_patch:
795        for p in config.get("patches", []):
796            patch(os.path.join(config_dir, p), source_dir)
797
798    compiler_rt_source_link = llvm_source_dir + "/projects/compiler-rt"
799
800    symlinks = [
801        (clang_source_dir, llvm_source_dir + "/tools/clang"),
802        (extra_source_dir, llvm_source_dir + "/tools/clang/tools/extra"),
803        (lld_source_dir, llvm_source_dir + "/tools/lld"),
804        (compiler_rt_source_dir, compiler_rt_source_link),
805        (libcxx_source_dir, llvm_source_dir + "/projects/libcxx"),
806        (libcxxabi_source_dir, llvm_source_dir + "/projects/libcxxabi"),
807    ]
808    for l in symlinks:
809        # On Windows, we have to re-copy the whole directory every time.
810        if not is_windows() and os.path.islink(l[1]):
811            continue
812        delete(l[1])
813        if os.path.exists(l[0]):
814            symlink(l[0], l[1])
815
816    package_name = "clang"
817    if build_clang_tidy:
818        package_name = "clang-tidy"
819        if not args.skip_patch:
820            import_clang_tidy(
821                source_dir, build_clang_tidy_alpha, build_clang_tidy_external
822            )
823
824    if not os.path.exists(build_dir):
825        os.makedirs(build_dir)
826
827    libcxx_include_dir = os.path.join(llvm_source_dir, "projects", "libcxx", "include")
828
829    stage1_dir = build_dir + "/stage1"
830    stage1_inst_dir = stage1_dir + "/" + package_name
831
832    final_stage_dir = stage1_dir
833    final_inst_dir = stage1_inst_dir
834
835    if is_darwin():
836        extra_cflags = []
837        extra_cxxflags = ["-stdlib=libc++"]
838        extra_cflags2 = []
839        extra_cxxflags2 = ["-stdlib=libc++"]
840        extra_asmflags = []
841        extra_ldflags = []
842    elif is_linux():
843        extra_cflags = []
844        extra_cxxflags = []
845        extra_cflags2 = ["-fPIC"]
846        # Silence clang's warnings about arguments not being used in compilation.
847        extra_cxxflags2 = [
848            "-fPIC",
849            "-Qunused-arguments",
850        ]
851        extra_asmflags = []
852        # Avoid libLLVM internal function calls going through the PLT.
853        extra_ldflags = ["-Wl,-Bsymbolic-functions"]
854        # For whatever reason, LLVM's build system will set things up to turn
855        # on -ffunction-sections and -fdata-sections, but won't turn on the
856        # corresponding option to strip unused sections.  We do it explicitly
857        # here.  LLVM's build system is also picky about turning on ICF, so
858        # we do that explicitly here, too.
859        extra_ldflags += ["-fuse-ld=gold", "-Wl,--gc-sections", "-Wl,--icf=safe"]
860    elif is_windows():
861        extra_cflags = []
862        extra_cxxflags = []
863        # clang-cl would like to figure out what it's supposed to be emulating
864        # by looking at an MSVC install, but we don't really have that here.
865        # Force things on.
866        extra_cflags2 = []
867        extra_cxxflags2 = [
868            "-fms-compatibility-version=19.15.26726",
869            "-Xclang",
870            "-std=c++14",
871        ]
872        extra_asmflags = []
873        extra_ldflags = []
874
875    if osx_cross_compile:
876        # undo the damage done in the is_linux() block above, and also simulate
877        # the is_darwin() block above.
878        extra_cflags = []
879        extra_cxxflags = ["-stdlib=libc++"]
880        extra_cxxflags2 = ["-stdlib=libc++"]
881
882        extra_flags = [
883            "-target",
884            "x86_64-apple-darwin",
885            "-mlinker-version=137",
886            "-B",
887            "%s/bin" % os.getenv("CROSS_CCTOOLS_PATH"),
888            "-isysroot",
889            os.getenv("CROSS_SYSROOT"),
890            # technically the sysroot flag there should be enough to deduce this,
891            # but clang needs some help to figure this out.
892            "-I%s/usr/include" % os.getenv("CROSS_SYSROOT"),
893            "-iframework",
894            "%s/System/Library/Frameworks" % os.getenv("CROSS_SYSROOT"),
895        ]
896        extra_cflags += extra_flags
897        extra_cxxflags += extra_flags
898        extra_cflags2 += extra_flags
899        extra_cxxflags2 += extra_flags
900        extra_asmflags += extra_flags
901        extra_ldflags = [
902            "-Wl,-syslibroot,%s" % os.getenv("CROSS_SYSROOT"),
903            "-Wl,-dead_strip",
904        ]
905
906    upload_dir = os.getenv("UPLOAD_DIR")
907    if assertions and upload_dir:
908        extra_cflags2 += ["-fcrash-diagnostics-dir=%s" % upload_dir]
909        extra_cxxflags2 += ["-fcrash-diagnostics-dir=%s" % upload_dir]
910
911    build_one_stage(
912        [cc] + extra_cflags,
913        [cxx] + extra_cxxflags,
914        [asm] + extra_asmflags,
915        [ld] + extra_ldflags,
916        ar,
917        ranlib,
918        libtool,
919        llvm_source_dir,
920        stage1_dir,
921        package_name,
922        build_libcxx,
923        osx_cross_compile,
924        build_type,
925        assertions,
926        python_path,
927        gcc_dir,
928        libcxx_include_dir,
929        build_wasm,
930        is_final_stage=(stages == 1),
931    )
932
933    runtimes_source_link = llvm_source_dir + "/runtimes/compiler-rt"
934
935    if stages >= 2:
936        stage2_dir = build_dir + "/stage2"
937        stage2_inst_dir = stage2_dir + "/" + package_name
938        final_stage_dir = stage2_dir
939        final_inst_dir = stage2_inst_dir
940        pgo_phase = "gen" if pgo else None
941        build_one_stage(
942            [stage1_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_cflags2,
943            [stage1_inst_dir + "/bin/%s%s" % (cxx_name, exe_ext)] + extra_cxxflags2,
944            [stage1_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_asmflags,
945            [ld] + extra_ldflags,
946            ar,
947            ranlib,
948            libtool,
949            llvm_source_dir,
950            stage2_dir,
951            package_name,
952            build_libcxx,
953            osx_cross_compile,
954            build_type,
955            assertions,
956            python_path,
957            gcc_dir,
958            libcxx_include_dir,
959            build_wasm,
960            compiler_rt_source_dir,
961            runtimes_source_link,
962            compiler_rt_source_link,
963            is_final_stage=(stages == 2),
964            android_targets=android_targets,
965            extra_targets=extra_targets,
966            pgo_phase=pgo_phase,
967        )
968
969    if stages >= 3:
970        stage3_dir = build_dir + "/stage3"
971        stage3_inst_dir = stage3_dir + "/" + package_name
972        final_stage_dir = stage3_dir
973        final_inst_dir = stage3_inst_dir
974        build_one_stage(
975            [stage2_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_cflags2,
976            [stage2_inst_dir + "/bin/%s%s" % (cxx_name, exe_ext)] + extra_cxxflags2,
977            [stage2_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_asmflags,
978            [ld] + extra_ldflags,
979            ar,
980            ranlib,
981            libtool,
982            llvm_source_dir,
983            stage3_dir,
984            package_name,
985            build_libcxx,
986            osx_cross_compile,
987            build_type,
988            assertions,
989            python_path,
990            gcc_dir,
991            libcxx_include_dir,
992            build_wasm,
993            compiler_rt_source_dir,
994            runtimes_source_link,
995            compiler_rt_source_link,
996            (stages == 3),
997            extra_targets=extra_targets,
998        )
999
1000    if stages >= 4:
1001        stage4_dir = build_dir + "/stage4"
1002        stage4_inst_dir = stage4_dir + "/" + package_name
1003        final_stage_dir = stage4_dir
1004        final_inst_dir = stage4_inst_dir
1005        pgo_phase = None
1006        if pgo:
1007            pgo_phase = "use"
1008            llvm_profdata = stage3_inst_dir + "/bin/llvm-profdata%s" % exe_ext
1009            merge_cmd = [llvm_profdata, "merge", "-o", "merged.profdata"]
1010            profraw_files = glob.glob(
1011                os.path.join(stage2_dir, "build", "profiles", "*.profraw")
1012            )
1013            if not os.path.exists(stage4_dir):
1014                os.mkdir(stage4_dir)
1015            run_in(stage4_dir, merge_cmd + profraw_files)
1016        build_one_stage(
1017            [stage3_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_cflags2,
1018            [stage3_inst_dir + "/bin/%s%s" % (cxx_name, exe_ext)] + extra_cxxflags2,
1019            [stage3_inst_dir + "/bin/%s%s" % (cc_name, exe_ext)] + extra_asmflags,
1020            [ld] + extra_ldflags,
1021            ar,
1022            ranlib,
1023            libtool,
1024            llvm_source_dir,
1025            stage4_dir,
1026            package_name,
1027            build_libcxx,
1028            osx_cross_compile,
1029            build_type,
1030            assertions,
1031            python_path,
1032            gcc_dir,
1033            libcxx_include_dir,
1034            build_wasm,
1035            compiler_rt_source_dir,
1036            runtimes_source_link,
1037            compiler_rt_source_link,
1038            (stages == 4),
1039            extra_targets=extra_targets,
1040            pgo_phase=pgo_phase,
1041        )
1042
1043    if build_clang_tidy:
1044        prune_final_dir_for_clang_tidy(
1045            os.path.join(final_stage_dir, package_name), osx_cross_compile
1046        )
1047
1048    # Copy the wasm32 builtins to the final_inst_dir if the archive is present.
1049    if "wasi-sysroot" in config:
1050        sysroot = config["wasi-sysroot"].format(**os.environ)
1051        if os.path.isdir(sysroot):
1052            for srcdir in glob.glob(
1053                os.path.join(sysroot, "lib", "clang", "*", "lib", "wasi")
1054            ):
1055                print("Copying from wasi-sysroot srcdir %s" % srcdir)
1056                # Copy the contents of the "lib/wasi" subdirectory to the
1057                # appropriate location in final_inst_dir.
1058                version = os.path.basename(os.path.dirname(os.path.dirname(srcdir)))
1059                destdir = os.path.join(
1060                    final_inst_dir, "lib", "clang", version, "lib", "wasi"
1061                )
1062                mkdir_p(destdir)
1063                copy_tree(srcdir, destdir)
1064
1065    if not args.skip_tar:
1066        build_tar_package("%s.tar.zst" % package_name, final_stage_dir, package_name)
1067