#!/bin/bash # Toolchain packages are treated a bit specially, since they take a # while to build and are generally more complicated to build they are # only built via catalyst and everyone else installs them as binpkgs. TOOLCHAIN_PKGS=( sys-devel/binutils sys-devel/gcc sys-kernel/linux-headers sys-libs/glibc ) # Portage profile to use for building out the cross compiler's SYSROOT. # This is only used as an intermediate step to be able to use the cross # compiler to build a full native toolchain. Packages are not uploaded. declare -A CROSS_PROFILES CROSS_PROFILES["x86_64-cros-linux-gnu"]="coreos:coreos/amd64/generic" CROSS_PROFILES["aarch64-cros-linux-gnu"]="coreos:coreos/arm64/generic" # Map board names to CHOSTs and portage profiles. This is the # definitive list, there is assorted code new and old that either # guesses or hard-code these. All that should migrate to this list. declare -A BOARD_CHOSTS BOARD_PROFILES BOARD_CHOSTS["amd64-usr"]="x86_64-cros-linux-gnu" BOARD_PROFILES["amd64-usr"]="coreos:coreos/amd64/generic" BOARD_CHOSTS["arm64-usr"]="aarch64-cros-linux-gnu" BOARD_PROFILES["arm64-usr"]="coreos:coreos/arm64/generic" BOARD_NAMES=( "${!BOARD_CHOSTS[@]}" ) # Declare the above globals as read-only to avoid accidental conflicts. declare -r \ TOOLCHAIN_PKGS \ CROSS_PROFILES \ BOARD_CHOSTS \ BOARD_NAMES \ BOARD_PROFILES ### Generic metadata fetching functions ### # map CHOST to portage ARCH, list came from crossdev # Usage: get_portage_arch chost get_portage_arch() { case "$1" in aarch64*) echo arm64;; alpha*) echo alpha;; arm*) echo arm;; hppa*) echo hppa;; ia64*) echo ia64;; i?86*) echo x86;; m68*) echo m68k;; mips*) echo mips;; powerpc64*) echo ppc64;; powerpc*) echo ppc;; sparc*) echo sparc;; s390*) echo s390;; sh*) echo sh;; x86_64*) echo amd64;; *) die "Unknown CHOST '$1'";; esac } # map CHOST to kernel ARCH # Usage: get_kernel_arch chost get_kernel_arch() { case "$1" in aarch64*) echo arm64;; alpha*) echo alpha;; arm*) echo arm;; hppa*) echo parisc;; ia64*) echo ia64;; i?86*) echo x86;; m68*) echo m68k;; mips*) echo mips;; powerpc*) echo powerpc;; sparc64*) echo sparc64;; sparc*) echo sparc;; s390*) echo s390;; sh*) echo sh;; x86_64*) echo x86;; *) die "Unknown CHOST '$1'";; esac } get_board_list() { local IFS=$'\n\t ' sort <<<"${BOARD_NAMES[*]}" } get_chost_list() { local IFS=$'\n\t ' sort -u <<<"${BOARD_CHOSTS[*]}" } get_profile_list() { local IFS=$'\n\t ' sort -u <<<"${BOARD_PROFILES[*]}" } # Usage: get_board_arch board [board...] get_board_arch() { local board for board in "$@"; do get_portage_arch $(get_board_chost "${board}") done } # Usage: get_board_chost board [board...] get_board_chost() { local board for board in "$@"; do if [[ ${#BOARD_CHOSTS["$board"]} -ne 0 ]]; then echo "${BOARD_CHOSTS["$board"]}" else die "Unknown board '$board'" fi done } # Usage: get_board_profile board [board...] get_board_profile() { local board for board in "$@"; do if [[ ${#BOARD_PROFILES["$board"]} -ne 0 ]]; then echo "${BOARD_PROFILES["$board"]}" else die "Unknown board '$board'" fi done } # Usage: get_board_binhost [-t] board [version...] # -t: toolchain only, full rebuilds re-using toolchain pkgs # If no versions are specified the current and SDK versions are used. get_board_binhost() { local toolchain_only=0 board ver if [[ "$1" == "-t" ]]; then toolchain_only=1 shift fi board="$1" shift local pkgs_include_toolchain=0 if [[ $# -eq 0 ]]; then if [[ "${FLATCAR_BUILD_ID}" =~ ^nightly-.*$ ]] ; then # containerised nightly build; this uses [VERSION]-[BUILD_ID] for binpkg url # and toolchain packages are at the same location as OS image ones set -- "${FLATCAR_VERSION_ID}+${FLATCAR_BUILD_ID}" pkgs_include_toolchain=1 else set -- "${FLATCAR_VERSION_ID}" fi fi for ver in "$@"; do if [[ $toolchain_only -eq 0 ]]; then echo "${FLATCAR_DEV_BUILDS}/boards/${board}/${ver}/pkgs/" fi if [[ $pkgs_include_toolchain -eq 0 ]]; then echo "${FLATCAR_DEV_BUILDS}/boards/${board}/${ver}/toolchain/" fi done } get_sdk_arch() { get_portage_arch $(uname -m) } get_sdk_profile() { echo "coreos:coreos/$(get_sdk_arch)/sdk" } get_sdk_libdir() { # Looking for LIBDIR_amd64 or similar portageq envvar "LIBDIR_$(get_sdk_arch)" } get_sdk_symlink_lib() { portageq envvar "SYMLINK_LIB" } # Usage: get_sdk_binhost [version...] # If no versions are specified the current and SDK versions are used. get_sdk_binhost() { local arch=$(get_sdk_arch) ver if [[ $# -eq 0 ]]; then set -- "${FLATCAR_SDK_VERSION}" fi if [ "${FLATCAR_DEV_BUILDS}" != "${SETTING_BINPKG_SERVER_DEV_CONTAINERISED}" ] ; then FLATCAR_DEV_BUILDS_SDK="${FLATCAR_DEV_BUILDS_SDK-${FLATCAR_DEV_BUILDS}/sdk}" else # ALWAYS use a released SDK version, never a nightly, for SDK binpkgs FLATCAR_DEV_BUILDS_SDK="${FLATCAR_DEV_BUILDS_SDK-${SETTING_BINPKG_SERVER_PROD}/sdk}" fi for ver in "$@"; do # Usually only crossdev needs to be fetched from /toolchain/ in the setup_board step. # The entry for /pkgs/ is there if something needs to be reinstalled in the SDK # but normally it is not needed because everything is already part of the tarball. # To install the crossdev Rust package, /toolchain-arm64/ is derived from /toolchain/ # when necessary in install_cross_toolchain(). echo "${FLATCAR_DEV_BUILDS_SDK}/${arch}/${ver}/toolchain/" echo "${FLATCAR_DEV_BUILDS_SDK}/${arch}/${ver}/pkgs/" done } # Usage: get_cross_pkgs chost [chost2...] get_cross_pkgs() { local cross_chost native_pkg for cross_chost in "$@"; do for native_pkg in "${TOOLCHAIN_PKGS[@]}"; do echo "${native_pkg/*\//cross-${cross_chost}/}" done done } # Get portage arguments restricting toolchains to binary packages only. get_binonly_args() { local pkgs=( "${TOOLCHAIN_PKGS[@]}" $(get_cross_pkgs "$@") ) echo "${pkgs[@]/#/--useoldpkg-atoms=}" "${pkgs[@]/#/--rebuild-exclude=}" } ### Toolchain building utilities ### # Create the crossdev overlay and repos.conf entry. # crossdev will try to setup this itself but doesn't do everything needed # to make the newer repos.conf based configuration system happy. This can # probably go away if crossdev itself is improved. configure_crossdev_overlay() { local root="$1" local location="$2" # may be called from either catalyst (root) or update_chroot (user) local sudo=("env") if [[ $(id -u) -ne 0 ]]; then sudo=("sudo" "-E") fi "${sudo[@]}" mkdir -p "${root}${location}/"{profiles,metadata} echo "x-crossdev" | \ "${sudo[@]}" tee "${root}${location}/profiles/repo_name" > /dev/null "${sudo[@]}" tee "${root}${location}/metadata/layout.conf" > /dev/null < /dev/null <-cros-linux-gnu/gcc # package. # # Example use: # # local -a emerge_atoms=() crossdev_flags=() # _get_cross_pkgs_for_emerge_and_crossdev x86_64-cros-linux-gnu emerge_atoms crossdev_flags # # emerge_atoms will have atoms like "=cross-x86_64-cros-linux-gnu/gcc-11.3.1_p20221209" # # crossdev_flags will have flags like "--gcc" "=11.3.1_p20221209" _get_cross_pkgs_for_emerge_and_crossdev() { local cross_chost="${1}"; shift local gcpfeac_emerge_atoms_var_name="${1}"; shift local gcpfeac_crossdev_pkg_flags_var_name="${1}"; shift local -n gcpfeac_emerge_atoms_var_ref="${gcpfeac_emerge_atoms_var_name}" local -n gcpfeac_crossdev_pkg_flags_var_ref="${gcpfeac_crossdev_pkg_flags_var_name}" local -a all_pkgs=( "${TOOLCHAIN_PKGS[@]}" sys-devel/gdb ) local -A crossdev_flags_map=( [binutils]=--binutils [gdb]=--gdb [gcc]=--gcc [linux-headers]=--kernel [glibc]=--libc ) local emerge_report pkg line version pkg_name crossdev_flag emerge_report=$(emerge --quiet --pretend --oneshot --nodeps "${all_pkgs[@]}") for pkg in "${all_pkgs[@]}"; do line=$(grep -o "${pkg}-[^ ]*" <<<"${emerge_report}") cross_pkg="${pkg/*\//cross-${cross_chost}/}" version="${line#${pkg}-}" gcpfeac_emerge_atoms_var_ref+=( "=${cross_pkg}-${version}" ) pkg_name="${pkg#*/}" crossdev_flag="${crossdev_flags_map[${pkg_name}]}" gcpfeac_crossdev_pkg_flags_var_ref+=( "${crossdev_flag}" "=${version}" ) done } # Build/install a toolchain w/ crossdev. # Usage: build_cross_toolchain chost [--portage-opts....] install_cross_toolchain() { local cross_chost="${1}"; shift local cross_cfg cross_cfg_data cbuild local -a cross_flags emerge_flags emerge_atoms cross_pkg_flags emerge_atoms=() cross_pkg_flags=() _get_cross_pkgs_for_emerge_and_crossdev "${cross_chost}" emerge_atoms cross_pkg_flags # build gdb as an extra step, use specific versions of toolchain packages cross_flags=( --ex-gdb --target "${cross_chost}" "${cross_pkg_flags[@]}" ) cross_cfg="/usr/${cross_chost}/etc/portage/${cross_chost}-crossdev" cross_cfg_data=$(_crossdev_info "${cross_flags[@]}") cbuild=$(portageq envvar CBUILD) emerge_flags=( "$@" --binpkg-respect-use=y --update --newuse ) # Forcing binary packages for toolchain packages breaks crossdev since it # prevents it from rebuilding with different use flags during bootstrap. local safe_flags=( "${@/#--useoldpkg-atoms=*/}" ) safe_flags=( "${safe_flags[@]/#--rebuild-exclude=*/}" ) cross_flags+=( --portage "${safe_flags[*]}" ) # may be called from either catalyst (root) or upgrade_chroot (user) local sudo=("env") if [[ $(id -u) -ne 0 ]]; then sudo=("sudo" "-E") fi # crossdev will arbitrarily choose an overlay that it finds first. # Force it to use the one created by configure_crossdev_overlay local cross_overlay cross_overlay=$(portageq get_repo_path / x-crossdev) if [[ -n "${cross_overlay}" ]]; then cross_flags+=( --ov-output "${cross_overlay}" ) else echo "No x-crossdev overlay found!" >&2 return 1 fi # Only call crossdev to regenerate configs if something has changed if [[ ! -d "${cross_overlay}/cross-${cross_chost}" ]] || ! cmp --quiet - "${cross_cfg}" <<<"${cross_cfg_data}" then "${sudo[@]}" crossdev "${cross_flags[@]}" --init-target "${sudo[@]}" tee "${cross_cfg}" <<<"${cross_cfg_data}" >/dev/null fi # Check if any packages need to be built from source. If so do a full # bootstrap via crossdev, otherwise just install the binaries (if enabled). # It is ok to build gdb from source outside of crossdev. if emerge "${emerge_flags[@]}" \ --pretend "${emerge_atoms[@]}" | grep -q '^\[ebuild' then echo "Doing a full bootstrap via crossdev" "${sudo[@]}" crossdev "${cross_flags[@]}" --stage4 else echo "Installing existing binaries" "${sudo[@]}" emerge "${emerge_flags[@]}" "${emerge_atoms[@]}" if [ "${cbuild}" = "x86_64-pc-linux-gnu" ] && [ "${cross_chost}" = aarch64-cros-linux-gnu ] && \ [ ! -d /usr/lib/rust-*/rustlib/aarch64-unknown-linux-gnu ] && [ ! -d /usr/lib/rustlib/aarch64-unknown-linux-gnu ]; then # If no aarch64 folder exists, warn about the situation but don't compile Rust here or download it as binary package echo "WARNING: No aarch64 cross-compilation Rust libraries found!" echo "In case building fails, make sure the old Rust version is deleted with: sudo emerge --unmerge virtual/rust dev-lang/rust" echo "Then install it again with: sudo emerge ${emerge_flags[@]} virtual/rust" echo "This will download the binary package or build from source." fi fi # Setup environment and wrappers for our shiny new toolchain binutils_set_latest_profile "${cross_chost}" gcc_set_latest_profile "${cross_chost}" "${sudo[@]}" CC_QUIET=1 sysroot-config --install-links "${cross_chost}" } # Build/install toolchain dependencies into the cross sysroot for a # given CHOST. This is required to build target board toolchains since # the target sysroot under /build/$BOARD is incomplete at this stage. # Usage: build_cross_toolchain chost [--portage-opts....] install_cross_libs() { local cross_chost="$1"; shift local ROOT="/usr/${cross_chost}" local package_provided="$ROOT/etc/portage/profile/package.provided" # may be called from either catalyst (root) or setup_board (user) local sudo=("env") if [[ $(id -u) -ne 0 ]]; then sudo=("sudo" "-E") fi CBUILD="$(portageq envvar CBUILD)" \ CHOST="${cross_chost}" \ ROOT="$ROOT" \ SYSROOT="$ROOT" \ _configure_sysroot "${CROSS_PROFILES[${cross_chost}]}" # In order to get a dependency list we must calculate it before # updating package.provided. Otherwise portage will no-op. "${sudo[@]}" rm -f "${package_provided}/cross-${cross_chost}" local cross_deps=$(ROOT="$ROOT" SYSROOT="$ROOT" _get_dependency_list \ "$@" "${TOOLCHAIN_PKGS[@]}" | "${sudo[@]}" tee \ "$ROOT/etc/portage/cross-${cross_chost}-depends") # Add toolchain to packages.provided since they are on the host system if [[ -f "${package_provided}" ]]; then # emerge-wrapper is trying a similar trick but doesn't work "${sudo[@]}" rm -f "${package_provided}" fi "${sudo[@]}" mkdir -p "${package_provided}" local native_pkg cross_pkg cross_pkg_version for native_pkg in "${TOOLCHAIN_PKGS[@]}"; do cross_pkg="${native_pkg/*\//cross-${cross_chost}/}" cross_pkg_version=$(portageq match / "${cross_pkg}") echo "${native_pkg%/*}/${cross_pkg_version#*/}" done | "${sudo[@]}" tee "${package_provided}/cross-${cross_chost}" >/dev/null # OK, clear as mud? Install those dependencies now! PORTAGE_CONFIGROOT="$ROOT" "${sudo[@]}" emerge --root="$ROOT" --sysroot="$ROOT" "$@" --update $cross_deps } install_cross_rust() { local cross_chost="$1"; shift local emerge_flags=( "$@" --binpkg-respect-use=y --update ) local cbuild="$(portageq envvar CBUILD)" # may be called from either catalyst (root) or upgrade_chroot (user) local sudo=("env") if [[ $(id -u) -ne 0 ]]; then sudo=("sudo" "-E") fi if [ "${cbuild}" = "x86_64-pc-linux-gnu" ] && [ "${cross_chost}" = "aarch64-cros-linux-gnu" ]; then echo "Building Rust for arm64" # If no aarch64 folder exists, try to remove any existing Rust packages. [ ! -d /usr/lib/rustlib/aarch64-unknown-linux-gnu ] && ("${sudo[@]}" emerge --unmerge dev-lang/rust || true) "${sudo[@]}" emerge "${emerge_flags[@]}" dev-lang/rust fi } # Update to the latest binutils profile for a given CHOST if required # Usage: binutils_set_latest_profile chost binutils_set_latest_profile() { local latest="$@-latest" if [[ -z "${latest}" ]]; then echo "Failed to detect latest binutils profile for $1" >&2 return 1 fi # may be called from either catalyst (root) or upgrade_chroot (user) local sudo=("env") if [[ $(id -u) -ne 0 ]]; then sudo=("sudo" "-E") fi "${sudo[@]}" binutils-config "${latest}" } # Get the latest GCC profile for a given CHOST # The extra flag can be blank, hardenednopie, and so on. See gcc-config -l # Usage: gcc_get_latest_profile chost [extra] gcc_get_latest_profile() { local prefix="${1}-" local suffix="${2+-$2}" local status gcc-config -l | cut -d' ' -f3 | grep "^${prefix}[0-9\\.]*${suffix}$" | tail -n1 # return 1 if anything in the above pipe failed for status in ${PIPESTATUS[@]}; do [[ $status -eq 0 ]] || return 1 done } # Update to the latest GCC profile for a given CHOST if required # The extra flag can be blank, hardenednopie, and so on. See gcc-config -l # Usage: gcc_set_latest_profile chost [extra] gcc_set_latest_profile() { local latest=$(gcc_get_latest_profile "$@") if [[ -z "${latest}" ]]; then echo "Failed to detect latest gcc profile for $1" >&2 return 1 fi # may be called from either catalyst (root) or upgrade_chroot (user) local sudo=("env") if [[ $(id -u) -ne 0 ]]; then sudo=("sudo" "-E") fi "${sudo[@]}" gcc-config "${latest}" }