#!/bin/bash

# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

. "$(dirname "$0")/common.sh" || exit 1
. "${SRC_ROOT}/platform/dev/toolchain_utils.sh" || exit 1

# Script must run inside the chroot
restart_in_chroot_if_needed "$@"

assert_not_root_user

# Developer-visible flags.
DEFINE_string board "$DEFAULT_BOARD" \
  "The name of the board to set up."
DEFINE_string board_root "" \
  "Path of the board root to install into.  Defaults to /build/<board>."
DEFINE_boolean configure $FLAGS_TRUE \
  "Update config files in <board_root>/etc after installation."
DEFINE_boolean force $FLAGS_FALSE \
  "Install toolchain even if already up to date."
DEFINE_string toolchain "" \
  "Toolchain.  For example: i686-pc-linux-gnu, armv7a-softfloat-linux-gnueabi"


FLAGS_HELP="usage: $(basename $0) [flags]

Installs cross toolchain libraries into a board_root.  This script is not
meant to be used by developers directly.  Run at your own risk.
"

show_help_if_requested "$@"

# Parse command line flags
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# Only now can we die on error.  shflags functions leak non-zero error codes,
# so will die prematurely if 'switch_to_strict_mode' is specified before now.
switch_to_strict_mode

# Get the version number of a toolchain package.
cross_get_version() {
  local pkg=$1
  local toolchain=${2:-${FLAGS_toolchain}}
  local cpv
  if [[ "$CHOST" != "${toolchain}" ]]; then
    if [[  "$pkg" = "gcc" ]]; then
      # Users can install multiple versions of gcc at once, so we need to call
      # gcc-config to find out which installed version is selected.
      local path=$(CTARGET="${toolchain}" gcc-config -B || true)
      cpv=$(portageq owners / "$path" | sed -e '/^\t/d')
    else
      cpv=$(portageq match / "cross-${toolchain}/${pkg}" || true)
    fi
  else
    if [[ "$pkg" = glibc ]] ; then
      cpv=$(portageq match / sys-libs/glibc || true)
    elif [[ "$pkg" = gcc ]] ; then
      cpv=$(portageq match / sys-devel/gcc || true)
    else
      die "Unknown pkg ${pkg}"
    fi
  fi
  local cp=$(echo $cpv | sed -e 's/-r[0-9]*$//; s/-[^-]*$//')
  local result="${cpv#$cp-}"
  local count="$(echo $result | wc -w)"
  if [ "$count" -gt "1" ]; then
    die "Multiple versions of $pkg installed"
  elif [ "$count" -lt "1" ]; then
    die "Cannot find $pkg"
  fi
  echo $result
}

# Checks whether the libc version installed in the board
# matches the one installed by the toolchain.
board_needs_libc_update() {
  if [[ ! -e "${BOARD_SETUP}" ]]; then
    return 0
  fi

  local board_version=$(. "${BOARD_SETUP}"; echo "${LIBC_VERSION:-}")
  local toolchain_version=$(cross_get_version glibc)
  if [[ "${board_version}" = "${toolchain_version}" ]]; then
    return 1
  fi
  return 0
}

install_toolchain_in_provided() {
  local gcc_ver="$1"
  local glibc_ver="$2"
  # Tell portage that toolchain packages are already installed in the sysroot.
  sudo mkdir -p "$BOARD_PROFILE"
  sudo_clobber "$BOARD_PROFILE/package.provided" << EOF
sys-devel/gcc-$gcc_ver
sys-libs/glibc-$glibc_ver
EOF
}

