#!/bin/bash

set -e

# This is a script to bootstrap a minimal "sysroot" for later builds of
# OPTENV subsystems for AOSC OS.
#
# WARNING:
# - This script is bare bones compared to the more sophisticated "Kaboom"
#   build system (whose purpose is to bootstrap a minimal AOSC OS build
#   environment "BuildKit" for later Stage 2 and Stage 3 builds).
# - This script makes plenty of bad assumptions like source code folder
#   name conventions (NAME-VER), the Autotools build system, tarball
#   download links, etc.
# - I won't "fix" the assumptions unless GNU changed their naming
#   conventions, URL scheme of tarball download links and etc.
# - Also, this script does NOT check for the integrity of the downloaded
#   tarballs!
# - This script also contains a hard-coded patch for GCC, which injects
#   a `-B /opt/32/bin' option to the driver, to force the driver to use
#   the assembler and linker from /opt/32/bin instead of /usr/bin. You
#   can find this patch in aosc-os-abbs source tree.

# Use the script(1) utility to capture build logs.
if [ "x$_RUN_" = "x" ] && [ -t 0 ] ; then
	if ! command -v script ; then
		echo "-- Can not find script utility from util-linux." >&2
		exit 1
	fi
	export _RUN_=1
	exec script -c "bash $_BASHOPT $0 $@" build.log
fi

# Number of parallel jobs to run when invoking `m' (alias to `make').
_DEFAULT_J=$(($(nproc) + 1))
_J=${J:-$_DEFAULT_J}

# System triplet definitions.
_TGT_TRIPLE="armv7l-aosc-linux-gnueabihf"
_HST_TRIPLE="aarch64-aosc-linux-gnu"
# Target prefix and sysroot paths.
_PFX="/opt/32"
_SYSROOT="/root/cross/sysroot"
_NATIVEINC="/opt/32/include"
# Path containing the source tarballs.
SRCS="/var/cache/acbs/tarballs/srcs"
# Directory to extract the source code and to perform builds.
BLDPFX="/root/cross/build"

# Definitions used to build the cross compilation tools.
TOOLS_DEF=("--host=$_HST_TRIPLE" "--target=$_TGT_TRIPLE" "--build=$_HST_TRIPLE" "--prefix=$_PFX" "--enable-year2038")
# Definitions used to build the target binaries, using the cross tools.
CROSS_DEF=("--host=$_TGT_TRIPLE" "--target=$_TGT_TRIPLE" "--build=$_HST_TRIPLE" "--prefix=$_PFX" "--enable-year2038")
# Definitions used to build the target binaries, using the already built target binaries.
NATIVE_DEF=("--host=$_TGT_TRIPLE" "--target=$_TGT_TRIPLE" "--build=$_TGT_TRIPLE" "--prefix=$_PFX" "--enable-year2038")

export OLDPATH="$PATH"

# As this script is intended to build just enough packages for the later
# builds, we hard code package names and versions here.
# Or use associated arrays?
gcc_VER=14.3.0
binutils_VER=2.44
glibc_VER=2.40
linux_VER=6.12.15
gmp_VER=6.3.0
mpc_VER=1.3.1
mpfr_VER=4.2.1

gcc_URL="https://mirrors.ustc.edu.cn/gnu/gcc/gcc-$gcc_VER/gcc-$gcc_VER.tar.xz"
binutils_URL="https://mirrors.ustc.edu.cn/gnu/binutils/binutils-$binutils_VER.tar.xz"
gmp_URL="https://mirrors.ustc.edu.cn/gnu/gmp/gmp-$gmp_VER.tar.xz"
mpc_URL="https://mirrors.ustc.edu.cn/gnu/mpc/mpc-$mpc_VER.tar.gz"
mpfr_URL="https://mirrors.ustc.edu.cn/gnu/mpfr/mpfr-$mpfr_VER.tar.xz"
glibc_URL="https://mirrors.ustc.edu.cn/gnu/glibc/glibc-$glibc_VER.tar.xz"
linux_URL="https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-$linux_VER.tar.xz"

