#! /bin/bash

# build-cc 0.4

if [ "$1" == "--version" ]; then
	echo 'build-cc 0.4'

	exit 0
fi

# Start in the same working directory as our script
OURSCP="$(which "$0" 2>/dev/null)"
if [ -f "${OURSCP}" ]; then
	TOPDIR="$(cd "$(dirname "${OURSCP}")"; pwd)"
fi
export TOPDIR

cd "${TOPDIR}" || exit 1

# Load configuration
## Provide defaults
### Root for cross-compiler toolchains
CCROOT="${HOME}/root/cross-compilers"

### Path to platform files
if [ -z "${BUILD_CC_PLATFORMDIR}" ]; then
	BUILD_CC_PLATFORMDIR="$(pwd)/platform"
fi

## Load configuration file
if [ -f "build-cc.conf" ]; then
	. "build-cc.conf"
fi

# Tool versions
BINUTILS_VERS='2.22'
GCC_VERS='4.6.2'
GMP_VERS='5.0.2'
MPFR_VERS='3.0.1'
MPC_VERS='0.9'

# Start of script
## Initialize default values
use_multilib='1'
use_gnu_ld='1'
use_gnu_as='1'

## Parse arguments
### Determine list of platforms
platforms[0]='armel-unknown-linux-uclibc'
idx=1
for platform in "${BUILD_CC_PLATFORMDIR}"/*-platform.tar.bz2; do
	if [ ! -f "${platform}" ]; then
		continue
	fi

	platform="$(basename "${platform}" | sed 's@-platform.tar.bz2$@@')"

	platforms[${idx}]="${platform}"
	idx=$[${idx} + 1]
done

if [ -z "$1" ]; then
	set -- list
fi

if [ "$1" = "list" ]; then
	echo 'Available Targets:'
	for platform in "${platforms[@]}"; do
		echo "  ${platform}"
	done

	exit 0
elif [ "$1" = "clean" -o "$1" = "distclean" ]; then
	true
else
	found='0'
	for platform in "${platforms[@]}"; do
		if [ "$1" = "${platform}" ]; then
			found='1'

			break
		fi
	done

	if [ "${found}" = '0' ]; then
		echo "Unknown target: $1, aborting." >&2

		exit 1
	fi
fi

## URLs
### Binutils
BINUTILS_URL="http://ftp.gnu.org/gnu/binutils/binutils-${BINUTILS_VERS}.tar.bz2"
BINUTILS_TARBALL="src/binutils-${BINUTILS_VERS}.tar.bz2"
BINUTILS_DIR="binutils-${BINUTILS_VERS}"

### GCC
GCC_URL="http://mirrors-us.seosue.com/gcc/releases/gcc-${GCC_VERS}/gcc-${GCC_VERS}.tar.bz2"
GCC_TARBALL="src/gcc-${GCC_VERS}.tar.bz2"
GCC_DIR="gcc-${GCC_VERS}"

### GMP
GMP_URL="http://ftp.gnu.org/gnu/gmp/gmp-${GMP_VERS}.tar.bz2"
GMP_TARBALL="src/gmp-${GMP_VERS}.tar.bz2"
GMP_DIR="gmp-${GMP_VERS}"

### MPFR
MPFR_URL="http://www.mpfr.org/mpfr-${MPFR_VERS}/mpfr-${MPFR_VERS}.tar.bz2"
MPFR_TARBALL="src/mpfr-${MPFR_VERS}.tar.bz2"
MPFR_DIR="mpfr-${MPFR_VERS}"

### MPC
MPC_URL="http://www.multiprecision.org/mpc/download/mpc-${MPC_VERS}.tar.gz"
MPC_TARBALL="src/mpc-${MPC_VERS}.tar.gz"
MPC_DIR="mpc-${MPC_VERS}"

## Clean-up
if [ "$1" = "clean" -o "$1" = "distclean" ]; then
	rm -rf "${BINUTILS_DIR}" "${GCC_DIR}" "${GMP_DIR}" "${MPFR_DIR}" "${MPC_DIR}"

	for platform in "${platforms[@]}"; do
		rm -rf "gcc-${platform}"
		rm -rf "binutils-${platform}"
	done

	for appscript in scripts/* scripts/pre/* scripts/post/*; do
		"${appscript}" "clean" >/dev/null 2>/dev/null
	done

	if [ "$1" = "clean" ]; then
		exit 0
	fi
fi

if [ "$1" = "distclean" ]; then
	rm -f "${BINUTILS_TARBALL}" "${GCC_TARBALL}" "${GMP_TARBALL}" "${MPFR_TARBALL}" "${MPC_TARBALL}"

	for appscript in scripts/* scripts/pre/* scripts/post/*; do
		"${appscript}" "distclean" >/dev/null 2>/dev/null
	done

	rmdir src >/dev/null 2>/dev/null

	exit 0
fi

## Tools
MAKE="${MAKE:-make}"
MAKE_SINGLE="$(echo " ${MAKE} " | sed 's@ -j *[0-9][0-9]* @ @g;s@^  *@@g;s@  *$@@g')"
export MAKE MAKE_SINGLE

## Functions
. 'scripts/common'

## Determine path for this cross-compiler
CCNAME="$1"
CCDIR="${CCROOT}/${CCNAME}"

## Determine attributes for this compiler
### Per CPU
case "${CCNAME}" in
	hppa64-*|hppa1*-)
		BUILD_CC_GCC_CONFIGURE_EXTRA="${BUILD_CC_GCC_CONFIGURE_EXTRA} --disable-libquadmath"
		;;
esac

### Per OS
case "${CCNAME}" in
	*-hpux*)
		#### Default to disabling multilib, will be re-enabled by target if supported
		use_multilib='0'
		;;
	*-freebsd*)
		#### Default to disabling multilib, will be re-enabled by target if supported
		use_multilib='0'
		;;
	*-darwin*)
		use_gnu_ld='0'
		use_gnu_as='0'
		use_ld64='0'

		if echo "${CCNAME}" | egrep '^(x86_64|ppc64)-' >/dev/null; then
			use_ld64='1'
		fi

		if [ "${use_ld64}" = '0' ]; then
			ldcmd='ld'
		else
			ldcmd='ld64'
		fi

		BUILD_CC_GCC_CONFIGURE_EXTRA="${BUILD_CC_GCC_CONFIGURE_EXTRA} --with-ld=${CCDIR}/${CCNAME}/bin/${ldcmd} --with-as=${CCDIR}/${CCNAME}/bin/as"

		unset ldcmd
		;;
esac

if [ "${use_gnu_ld}" = "1" ]; then
	BUILD_CC_GCC_CONFIGURE_EXTRA="${BUILD_CC_GCC_CONFIGURE_EXTRA} --with-gnu-ld"
fi

if [ "${use_gnu_as}" = "1" ]; then
	BUILD_CC_GCC_CONFIGURE_EXTRA="${BUILD_CC_GCC_CONFIGURE_EXTRA} --with-gnu-as"
fi

## Determine platform file tarball
PLATFORM_TARBALL="${BUILD_CC_PLATFORMDIR}/${CCNAME}-platform.tar.bz2"
ADDONS_TARBALL="${BUILD_CC_PLATFORMDIR}/platform/${CCNAME}-addons.tar.bz2"

### Install platform files (needed for libgcc)
(
	mkdir -p "${CCDIR}/${CCNAME}"
	cd "${CCDIR}/${CCNAME}" || exit 1

	if [ ! -e 'usr' ]; then
		#### Create symlink so things like "./usr/include" work.
		ln -s . usr
	fi

	if [ -f "${PLATFORM_TARBALL}" ]; then
		bzip2 -dc "${PLATFORM_TARBALL}" | "${TAR:-tar}" --keep-old-files -xf - >/dev/null 2>/dev/null
	fi

	if [ -f "${ADDONS_TARBALL}" ]; then
		bzip2 -dc "${ADDONS_TARBALL}" | "${TAR:-tar}" --keep-old-files -xf - >/dev/null 2>/dev/null
	fi
)

### Set use_multilib authoritatively if possible
if [ -f "${CCDIR}/${CCNAME}/multilib" ]; then
	use_multilib="$(cat "${CCDIR}/${CCNAME}/multilib")"
	rm -f "${CCDIR}/${CCNAME}/multilib"
fi

### Set C compiler flags from tarball
if [ -f "${CCDIR}/${CCNAME}/BUILD_CC_GCC_CONFIGURE_EXTRA" ]; then
	add="$(cat "${CCDIR}/${CCNAME}/BUILD_CC_GCC_CONFIGURE_EXTRA")"

	#### Ensure these flags aren't malicious
	##### XXX: TODO: Build the extra flags as an array for safety
	if ! echo "${add}" | grep '[;|<>]' >/dev/null; then
		BUILD_CC_GCC_CONFIGURE_EXTRA="${BUILD_CC_GCC_CONFIGURE_EXTRA} ${add}"
	fi
	
	rm -f "${CCDIR}/${CCNAME}/BUILD_CC_GCC_CONFIGURE_EXTRA"
fi

## Determine stage of build process
STAGE="$2"

### If we have a platform tarball and a stage has not been specified, assume we are at stage 2
if [ -z "${STAGE}" ]; then
	if [ -f "${PLATFORM_TARBALL}" ]; then
		STAGE="stage2"
	else
		STAGE="stage1"
	fi
fi

### Normalize stage names
if [ "${STAGE}" != "stage1" ]; then
	STAGE="stage2"
fi
export STAGE

### Do pre-compilation steps
for prescript in scripts/pre/*; do
	if [ ! -x "${prescript}" ]; then
		continue
	fi

	sourcefile="${TMPDIR:-/tmp}/build-cc-sourcefile-$$${RANDOM}${RANDOM}${RANDOM}"
	rm -f "${sourcefile}"

	"${prescript}" "${CCNAME}" "${CCDIR}" "${CCDIR}/${CCNAME}" "${STAGE}" "${sourcefile}" || exit 1

	if [ -f "${sourcefile}" ]; then
		. "${sourcefile}"
		rm -f "${sourcefile}"
	fi
done

## Compile binutils for this platform if needed
(
	if [ -f "${CCDIR}/bin/${CCNAME}-objcopy" ]; then
		exit 0
	fi

	if [ ! -d "${BINUTILS_DIR}" ]; then
		download "${BINUTILS_URL}" "${BINUTILS_TARBALL}"

		bzip2 -dc "${BINUTILS_TARBALL}" | tar -xf -
	fi

	rm -rf "binutils-${CCNAME}"
	mkdir "binutils-${CCNAME}"
	cd "binutils-${CCNAME}" || exit -1

	"../${BINUTILS_DIR}/configure" --target="${CCNAME}" --prefix="${CCDIR}" --disable-nls --with-sysroot="${CCDIR}/${CCNAME}" --with-build-sysroot="${CCDIR}/${CCNAME}"

	${MAKE} || exit 1

	${MAKE} install || exit 1
) || exit 1
rm -rf "binutils-${CCNAME}"

## Compile C compiler (GCC) if needed
### Prepare GCC fix-ups (stage1)
if [ "${STAGE}" = "stage1" ]; then
	### XXX: GCC produces a broken compiler -- compiles require libgcc_eh.a, which it doesn't emit
	libgcceha="${CCDIR}/lib/gcc/${CCNAME}/${GCC_VERS}/libgcc_eh.a"
	if [ -h "${libgcceha}" ]; then
		rm -f "${libgcceha}"
	fi
fi

### If multilib was not requested, disable it
if [ "${use_multilib}" = "0" ]; then
	BUILD_CC_GCC_CONFIGURE_EXTRA="--disable-multilib ${BUILD_CC_GCC_CONFIGURE_EXTRA}"
fi

## Compile
(
	### Determine if compilation is needed
	if [ -f "${CCDIR}/bin/${CCNAME}-gcc" ]; then
		if find "${CCDIR}/lib/gcc" -type f -name 'libgcc.a' | grep libgcc >/dev/null; then
			if [ "${STAGE}" = "stage1" ]; then
				#### A stage1 compiler is available
				exit 2
			else
				#### Determine if a stage2 compiler has been built
				if find "${CCDIR}" -type f -name 'libgcc_s.*' | grep libgcc_s >/dev/null; then
					exit 2
				fi
			fi
		fi
	fi

	if [ ! -d "${GCC_DIR}" ]; then
		# Download sources required
		download "${GCC_URL}" "${GCC_TARBALL}"
		download "${GMP_URL}" "${GMP_TARBALL}"
		download "${MPFR_URL}" "${MPFR_TARBALL}"
		download "${MPC_URL}" "${MPC_TARBALL}"

		# Extract sources
		bzip2 -dc "${GCC_TARBALL}" | "${TAR:-tar}" -xf -
		bzip2 -dc "${GMP_TARBALL}" | "${TAR:-tar}" -xf -
		bzip2 -dc "${MPFR_TARBALL}" | "${TAR:-tar}" -xf -
		gzip -dc "${MPC_TARBALL}" | "${TAR:-tar}" -xf -

		# Arroung sources as needed
		rm -rf "${GCC_DIR}/gmp"
		rm -rf "${GCC_DIR}/mpfr"
		rm -rf "${GCC_DIR}/mpc"

		mv "${GMP_DIR}" "${GCC_DIR}/gmp"
		mv "${MPFR_DIR}" "${GCC_DIR}/mpfr"
		mv "${MPC_DIR}" "${GCC_DIR}/mpc"

		# Apply patches
		## Apply patch files
		for patchfile in "$(pwd)/patches/gcc"/*.diff; do
			if [ ! -f "${patchfile}" ]; then
				continue
			fi

			(
				echo " * Applying patch ${patchfile}"

				cd "${GCC_DIR}" || exit 1

				"${PATCH:-patch}" -p1 < "${patchfile}" || exit 1
			) || exit 1
		done

		## Apply patch scripts
		export GCC_DIR
		for gccscript in scripts/gcc/*; do
			if [ ! -x "${gccscript}" ]; then
				continue
			fi

			sourcefile="${TMPDIR:-/tmp}/build-cc-sourcefile-$$${RANDOM}${RANDOM}${RANDOM}"
			rm -f "${sourcefile}"

			"${gccscript}" "${CCNAME}" "${CCDIR}" "${CCDIR}/${CCNAME}" "${STAGE}" "${sourcefile}" || exit 1

			if [ -f "${sourcefile}" ]; then
				. "${sourcefile}"
				rm -f "${sourcefile}"
			fi
		done
	fi

	# Build GCC with shared object support
	## Create build directory
	rm -rf "gcc-${CCNAME}"
	mkdir "gcc-${CCNAME}"
	cd "gcc-${CCNAME}" || exit -1

	## Unset "CPPFLAGS" since GOMP/OpenMP does not properly sanitize it
	unset CPPFLAGS

	## Build GCC
	if [ "${STAGE}" = "stage1" ]; then
		### Stage 1 -- just GCC and libgcc, no more will build until we get some headers
		"../${GCC_DIR}/configure" --target="${CCNAME}" --prefix="${CCDIR}" --disable-nls --enable-languages='c' --without-headers --disable-threads --disable-shared --with-newlib --with-sysroot="${CCDIR}/${CCNAME}" --with-build-sysroot="${CCDIR}/${CCNAME}" ${BUILD_CC_GCC_CONFIGURE_EXTRA}

		${MAKE} all-gcc all-target-libgcc || exit 1
		${MAKE} install-gcc install-target-libgcc || exit 1
	else
		### Stage 2 -- the full compiler suite
		"../${GCC_DIR}/configure" --target="${CCNAME}" --prefix="${CCDIR}" --with-headers="${CCDIR}/${CCNAME}/include" --disable-nls --enable-languages='c,c++' --with-sysroot="${CCDIR}/${CCNAME}" --with-build-sysroot="${CCDIR}/${CCNAME}" ${BUILD_CC_GCC_CONFIGURE_EXTRA}

		${MAKE} || exit 1
		${MAKE} install || exit 1
	fi
)
retval="$?"

### XXX: Create symlink for libgcc_eh.a since GCC references
### it without building it for static (disable-shared) builds
if [ "${STAGE}" = "stage1" ]; then
	if [ -h "${libgcceha}" ]; then
		rm -f "${libgcceha}"
	fi

	if [ ! -f "${libgcceha}" ]; then
		ln -s libgcc.a "${libgcceha}"
	fi
fi

### If we exited with failure above, abort
if [ "${retval}" != "2" -a "${retval}" != "0" ]; then
	exit 1
fi
rm -rf "gcc-${CCNAME}"

## Install libraries for this platform
for appscript in scripts/* scripts/post/*; do
	if echo "${appscripts}" | grep '/common$' >/dev/null; then
		continue
	fi

	if [ ! -f "${appscript}" -o ! -x "${appscript}" ]; then
		continue
	fi

	(
		PATH="${PATH}:${CCDIR}/bin"
		CC="${CCNAME}-gcc"
		LD="${CCNAME}-ld"
		CXX="${CCNAME}-g++"
		AR="${CCNAME}-ar"
		STRIP="${CCNAME}-strip"
		RANLIB="${CCNAME}-ranlib"
		export PATH CC LD CXX AR STRIP RANLIB

		"${appscript}" "${CCNAME}" "${CCDIR}" "${CCDIR}/${CCNAME}" "${STAGE}" || exit 1
	) || exit 1
done

## Do it all again for the next stage
if [ "${STAGE}" = "stage1" ]; then
	"$0" "${CCNAME}" "stage2" || exit "$?"
fi

## Clean up
exit 0
