blob: e26ae1a9d27105995c612fa7e6eb381c246000f3 [file] [log] [blame]
#!/bin/bash
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd $(dirname "$0"); pwd)"
INVOCATION_DIR="$(pwd)"
# Things we want to build
HOST_LIBS="gmp mpfr mpc"
TOOLS="binutils gcc"
ALL_GNU_TOOLS="ar as gcc ld nm objcopy objdump ranlib readelf"
# Extra tools to build when targeting "*-none" platforms
TOOLS_none="gdb"
SUPPORTED_OSES="none linux fuchsia darwin"
SUPPORTED_ARCHES="arm aarch64 x86_64"
### Start of configuration options
## In the configuration options below, all arch/os suffixes relate to the
## target. e.g., "CONFIG_GCC_fuchsia" provides options that should be
## used when building executables that will run on fuchsia, regardless
## of the architecture. "CONFIG_GCC_aarch64_fuchsia" would give options that
## should be used when building executables that will run on aarch64-fuchsia.
## Uses ':' as an option delimiter, to facilitate the use of spaces inside
## some options (e.g., CFLAGS).
# GMP
CONFIG_GMP_fuchsia="--with-pic=yes"
# MPFR
CONFIG_MPFR="--with-pic=yes: \
--with-gmp="
# MPC
CONFIG_MPC="--with-gmp=: \
--with-mpfr="
# BINUTILS
CONFIG_BINUTILS="--with-included-gettext: \
--disable-werror: \
--enable-initfini-array"
CONFIG_BINUTILS_none="--enable-gold"
CONFIG_BINUTILS_x86_64_fuchsia="--enable-gold=default"
CONFIG_BINUTILS_aarch64_fuchsia="--enable-gold"
CONFIG_BINUTILS_fuchsia="--enable-plugins: \
--enable-relro"
CONFIG_BINUTILS_x86_64_none="--enable-targets=x86_64-pep"
# GDB
CONFIG_GDB="--with-included-gettext:--disable-werror"
CONFIG_GDB_aarch64_none="--enable-targets=arm-eabi"
# GCC
CONFIG_GCC="--with-included-gettext: \
--disable-werror: \
--enable-languages=c,c++:\
--with-gmp=: \
--with-mpfr=: \
--with-mpc="
CONFIG_GCC_none="--disable-libstdcxx: \
--disable-libssp: \
--disable-libquadmath"
CONFIG_GCC_fuchsia="--enable-default-pie: \
--disable-multilib"
CONFIG_GCC_arm="--with-cpu=arm926ej-s: \
--with-fpu=vfp"
CONFIG_GCC_TOOLS_llvm="CXXFLAGS=-fbracket-depth=1024"
### End of configuration options
usage()
{
echo "Usage: $SCRIPT_NAME [OPTION]..."
echo "Build a set of compiler tools."
echo "Options:"
echo " --force Build even if configuration is not supported."
echo " --help Produce this message"
echo " --host <ARCH-OS> Specify type of host where the tools will run."
echo " (default is to use the current build machine)"
echo " -j <#> Specify number of parallel build instances"
echo " --list-supported List all supported configurations and exit"
echo " --strip Strip all binaries"
echo " --sysroot <DIR> Specify the location of the sysroot to be"
echo " used for non-native builds"
echo " --target <ARCH-OS> Specify type of target the tools will generate"
echo " code for"
}
die()
{
echo "$SCRIPT_NAME: $1" >&2
shift
while [ ! -z "$1" ]
do
echo "$1" >&2
shift
done
exit 1
}
gather_host_info()
{
# Determine build machine attributes
BUILD_ARCH="$(uname -m)"
BUILD_OS="$(uname | tr '[:upper:]' '[:lower:]')"
BUILD="$BUILD_ARCH-$BUILD_OS"
case "$BUILD_OS" in
linux)
PARALLEL_BUILDS="$(grep processor /proc/cpuinfo | wc -l)"
;;
darwin)
PARALLEL_BUILDS="$(sysctl -n hw.ncpu)"
;;
# No fallback case needed - we'll just build with "-j".
esac
if cc --version | grep -q "LLVM"
then
HOST_TOOLS="llvm"
fi
}
verify_arch_os_tuple()
{
tuple_to_check="$1"
for arch in $SUPPORTED_ARCHES
do
for os in $SUPPORTED_OSES
do
if [ "$arch-$os" == "$tuple_to_check" ]
then
return
fi
done
done
die "Unrecognized arch-os pair '$tuple_to_check'" \
"Use --list-supported to show all supported configurations"
}
split_arch_os()
{
local tuple="$1"
RESULT_ARCH="$(echo "$tuple" | sed -E -e 's/([^-]+)-[^-]+$/\1/')"
RESULT_OS="$(echo "$tuple" | sed -E -e 's/[^-]+-([^-]+)$/\1/')"
}
is_configuration_supported()
{
split_arch_os "$TARGET"
local target_arch="$RESULT_ARCH"
local target_os="$RESULT_OS"
if [ "$BUILD" == "$HOST" ]
then
# Simple (not Canadian) cross-compiler
if [ "$target_os" == "none" ] || [ "$target_os" == "fuchsia" ]
then
return 0
fi
elif [ "$HOST" == "$TARGET" ] && [ "$target_os" == "fuchsia" ]
then
# Build a natively-running fuchsia compiler
if [ "$target_arch" == "arm" ]
then
# ARM native builds don't work at the moment -- we need
# synchronization primitive support for pre-v6 architectures.
return 1
fi
return 0
fi
return 1
}
list_all_supported()
{
local host_arch
local host_os
local target_arch
local target_os
for host_arch in $SUPPORTED_ARCHES
do
for host_os in $SUPPORTED_OSES
do
for target_arch in $SUPPORTED_ARCHES
do
for target_os in $SUPPORTED_OSES
do
HOST="$host_arch-$host_os"
TARGET="$target_arch-$target_os"
if is_configuration_supported
then
echo " $HOST -> $TARGET"
fi
done
done
done
done
}
get_argument()
{
if echo "$1" | grep -q "="
then
RESULT=$(echo "$1" | sed -E -e 's/^[^=]+.*=//')
else
if [ -z "$2" ]
then
die "Argument required for option '$1'"
fi
SKIP_EXTRA_ARG="yes"
RESULT="$2"
fi
}
process_opts()
{
local force=""
# Set defaults
OUT_DIR="$INVOCATION_DIR"
HOST="$BUILD"
STRIP=""
while [ ! -z "$1" ]
do
SKIP_EXTRA_ARG=""
case "$1" in
--force)
force="yes"
;;
--help)
usage
exit 0
;;
--host | --host=*)
get_argument $*
HOST="$RESULT"
verify_arch_os_tuple "$HOST"
;;
-j | -j=*)
get_argument $*
NUM_PARALLEL_BUILDS="$RESULT"
;;
--list-supported)
echo "All supported configurations are (host -> target):"
list_all_supported
exit 0
;;
--outdir | --outdir=*)
get_argument $*
OUT_DIR="$RESULT"
;;
--strip)
STRIP="yes"
;;
--sysroot | --sysroot=*)
get_argument $*
SYSROOT="$RESULT"
;;
--target | --target=*)
get_argument $*
TARGET="$RESULT"
verify_arch_os_tuple "$TARGET"
;;
*)
die "Unrecognized option '$1'. Use '--help' for usage."
;;
esac
shift
if [ -n "$SKIP_EXTRA_ARG" ]
then
shift
fi
done
# Post-processing
if [ -z "$TARGET" ]
then
die "--target required"
fi
if ! is_configuration_supported && [ -z "$force" ]
then
die "Configuration not supported. Use --force to try anyway."
fi
if [ "$BUILD" != "$HOST" ] && [ -z "$SYSROOT" ] && [ -z "$force" ]
then
die "Sysroot expected when host differs from build system. Use --force to try anyway."
fi
ARCHIVE_DIR="$OUT_DIR/archives"
}
get_tool_verinfo()
{
. toolvers
GNU_MIRROR="https://mirrors.kernel.org/gnu"
BINUTILS_REPO="$GNU_MIRROR/binutils/binutils-$BINUTILS_VER.tar.bz2"
GCC_REPO="$GNU_MIRROR/gcc/gcc-$GCC_VER/gcc-$GCC_VER.tar.bz2"
GDB_REPO="$GNU_MIRROR/gdb/gdb-$GDB_VER.tar.xz"
GMP_REPO="$GNU_MIRROR/gmp/gmp-$GMP_VER.tar.bz2"
MPC_REPO="$GNU_MIRROR/mpc/mpc-$MPC_VER.tar.gz"
MPFR_REPO="$GNU_MIRROR/mpfr/mpfr-$MPFR_VER.tar.bz2"
}
get_component_attribute()
{
local component="$1"
local suffix="$2"
local component_uppercase="$(echo "$component" \
| tr '[:lower:]' '[:upper:]')"
local var_name="${component_uppercase}_${suffix}"
RESULT="${!var_name}"
}
# See if there are any additional tools specified with TOOLS_<arch> or
# TOOLS_<os>
build_tool_list()
{
split_arch_os "$TARGET"
local target_arch="$RESULT_ARCH"
local target_os="$RESULT_OS"
RESULT="$TOOLS"
local arch_specific_var_name="TOOLS_${target_arch}"
if [ ! -z "${!arch_specific_var_name}" ]
then
RESULT="$RESULT ${!arch_specific_var_name}"
fi
local os_specific_var_name="TOOLS_${target_os}"
if [ ! -z "${!os_specific_var_name}" ]
then
RESULT="$RESULT ${!os_specific_var_name}"
fi
}
maybe_download_sources()
{
build_tool_list
local all_tools="$RESULT"
local component
for component in $all_tools $HOST_LIBS
do
get_component_attribute "$component" "REPO"
local component_repo="$RESULT"
local component_tarfile="$(basename "$component_repo")"
get_component_attribute "$component" "VER"
local component_ver="$RESULT"
local component_source_dir="$OUT_DIR/$component-$component_ver"
if [ ! -f "$component_source_dir/.extracted" ]
then
# Download tarfile
if [ ! -f "$ARCHIVE_DIR/$component_tarfile" ]
then
echo "Fetching $component-$component_ver"
wget -P "$ARCHIVE_DIR" -N "$component_repo" \
|| die "Failed to retrieve from $component_repo"
fi
echo "Checking $component_tarfile integrity"
get_component_attribute "$component" "HASH"
local component_hash="$RESULT"
[ "$(shasum -a 256 -b "$ARCHIVE_DIR/$component_tarfile" \
| cut -f1 -d' ')" \
== "$component_hash" ] || \
die "$component_tarfile failed integrity check"
echo "Extracting $component_tarfile"
mkdir -p "$OUT_DIR" || die "Unable to create directory $OUT_DIR"
pushd "$OUT_DIR" > /dev/null || die "Unable to change to $OUT_DIR"
rm -rf "$component_source_dir" \
|| die "Failed removing $component_source_dir"
tar xf "$ARCHIVE_DIR/$component_tarfile" \
|| die "Failed extracting $component_tarfile"
local patch_filename="$SCRIPT_DIR/patches/${component}-patch.txt"
if [ -f "$patch_filename" ]
then
echo "Patching $component"
local src_dir="${component}-${component_ver}"
patch -d "${src_dir}" -p1 < "$patch_filename" \
|| die "Failed to patch '${src_dir}'"
fi
touch "${component_source_dir}/.extracted" \
|| die "Failed to create ${component_source_dir}/.extracted"
popd > /dev/null || die "popd failed"
fi
done
}
get_build_dir()
{
local component="$1"
get_component_attribute "$component" "VER"
local component_ver="$RESULT"
if [ "$HOST" == "$TARGET" ]
then
RESULT="$OUT_DIR/build/${component}-${component_ver}_${HOST}"
else
RESULT="$OUT_DIR/build/${component}-${component_ver}_${HOST}_to_${TARGET}"
fi
}
# Determine where the tools will be installed.
get_install_dir()
{
canonicalize_target_name "$TARGET"
local normalized_target="$RESULT"
get_component_attribute "gcc" "VER"
local gcc_ver="$RESULT"
if [ "$HOST" == "$TARGET" ]
then
RESULT="$OUT_DIR/$normalized_target-$gcc_ver-native"
else
canonicalize_target_name "$HOST"
local normalized_host="$RESULT"
split_arch_os "$normalized_host"
local normalized_host_arch="$RESULT_ARCH"
# The output dir has an uppercase OS name.
local normalized_host_os="$(echo ${RESULT_OS:0:1} | tr '[:lower:]' '[:upper:]')${RESULT_OS:1}"
RESULT="$OUT_DIR/$normalized_target-$gcc_ver-$normalized_host_os-$normalized_host_arch"
fi
}
get_host_lib_install_dir()
{
local lib_name="$1"
get_component_attribute "$lib_name" "VER"
local lib_ver="$RESULT"
RESULT="$OUT_DIR/host_libs/${HOST}/${lib_name}-${lib_ver}"
}
init_config_options()
{
CONFIG_OPTIONS="$(echo "$*" | sed -E -e 's/ +/ /g')"
}
add_config_options()
{
local config_var_name="CONFIG_$1"
if [ ! -z "${!config_var_name}" ]
then
if [ -z "$CONFIG_OPTIONS" ]
then
CONFIG_OPTIONS="${!config_var_name}"
else
CONFIG_OPTIONS="${CONFIG_OPTIONS}:${!config_var_name}"
fi
CONFIG_OPTIONS=$(echo "${CONFIG_OPTIONS}" \
| sed -E -e "s/[ \t\n]*:[ \t\n]*/:/g")
fi
}
fix_lib_configs()
{
for host_lib in $HOST_LIBS
do
get_host_lib_install_dir "$host_lib"
local install_dir="$RESULT"
local escaped_dir="$(echo "$install_dir" | sed -E -e 's/\//\\\//g')"
CONFIG_OPTIONS="$(echo $CONFIG_OPTIONS \
| sed -E -e "s/(--with-${host_lib}=)/\1${escaped_dir}/g")"
done
}
# Add a flag into the configuration options. If the flag is already present
# in the configuration option string, inserts the value into the
add_to_flags()
{
local flagname="$1"
local flagvalue="$2"
# This test assumes that the configuration option will not be at the start
# of the string.
if $(echo $CONFIG_OPTIONS | grep -q ":${flagname}=")
then
CONFIG_OPTIONS=$(echo $CONFIG_OPTIONS \
| sed -E -e "s/${flagname}=/${flagname}=${flagvalue} /")
else
CONFIG_OPTIONS="$CONFIG_OPTIONS:${flagname}=${flagvalue}"
fi
}
# Add extra flags needed to build as needed for the build/host/target
# combo (e.g., CFLAGS, CFLAGS_FOR_TARGET...)
add_cross_build_options()
{
local tool
local uppercase_tool
if [ "$BUILD" != "$HOST" ]
then
add_to_flags "CC_FOR_BUILD" "gcc"
fi
if [ "$BUILD" != "$TARGET" ]
then
if ! [ -z "$SYSROOT" ]
then
CONFIG_OPTIONS="$CONFIG_OPTIONS:--with-sysroot=${SYSROOT}"
fi
# Specify target-specific tools. Surprisingly, when gcc builds
# libgcc if these aren't specified it will use the unprefixed
# tools even if the prefixed tools are in the PATH.
local tool
for tool in $ALL_GNU_TOOLS
do
uppercase_tool="$(echo "$tool" | tr '[:lower:]' '[:upper:]')"
add_to_flags "${uppercase_tool}_FOR_TARGET" "${TARGET}-${tool}"
done
add_to_flags "CC_FOR_TARGET" "${TARGET}-gcc"
add_to_flags "CXX_FOR_TARGET" "${TARGET}-g++"
fi
}
# Build the configuration options for a specific module, including relevant
# environment variables of the forms:
# CONFIG_<module>
# CONFIG_<module>_<arch>
# CONFIG_<module>_<os>
# CONFIG_<module>_<arch>_<os>
# CONFIG_<module>_TOOLS_<host-tools>
build_config_options()
{
local component="$1"
local is_host_lib="$2"
local uppercase_component="$(echo "$component" \
| tr '[:lower:]' '[:upper:]')"
split_arch_os "$TARGET"
local target_arch="$RESULT_ARCH"
local target_os="$RESULT_OS"
init_config_options "--prefix=$install_dir"
if [ "$HOST" != "$BUILD" ]
then
canonicalize_target_name "$HOST"
CONFIG_OPTIONS="$CONFIG_OPTIONS:--host=${RESULT}"
fi
if [ "$TARGET" != "$HOST" ] && [ "$is_host_lib" == '0' ]
then
canonicalize_target_name "$TARGET"
CONFIG_OPTIONS="$CONFIG_OPTIONS:--target=${RESULT}"
fi
add_config_options "$uppercase_component"
add_config_options "${uppercase_component}_${target_arch}"
add_config_options "${uppercase_component}_${target_os}"
add_config_options "${uppercase_component}_${target_arch}_${target_os}"
# Apply options that are specific to the host compiler, if we will
# be using it (if we're doing a cross-compilation then we are
# using a compiler that we built, and these options aren't relevant).
if [ "$HOST" == "$BUILD" ] && [ ! -z "$HOST_TOOLS" ]
then
add_config_options "${uppercase_component}_TOOLS_${HOST_TOOLS}"
fi
# Fix references to libraries (gmp, etc.)
fix_lib_configs
# Indicate location of sysroot
if [ "$is_host_lib" == "0" ] # || [ "$BUILD" != "$HOST" ]
then
add_cross_build_options
fi
}
canonicalize_target_name()
{
RESULT="$(echo "$1" \
| sed -E -e 's/arm-none/arm-eabi/' \
| sed -E -e 's/([a-zA-Z0-9_]+)-none/\1-elf/')"
}
do_configure()
{
build_config_options "$component" "$is_host_lib"
(IFS=':' && "$OUT_DIR/${component}-${component_ver}/configure" \
$CONFIG_OPTIONS)
if [ "$?" != '0' ]
then
die "Failed to configure $component"
fi
}
build_component()
{
local component="$1"
local is_host_lib="$2"
get_component_attribute "$component" "VER"
local component_ver="$RESULT"
get_build_dir "$component"
local build_dir="$RESULT"
if [ "$is_host_lib" == "0" ]
then
get_install_dir
else
get_host_lib_install_dir "$component"
fi
local install_dir="$RESULT"
if [ ! -f $build_dir/built.txt ]
then
mkdir -p "$build_dir" || die "Unable to create directory $build_dir"
pushd "$build_dir" > /dev/null || die "Unable to pushd $build_dir"
echo "Configuring $component"
do_configure
echo "Building $component"
make -j ${NUM_PARALLEL_BUILDS} || die "Failed to build $component"
echo "Installing $component"
make -j ${NUM_PARALLEL_BUILDS} install || die "Failed to install $component"
touch built.txt || die "Failed to create $build_dir/built.txt"
popd > /dev/null || die "Unable to popd"
fi
}
build_components()
{
maybe_download_sources
get_install_dir
local install_dir="$RESULT"
# Mucking with the path makes configuration significantly easier.
if [ "$BUILD" == "$HOST" ]
then
export PATH="$PATH:${install_dir}/bin"
fi
local lib
for lib in $HOST_LIBS
do
build_component $lib 1
done
build_tool_list
local all_tools="$RESULT"
for tool in $all_tools
do
build_component $tool 0
done
if [ -n "$STRIP" ]
then
if [ "$BUILD" == "$HOST" ]
then
local strip_util="strip"
else
local strip_util="${HOST}-strip"
fi
local filename
for filename in $(find "${install_dir}/bin" -type f) \
$(find "${install_dir}/libexec" -type f)
do
(file "${filename}" | grep -q ELF) && ${strip_util} "${filename}"
done
fi
}
gather_host_info
process_opts $*
get_tool_verinfo
if [ "$BUILD" != "$HOST" ]
then
# First, build a BUILD->HOST cross-compiler
ORIG_TARGET="$TARGET"
TARGET="$HOST"
HOST="$BUILD"
build_components
HOST="$TARGET"
TARGET="$ORIG_TARGET"
fi
build_components
exit 0