# pkgname_DEF: Options to be passed to the configure script.
# pkgname_DEF_TOOLS: Options to be passed to the configure script,
# for building at stage 0 (Cross tools stage).
# pkgname_DEF_CROSS: Options to be passed to the configure script,
# for building at stage 1 (Cross-compiling native tools).
BINUTILS_DEF=(
		"--enable-ld"
		"--enable-lto"
		"--enable-shared"
		"--enable-gprofng=no"
		"--enable-new-tags"
		"--enable-default-hash-style=gnu"
		"--disable-nls"
		"--enable-64-bit-bfd"
		"--disable-multilib"
		"--with-native-system-header-dir=/opt/32/include"
)

GCC_DEF_TOOLS=(
	"--with-glibc-version=2.40"
	"--with-newlib"
	"--without-headers"
	"--disable-libstdcxx"
	"--disable-multilib"
	"--disable-threads"
	"--disable-libatomic"
	"--disable-libssp"
	"--disable-shared"
	"--disable-libatomic"
	"--disable-libgomp"
	"--disable-libquadmath"
	"--disable-libssp"
	"--disable-libvtv"
	"--disable-libstdcxx"
	"--enable-languages=c,c++"
	"--with-arch=armv7-a"
	"--with-fpu=neon-vfpv3"
	"--with-float=hard"
	"--with-mode=thumb"
	"--with-native-system-header-dir=/opt/32/include"
	"--libdir=/opt/32/lib"
	"--includedir=/opt/32/include"
	"--with-as=$_PFX/bin/$_TGT_TRIPLE-as"
	"--with-ld=$_PFX/bin/$_TGT_TRIPLE-ld"
)

GCC_DEF_NATIVE=(
	"--with-glibc-version=2.40"
	"--disable-multilib"
	"--enable-threads"
	"--enable-libatomic"
	"--enable-libgomp"
	"--enable-libquadmath"
	"--enable-libssp"
	"--enable-libvtv"
	"--enable-libstdcxx"
	"--enable-languages=c,c++"
	"--with-arch=armv7-a"
	"--with-fpu=neon-vfpv3"
	"--with-float=hard"
	"--with-mode=thumb"
	"--with-native-system-header-dir=/opt/32/include"
	"--libdir=/opt/32/lib"
	"--includedir=/opt/32/include"
)

GLIBC_DEF_CROSS=(
	"--enable-kernel=4.19"
	"--with-headers=/opt/32/include"
	"--disable-nscd"
	"libc_cv_slibdir=/opt/32/lib"
)


LIBCXX_DEF_CROSS=(
	"--disable-multilib"
	"--disable-nls"
	"--disable-libstdcxx-pch"
	"--with-gxx-include-dir=$_PFX/include/c++/14.2.0/"
)

getver() {
	local _var _ver _pkg
	if [ "x$1" = "x" ] ; then
		echo "Package name required!" >&2
		return 1
	fi
	_pkg="$1"
	_var="${_pkg,,}_VER"
	_ver="${!_var}"
	if [ "x$_ver" = "x" ] ; then
		echo "Version not found!" >&2
		return 1
	fi
	echo "$_ver"
}

dl_src() {
	# As all packages are from GNU, we can download all of them from
	# sourceware.org or ftp.gnu.org.
	local name ver _succ _cnt _var _url outfile WGET
	WGET=wget
	name=$1
	ver=$2
	if [ "x$name" = "x" ] || [ "x$ver" = "x" ] ; then
		echo "-- Expected name and version! Got $name-$ver"
		return 1
	fi
	outfile="$SRCS/$name-$ver.tar.gz"
	_succ=0
	_cnt=1
	_var="${name}_URL"
	_url="${!_var}"
	_ret=0
	while [ "$_succ" != "1" ] ; do
		$WGET -O "$outfile" "$_url" || _ret=$?
		if [ "$_ret" != "0" ] ; then
			case "$_ret" in
				4|5)
				if [ "$_cnt" -ge 5 ] ; then
					echo "-- Can not download $name-$ver! Aborting." >&2
					return 1
				fi
				_cnt=$(($_cnt + 1))
				echo "-- Network error encountered, retrying ..." >&2
				_ret=0
				continue
				;;
				8)
				echo "-- Can not download $name-$ver! Aborting." >&2
				return 1
				;;
				*)
				echo "-- Unknown error occurred! Aborting." >&2
				return 1
				;;
			esac
		else
			echo "-- Finished downloading $name-$ver.tar.gz." >&2
			return 0
		fi
	done
}

