blob: bb40e6f7f3acace17742f7bb62914ef9f75587ec [file] [log] [blame]
#
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Builder instances for various targets."""
import contextlib
from pathlib import Path
import os
import re
import shutil
import textwrap
from typing import cast, Dict, Iterator, List, Optional, Set
import base_builders
import configs
import constants
import hosts
import mapfile
import paths
import toolchains
import utils
class AsanMapFileBuilder(base_builders.Builder):
name: str = 'asan-mapfile'
config_list: List[configs.Config] = configs.android_configs()
def _build_config(self) -> None:
arch = self._config.target_arch
# We can not build asan_test using current CMake building system. Since
# those files are not used to build AOSP, we just simply touch them so that
# we can pass the build checks.
asan_test_path = self.output_toolchain.path / 'test' / arch.llvm_arch / 'bin'
asan_test_path.mkdir(parents=True, exist_ok=True)
asan_test_bin_path = asan_test_path / 'asan_test'
asan_test_bin_path.touch(exist_ok=True)
lib_dir = self.output_toolchain.resource_dir
self._build_sanitizer_map_file('asan', arch, lib_dir)
self._build_sanitizer_map_file('ubsan_standalone', arch, lib_dir)
if arch == hosts.Arch.AARCH64:
self._build_sanitizer_map_file('hwasan', arch, lib_dir)
@staticmethod
def _build_sanitizer_map_file(san: str, arch: hosts.Arch, lib_dir: Path) -> None:
lib_file = lib_dir / f'libclang_rt.{san}-{arch.llvm_arch}-android.so'
map_file = lib_dir / f'libclang_rt.{san}-{arch.llvm_arch}-android.map.txt'
mapfile.create_map_file(lib_file, map_file)
class Stage1Builder(base_builders.LLVMBuilder):
name: str = 'stage1'
install_dir: Path = paths.OUT_DIR / 'stage1-install'
build_android_targets: bool = False
config_list: List[configs.Config] = [configs.host_config()]
@property
def llvm_targets(self) -> Set[str]:
if self.build_android_targets:
return constants.HOST_TARGETS | constants.ANDROID_TARGETS
else:
return constants.HOST_TARGETS
@property
def llvm_projects(self) -> Set[str]:
proj = {'clang', 'lld', 'libcxxabi', 'libcxx', 'compiler-rt'}
if self.build_lldb:
proj.add('lldb')
return proj
@property
def ldflags(self) -> List[str]:
ldflags = super().ldflags
# Use -static-libstdc++ to statically link the c++ runtime [1]. This
# avoids specifying self.toolchain.lib_dir in rpath to find libc++ at
# runtime.
# [1] libc++ in our case, despite the flag saying -static-libstdc++.
ldflags.append('-static-libstdc++')
return ldflags
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
defines['CLANG_ENABLE_ARCMT'] = 'OFF'
defines['CLANG_ENABLE_STATIC_ANALYZER'] = 'OFF'
defines['LLVM_BUILD_TOOLS'] = 'ON'
# Make libc++.so a symlink to libc++.so.x instead of a linker script that
# also adds -lc++abi. Statically link libc++abi to libc++ so it is not
# necessary to pass -lc++abi explicitly. This is needed only for Linux.
if self._config.target_os.is_linux:
defines['LIBCXX_ENABLE_ABI_LINKER_SCRIPT'] = 'OFF'
defines['LIBCXX_ENABLE_STATIC_ABI_LIBRARY'] = 'ON'
# Do not build compiler-rt for Darwin. We don't ship host (or any
# prebuilt) runtimes for Darwin anyway. Attempting to build these will
# fail compilation of lib/builtins/atomic_*.c that only get built for
# Darwin and fail compilation due to us using the bionic version of
# stdatomic.h.
if self._config.target_os.is_darwin:
defines['LLVM_BUILD_EXTERNAL_COMPILER_RT'] = 'ON'
# Don't build libfuzzer as part of the first stage build.
defines['COMPILER_RT_BUILD_LIBFUZZER'] = 'OFF'
return defines
class Stage2Builder(base_builders.LLVMBuilder):
name: str = 'stage2'
install_dir: Path = paths.OUT_DIR / 'stage2-install'
config_list: List[configs.Config] = [configs.host_config()]
remove_install_dir: bool = True
debug_build: bool = False
build_instrumented: bool = False
profdata_file: Optional[Path] = None
lto: bool = True
@property
def llvm_targets(self) -> Set[str]:
return constants.ANDROID_TARGETS
@property
def llvm_projects(self) -> Set[str]:
proj = {'clang', 'lld', 'libcxxabi', 'libcxx', 'compiler-rt',
'clang-tools-extra', 'openmp', 'polly'}
if self.build_lldb:
proj.add('lldb')
return proj
@property
def env(self) -> Dict[str, str]:
env = super().env
# Point CMake to the libc++ from stage1. It is possible that once built,
# the newly-built libc++ may override this because of the rpath pointing to
# $ORIGIN/../lib64. That'd be fine because both libraries are built from
# the same sources.
env['LD_LIBRARY_PATH'] = str(self.toolchain.lib_dir)
return env
@property
def ldflags(self) -> List[str]:
ldflags = super().ldflags
if self.build_instrumented:
# Building libcxx, libcxxabi with instrumentation causes linker errors
# because these are built with -nodefaultlibs and prevent libc symbols
# needed by libclang_rt.profile from being resolved. Manually adding
# the libclang_rt.profile to linker flags fixes the issue.
resource_dir = self.toolchain.resource_dir
ldflags.append(str(resource_dir / 'libclang_rt.profile-x86_64.a'))
return ldflags
@property
def cflags(self) -> List[str]:
cflags = super().cflags
if self.profdata_file:
cflags.append('-Wno-profile-instr-out-of-date')
cflags.append('-Wno-profile-instr-unprofiled')
return cflags
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
defines['SANITIZER_ALLOW_CXXABI'] = 'OFF'
defines['OPENMP_ENABLE_OMPT_TOOLS'] = 'FALSE'
defines['LIBOMP_ENABLE_SHARED'] = 'FALSE'
defines['CLANG_PYTHON_BINDINGS_VERSIONS'] = '3'
if (self.lto and
not self._config.target_os.is_darwin and
not self.build_instrumented and
not self.debug_build):
defines['LLVM_ENABLE_LTO'] = 'Thin'
# Build libFuzzer here to be exported for the host fuzzer builds. libFuzzer
# is not currently supported on Darwin.
if self._config.target_os.is_darwin:
defines['COMPILER_RT_BUILD_LIBFUZZER'] = 'OFF'
else:
defines['COMPILER_RT_BUILD_LIBFUZZER'] = 'ON'
if self.debug_build:
defines['CMAKE_BUILD_TYPE'] = 'Debug'
if self.build_instrumented:
defines['LLVM_BUILD_INSTRUMENTED'] = 'ON'
# llvm-profdata is only needed to finish CMake configuration
# (tools/clang/utils/perf-training/CMakeLists.txt) and not needed for
# build
llvm_profdata = self.toolchain.path / 'bin' / 'llvm-profdata'
defines['LLVM_PROFDATA'] = str(llvm_profdata)
elif self.profdata_file:
defines['LLVM_PROFDATA_FILE'] = str(self.profdata_file)
# Make libc++.so a symlink to libc++.so.x instead of a linker script that
# also adds -lc++abi. Statically link libc++abi to libc++ so it is not
# necessary to pass -lc++abi explicitly. This is needed only for Linux.
if self._config.target_os.is_linux:
defines['LIBCXX_ENABLE_STATIC_ABI_LIBRARY'] = 'ON'
defines['LIBCXX_ENABLE_ABI_LINKER_SCRIPT'] = 'OFF'
# Do not build compiler-rt for Darwin. We don't ship host (or any
# prebuilt) runtimes for Darwin anyway. Attempting to build these will
# fail compilation of lib/builtins/atomic_*.c that only get built for
# Darwin and fail compilation due to us using the bionic version of
# stdatomic.h.
if self._config.target_os.is_darwin:
defines['LLVM_BUILD_EXTERNAL_COMPILER_RT'] = 'ON'
return defines
def install_config(self) -> None:
super().install_config()
lldb_wrapper_path = self.install_dir / 'bin' / 'lldb.sh'
lib_path_env = 'LD_LIBRARY_PATH' if self._config.target_os.is_linux else 'DYLD_LIBRARY_PATH'
lldb_wrapper_path.write_text(textwrap.dedent(f"""\
#!/bin/bash
CURDIR=$(cd $(dirname $0) && pwd)
export PYTHONHOME="$CURDIR/../python3"
export {lib_path_env}="$CURDIR/../python3/lib:${lib_path_env}"
"$CURDIR/lldb" "$@"
"""))
lldb_wrapper_path.chmod(0o755)
class BuiltinsBuilder(base_builders.LLVMRuntimeBuilder):
name: str = 'builtins'
src_dir: Path = paths.LLVM_PATH / 'compiler-rt' / 'lib' / 'builtins'
# Only target the NDK, not the platform. The NDK copy is sufficient for the
# platform builders, and both NDK+platform builders use the same toolchain,
# which can only have a single copy installed into its resource directory.
@property
def config_list(self) -> List[configs.Config]:
result = configs.android_configs(platform=False, extra_config={'is_exported': False})
# For arm32 and x86, build a special version of the builtins library
# where the symbols are exported, not hidden. This version is needed
# to continue exporting builtins from libc.so and libm.so.
for arch in [configs.AndroidARMConfig(), configs.AndroidI386Config()]:
arch.platform = False
arch.extra_config = {'is_exported': True}
result.append(arch)
return result
@property
def is_exported(self) -> bool:
return cast(Dict[str, bool], self._config.extra_config)['is_exported']
@property
def output_dir(self) -> Path:
old_path = super().output_dir
suffix = '-exported' if self.is_exported else ''
return old_path.parent / (old_path.name + suffix)
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
arch = self._config.target_arch
defines['COMPILER_RT_BUILTINS_HIDE_SYMBOLS'] = \
'TRUE' if not self.is_exported else 'FALSE'
defines['COMPILER_RT_DEFAULT_TARGET_TRIPLE'] = arch.llvm_triple
# For CMake feature testing, create an archive instead of an executable,
# because we can't link an executable until builtins have been built.
defines['CMAKE_TRY_COMPILE_TARGET_TYPE'] = 'STATIC_LIBRARY'
defines['COMPILER_RT_EXCLUDE_ATOMIC_BUILTIN'] = 'OFF'
return defines
def install_config(self) -> None:
# Copy the library into the toolchain resource directory (lib/linux) and
# runtimes_ndk_cxx.
arch = self._config.target_arch
sarch = 'i686' if arch == hosts.Arch.I386 else arch.value
filename = 'libclang_rt.builtins-' + sarch + '-android.a'
filename_exported = 'libclang_rt.builtins-' + sarch + '-android-exported.a'
src_path = self.output_dir / 'lib' / 'android' / filename
if self.is_exported:
# This special copy exports its symbols and is only intended for use
# in Bionic's libc.so.
shutil.copy2(src_path, self.output_toolchain.resource_dir / filename_exported)
else:
shutil.copy2(src_path, self.output_toolchain.resource_dir / filename)
# Also install to self.toolchain.resource_dir, if it's different,
# for use when building target libraries.
if self.toolchain.resource_dir != self.output_toolchain.resource_dir:
shutil.copy2(src_path, self.toolchain.resource_dir / filename)
# Make a copy for the NDK.
dst_dir = self.output_toolchain.path / 'runtimes_ndk_cxx'
dst_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_path, dst_dir / filename)
class CompilerRTBuilder(base_builders.LLVMRuntimeBuilder):
name: str = 'compiler-rt'
src_dir: Path = paths.LLVM_PATH / 'compiler-rt'
config_list: List[configs.Config] = (
configs.android_configs(platform=True) +
configs.android_configs(platform=False)
)
@property
def install_dir(self) -> Path:
if self._config.platform:
return self.output_toolchain.clang_lib_dir
# Installs to a temporary dir and copies to runtimes_ndk_cxx manually.
output_dir = self.output_dir
return output_dir.parent / (output_dir.name + '-install')
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
arch = self._config.target_arch
defines['COMPILER_RT_BUILD_BUILTINS'] = 'OFF'
defines['COMPILER_RT_USE_BUILTINS_LIBRARY'] = 'ON'
# FIXME: Disable WError build until upstream fixed the compiler-rt
# personality routine warnings caused by r309226.
# defines['COMPILER_RT_ENABLE_WERROR'] = 'ON'
defines['COMPILER_RT_TEST_COMPILER_CFLAGS'] = defines['CMAKE_C_FLAGS']
defines['COMPILER_RT_DEFAULT_TARGET_TRIPLE'] = arch.llvm_triple
defines['COMPILER_RT_INCLUDE_TESTS'] = 'OFF'
defines['SANITIZER_CXX_ABI'] = 'libcxxabi'
# With CMAKE_SYSTEM_NAME='Android', compiler-rt will be installed to
# lib/android instead of lib/linux.
del defines['CMAKE_SYSTEM_NAME']
libs: List[str] = []
if self._config.api_level < 21:
libs += ['-landroid_support']
# Currently, -rtlib=compiler-rt (even with -unwindlib=libunwind) does
# not automatically link libunwind.a on Android.
libs += ['-lunwind']
defines['SANITIZER_COMMON_LINK_LIBS'] = ' '.join(libs)
# compiler-rt's CMakeLists.txt file deletes -Wl,-z,defs from
# CMAKE_SHARED_LINKER_FLAGS when COMPILER_RT_USE_BUILTINS_LIBRARY is
# set. We want this flag on instead to catch unresolved references
# early.
defines['SANITIZER_COMMON_LINK_FLAGS'] = '-Wl,-z,defs'
if self._config.platform:
defines['COMPILER_RT_HWASAN_WITH_INTERCEPTORS'] = 'OFF'
return defines
@property
def cflags(self) -> List[str]:
cflags = super().cflags
cflags.append('-funwind-tables')
return cflags
def install_config(self) -> None:
# Still run `ninja install`.
super().install_config()
# Install the fuzzer library to the old {arch}/libFuzzer.a path for
# backwards compatibility.
arch = self._config.target_arch
sarch = 'i686' if arch == hosts.Arch.I386 else arch.value
static_lib_filename = 'libclang_rt.fuzzer-' + sarch + '-android.a'
lib_dir = self.install_dir / 'lib' / 'linux'
arch_dir = lib_dir / arch.value
arch_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(lib_dir / static_lib_filename, arch_dir / 'libFuzzer.a')
if not self._config.platform:
dst_dir = self.output_toolchain.path / 'runtimes_ndk_cxx'
shutil.copytree(lib_dir, dst_dir, dirs_exist_ok=True)
def install(self) -> None:
# Install libfuzzer headers once for all configs.
header_src = self.src_dir / 'lib' / 'fuzzer'
header_dst = self.output_toolchain.path / 'prebuilt_include' / 'llvm' / 'lib' / 'Fuzzer'
header_dst.mkdir(parents=True, exist_ok=True)
for f in header_src.iterdir():
if f.suffix in ('.h', '.def'):
shutil.copy2(f, header_dst)
symlink_path = self.output_toolchain.resource_dir / 'libclang_rt.hwasan_static-aarch64-android.a'
symlink_path.unlink(missing_ok=True)
os.symlink('libclang_rt.hwasan-aarch64-android.a', symlink_path)
class CompilerRTHostI386Builder(base_builders.LLVMRuntimeBuilder):
name: str = 'compiler-rt-i386-host'
src_dir: Path = paths.LLVM_PATH / 'compiler-rt'
config_list: List[configs.Config] = [configs.LinuxConfig(is_32_bit=True)]
@property
def install_dir(self) -> Path:
return self.output_toolchain.clang_lib_dir
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
# Due to CMake and Clang oddities, we need to explicitly set
# CMAKE_C_COMPILER_TARGET and use march=i686 in cflags below instead of
# relying on auto-detection from the Compiler-rt CMake files.
defines['CMAKE_C_COMPILER_TARGET'] = 'i386-linux-gnu'
defines['COMPILER_RT_INCLUDE_TESTS'] = 'ON'
defines['COMPILER_RT_ENABLE_WERROR'] = 'ON'
defines['SANITIZER_CXX_ABI'] = 'libstdc++'
return defines
@property
def cflags(self) -> List[str]:
cflags = super().cflags
# compiler-rt/lib/gwp_asan uses PRIu64 and similar format-specifier macros.
# Add __STDC_FORMAT_MACROS so their definition gets included from
# inttypes.h. This explicit flag is only needed here. 64-bit host runtimes
# are built in stage1/stage2 and get it from the LLVM CMake configuration.
# These are defined unconditionaly in bionic and newer glibc
# (https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=1ef74943ce2f114c78b215af57c2ccc72ccdb0b7)
cflags.append('-D__STDC_FORMAT_MACROS')
cflags.append('--target=i386-linux-gnu')
cflags.append('-march=i686')
return cflags
def _build_config(self) -> None:
# Also remove the "stamps" created for the libcxx included in libfuzzer so
# CMake runs the configure again (after the cmake caches are deleted).
stamp_path = self.output_dir / 'lib' / 'fuzzer' / 'libcxx_fuzzer_i386-stamps'
if stamp_path.exists():
shutil.rmtree(stamp_path)
super()._build_config()
class LibUnwindBuilder(base_builders.LLVMRuntimeBuilder):
name: str = 'libunwind'
src_dir: Path = paths.LLVM_PATH / 'libunwind'
# Build two copies of the builtins library:
# - A copy targeting the NDK with hidden symbols.
# - A copy targeting the platform with exported symbols.
# Bionic's libc.so exports the unwinder, so it needs a copy with exported
# symbols. Everything else uses the NDK copy.
config_list: List[configs.Config] = (
configs.android_configs(platform=True) +
configs.android_configs(platform=False)
)
@property
def is_exported(self) -> bool:
return self._config.platform
@property
def output_dir(self) -> Path:
old_path = super().output_dir
suffix = '-exported' if self.is_exported else '-hermetic'
return old_path.parent / (old_path.name + suffix)
@property
def cflags(self) -> List[str]:
return super().cflags + ['-D_LIBUNWIND_USE_DLADDR=0']
@property
def ldflags(self) -> List[str]:
# Override the default -unwindlib=libunwind. libunwind.a doesn't exist
# when libunwind is built, and libunwind can't use
# CMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY because
# LIBUNWIND_HAS_PTHREAD_LIB must be set to false.
return super().ldflags + ['-unwindlib=none']
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
defines['LIBUNWIND_HIDE_SYMBOLS'] = 'TRUE' if not self.is_exported else 'FALSE'
defines['LIBUNWIND_ENABLE_SHARED'] = 'FALSE'
if self.enable_assertions:
defines['LIBUNWIND_ENABLE_ASSERTIONS'] = 'TRUE'
else:
defines['LIBUNWIND_ENABLE_ASSERTIONS'] = 'FALSE'
# Enable the FrameHeaderCache for the libc.so unwinder only. It can't be
# enabled generally for Android because it needs the
# dlpi_adds/dlpi_subs fields, which were only added to Bionic in
# Android R. See llvm.org/pr46743.
defines['LIBUNWIND_USE_FRAME_HEADER_CACHE'] = 'TRUE' if self.is_exported else 'FALSE'
return defines
def install_config(self) -> None:
# We need to install libunwind manually.
src_path = self.output_dir / 'lib64' / 'libunwind.a'
arch = self._config.target_arch
out_res_dir = self.output_toolchain.resource_dir / arch.value
out_res_dir.mkdir(parents=True, exist_ok=True)
if self.is_exported:
# This special copy exports its symbols and is only intended for use
# in Bionic's libc.so.
shutil.copy2(src_path, out_res_dir / 'libunwind-exported.a')
else:
shutil.copy2(src_path, out_res_dir / 'libunwind.a')
# Also install to self.toolchain.resource_dir, if it's different, for
# use when building runtimes.
if self.toolchain.resource_dir != self.output_toolchain.resource_dir:
res_dir = self.toolchain.resource_dir / arch.value
res_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_path, res_dir / 'libunwind.a')
# Make a copy for the NDK.
ndk_dir = self.output_toolchain.path / 'runtimes_ndk_cxx' / arch.value
ndk_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_path, ndk_dir / 'libunwind.a')
class LibOMPBuilder(base_builders.LLVMRuntimeBuilder):
name: str = 'libomp'
src_dir: Path = paths.LLVM_PATH / 'openmp'
config_list: List[configs.Config] = (
configs.android_configs(platform=True, extra_config={'is_shared': False}) +
configs.android_configs(platform=False, extra_config={'is_shared': False}) +
configs.android_configs(platform=False, extra_config={'is_shared': True})
)
@property
def is_shared(self) -> bool:
return cast(Dict[str, bool], self._config.extra_config)['is_shared']
@property
def output_dir(self) -> Path:
old_path = super().output_dir
suffix = '-shared' if self.is_shared else '-static'
return old_path.parent / (old_path.name + suffix)
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
defines['OPENMP_ENABLE_LIBOMPTARGET'] = 'FALSE'
defines['OPENMP_ENABLE_OMPT_TOOLS'] = 'FALSE'
defines['LIBOMP_ENABLE_SHARED'] = 'TRUE' if self.is_shared else 'FALSE'
# Some compiler-rt math builtins depend on libm, so link against it.
# TODO: Try to break the builtins->libm dependency (llvm.org/PR32279).
defines['LIBOMP_LIBFLAGS'] = '-lm'
# Minimum version for OpenMP's CMake is too low for the CMP0056 policy
# to be ON by default.
defines['CMAKE_POLICY_DEFAULT_CMP0056'] = 'NEW'
return defines
def install_config(self) -> None:
# We need to install libomp manually.
libname = 'libomp.' + ('so' if self.is_shared else 'a')
src_lib = self.output_dir / 'runtime' / 'src' / libname
dst_dir = self.install_dir
dst_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_lib, dst_dir / libname)
class LibNcursesBuilder(base_builders.AutoconfBuilder, base_builders.LibInfo):
name: str = 'libncurses'
src_dir: Path = paths.LIBNCURSES_SRC_DIR
config_list: List[configs.Config] = [configs.host_config()]
lib_version: str = '6'
@property
def config_flags(self) -> List[str]:
return super().config_flags + [
'--with-shared',
]
@property
def _lib_names(self) -> List[str]:
return ['libncurses', 'libform', 'libpanel']
class LibEditBuilder(base_builders.AutoconfBuilder, base_builders.LibInfo):
name: str = 'libedit'
src_dir: Path = paths.LIBEDIT_SRC_DIR
config_list: List[configs.Config] = [configs.host_config()]
libncurses: base_builders.LibInfo
lib_version: str = '0'
@property
def ldflags(self) -> List[str]:
return [
f'-L{self.libncurses.link_libraries[0].parent}',
] + super().ldflags
@property
def cflags(self) -> List[str]:
flags = []
flags.append('-I' + str(self.libncurses.include_dir))
flags.append('-I' + str(self.libncurses.include_dir / 'ncurses'))
return flags + super().cflags
def build(self) -> None:
files: List[Path] = []
super().build()
class SwigBuilder(base_builders.AutoconfBuilder):
name: str = 'swig'
src_dir: Path = paths.SWIG_SRC_DIR
config_list: List[configs.Config] = [configs.host_config()]
@property
def config_flags(self) -> List[str]:
flags = super().config_flags
flags.append('--without-pcre')
return flags
@property
def ldflags(self) -> List[str]:
ldflags = super().ldflags
# Point to the libc++.so from the toolchain.
ldflags.append(f'-Wl,-rpath,{self.toolchain.lib_dir}')
return ldflags
class XzBuilder(base_builders.CMakeBuilder, base_builders.LibInfo):
name: str = 'liblzma'
src_dir: Path = paths.XZ_SRC_DIR
config_list: List[configs.Config] = [configs.host_config()]
static_lib: bool = True
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
# CMake actually generates a malformed archive command. llvm-ranlib does
# not accept it, but the Apple ranlib accepts this. Workaround to use
# the system ranlib until either CMake fixes this or llvm-ranlib also
# supports this common malformed input.
# See LIBTOOL(1).
if self._config.target_os.is_darwin:
defines.pop("CMAKE_RANLIB")
return defines
class LibXml2Builder(base_builders.CMakeBuilder, base_builders.LibInfo):
name: str = 'libxml2'
src_dir: Path = paths.LIBXML2_SRC_DIR
config_list: List[configs.Config] = [configs.host_config()]
lib_version: str = '2.9.10'
@contextlib.contextmanager
def _backup_file(self, file_to_backup: Path) -> Iterator[None]:
backup_file = file_to_backup.parent / (file_to_backup.name + '.bak')
if file_to_backup.exists():
file_to_backup.rename(backup_file)
try:
yield
finally:
if backup_file.exists():
backup_file.rename(file_to_backup)
def build(self) -> None:
# The src dir contains configure files for Android platform. Rename them
# so that they will not be used during our build.
# We don't delete them here because the same libxml2 may be used to build
# Android platform later.
with self._backup_file(self.src_dir / 'include' / 'libxml' / 'xmlversion.h'):
with self._backup_file(self.src_dir / 'config.h'):
super().build()
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
defines['LIBXML2_WITH_PYTHON'] = 'OFF'
defines['LIBXML2_WITH_PROGRAMS'] = 'OFF'
defines['LIBXML2_WITH_LZMA'] = 'OFF'
defines['LIBXML2_WITH_ICONV'] = 'OFF'
defines['LIBXML2_WITH_ZLIB'] = 'OFF'
return defines
@property
def include_dir(self) -> Path:
return self.install_dir / 'include' / 'libxml2'
@property
def symlinks(self) -> List[Path]:
if self._config.target_os.is_windows:
return []
ext = 'so' if self._config.target_os.is_linux else 'dylib'
return [self.install_dir / 'lib' / f'libxml2.{ext}']
class LldbServerBuilder(base_builders.LLVMRuntimeBuilder):
name: str = 'lldb-server'
src_dir: Path = paths.LLVM_PATH / 'llvm'
config_list: List[configs.Config] = configs.android_configs(platform=False, static=True)
ninja_targets: List[str] = ['lldb-server']
@property
def cflags(self) -> List[str]:
cflags: List[str] = super().cflags
# The build system will add '-stdlib=libc++' automatically. Since we
# have -nostdinc++ here, -stdlib is useless. Adds a flag to avoid the
# warnings.
cflags.append('-Wno-unused-command-line-argument')
return cflags
@property
def ldflags(self) -> List[str]:
# Currently, -rtlib=compiler-rt (even with -unwindlib=libunwind) does
# not automatically link libunwind.a on Android.
return super().ldflags + ['-lunwind']
@property
def _llvm_target(self) -> str:
return {
hosts.Arch.ARM: 'ARM',
hosts.Arch.AARCH64: 'AArch64',
hosts.Arch.I386: 'X86',
hosts.Arch.X86_64: 'X86',
}[self._config.target_arch]
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
# lldb depends on support libraries.
defines['LLVM_ENABLE_PROJECTS'] = 'clang;lldb'
defines['LLVM_TARGETS_TO_BUILD'] = self._llvm_target
defines['LLVM_TABLEGEN'] = str(self.toolchain.build_path / 'bin' / 'llvm-tblgen')
defines['CLANG_TABLEGEN'] = str(self.toolchain.build_path / 'bin' / 'clang-tblgen')
defines['LLDB_TABLEGEN'] = str(self.toolchain.build_path / 'bin' / 'lldb-tblgen')
triple = self._config.target_arch.llvm_triple
defines['LLVM_HOST_TRIPLE'] = triple.replace('i686', 'i386')
return defines
def install_config(self) -> None:
src_path = self.output_dir / 'bin' / 'lldb-server'
install_dir = self.install_dir
install_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_path, install_dir)
class LibCxxAbiBuilder(base_builders.LLVMRuntimeBuilder):
name = 'libcxxabi'
src_dir: Path = paths.LLVM_PATH / 'libcxxabi'
@property
def install_dir(self):
return paths.OUT_DIR / 'windows-x86-64-install'
@property
def cmake_defines(self) -> Dict[str, str]:
defines: Dict[str, str] = super().cmake_defines
defines['LIBCXXABI_ENABLE_NEW_DELETE_DEFINITIONS'] = 'OFF'
defines['LIBCXXABI_LIBCXX_INCLUDES'] = str(paths.LLVM_PATH /'libcxx' / 'include')
# Build only the static library.
defines['LIBCXXABI_ENABLE_SHARED'] = 'OFF'
if self.enable_assertions:
defines['LIBCXXABI_ENABLE_ASSERTIONS'] = 'ON'
return defines
@property
def cflags(self) -> List[str]:
cflags: List[str] = super().cflags
# Disable libcxx visibility annotations and enable WIN32 threads. These
# are needed because the libcxxabi build happens before libcxx and uses
# headers directly from the sources.
cflags.append('-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS')
cflags.append('-D_LIBCPP_HAS_THREAD_API_WIN32')
return cflags
class SysrootsBuilder(base_builders.Builder):
name: str = 'sysroots'
config_list: List[configs.Config] = (
configs.android_configs(platform=True) +
configs.android_configs(platform=False)
)
def _build_config(self) -> None:
config: configs.AndroidConfig = cast(configs.AndroidConfig, self._config)
arch = config.target_arch
platform = config.platform
sysroot = config.sysroot
if sysroot.exists():
shutil.rmtree(sysroot)
sysroot.mkdir(parents=True, exist_ok=True)
# Copy the NDK prebuilt's sysroot, but for the platform variant, omit
# the STL and android_support headers and libraries.
src_sysroot = paths.NDK_BASE / 'toolchains' / 'llvm' / 'prebuilt' / 'linux-x86_64' / 'sysroot'
# Copy over usr/include.
shutil.copytree(src_sysroot / 'usr' / 'include',
sysroot / 'usr' / 'include', symlinks=True)
if platform:
# Remove the STL headers.
shutil.rmtree(sysroot / 'usr' / 'include' / 'c++')
else:
# Add the android_support headers from usr/local/include.
shutil.copytree(src_sysroot / 'usr' / 'local' / 'include',
sysroot / 'usr' / 'local' / 'include', symlinks=True)
# Copy over usr/lib/$TRIPLE.
src_lib = src_sysroot / 'usr' / 'lib' / arch.ndk_triple
dest_lib = sysroot / 'usr' / 'lib' / arch.ndk_triple
shutil.copytree(src_lib, dest_lib, symlinks=True)
# Remove the NDK's libcompiler_rt-extras. For the platform, also remove
# the NDK libc++.
(dest_lib / 'libcompiler_rt-extras.a').unlink()
if platform:
(dest_lib / 'libc++abi.a').unlink()
(dest_lib / 'libc++_static.a').unlink()
(dest_lib / 'libc++_shared.so').unlink()
# Each per-API-level directory has libc++.so, libc++.a, and libcompiler_rt-extras.a.
for subdir in dest_lib.iterdir():
if subdir.is_symlink() or not subdir.is_dir():
continue
if not re.match(r'\d+$', subdir.name):
continue
(subdir / 'libcompiler_rt-extras.a').unlink()
if platform:
(subdir / 'libc++.a').unlink()
(subdir / 'libc++.so').unlink()
# Verify that there aren't any extra copies somewhere else in the
# directory hierarchy.
verify_gone = ['libcompiler_rt-extras.a', 'libunwind.a']
if platform:
verify_gone += [
'libc++abi.a',
'libc++_static.a',
'libc++_shared.so',
'libc++.a',
'libc++.so',
]
for (parent, _, files) in os.walk(sysroot):
for f in files:
if f in verify_gone:
raise RuntimeError('sysroot file should have been ' +
f'removed: {os.path.join(parent, f)}')
if platform:
# Create a stub library for the platform's libc++.
platform_stubs = paths.OUT_DIR / 'platform_stubs' / arch.ndk_arch
platform_stubs.mkdir(parents=True, exist_ok=True)
libdir = sysroot / 'usr' / ('lib64' if arch == hosts.Arch.X86_64 else 'lib')
libdir.mkdir(parents=True, exist_ok=True)
with (platform_stubs / 'libc++.c').open('w') as f:
f.write(textwrap.dedent("""\
void __cxa_atexit() {}
void __cxa_demangle() {}
void __cxa_finalize() {}
void __dynamic_cast() {}
void _ZTIN10__cxxabiv117__class_type_infoE() {}
void _ZTIN10__cxxabiv120__si_class_type_infoE() {}
void _ZTIN10__cxxabiv121__vmi_class_type_infoE() {}
void _ZTISt9type_info() {}
"""))
utils.check_call([self.toolchain.cc,
f'--target={arch.llvm_triple}',
'-fuse-ld=lld', '-nostdlib', '-shared',
'-Wl,-soname,libc++.so',
'-o{}'.format(libdir / 'libc++.so'),
str(platform_stubs / 'libc++.c')])
class PlatformLibcxxAbiBuilder(base_builders.LLVMRuntimeBuilder):
name = 'platform-libcxxabi'
src_dir: Path = paths.LLVM_PATH / 'libcxxabi'
config_list: List[configs.Config] = configs.android_configs(
platform=True, suppress_libcxx_headers=True)
@property
def cmake_defines(self) -> Dict[str, str]:
defines: Dict[str, str] = super().cmake_defines
defines['LIBCXXABI_LIBCXX_INCLUDES'] = str(paths.LLVM_PATH / 'libcxx' / 'include')
defines['LIBCXXABI_ENABLE_SHARED'] = 'OFF'
return defines
def _is_64bit(self) -> bool:
return self._config.target_arch in (hosts.Arch.AARCH64, hosts.Arch.X86_64)
def _build_config(self) -> None:
if self._is_64bit():
# For arm64 and x86_64, build static cxxabi library from
# toolchain/libcxxabi and use it when building runtimes. This
# should affect all compiler-rt runtimes that use libcxxabi
# (e.g. asan, hwasan, scudo, tsan, ubsan, xray).
super()._build_config()
else:
self.install_config()
def install_config(self) -> None:
arch = self._config.target_arch
lib_name = 'lib64' if arch == hosts.Arch.X86_64 else 'lib'
install_dir = self._config.sysroot / 'usr' / lib_name
if self._is_64bit():
src_path = self.output_dir / 'lib64' / 'libc++abi.a'
shutil.copy2(src_path, install_dir / 'libc++abi.a')
else:
with (install_dir / 'libc++abi.so').open('w') as f:
f.write('INPUT(-lc++)')
class LibCxxBuilder(base_builders.LLVMRuntimeBuilder):
name = 'libcxx'
src_dir: Path = paths.LLVM_PATH / 'libcxx'
libcxx_abi_path: Path
@property
def install_dir(self):
return paths.OUT_DIR / 'windows-x86-64-install'
@property
def cmake_defines(self) -> Dict[str, str]:
defines: Dict[str, str] = super().cmake_defines
defines['LIBCXX_ENABLE_STATIC_ABI_LIBRARY'] = 'ON'
defines['LIBCXX_ENABLE_NEW_DELETE_DEFINITIONS'] = 'ON'
defines['LIBCXX_CXX_ABI'] = 'libcxxabi'
defines['LIBCXX_HAS_WIN32_THREAD_API'] = 'ON'
# Use cxxabi header from the source directory since it gets installed
# into install_dir only during libcxx's install step. But use the
# library from install_dir.
defines['LIBCXX_CXX_ABI_INCLUDE_PATHS'] = str(paths.LLVM_PATH / 'libcxxabi' / 'include')
defines['LIBCXX_CXX_ABI_LIBRARY_PATH'] = str(self.libcxx_abi_path / 'lib64')
# Build only the static library.
defines['LIBCXX_ENABLE_SHARED'] = 'OFF'
defines['LIBCXX_ENABLE_EXPERIMENTAL_LIBRARY'] = 'OFF'
if self.enable_assertions:
defines['LIBCXX_ENABLE_ASSERTIONS'] = 'ON'
return defines
@property
def cflags(self) -> List[str]:
cflags: List[str] = super().cflags
# Disable libcxxabi visibility annotations since we're only building it
# statically.
cflags.append('-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS')
return cflags
class WindowsToolchainBuilder(base_builders.LLVMBuilder):
name: str = 'windows-x86-64'
toolchain_name: str = 'stage1'
build_lldb: bool = True
libcxx_path: Optional[Path] = None
@property
def _is_msvc(self) -> bool:
return isinstance(self._config, configs.MSVCConfig)
@property
def install_dir(self) -> Path:
return paths.OUT_DIR / 'windows-x86-64-install'
@property
def llvm_targets(self) -> Set[str]:
return constants.ANDROID_TARGETS
@property
def llvm_projects(self) -> Set[str]:
proj = {'clang', 'clang-tools-extra', 'lld', 'polly'}
if self.build_lldb:
proj.add('lldb')
return proj
@property
def cmake_defines(self) -> Dict[str, str]:
defines = super().cmake_defines
# Don't build compiler-rt, libcxx etc. for Windows
defines['LLVM_BUILD_RUNTIME'] = 'OFF'
# Build clang-tidy/clang-format for Windows.
defines['LLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD'] = 'ON'
defines['LLVM_TOOL_OPENMP_BUILD'] = 'OFF'
# Don't build tests for Windows.
defines['LLVM_INCLUDE_TESTS'] = 'OFF'
defines['LLVM_CONFIG_PATH'] = str(self.toolchain.build_path / 'bin' / 'llvm-config')
defines['LLVM_TABLEGEN'] = str(self.toolchain.build_path / 'bin' / 'llvm-tblgen')
defines['CLANG_TABLEGEN'] = str(self.toolchain.build_path / 'bin' / 'clang-tblgen')
if self.build_lldb:
defines['LLDB_TABLEGEN'] = str(self.toolchain.build_path / 'bin' / 'lldb-tblgen')
if self._is_msvc:
# Generating libLLVM is not supported on MSVC.
defines['LLVM_BUILD_LLVM_DYLIB'] = 'OFF'
# But we still want LLVMgold.dll.
defines['LLVM_ENABLE_PLUGINS'] = 'ON'
defines['CMAKE_CXX_STANDARD'] = '17'
defines['LLVM_BUILD_LLVM_C_DYLIB'] = 'OFF'
defines['ZLIB_INCLUDE_DIR'] = str(paths.WIN_ZLIB_INCLUDE_PATH)
defines['ZLIB_LIBRARY_DEBUG'] = str(paths.WIN_ZLIB_LIB_PATH / 'libz.a')
defines['ZLIB_LIBRARY_RELEASE'] = str(paths.WIN_ZLIB_LIB_PATH / 'libz.a')
return defines
@property
def ldflags(self) -> List[str]:
ldflags = super().ldflags
if not self._is_msvc:
# Use static-libgcc to avoid runtime dependence on libgcc_eh.
ldflags.append('-static-libgcc')
# pthread is needed by libgcc_eh.
ldflags.append('-pthread')
ldflags.append('-Wl,--dynamicbase')
ldflags.append('-Wl,--nxcompat')
ldflags.append('-Wl,--high-entropy-va')
ldflags.append('-Wl,--Xlink=-Brepro')
libpath_prefix = '-L'
else:
ldflags.append('/dynamicbase')
ldflags.append('/nxcompat')
ldflags.append('/highentropyva')
ldflags.append('/Brepro')
libpath_prefix = '/LIBPATH:'
ldflags.append(libpath_prefix + str(paths.WIN_ZLIB_LIB_PATH))
if self.libcxx_path:
# Add path to libc++, libc++abi.
libcxx_lib = self.libcxx_path / 'lib64'
ldflags.append(libpath_prefix + str(libcxx_lib))
return ldflags
@property
def cflags(self) -> List[str]:
cflags = super().cflags
cflags.append('-DLZMA_API_STATIC')
cflags.append('-DMS_WIN64')
cflags.append(f'-I{paths.WIN_ZLIB_INCLUDE_PATH}')
return cflags
@property
def cxxflags(self) -> List[str]:
cxxflags = super().cxxflags
# Use -fuse-cxa-atexit to allow static TLS destructors. This is needed for
# clang-tools-extra/clangd/Context.cpp
cxxflags.append('-fuse-cxa-atexit')
if self.libcxx_path:
# Explicitly add the path to libc++ headers. We don't need to configure
# options like visibility annotations, win32 threads etc. because the
# __generated_config header in the patch captures all the options used when
# building libc++.
cxx_headers = self.libcxx_path / 'include' / 'c++' / 'v1'
cxxflags.append(f'-I{cxx_headers}')
return cxxflags
def install_config(self) -> None:
super().install_config()
lldb_wrapper_path = self.install_dir / 'bin' / 'lldb.cmd'
lldb_wrapper_path.write_text(textwrap.dedent("""\
@ECHO OFF
SET PYTHONHOME=%~dp0..\python3
SET PATH=%~dp0..\python3;%PATH%
%~dp0lldb.exe %*
EXIT /B %ERRORLEVEL%
"""))