blob: 323f7887c34db2a048c79d7b3353ea0d54f40dfe [file] [log] [blame] [edit]
#!/bin/bash
# Copyright 2016 Google Inc. All Rights Reserved.
# This script downloads, compiles, and installs various versions of gcc.
script_path="$(readlink -f ${BASH_SOURCE[0]})"
script_dir="$(dirname "${script_path}")"
script_name="$(basename "${script_path}")"
dirty_build=
force_build=
local_build=
verbose=
arm_git_url="https://nest-open-source.googlesource.com/stadia-controller/gcc-arm-none-eabi"
function wget() {
1>&2 command wget --no-show-progress -c "$@"
}
function log() {
1>&2 echo "${script_name}: $*"
}
function is_verbose() {
[[ "${verbose}" == "true" ]]
}
function is_dirty_build() {
[[ "${dirty_build}" == "true" ]]
}
function is_forced_build() {
[[ "${force_build}" == "true" ]]
}
function is_local_build() {
[[ "${local_build}" == "true" ]]
}
function log_verbose() {
if is_verbose; then
log "$@"
fi
}
exit_commands=()
function run_exit_commands() {
log_verbose "Cleaning up."
for command_id in "${exit_commands[@]}"; do
typeset -n command_line=${command_id}
(
if is_verbose; then
set -x
fi
"${command_line[@]}"
)
eval "${command_id}"'=""'
done
}
function add_exit_command() {
local index="${#exit_commands[@]}"
local command_id="__exit_command_$index"
eval "${command_id}"'=( "$@" )'
exit_commands+=("${command_id}")
}
function make_clean_dir() {
local dir="$1"
if [[ -d "${dir}" ]]; then
log "Removing ${dir}"
rm -rf "${dir}" || return
fi
log_verbose "Creating ${dir}"
mkdir -p "${dir}"
}
function build_gcc() {
local toolchain_name="gcc"
local version="${1:-7.3.0}"
shift
local arch="${1:-x86_64}"
shift
local vendor="${1:-unknown}"
shift
local os="${1:-linux-gnu}"
shift
local get_source_command="${1:-default_get_source}"
shift
local get_dependencies_command="${1:-default_get_dependencies}"
shift
local configure_gcc_command="${1:-default_configure_gcc}"
shift
local make_gcc_command="${1:-default_make_gcc}"
shift
local install_gcc_command="${1:-default_install_gcc}"
shift
local build_duration="3 hours"
local triplet="${arch}-${vendor}-${os}"
if [[ "${vendor}" == "unknown" ]]; then
triplet="${arch}-${os}"
fi
local target_prefix=""
local target_arguments=()
if [[ "${triplet}" != "x86_64-linux"* ]]; then
target_prefix="${triplet}-"
target_arguments=("--target=${triplet}")
fi
local toolchain_full_name="${toolchain_name}-${triplet}-${version}"
local canonical_path="${HOME}/.cache/toolchains"
local base_path="${TOOLCHAINS_DIR:=${canonical_path}}"
local toolchain_path="${base_path}/${toolchain_full_name}"
local toolchain_binary="${toolchain_path}/bin/${target_prefix}g++"
local build_date="$(command date '+%Y.%m.%d-%H%M%S')"
local logfile="${base_path}/log-${toolchain_full_name}-${build_date}.txt"
if [[ -x "${toolchain_binary}" ]] && "${toolchain_binary}" --version &> /dev/null; then
# The toolchain binary already exists.
if is_forced_build; then
log "The force flag was specified."
log "We will remove the existing toolchain during the install phase."
log "Toolchain path to remove: ${toolchain_path}"
else
log "${toolchain_full_name} already available."
return 0
fi
elif [[ -d "${toolchain_path}" ]]; then
cat <<EOF 1>&2
WARNING: ${toolchain_full_name} directory
${toolchain_path}
already exists, but it does not contain a valid binary. Perhaps a previous build
was interrupted or failed?
We will remove this directory during the install phase.
EOF
fi
cat <<EOF 1>&2
Downloading and building ${toolchain_full_name} into:
${toolchain_path}
Log file can be found at:
${logfile}
This may take up to ${build_duration}.
EOF
local work_dir="$(mktemp -d -t "${toolchain_name}-XXXXXX")"
if ! is_dirty_build; then
add_exit_command rm -rf "${work_dir}"
fi
local gcc_build_dir="${work_dir}/gcc-build"
local gcc_install_dir="${toolchain_path}"
mkdir -p "$(dirname "${gcc_install_dir}")"
(
set -x
log "Fetching ${toolchain_full_name} source code"
cd "${work_dir}"
local gcc_source_dir="${work_dir}/$("${get_source_command}" "${version}")"
if [[ ! -d "${gcc_source_dir}" ]]; then
log "Unable to get source with ${get_source_command}."
return 1
fi
log "Fetching ${toolchain_full_name} dependencies"
cd "${gcc_source_dir}"
"${get_dependencies_command}" || return
log "Configuring ${toolchain_full_name} for installation into ${gcc_install_dir}"
mkdir -p "${gcc_build_dir}"
cd "${gcc_build_dir}"
"${configure_gcc_command}" \
"${gcc_source_dir}" \
"${gcc_install_dir}" \
"${target_arguments[@]}" || return
# Build and install gcc to toolchain directory.
log "Building ${toolchain_full_name}"
"${make_gcc_command}" "${gcc_source_dir}" "${gcc_install_dir}" || return
make_clean_dir "${toolchain_path}"
log "Installing ${toolchain_full_name}"
"${install_gcc_command}" "${gcc_source_dir}" "${gcc_install_dir}"
if [[ ! -f "${toolchain_binary}" ]]; then
log "No resulting binary found, assuming build failure."
rm -rf "${toolchain_path}"
return 1
fi
"${toolchain_binary}" --version
) |& tee "${logfile}"
}
function default_get_source() {
local version="$1"
local gcc_version="gcc-${version}"
local tar_file="${gcc_version}.tar"
local mirror_url="http://ftpmirror.gnu.org/gcc/${gcc_version}"
local archive_extensions=("xz" "bz2" "gz")
for extension in "${archive_extensions[@]}"; do
local file="${tar_file}.${extension}"
wget "${mirror_url}/${file}"
if ! $?; then
break
fi
done
if [[ ! -f "${file}" ]]; then
log "Unable to get source."
return 1
fi
wget "${mirror_url}/${file}.sig" || return
wget "http://ftp.gnu.org/gnu/gnu-keyring.gpg" || return
local signature_invalid="$(gpg --verify --no-default-keyring --keyring ./gnu-keyring.gpg "${file}.sig")"
if [[ -n "${signature_invalid}" ]]; then
log "Invalid signature, aborting."
return 1
fi
tar -xf "${file}" || return
echo -n "${file%.tar*}"
}
function default_get_dependencies() {
if [[ -x ./contrib/download_prerequisites ]]; then
./contrib/download_prerequisites
fi
}
function default_configure_gcc() {
local gcc_source_dir="$1"
shift
local gcc_install_dir="$1"
shift
"${gcc_source_dir}/configure" \
--prefix="${gcc_install_dir}" \
--enable-languages=c,c++ \
"$@"
}
function default_make_gcc() {
local gcc_source_dir="$1"
shift
local gcc_install_dir="$1"
shift
make -j"$(nproc)"
}
function default_install_gcc() {
local gcc_source_dir="$1"
shift
local gcc_install_dir="$1"
shift
make install-strip
}
function arm_get_source() {
if is_local_build; then
1>&2 cp -R "${script_dir}" "gcc-arm-none-eabi"
echo -n "gcc-arm-none-eabi"
fi
1>&2 git clone "${arm_git_url}" || return
echo -n "gcc-arm-none-eabi"
}
function no_op() {
return # NO-OP
}
function arm_make_gcc() {
local gcc_source_dir="$1"
shift
local gcc_install_dir="$1"
shift
(
cd "${gcc_source_dir}" || return
./build-prerequisites.sh --skip_steps=mingw32 || return
./build-toolchain.sh \
--skip_steps=manual,gdb-with-python,mingw32,mingw32-gdb-with-python \
--threads=posix
)
}
function arm_install_gcc() {
local gcc_source_dir="$1"
shift
local gcc_install_dir="$1"
shift
local package_dir="${gcc_source_dir}/pkg"
(
if [[ ! -d "${package_dir}" ]]; then
log "No package directory: ${package_dir}"
fi
cd "${package_dir}"
shopt -s nullglob
local archive=(gcc-*-linux.tar.*)
if (( ${#archive[@]} < 1 )); then
log "No toolchain archive found in: ${package_dir}"
return 1
fi
local archive_dir="${archive%-linux.tar*}"
tar xf "${archive}" || return
local archive_files=("${archive_dir}"/*)
if (( ${#archive_files[@]} < 1 )); then
log "${archive} didn't extract to ${archive_dir}"
return 1
fi
cp -R "${archive_files[@]}" "${gcc_install_dir}/"
)
}
function usage() {
cat <<EOF 1>&2
usage: ${script_name} [-fhv]
where:
-d Dirty. Do not clean up the work directory.
-f Force a rebuild even if toolchain exists.
-h Show this help.
-l Build using the source in the script directory.
-v Increase verbosity.
EOF
}
function main() {
trap run_exit_commands EXIT
OPTIND=1
while getopts "dfhlv?" opt; do
case "$opt" in
h|\?)
usage
return 0
;;
d)
dirty_build=true
;;
f)
force_build=true
;;
l)
local_build=true
;;
v)
verbose=true
;;
esac
done
shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift
build_gcc 7.2.1 arm none eabi arm_get_source no_op no_op arm_make_gcc \
arm_install_gcc
}
main "$@"