prep() {
	local ver name srctbl
	if [ "x$1" = "x" ] ; then
		echo "Package name required!" >&2
		return 1
	fi
	pushd $BLDPFX
	name=$1
	ver=$(getver $name)
	echo "-- Prepairing $name ($ver)..." >&2
	if [ -d "$name-$ver" ] && [ "x$2" = "x" ] ; then
		echo "-- Removing source and build directories ..." >&2
		rm -r "$name-$ver"
	fi
	srctbl=($(find $SRCS -name $name-$ver'.tar*'))
	if [ "${#srctbl[@]}" -gt 1 ] ; then
		echo "-- More than one source tarballs of $name found." >&2
		return 1
	elif [ "${#srctbl[@]}" -eq 0 ] ; then
		echo "-- No source tarball for $name is found, downloading ..." >&2
		dl_src $name $ver
		srctbl=("$SRCS/$name-$ver.tar.gz")
	fi
	tar xf $srctbl
	popd
	pushd $BLDPFX/$name-$ver
	echo "-- Building $name ..." >&2
}

md() {
	if [ ! -d $1 ] ; then
		echo "  MKDIR	$1" >&2
		mkdir $1
	fi
	echo "  CD	$1" >&2
	cd $1
}

c() {
	if   [ -x $PWD/../configure ] ; then
		echo "-- Running ../configure $DEF $@" >&2
		$PWD/../configure $DEF "$@"
	elif [ -x $PWD/configure ] ; then
		echo "-- Running ./configure $DEF $@" >&2
		$PWD/configure $DEF "$@"
	else
		echo "No configure found." >&2
	fi
}

m() {
	if   [ -e "$PWD"/build/config.status ] && \
	     [ -e "$PWD"/build/Makefile ] ; then
		md build
	elif [ -e "$PWD"/config.status ] && \
	     [ -e "$PWD"/Makefile ] ; then
		true
	else
		echo "Configure step may not succeed." >&2
		return 1
	fi
	echo "-- Running make -j${_J} $@" >&2
	make -j${_J} "$@"
}

mi() {
	if   [ -e "$PWD"/build/config.status ] && \
	     [ -e "$PWD"/build/Makefile ] ; then
		md build
	elif [ -e "$PWD"/config.status ] && \
	     [ -e "$PWD"/Makefile ] ; then
		true
	else
		echo "Configure step may not succeed." >&2
		return 1
	fi
	echo "-- Running make install $@" >&2
	make install "$@"
}

mid() {
	mi DESTDIR="$_SYSROOT"
}

binutils_stage0() {
	prep binutils
	md build-cross
	c "${TOOLS_DEF[@]}" "${BINUTILS_DEF[@]}"
	m
	mi
	mv $_PFX/$_TGT_TRIPLE/lib/ldscripts $_PFX/lib/
	rm -r $_PFX/$_TGT_TRIPLE
	ln -sf "." $_PFX/$_TGT_TRIPLE
	popd
}

binutils_stage1() {
	prep binutils 0
	md build-stage1
	c "${CROSS_DEF[@]}" "${BINUTILS_DEF[@]}"
	m
	mid
	mv $_SYSROOT/$_PFX/$_TGT_TRIPLE/lib/ldscripts $_SYSROOT/$_PFX/lib/
	rm -r $_SYSROOT/$_PFX/$_TGT_TRIPLE
	popd
}

gcc_stage0() {
	local gcc_inc_path
	prep gcc
	md build-cross
	c "${TOOLS_DEF[@]}" "${GCC_DEF_TOOLS[@]}"
	m
	mi
	pushd ..
	gcc_inc_path=$(dirname -- $($_PFX/bin/$_TGT_TRIPLE-gcc --print-libgcc-file-name))/include
	cat gcc/limitx.h gcc/glimits.h gcc/limity.h | tee $gcc_inc_path/limits.h > /dev/null
	popd
	popd
}

