diff options
author | Chris Wailes <chriswailes@google.com> | 2024-04-12 17:30:51 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2024-04-12 17:30:51 +0000 |
commit | 9801b686e453221b5c8caaeb95061378bd908fc2 (patch) | |
tree | bf04c25027e81d8c7f7296bdbc573d427f87e611 | |
parent | ab8ff3dcd463466c7bb15e5600f0deab3330ed8f (diff) | |
parent | 7fc8801087677e921bb8f4c1ab19349b83ab7a57 (diff) | |
download | android_rust-9801b686e453221b5c8caaeb95061378bd908fc2.tar.gz |
Merge changes If90e39c5,Ibecbb6fc,Iab509f23,I1a53525d,Ia18f82f3 into main
* changes:
Return the ability to invoke BOLT when building the toolchain
Take the Stage 0 Rust toolchain as a command line option
Add a RustToolchain class to manage paths
Add a ClangToolchain class to manage paths
Clean up error/exception handling
-rw-r--r-- | src/android_rust/cargo.py | 7 | ||||
-rw-r--r-- | src/android_rust/config.py | 82 | ||||
-rw-r--r-- | src/android_rust/paths.py | 25 | ||||
-rw-r--r-- | src/android_rust/source_manager.py | 33 | ||||
-rw-r--r-- | src/android_rust/toolchains.py | 159 | ||||
-rw-r--r-- | src/android_rust/utils.py | 50 | ||||
-rwxr-xr-x | tools/audit.py | 5 | ||||
-rwxr-xr-x | tools/boltifyer.py | 37 | ||||
-rwxr-xr-x | tools/build.py | 119 | ||||
-rwxr-xr-x | tools/fetch_source.py | 11 | ||||
-rwxr-xr-x | tools/pipeline.py | 4 | ||||
-rwxr-xr-x | tools/update_prebuilts.py | 13 |
12 files changed, 407 insertions, 138 deletions
diff --git a/src/android_rust/cargo.py b/src/android_rust/cargo.py index 6bea1aa..3df245a 100644 --- a/src/android_rust/cargo.py +++ b/src/android_rust/cargo.py @@ -16,7 +16,8 @@ import os from pathlib import Path import shutil -from android_rust.paths import CARGO_DENY_CONFIG_PATH, CARGO_PATH, OUT_PATH +from android_rust.paths import CARGO_DENY_CONFIG_PATH, OUT_PATH +from android_rust.toolchains import RUST_TOOLCHAIN_HOST from android_rust.utils import run_and_exit_on_failure @@ -34,7 +35,7 @@ class Crate: self, crate_path: Path, env: dict[str, str], - cargo_path: Path | None = None, + cargo_path: Path = RUST_TOOLCHAIN_HOST.cargo(), target: str | None = None, linker: Path | None = None, ) -> None: @@ -42,7 +43,7 @@ class Crate: self.env = env.copy() # TODO: Use tomllib to get the crate name. But this requires python 3.11 self.build_dir = OUT_PATH / crate_path.name - self.cargo_path = cargo_path if cargo_path else CARGO_PATH + self.cargo_path = cargo_path # Make sure that cargo is in our path, so plugins, rustc, etc. are run from there. self.env["PATH"] = os.pathsep.join( [str(self.cargo_path.resolve().parent), self.env["PATH"]]) diff --git a/src/android_rust/config.py b/src/android_rust/config.py index 2b381ec..f6f1268 100644 --- a/src/android_rust/config.py +++ b/src/android_rust/config.py @@ -18,12 +18,12 @@ import argparse import os from pathlib import Path import subprocess -import sys from typing import Any, Tuple, Union import android_rust.build_platform as build_platform from android_rust.paths import * -from android_rust.utils import instantiate_template, instantiate_template_file, instantiate_template_exec +from android_rust.toolchains import LLVM_TOOLCHAIN_HOST, LLVM_TOOLCHAIN_LINUX, LLVM_TOOLCHAIN_WINDOWS +from android_rust.utils import ScriptException, instantiate_template, instantiate_template_file, instantiate_template_exec # # Constants @@ -179,16 +179,25 @@ def host_config( env[f"CC_{envified_target}"] = str(cc_wrapper_name) env[f"CXX_{envified_target}"] = str(cxx_wrapper_name) env[f"LD_{envified_target}"] = str(ld_wrapper_name) - env[f"RANLIB_{envified_target}"] = str(RANLIB_PATH) + env[f"RANLIB_{envified_target}"] = str(LLVM_TOOLCHAIN_HOST.ranlib()) instantiate_template_exec( - HOST_CC_WRAPPER_TEMPLATE, cc_wrapper_name, real_cc=CC_PATH, cc_flags=cc_flags_str) + HOST_CC_WRAPPER_TEMPLATE, + cc_wrapper_name, + real_cc=LLVM_TOOLCHAIN_HOST.clang(), + cc_flags=cc_flags_str) instantiate_template_exec( - HOST_CXX_WRAPPER_TEMPLATE, cxx_wrapper_name, real_cxx=CXX_PATH, cxx_flags=cxx_flags_str) + HOST_CXX_WRAPPER_TEMPLATE, + cxx_wrapper_name, + real_cxx=LLVM_TOOLCHAIN_HOST.clangxx(), + cxx_flags=cxx_flags_str) instantiate_template_exec( - HOST_LINKER_WRAPPER_TEMPLATE, ld_wrapper_name, real_cc=CC_PATH, ld_flags=ld_flags_str) + HOST_LINKER_WRAPPER_TEMPLATE, + ld_wrapper_name, + real_cc=LLVM_TOOLCHAIN_HOST.clang(), + ld_flags=ld_flags_str) # as_posix() is necessary here to prevent the backslashes from being # interpreted as the start of an esacape sequence on Windows hosts. @@ -198,8 +207,8 @@ def host_config( cc=cc_wrapper_name.as_posix(), cxx=cxx_wrapper_name.as_posix(), linker=ld_wrapper_name.as_posix(), - ar=AR_PATH.as_posix(), - ranlib=RANLIB_PATH.as_posix(), + ar=LLVM_TOOLCHAIN_HOST.ar().as_posix(), + ranlib=LLVM_TOOLCHAIN_HOST.ranlib().as_posix(), profiler="true" if "linux" in target else "false", musl_root=f"musl-root = \"{HOST_SYSROOTS[target]}\"" if "musl" in target else "") @@ -214,10 +223,16 @@ def bare_config(target: str, cc_flags: list[str], ld_flags: list[str], env: dict env[f"LD_{envified_target}"] = str(ld_wrapper_name) instantiate_template_exec( - BARE_CC_WRAPPER_TEMPLATE, cc_wrapper_name, real_cc=CC_PATH, cc_flags=cc_flags_str) + BARE_CC_WRAPPER_TEMPLATE, + cc_wrapper_name, + real_cc=LLVM_TOOLCHAIN_HOST.clang(), + cc_flags=cc_flags_str) instantiate_template_exec( - BARE_LINKER_WRAPPER_TEMPLATE, ld_wrapper_name, real_cc=CC_PATH, ld_flags=ld_flags_str) + BARE_LINKER_WRAPPER_TEMPLATE, + ld_wrapper_name, + real_cc=LLVM_TOOLCHAIN_HOST.clang(), + ld_flags=ld_flags_str) # as_posix() is necessary here to prevent the backslashes from being # interpreted as the start of an esacape sequence on Windows hosts. @@ -227,7 +242,7 @@ def bare_config(target: str, cc_flags: list[str], ld_flags: list[str], env: dict cc=cc_wrapper_name.as_posix(), cxx=cc_wrapper_name.as_posix(), linker=ld_wrapper_name.as_posix(), - ar=AR_PATH.as_posix()) + ar=LLVM_TOOLCHAIN_HOST.ar().as_posix()) def device_config( @@ -242,10 +257,16 @@ def device_config( env[f"LD_{envified_target}"] = str(ld_wrapper_name) instantiate_template_exec( - DEVICE_CC_WRAPPER_TEMPLATE, cc_wrapper_name, real_cc=CC_PATH, cc_flags=cc_flags_str) + DEVICE_CC_WRAPPER_TEMPLATE, + cc_wrapper_name, + real_cc=LLVM_TOOLCHAIN_HOST.clang(), + cc_flags=cc_flags_str) instantiate_template_exec( - DEVICE_LINKER_WRAPPER_TEMPLATE, ld_wrapper_name, real_cc=CC_PATH, ld_flags=ld_flags_str) + DEVICE_LINKER_WRAPPER_TEMPLATE, + ld_wrapper_name, + real_cc=LLVM_TOOLCHAIN_HOST.clang(), + ld_flags=ld_flags_str) # as_posix() is necessary here to prevent the backslashes from being # interpreted as the start of an esacape sequence on Windows hosts. @@ -255,7 +276,7 @@ def device_config( cc=cc_wrapper_name.as_posix(), cxx=cc_wrapper_name.as_posix(), linker=ld_wrapper_name.as_posix(), - ar=AR_PATH.as_posix()) + ar=LLVM_TOOLCHAIN_HOST.ar().as_posix()) # @@ -312,7 +333,7 @@ HOST_LINUX_LD_FLAGS: list[str] = [ f"-B{GCC_LIBGCC_PATH}", f"-L{GCC_LIBGCC_PATH}", f"-L{GCC_LIB_PATH}", - f"-L{LLVM_CXX_RUNTIME_PATH_HOST}", + f"-L{LLVM_TOOLCHAIN_HOST.lib()}", LD_OLD_DTAGS, LD_FLAG_RPATH, ] @@ -321,7 +342,7 @@ HOST_WINDOWS_LDFLAGS: list[str] = [ f"-B{MINGW_LIBGCC_PATH}", f"-L{MINGW_LIBGCC_PATH}", f"-L{MINGW_LIB_PATH}", - f"-L{LLVM_CXX_RUNTIME_PATH_WINDOWS}", + f"-L{LLVM_TOOLCHAIN_WINDOWS.lib()}", ] TARGET_LD_FLAGS: dict[str, list[str]] = { # When performing LTO, the LLVM IR generator doesn't know about these @@ -337,17 +358,17 @@ TARGET_LD_FLAGS: dict[str, list[str]] = { "x86_64-unknown-linux-gnu": HOST_LINUX_LD_FLAGS, "i686-unknown-linux-gnu": HOST_LINUX_LD_FLAGS, "x86_64-apple-darwin": [ - f"-L{LLVM_CXX_RUNTIME_PATH_HOST}", + f"-L{LLVM_TOOLCHAIN_HOST.lib()}", LD_FLAG_RPATH, ], "aarch64-apple-darwin": [ - f"-L{LLVM_CXX_RUNTIME_PATH_HOST}", + f"-L{LLVM_TOOLCHAIN_HOST.lib()}", LD_FLAG_RPATH, ], "x86_64-unknown-linux-musl": [ LD_FLAG_USE_LLD, "--rtlib=compiler-rt", - f"-L{LLVM_CXX_RUNTIME_PATH_LINUX_MUSL}", + f"-L{LLVM_TOOLCHAIN_HOST.musl_lib()}", f"-L{MUSL_SYSROOT64_PATH}/lib", LD_OLD_DTAGS, LD_FLAG_RPATH, @@ -355,7 +376,7 @@ TARGET_LD_FLAGS: dict[str, list[str]] = { "i686-unknown-linux-musl": [ LD_FLAG_USE_LLD, "--rtlib=compiler-rt", - f"-L{LLVM_CXX_RUNTIME_PATH_LINUX_MUSL}", + f"-L{LLVM_TOOLCHAIN_HOST.musl_lib()}", f"-L{MUSL_SYSROOT32_PATH}/lib", LD_OLD_DTAGS, LD_FLAG_RPATH, @@ -444,7 +465,7 @@ def configure(args: argparse.Namespace, env: dict[str, str]) -> None: # Use LD_LIBRARY_PATH to tell the build system where to find libc++.so.1 # without polluting the rpath of the produced artifacts. if not build_platform.is_windows(): - env["LD_LIBRARY_PATH"] = str(LLVM_CXX_RUNTIME_PATH_HOST) + env["LD_LIBRARY_PATH"] = str(LLVM_TOOLCHAIN_HOST.lib()) # Tell the rust bootstrap system where to place its final products env["DESTDIR"] = str(OUT_PATH_PACKAGE) @@ -465,7 +486,7 @@ def configure(args: argparse.Namespace, env: dict[str, str]) -> None: # Explicitly set the host AR path; this is required to build the # openssl-src crate. - env["AR"] = str(AR_PATH) + env["AR"] = str(LLVM_TOOLCHAIN_HOST.ar()) env["CARGO_HOME"] = str(OUT_PATH_CARGO_HOME) @@ -513,7 +534,7 @@ def configure(args: argparse.Namespace, env: dict[str, str]) -> None: # All flags from host_cc_flags will be added to host_cxx_flags host_cxx_flags = [ "--stdlib=libc++", - f"-I{CXXSTD_PATH}", + f"-I{LLVM_TOOLCHAIN_HOST.cxxstd()}", ] host_ld_flags = [ LD_FLAG_PIC, @@ -540,7 +561,7 @@ def configure(args: argparse.Namespace, env: dict[str, str]) -> None: env_cflags: list[str] = [] env_rustflags: list[str] = [ "-Crelocation-model=pic", - f"-Cdlltool={DLLTOOL_PATH}", + f"-Cdlltool={LLVM_TOOLCHAIN_HOST.dlltool()}", ] + ([] if args.verbose else ["-Awarnings"]) env_rustflags_bootstrap: list[str] = [] env_rustflags_not_bootstrap: list[str] = [] @@ -561,8 +582,11 @@ def configure(args: argparse.Namespace, env: dict[str, str]) -> None: # libunwind.a. So, when building on Windows, we need to point to the right directory in # in the Linux build of clang. if build_platform.is_windows(): + device_lib = LLVM_TOOLCHAIN_LINUX.device_lib() + assert (device_lib is not None) + for target, arch in DEVICE_TARGET_AND_ARCH.items(): - unwind_path = DEVICE_LIB_PATH / arch + unwind_path = device_lib / arch if not unwind_path.is_dir(): raise RuntimeError(f"{unwind_path} is not a directory") @@ -615,7 +639,7 @@ def configure(args: argparse.Namespace, env: dict[str, str]) -> None: rustc_pgo_config = f"profile-use = \"{profile_path_rust}\"" if not (profile_path_llvm.exists() or profile_path_rust.exists()): - sys.exit(f"No profiles found in specified directory: {args.profile_use}") + raise ScriptException(f"No profiles found in specified directory: {args.profile_use}") if args.cs_profile_generate != None: # TODO: The vp-counters-per-site value needs to be tuned to eliminate @@ -758,13 +782,13 @@ def configure(args: argparse.Namespace, env: dict[str, str]) -> None: llvm_cxxflags=llvm_cc_flags_str, llvm_ldflags=llvm_ld_flags_str, llvm_lto_config=llvm_lto_config, - llvm_cxx_runtime_path_host=LLVM_CXX_RUNTIME_PATH_HOST.as_posix(), + llvm_cxx_runtime_path_host=LLVM_TOOLCHAIN_HOST.lib(), llvm_pgo_config=llvm_pgo_config, stage0_host_triple=RUST_STAGE0_TRIPLE, new_host_triple=args.host, all_targets=quoted_target_list, - cargo=CARGO_PATH.as_posix(), - rustc=RUSTC_PATH.as_posix(), + cargo=args.rust_stage0.cargo().as_posix(), + rustc=args.rust_stage0.rustc().as_posix(), python=PYTHON_PATH.as_posix(), verbose=1 if args.verbose else 0, description=f"\"Android Rust Toolchain version {args.build_name}\"", diff --git a/src/android_rust/paths.py b/src/android_rust/paths.py index 26ed5ef..3201240 100644 --- a/src/android_rust/paths.py +++ b/src/android_rust/paths.py @@ -111,10 +111,6 @@ RUST_HOST_STAGE0_PATH: Path = RUST_PREBUILT_PATH / build_platform.pr LLVM_PREBUILT_PATH_HOST: Path = PREBUILT_PATH / "clang" / "host" / build_platform.prebuilt() / CLANG_NAME LLVM_PREBUILT_PATH_LINUX: Path = PREBUILT_PATH / "clang" / "host" / "linux-x86" / CLANG_NAME LLVM_PREBUILT_PATH_WINDOWS: Path = PREBUILT_PATH / "clang" / "host" / "windows-x86" / CLANG_NAME -LLVM_CXX_RUNTIME_PATH_HOST: Path = LLVM_PREBUILT_PATH_HOST / "lib" -LLVM_CXX_RUNTIME_PATH_LINUX_GLIBC: Path = LLVM_PREBUILT_PATH_LINUX / "lib" -LLVM_CXX_RUNTIME_PATH_LINUX_MUSL: Path = LLVM_PREBUILT_PATH_LINUX / "musl" / "lib" / "x86_64-unknown-linux-musl" -LLVM_CXX_RUNTIME_PATH_WINDOWS: Path = LLVM_PREBUILT_PATH_WINDOWS / "lib" GCC_TOOLCHAIN_PATH: Path = PREBUILT_PATH / "gcc" / build_platform.prebuilt() / "host" / ("x86_64-linux-glibc" + GLIBC_VERSION) GCC_LIB_PATH: Path = GCC_TOOLCHAIN_PATH / "x86_64-linux" / "lib64" @@ -149,26 +145,6 @@ NDK_SYSROOT_PATH_SUFFIX: Path = Path("sysroot") SOONG_PATH: Path = WORKSPACE_PATH / "build" / "soong" # -# Paths to toolchain executables -# -_EXE = ".exe" if build_platform.is_windows() else "" -CARGO_PATH: Path = RUST_HOST_STAGE0_PATH / "bin" / ("cargo" + _EXE) -RUSTC_PATH: Path = RUST_HOST_STAGE0_PATH / "bin" / ("rustc" + _EXE) -NEW_CARGO_PATH: Path = OUT_PATH_PACKAGE / "bin" / ("cargo" + _EXE) -CC_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("clang" + _EXE) -CXX_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("clang++" + _EXE) -AR_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("llvm-ar" + _EXE) -DLLTOOL_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("llvm-dlltool" + _EXE) -BOLT_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("llvm-bolt" + _EXE) -RANLIB_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("llvm-ranlib" + _EXE) -PROFDATA_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("llvm-profdata" + _EXE) -OBJCOPY_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("llvm-objcopy" + _EXE) -RANLIB_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("llvm-ranlib" + _EXE) -READELF_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("llvm-readelf" + _EXE) -MERGE_FDATA_PATH: Path = LLVM_PREBUILT_PATH_HOST / "bin" / ("merge-fdata" + _EXE) -CXXSTD_PATH: Path = LLVM_PREBUILT_PATH_HOST / "include" / "c++" / "v1" - -# # Paths to utilities used by the build script. # @@ -186,7 +162,6 @@ if build_platform.is_windows(): GIT_USR_BIN_PATH: Path = Path("C:\\") / "program files" / "git" / "usr" / "bin" FILE_PATH: Path = GIT_USR_BIN_PATH / "file.exe" PATCH_PATH: Path = GIT_USR_BIN_PATH / "patch.exe" - DEVICE_LIB_PATH: Path = LLVM_CXX_RUNTIME_PATH_LINUX_GLIBC / "clang" / CLANG_VERSION / "lib" / "linux" TAR_PATH: Path = Path("C:\\") / "phony" / "path" / "do" / "not" / "use" else: PYTHON_PATH: Path = PYTHON_PREBUILT_PATH / "bin" / "python3" diff --git a/src/android_rust/source_manager.py b/src/android_rust/source_manager.py index 9d90b5d..eba520b 100644 --- a/src/android_rust/source_manager.py +++ b/src/android_rust/source_manager.py @@ -17,14 +17,13 @@ Package to manage Rust source files when building a toolchain distributable. """ +import io from pathlib import Path import shutil import subprocess -import sys -from typing import Any from android_rust.paths import OUT_PATH_PATCHS_LOG, PATCH_PATH, OUT_PATH_PATCHED_FILES -from android_rust.utils import prepare_command +from android_rust.utils import ScriptException, prepare_command def ensure_unique_patch_numbers(patch_list: list[Path]) -> None: @@ -69,7 +68,7 @@ def copy_or_restore( patched_files.write(f"{file}\n") -def apply_patches(code_dir: Path, patch_list: list[Path], patch_abort: bool = False) -> None: +def apply_patches(code_dir: Path, patch_list: list[Path]) -> None: count_padding = len(str(len(patch_list))) # We will overwrite the log file if it already existed. @@ -93,20 +92,18 @@ def apply_patches(code_dir: Path, patch_list: list[Path], patch_abort: bool = Fa if result.stderr: f.write(result.stderr.decode("UTF-8") + "\n") - if result.returncode != 0 and patch_abort: - print(f"\nBuild failed when applying patch {filepath}") - print("If developing locally, try the --no-patch-abort flag") + if result.returncode != 0: + err_msg = io.StringIO() + err_msg.write(f"Failed to apply patch {filepath}\n") if result.stdout: - print("\nOutput (stdout):") - print(result.stdout.decode("UTF-8")) + err_msg.write("\nOutput (stdout):") + err_msg.write(result.stdout.decode("UTF-8")) if result.stderr: - print("\nOutput (stderr):") - print(result.stderr.decode("UTF-8")) - - print("Failed") + err_msg.write("\nOutput (stderr):") + err_msg.write(result.stderr.decode("UTF-8")) f.truncate() - sys.exit(result.returncode) + raise ScriptException(err_msg.getvalue()) # Remove any possible leftovers from the previous log f.truncate() @@ -117,11 +114,7 @@ def apply_patches(code_dir: Path, patch_list: list[Path], patch_abort: bool = Fa def setup_files( - input_dir: Path, - output_dir: Path, - patches_dir: Path, - patch_abort: bool = False, - repatch: bool = False) -> None: + input_dir: Path, output_dir: Path, patches_dir: Path, repatch: bool = False) -> None: """Copy source and apply patches in a performant and fault-tolerant manner. This function creates a copy of the source directory and @@ -132,4 +125,4 @@ def setup_files( ensure_unique_patch_numbers(patch_list) copy_or_restore(input_dir, output_dir, patch_list, repatch, True) - apply_patches(output_dir, patch_list, patch_abort=patch_abort) + apply_patches(output_dir, patch_list) diff --git a/src/android_rust/toolchains.py b/src/android_rust/toolchains.py new file mode 100644 index 0000000..fa9585e --- /dev/null +++ b/src/android_rust/toolchains.py @@ -0,0 +1,159 @@ +# Copyright (C) 2024 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. + +import inspect +from pathlib import Path + +from android_rust.paths import CLANG_VERSION, LLVM_PREBUILT_PATH_HOST, LLVM_PREBUILT_PATH_LINUX, LLVM_PREBUILT_PATH_WINDOWS, RUST_HOST_STAGE0_PATH + + +class ClangToolchain: + PATH_METHODS = [ + "ar", + "bolt", + "clang", + "clangxx", + "dlltool", + "merge_fdata", + "objcopy", + "profdata", + "ranlib", + "readelf", + ] + + def __init__(self, base_dir: Path): + self.base_dir = base_dir + self.bin_dir = base_dir / "bin" + self.exe = ".exe" if self.is_windows() else "" + + for method_name in self.__class__.PATH_METHODS: + path = getattr(self, method_name)() + if path is not None and not path.exists(): + raise RuntimeError(f"Clang toolchain path does not exist: {path}") + + # + # Binary Paths + # + + def ar(self) -> Path: + return self.bin_dir / ("llvm-ar" + self.exe) + + def bolt(self) -> Path | None: + if self.is_linux(): + return self.bin_dir / ("llvm-bolt" + self.exe) + else: + return None + + def clang(self) -> Path: + return self.bin_dir / ("clang" + self.exe) + + def clangxx(self) -> Path: + return self.bin_dir / ("clang++" + self.exe) + + def dlltool(self) -> Path: + return self.bin_dir / ("llvm-dlltool" + self.exe) + + def merge_fdata(self) -> Path | None: + if self.is_linux(): + return self.bin_dir / ("merge-fdata" + self.exe) + else: + return None + + def objcopy(self) -> Path: + return self.bin_dir / ("llvm-objcopy" + self.exe) + + def profdata(self) -> Path: + return self.bin_dir / ("llvm-profdata" + self.exe) + + def ranlib(self) -> Path: + return self.bin_dir / ("llvm-ranlib" + self.exe) + + def readelf(self) -> Path: + return self.bin_dir / ("llvm-readelf" + self.exe) + + # + # Include Paths + # + + def cxxstd(self) -> Path: + return self.base_dir / "include" / "c++" / "v1" + + def lib(self) -> Path: + return self.base_dir / "lib" + + def musl_lib(self) -> Path | None: + if self.is_linux(): + return self.base_dir / "musl" / "lib" / "x86_64-unknown-linux-musl" + else: + return None + + def device_lib(self) -> Path | None: + if self.is_linux(): + return self.base_dir / "lib" / "clang" / CLANG_VERSION / "lib" / "linux" + else: + return None + + # + # Helper Functions + # + + def is_linux(self) -> bool: + return "linux" in str(self.base_dir) + + def is_windows(self) -> bool: + return "windows" in str(self.base_dir) + + +class RustToolchain: + PATH_METHODS = [ + "cargo", + "rustc", + ] + + def __init__(self, base_dir: Path): + self.base_dir = base_dir + self.bin_dir = base_dir / "bin" + self.exe = ".exe" if self.is_windows() else "" + + for method_name in self.__class__.PATH_METHODS: + path = getattr(self, method_name)() + if path is not None and not path.exists(): + raise RuntimeError(f"Clang toolchain path does not exist: {path}") + + # + # Binary Paths + # + + def cargo(self) -> Path: + return self.bin_dir / ("cargo" + self.exe) + + def rustc(self) -> Path: + return self.bin_dir / ("rustc" + self.exe) + + # + # Helper Functions + # + + def is_linux(self) -> bool: + return "linux" in str(self.base_dir) + + def is_windows(self) -> bool: + return "windows" in str(self.base_dir) + + +LLVM_TOOLCHAIN_HOST = ClangToolchain(LLVM_PREBUILT_PATH_HOST) +LLVM_TOOLCHAIN_LINUX = ClangToolchain(LLVM_PREBUILT_PATH_LINUX) +LLVM_TOOLCHAIN_WINDOWS = ClangToolchain(LLVM_PREBUILT_PATH_WINDOWS) + +RUST_TOOLCHAIN_HOST = RustToolchain(RUST_HOST_STAGE0_PATH) diff --git a/src/android_rust/utils.py b/src/android_rust/utils.py index 221bd22..94edac8 100644 --- a/src/android_rust/utils.py +++ b/src/android_rust/utils.py @@ -30,8 +30,6 @@ from typing import Any, Optional, TextIO, TypedDict, Tuple, Union, cast import android_rust.build_platform as build_platform from android_rust.paths import ( FILE_PATH, - MERGE_FDATA_PATH, - OBJCOPY_PATH, OUT_PATH, PROFILE_NAME_LLVM, PROFILE_NAME_LLVM_CS, @@ -39,12 +37,12 @@ from android_rust.paths import ( PROFILE_NAMES, PROFILE_SUBDIR_BOLT, PROFILE_SUBDIRS, - PROFDATA_PATH, SOONG_UI_PATH, TAR_PATH, UNZIP_LINK_PATH, WORKSPACE_PATH, XZ_PATH) +from android_rust.toolchains import LLVM_TOOLCHAIN_HOST class RunQuietArgDct(TypedDict): @@ -52,6 +50,18 @@ class RunQuietArgDct(TypedDict): stderr: int +class ScriptException(Exception): + + """ + Indicates that the arguments given either don't work or don't make sense + in the context of the system state + """ + + def __init__(self, message: str): + self.message = message + super().__init__(message) + + GIT_REFERENCE_BRANCH = "aosp/main" # BOLT profile name examples: @@ -270,7 +280,7 @@ class GitRepo: self.checkout(branch_name) return False else: - sys.exit( + raise ScriptException( f"Branch {branch_name} already exists and the 'overwrite' option was not set") else: print(f"Creating branch {branch_name}") @@ -295,7 +305,7 @@ class GitRepo: elif retcode == 1: return True else: - sys.exit("Failed to compute diff for Git repo {self.path}") + raise RuntimeError("Failed to compute diff for Git repo {self.path}") def reset_hard(self, number: int) -> None: run_and_exit_on_failure(["git", "reset", "--hard", f"HEAD~{number}"], @@ -439,18 +449,22 @@ def get_prebuilt_binary_paths(root: Path, toplevel_only: bool = False) -> list[P def merge_profdata(inputs: list[Path], outpath: Path) -> None: - run_and_exit_on_failure([ - PROFDATA_PATH, + run_and_exit_on_failure( + [ + LLVM_TOOLCHAIN_HOST.profdata(), "merge", - "-o", outpath, + "-o", + outpath, ] + inputs, f"Failed to produce merged PGO profile {outpath}") # yapf: disable def merge_fdata(inputs: list[Path], outpath: Path) -> None: - run_quiet_and_exit_on_failure([ - MERGE_FDATA_PATH, - "-o", outpath, + run_quiet_and_exit_on_failure( + [ + LLVM_TOOLCHAIN_HOST.merge_fdata(), + "-o", + outpath, ] + inputs, f"Failed to produce merged BOLT profiles {outpath}") # yapf: disable @@ -509,7 +523,8 @@ def export_profiles(src_path: Optional[Path], dist_path: Path) -> None: merge_fdata( list(src_path.glob(f"{binary}.*.fdata")), bolt_output_path / f"{binary}.fdata") - archive_create(dist_path / "bolt-profiles", dist_path, PROFILE_SUBDIR_BOLT, overwrite=True) + archive_create( + dist_path / "bolt-profiles", dist_path, PROFILE_SUBDIR_BOLT, overwrite=True) else: for subdir, profile_name in zip(PROFILE_SUBDIRS, PROFILE_NAMES): @@ -535,8 +550,9 @@ def merge_project_profiles(indirs: list[Path], outdir: Path) -> None: def strip_symbols(obj_path: Path, flag: str = "--strip-unneeded") -> int: - return subprocess.run([str(OBJCOPY_PATH), "--keep-section='.rustc'", flag, - str(obj_path)]).returncode + return subprocess.run( + [str(LLVM_TOOLCHAIN_HOST.objcopy()), "--keep-section='.rustc'", flag, + str(obj_path)]).returncode # @@ -587,7 +603,11 @@ def instantiate_template(template_path: Path, **kwargs: Any) -> str: # -def archive_create(out_path_stem: Path, root_dir: Path, base_dir: Path = Path("."), overwrite: bool = False) -> int: +def archive_create( + out_path_stem: Path, + root_dir: Path, + base_dir: Path = Path("."), + overwrite: bool = False) -> int: archive_path = out_path_stem.with_suffix(out_path_stem.suffix + ".tar") if overwrite: diff --git a/tools/audit.py b/tools/audit.py index 6d2aa2c..d262f6b 100755 --- a/tools/audit.py +++ b/tools/audit.py @@ -25,7 +25,8 @@ import sys import context -from android_rust.paths import READELF_PATH, TOOLCHAIN_RESOURCE_PATH +from android_rust.paths import TOOLCHAIN_RESOURCE_PATH +from android_rust.toolchains import LLVM_TOOLCHAIN_HOST from android_rust.utils import ResolvedPath, print_colored, TERM_GREEN, TERM_RED @@ -69,7 +70,7 @@ def get_required_libs_map(scandir: Path) -> dict[str, list[str]]: for lib_path in lib_paths: local_libs.append(lib_path.name) - result = subprocess.run([READELF_PATH, "--dynamic", lib_path], + result = subprocess.run([LLVM_TOOLCHAIN_HOST.readelf(), "--dynamic", lib_path], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) diff --git a/tools/boltifyer.py b/tools/boltifyer.py index a9cd856..a73bb38 100755 --- a/tools/boltifyer.py +++ b/tools/boltifyer.py @@ -26,7 +26,9 @@ from typing import Optional, TextIO import context +from android_rust import build_platform from android_rust.paths import * +from android_rust.toolchains import LLVM_TOOLCHAIN_HOST from android_rust.utils import ( ExtantPath, ResolvedPath, @@ -97,13 +99,16 @@ def parse_args() -> argparse.Namespace: @contextmanager -def unpack_prebuilt_archive(archive_path: Path) -> Iterator[Path]: - with TemporaryDirectory() as temp_dir: - temp_dir_path = Path(temp_dir) - print(f"Unpacking archive into {temp_dir}") - if archive_extract(archive_path, temp_dir_path) != 0: - raise RuntimeError("Failed to unpack prebuilt archive") - yield temp_dir_path +def handle_input_path(input_path: Path) -> Iterator[Path]: + if is_archive(input_path): + with TemporaryDirectory() as temp_dir: + temp_dir_path = Path(temp_dir) + print(f"Unpacking archive into {temp_dir}") + if archive_extract(input_path, temp_dir_path) != 0: + raise RuntimeError("Failed to unpack prebuilt archive") + yield temp_dir_path + else: + yield input_path @contextmanager @@ -123,12 +128,15 @@ def expand_profiles(profile_path: Path | None) -> Iterator[Path | None]: def invoke_bolt(obj_path: Path, bolt_log: TextIO, options: list[str] = []) -> int: + bolt_path = LLVM_TOOLCHAIN_HOST.bolt() + assert (bolt_path is not None) + obj_path_bolted = extend_suffix(obj_path, ".bolt") bolt_log.write(f"BOLTing {str(obj_path)}\n") print(f"BOLTing {str(obj_path)}\n") bolt_log.flush() - retcode = subprocess.run([BOLT_PATH] + options + ["-o", obj_path_bolted, obj_path], + retcode = subprocess.run([bolt_path] + options + ["-o", obj_path_bolted, obj_path], stdout=bolt_log, stderr=bolt_log).returncode @@ -195,14 +203,14 @@ def process_objects( return 0 -def boltify_archive( +def boltify_toolchain( input_path: Path, dist_path: Path, build_name: str, profile_generate: Path | None = None, profile_use: Path | None = None) -> int: with (dist_path / BOLT_LOG_NAME).open("w") as bolt_log: - with unpack_prebuilt_archive(input_path) as toolchain_dir_path: + with handle_input_path(input_path) as toolchain_dir_path: print_fs_tree(toolchain_dir_path) with expand_profiles(profile_use) as profile_use_path: @@ -217,15 +225,20 @@ def boltify_archive( return retcode print(f"Creating BOLTed archive") - retcode = archive_create(dist_path / f"rust-{build_name}", toolchain_dir_path, overwrite=True) + retcode = archive_create( + dist_path / f"rust-{build_name}", toolchain_dir_path, overwrite=True) if retcode != 0: print("Error creating the BOLTed archive") return retcode def main() -> int: + if not build_platform.is_linux(): + print("BOLT tools are only available on Linux hosts") + return 1 + args = parse_args() - retval = boltify_archive( + retval = boltify_toolchain( args.archive_path, args.dist_path, args.build_name, diff --git a/tools/build.py b/tools/build.py index bc31852..ad8d204 100755 --- a/tools/build.py +++ b/tools/build.py @@ -28,16 +28,19 @@ import tarfile import tempfile import urllib.request +from boltifyer import boltify_toolchain import context import audit from android_rust import build_platform, cargo, config, source_manager from android_rust.paths import * +from android_rust.toolchains import LLVM_TOOLCHAIN_HOST, RustToolchain from android_rust.utils import ( TERM_GREEN, TERM_RED, TERM_YELLOW, + ScriptException, ExtantPath, ResolvedPath, archive_create, @@ -96,7 +99,12 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace: """Parses arguments and returns the parsed structure.""" parser = argparse.ArgumentParser("Build the Rust Toolchain") parser.add_argument( - "--build-name", "-b", default="dev", help="Release name for the dist result") + "--build-name", + "-b", + default=build_platform.system(), + help="Release name for the dist result") + parser.add_argument( + "--bolt-name", help="Name for the archive containing the BOLTed version of the toolchain") parser.add_argument( "--dist", "-d", @@ -114,11 +122,6 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace: action="store_true", help="Don't copy the whole source. Just copy and repatch the files affected by the patches.") parser.add_argument( - "--patch-abort", - action=argparse.BooleanOptionalAction, - default=True, - help="Abort on patch failure") - parser.add_argument( "--stage", "-s", type=int, choices=[1, 2, 3], help="Target Rust boostrap stage") parser.add_argument( "--config-only", @@ -132,6 +135,12 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace: parser.add_argument( "--verbose", action=argparse.BooleanOptionalAction, default=True, help="Verbose") + parser.add_argument( + "--rust-stage0", + type=ExtantPath, + default=RUST_HOST_STAGE0_PATH, + help="Path to Rust toolchain to bootstrap this build") + ndk_group = parser.add_mutually_exclusive_group() ndk_group.add_argument( "--ndk", @@ -164,6 +173,22 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace: nargs="?", const=OUT_PATH_PROFILES, help="Instrument the LLVM libraries to generate context-sensitive profiles") + + parser.add_argument( + "--bolt-opt", + action=argparse.BooleanOptionalAction, + default=False, + help="Produce an additional BOLT optimized copy of the toolchain") + + bolt_group = parser.add_mutually_exclusive_group() + bolt_group.add_argument( + "--bolt-profile-generate", + type=ExtantPath, + nargs="?", + const=OUT_PATH_PROFILES, + help="Add BOLT instrumentation to the toolchain") + bolt_group.add_argument("--bolt-profile-use", type=ExtantPath, help="Path to BOLT profiles") + parser.add_argument( "--lto", "-l", @@ -241,6 +266,15 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace: args.device_targets = False args.host_multilibs = False + if args.bolt_name is None: + args.bolt_name = args.build_name + "-bolt" + + if args.bolt_profile_use is not None or args.bolt_profile_generate is not None: + args.bolt_opt = True + + if args.bolt_opt: + args.emit_relocs = True + return args @@ -291,6 +325,16 @@ def initialize_ndk(args: argparse.Namespace) -> int: return 1 +def initialize_rust_stage0(args: argparse.Namespace) -> None: + if is_archive(args.rust_stage0): + print("Extracting Rust Stage 0 archive") + extract_dir = Path(tempfile.mkdtemp(prefix="rust-stage0-")) + archive_extract(args.rust_stage0, extract_dir) + args.rust_stage0 = extract_dir + + args.rust_stage0 = RustToolchain(args.rust_stage0) + + def extract_ndk_archive(archive_path: Path) -> Path: print(f"Unzipping NKD archive {str(archive_path)}") @@ -306,11 +350,12 @@ def extract_ndk_archive(archive_path: Path) -> Path: return extract_dir / archive_top_level[0].name -def regenerate_lockfile(project_path: Path, env: dict[str, str]) -> None: +def regenerate_lockfile( + rust_toolchain: RustToolchain, project_path: Path, env: dict[str, str]) -> None: """Offline fetch to regenerate lockfiles""" - run_and_exit_on_failure([CARGO_PATH, "fetch", "--offline"], - f"Failed to rebuild {project_path}/Cargo.lock via cargo-fetch operation", + run_and_exit_on_failure([rust_toolchain.cargo(), "fetch", "--offline"], + f"Failed to rebuilt {project_path}/Cargo.lock via cargo-fetch operation", cwd=project_path, env=env) @@ -367,23 +412,21 @@ def main(argv: list[str] | None = None) -> int: raise RuntimeError("Unable to find sh executable") os.symlink(ZIPTOOL_PATH, UNZIP_LINK_PATH) - os.symlink(OBJCOPY_PATH, STRIP_LINK_PATH) + os.symlink(LLVM_TOOLCHAIN_HOST.objcopy(), STRIP_LINK_PATH) retcode = initialize_ndk(args) if retcode != 0: return retcode + initialize_rust_stage0(args) + # # Setup source files # if args.copy_and_patch: source_manager.setup_files( - RUST_SOURCE_PATH, - OUT_PATH_RUST_SOURCE, - PATCHES_PATH, - patch_abort=args.patch_abort, - repatch=args.repatch) + RUST_SOURCE_PATH, OUT_PATH_RUST_SOURCE, PATCHES_PATH, repatch=args.repatch) if build_platform.is_darwin(): # To avoid using system libc++.dylib on Darwin hosts we need to copy @@ -391,7 +434,7 @@ def main(argv: list[str] | None = None) -> int: # necessary because buildbot security policies do not allow for # modifying the DYLD_LIBRARY_PATH environment variable. OUT_PATH_LLVM_LIB_DIR.mkdir(parents=True, exist_ok=True) - shutil.copy2(LLVM_CXX_RUNTIME_PATH_HOST / "libc++.dylib", OUT_PATH_LLVM_LIB_DIR) + shutil.copy2(LLVM_TOOLCHAIN_HOST.lib() / "libc++.dylib", OUT_PATH_LLVM_LIB_DIR) # # Configure Rust @@ -421,10 +464,10 @@ def main(argv: list[str] | None = None) -> int: # Because some patches may have touched vendored source we will rebuild # specific Cargo.lock files. - regenerate_lockfile(OUT_PATH_RUST_SOURCE, env) + regenerate_lockfile(args.rust_stage0, OUT_PATH_RUST_SOURCE, env) # TODO: Required by patch #40 - regenerate_lockfile(OUT_PATH_RUST_BOOTSTRAP_SOURCE, env) - regenerate_lockfile(OUT_PATH_RUST_CARGO_SOURCE, env) + regenerate_lockfile(args.rust_stage0, OUT_PATH_RUST_BOOTSTRAP_SOURCE, env) + regenerate_lockfile(args.rust_stage0, OUT_PATH_RUST_CARGO_SOURCE, env) # We only need to perform stage 3 of the bootstrap process when we are # collecting profile data. @@ -524,13 +567,16 @@ def main(argv: list[str] | None = None) -> int: copy_libs = [] # Install the libc++ library to out/package/lib64/ if build_platform.is_darwin(): - copy_libs.append(LLVM_CXX_RUNTIME_PATH_HOST / "libc++.dylib") - copy_libs.append(LLVM_CXX_RUNTIME_PATH_HOST / "libc++abi.dylib") + copy_libs.append(LLVM_TOOLCHAIN_HOST.lib() / "libc++.dylib") + copy_libs.append(LLVM_TOOLCHAIN_HOST.lib() / "libc++abi.dylib") elif args.host == "x86_64-unknown-linux-musl": - copy_libs.append(LLVM_CXX_RUNTIME_PATH_LINUX_MUSL / "libc++.so") + musl_lib = LLVM_TOOLCHAIN_HOST.musl_lib() + assert (musl_lib) + + copy_libs.append(musl_lib / "libc++.so") copy_libs.append(MUSL_SYSROOT64_PATH / "lib" / "libc_musl.so") elif not build_platform.is_windows(): - copy_libs.append(LLVM_CXX_RUNTIME_PATH_HOST / "libc++.so") + copy_libs.append(LLVM_TOOLCHAIN_HOST.lib() / "libc++.so") if copy_libs: lib64_path = OUT_PATH_PACKAGE / "lib64" @@ -553,17 +599,18 @@ def main(argv: list[str] | None = None) -> int: # if args.host == build_platform.triple(): + new_toolchain = RustToolchain(OUT_PATH_PACKAGE) cargo_deny = cargo.Crate( WORKSPACE_PATH / "toolchain" / "cargo-deny", env, - cargo_path=NEW_CARGO_PATH, + cargo_path=new_toolchain.cargo(), target=args.host, linker=config.get_wrapper_paths(args.host)[2]) cargo_deny.cargo_install(OUT_PATH_PACKAGE) cargo_vet = cargo.Crate( WORKSPACE_PATH / "toolchain" / "cargo-vet", env, - cargo_path=NEW_CARGO_PATH, + cargo_path=new_toolchain.cargo(), target=args.host, linker=config.get_wrapper_paths(args.host)[2]) cargo_vet.cargo_install(OUT_PATH_PACKAGE) @@ -625,14 +672,34 @@ def main(argv: list[str] | None = None) -> int: print("") # + # BOLT + # + + if args.bolt_opt: + boltify_toolchain( + OUT_PATH_PACKAGE, + args.dist_path, + args.bolt_name, + profile_generate=args.bolt_profile_generate, + profile_use=args.bolt_profile_use) + + # # Cleanup # if args.ndk_path.is_relative_to(tempfile.gettempdir()): shutil.rmtree(args.ndk_path) + if args.rust_stage0.base_dir.is_relative_to(tempfile.gettempdir()): + shutil.rmtree(args.rust_stage0.base_dir.as_posix()) + return retcode if __name__ == "__main__": - sys.exit(main()) + try: + sys.exit(main()) + + except (ScriptException, argparse.ArgumentTypeError) as err: + print_colored(str(err), TERM_RED) + sys.exit(1) diff --git a/tools/fetch_source.py b/tools/fetch_source.py index 3948a18..9baa460 100755 --- a/tools/fetch_source.py +++ b/tools/fetch_source.py @@ -15,6 +15,7 @@ # limitations under the License. import argparse +import sys import context @@ -138,7 +139,7 @@ def fetch_and_extract_archive(build_type: str, rust_version: str) -> None: RUST_REPO.add('.') -def main() -> None: +def main() -> int: args = parse_args() rust_version: str = args.rust_version + get_extra_tag(args.build_type) branch_name: str = args.branch or (BRANCH_NAME_TEMPLATE % rust_version) @@ -149,7 +150,13 @@ def main() -> None: fetch_and_extract_archive(args.build_type, rust_version) RUST_REPO.amend_or_commit(make_commit_message(rust_version, args.issue)) print("Done") + return 0 if __name__ == '__main__': - main() + try: + sys.exit(main()) + + except utils.ScriptException as err: + utils.print_colored(str(err), utils.TERM_RED) + sys.exit(1) diff --git a/tools/pipeline.py b/tools/pipeline.py index c96eb19..d29c199 100755 --- a/tools/pipeline.py +++ b/tools/pipeline.py @@ -224,7 +224,7 @@ def main() -> None: if args.start <= 4 and 4 <= args.end: print("Executing Stage 4") dist_path_stage4 = setup_dist_instance("stage4") - retcode = boltifyer.boltify_archive( + retcode = boltifyer.boltify_toolchain( dist_path_stage3 / f"rust-{args.build_name}-stage3.tar.xz", dist_path_stage4, f"rust-{args.build_name}-stage4", # .tar.xz added by archive function @@ -290,7 +290,7 @@ def main() -> None: if args.start <= 6 and 6 <= args.end: print("Executing Stage 6") dist_path_stage6 = setup_dist_instance("stage6") - boltifyer.boltify_archive( + boltifyer.boltify_toolchain( dist_path_stage3 / f"rust-{args.build_name}-stage3.tar.xz", dist_path_stage6, f"rust-{args.build_name}-stage6", # .tar.xz added by archive function diff --git a/tools/update_prebuilts.py b/tools/update_prebuilts.py index 02f9e27..8ea3e24 100755 --- a/tools/update_prebuilts.py +++ b/tools/update_prebuilts.py @@ -41,11 +41,14 @@ from android_rust.paths import ( TOOLCHAIN_PATH, ) from android_rust.utils import ( + ScriptException, GitRepo, archive_extract, + print_colored, replace_file_contents, run_and_exit_on_failure, run_quiet, + TERM_RED, VERSION_PATTERN, version_string_type, ) @@ -478,7 +481,7 @@ def update_soong(branch_name: str, overwrite: bool, version: str, bid: int, issu SOONG_REPO.commit(make_commit_message(version, bid, issue)) -def main() -> None: +def main() -> int: args = parse_args() branch_name: str = args.branch or make_branch_name(args.version) bid: int = args.bid or get_lkgb() @@ -502,7 +505,13 @@ def main() -> None: update_soong(branch_name, args.overwrite, args.version, bid, args.issue) print("Done") + return 0 if __name__ == "__main__": - main() + try: + sys.exit(main()) + + except ScriptException as err: + print_colored(str(err), TERM_RED) + sys.exit(1) |