# Install all of the stuff that depends on the toolchain versions
# into the board root.
install_toolchain_in_board() {
  local cmds
  local gcc_ver=$(cross_get_version gcc)
  local libc_ver=$(cross_get_version glibc)
  if [[ -z ${gcc_ver} || -z ${libc_ver} ]]; then
    die "Cannot find toolchain to install into board root"
  fi
  echo "Installing the toolchain into the board root."
  # Untar glibc to get most of the headers required to build.
  local libc_tar="glibc-${libc_ver}.tbz2"

  # Install libc libraries.
  if [ "${CHOST}" != "$FLAGS_toolchain" ] ; then
    local libc_path="${PKGDIR}/cross-${FLAGS_toolchain}/${libc_tar}"
    cmds=(
      "tar jxpf '${libc_path}' -C '${BOARD_ROOT}' \
          './usr/${FLAGS_toolchain}' --strip-components=3"
      "mkdir -p '${BOARD_ROOT}/usr/lib/debug'"
      "tar jxpf '${libc_path}' -C '${BOARD_ROOT}/usr/lib/debug' \
          './usr/lib/debug/usr/${FLAGS_toolchain}' --strip-components=6 \
          || warn 'libc debug info not copied.'"
    )
    # TODO(asharif): Remove this hack after a while.
    local board_gcc_dir="${BOARD_ROOT}/usr/lib/gcc"
    if [[ -L "${board_gcc_dir}" ]] ; then
      cmd+=("rm -f '${board_gcc_dir}'")
    fi
    sudo_multi "${cmds[@]}"
  else
    cmds=(
      "mkdir -p '${BOARD_ROOT}'{/usr,}/lib64 '${BOARD_ROOT}/usr/lib/debug'"
      "ln -sfT '${BOARD_ROOT}/usr/lib64' '${BOARD_ROOT}/usr/lib'"
      "ln -sfT '${BOARD_ROOT}/lib64' '${BOARD_ROOT}/lib'"
      "emerge --oneshot --nodeps -k --root='${BOARD_ROOT}' \
          =sys-libs/glibc-${libc_ver}"
    )
    sudo_multi "${cmds[@]}"
  fi

  # Some header files are needed also for rpcbind (NFS support)
  # TODO: Figure out a better way of doing this too?
  cmds=(
    "cp -a /usr/include/rpcsvc/mount.h '${BOARD_ROOT}/usr/include/rpcsvc'"
    "cp -a /usr/include/rpcsvc/rquota.h '${BOARD_ROOT}/usr/include/rpcsvc'"
    "cp -a /usr/include/rpcsvc/nfs_prot.h '${BOARD_ROOT}/usr/include/rpcsvc'"
    "cp -a /usr/include/rpcsvc/yppasswd.h '${BOARD_ROOT}/usr/include/rpcsvc'"
  )
  sudo_multi "${cmds[@]}"

  if [[ ${FLAGS_configure} -eq ${FLAGS_TRUE} ]]; then
    install_toolchain_in_provided "$gcc_ver" "$libc_ver"

    # Configure new libc version in make.conf.board_setup.
    if [[ -e ${BOARD_SETUP} ]]; then
      sudo sed -i -e "/^LIBC_VERSION=/d" "${BOARD_SETUP}"
    fi
    echo "LIBC_VERSION=\"$libc_ver\"" | sudo_append "$BOARD_SETUP"
  fi
}

set -u

if [ -z "$FLAGS_board" ] ; then
  die "--board required."
fi

get_board_and_variant $FLAGS_board ""

BOARD_ROOT="${FLAGS_board_root:-/build/${BOARD_VARIANT}}"
BOARD_ETC="${BOARD_ROOT}/etc"
BOARD_SETUP="${BOARD_ETC}/make.conf.board_setup"
BOARD_PROFILE="${BOARD_ETC}/portage/profile"

all_toolchains=( $(get_all_board_toolchains "${BOARD}") )
: ${FLAGS_toolchain:=${all_toolchains[0]}}

if [ -z "${FLAGS_toolchain}" ]; then
  die "No toolchain specified in board overlay or on command line."
fi

eval "$(portageq envvar -v CHOST PKGDIR)"

if [[ ${FLAGS_force} -eq ${FLAGS_TRUE} ]] || \
    board_needs_libc_update; then
  info "Updating libc in the board."
  install_toolchain_in_board
else
  info "Cross toolchain already up to date.  Nothing to do."
fi