gcc_stage1() {
	prep gcc 0
	for p in gmp mpfr mpc ; do
		continue
		prep $p 0
		popd
		cp -r $BLDPFX/$p-$(getver $p) ./$p
	done
	md build-stage1
	c "${CROSS_DEF[@]}" "${GCC_DEF_NATIVE[@]}"
	m
	mid
	popd
}

linux_headers() {
	prep linux
	make ARCH=arm CROSS_COMPILE=$_PFX/bin/$_TGT_TRIPLE- headers
	find usr/include -type f -not -name '*.h' -delete
	cp -r usr/include/* $_PFX/include/
	cp -r usr/include/* $_SYSROOT/$_PFX/include/
	popd
}

glibc() {
	prep glibc
	md build
	echo "sbindir=/opt/32/bin" >> configparms
	echo "rootsbindir=/opt/32/bin" >> configparms
	echo "slibdir=/opt/32/lib" >> configparms
	echo "rtlddir=/opt/32/lib" >> configparms
	c "${CROSS_DEF[@]}" "${GLIBC_DEF_CROSS[@]}"
	m
	mi
	mid
	# Create symlinks for the dynamic linker.
	echo "-- glibc: Creating symlinks for the dynamic linker ..." >&2
	mkdir -pv $_SYSROOT/lib
	ln -sfv "../opt/32/lib/ld-linux-armhf.so.3" /lib/ld-linux-armhf.so.3
	ln -sfv "../opt/32/lib/ld-linux-armhf.so.3" $_SYSROOT/lib/ld-linux-armhf.so.3
	popd
}

libstdcxx() {
	pushd $BLDPFX/gcc-$gcc_VER
	md build-libcxx
	../libstdc++-v3/configure "${CROSS_DEF[@]}" "${LIBCXX_DEF_CROSS[@]}"
	m
	mi
	# Actually we don't need this, since this installation of libstdc++
	# is only for the purpose of building the target GMP and GCC.
	# mid
	popd
}

gmp_stage1() {
	prep gmp
	md build-cross
	c "${CROSS_DEF[@]}" --enable-cxx
	m
	mi
	mid
	popd
}

mpfr_stage1() {
	prep mpfr
	md build-cross
	c "${CROSS_DEF[@]}" --enable-thread-safe
	m
	mi
	mid
	popd
}

mpc_stage1() {
	prep mpc
	md build-cross
	c "${CROSS_DEF[@]}"
	m
	mi
	mid
	popd
}

if [ ! -d "$BLDPFX" ] ; then
	mkdir -pv $BLDPFX
fi

echo "-- Creating directories for sysroot ..." >&2
mkdir -pv "$SRCS"
mkdir -pv $_PFX/bin $_PFX/lib $_PFX/include
mkdir -pv $_SYSROOT/$_PFX/bin $_SYSROOT/$_PFX/lib $_SYSROOT/$_PFX/include
export PATH="$_PFX/bin:$PATH"

echo "-- Starting build ..." >&2
#: Stage 0 (Build cross compile toolchain)
binutils_stage0
gcc_stage0
linux_headers
# glibc and libstdc++ are installed to both /opt/32 and $_SYSROOT/opt/32.
glibc
libstdcxx
#: Stage 1 (Build native tools using the cross tools)
#: Note: binutils and GCC are not installed at this time.
#: They are installed to a separate sysroot at $_SYSROOT, and then the
#: entire /opt/32 on the host system can be replaced.
binutils_stage1
gmp_stage1
mpfr_stage1
mpc_stage1
gcc_stage1
#: We now have sufficient tools to build the optenv32 from scratch.
#: Remember to build zlib and zstd, and then rebuild these packages again!

echo "-- Sysroot is at $_SYSROOT." >&2
echo "   Cross toolchain is at /opt/32." >&2
echo "-- Now you can remove /opt/32 on the host system, and copy /opt/32 in $_SYSROOT to the host system." >&2
