# Design: this thing is intended to build two mksh binaries,
# mksh-full and mksh-static (with the latter being statically linked),
# and lksh, according to DEB_BUILD_OPTIONS set:
# - nocheck: do not run mksh's regression test suite
# - noopt: build mksh with -O0, for glibc builds only
# - nodbg: do not build with debugging info
# - nostrip: handled by dh_strip, so ignored here
# - parallel=n: currently ignored, maybe -flto=jobserver could use it
# - mksh-firstbuilt: do not run a simple test for operational functionality,
#   but use the first built binary, i.e. the glibc one (e.g. cross-builds)
#
# If we are cross-building, mksh-firstbuilt will be set.


echo '
┌─┤ Dear buildd maintainers, ├───────────────────────┐
│                                                    │
│ this package uses the Built-Using header as per    │
│ Debian Policy 3.9.4 and later. This means that,    │
│ while compilation will succeed even if your buildd │
│ chroot is not up to date, uploading may fail, and  │
│ you will get a REJECT mail. In this case, please   │
│ update your unstable buildd chroot to the latest   │
│ sid versions of packages, or, for infrastructure   │
│ (glibc, linux-libc-dev), versions that are known   │
│ to be held in the archive anyway, such as these    │
│ from stable, shortly after a release; later on,    │
│ up-to-date sid ones are really recommended.        │
│                                                    │
│ Also remember /dev/tty and /dev/ptmx are needed.   │
└────────────────────────────────────────────────────┘
'


# --- functions ---
buildok=0
normalise() {
	varname=$1
	eval varvalue=\$$varname
	newvalue=
	unset newvalue
	for tmpvalue in $varvalue; do
		newvalue=$newvalue${newvalue+ }$tmpvalue
	done
	eval $varname=\$newvalue
}
logbuildinfo() {
	where=$1
	{
		echo "Build information for $where mksh"
		fgrep 'set -A check_categories' builddir/$where/test.sh
		#echo "From: $startdate"
		$CC --version 2>&1 | grep ^gcc
		echo "Result: $buildinfo (broken<firstbuilt<checked<unattended<regressed)"
		if test x"$buildinfo" = x"regressed"; then
			echo "Regression test results:"
			echo "$resultest" | sed 's/^/| /'
		fi
		echo "Variables used:"
		for v in CC CFLAGS CPPFLAGS LDFLAGS LDSTATIC LIBS; do
			eval x=\$$v
			echo "| $v='$x'"
		done
		echo "Actual compilation options:"
		fgrep main builddir/$where/Rebuild.sh | sed 's/^/] /'
		echo "Resulting binary:"
		size builddir/$where/?ksh 2>&1 | sed 's/^/| /'
		#echo "Date: $(date -u)"
	} >builddir/buildinfo.$where
}
trybuild() {
	where=$1; shift
	normalise CC
	normalise CFLAGS
	normalise CPPFLAGS
	normalise LDFLAGS
	# do not normalise LDSTATIC
	normalise LIBS
	cd builddir/$where
	echo "Attempting compilation of mksh in $where with CC='$CC'"
	echo "CFLAGS='$CFLAGS' CPPFLAGS='$CPPFLAGS'"
	echo "LDFLAGS='$LDFLAGS' LDSTATIC='$LDSTATIC' LIBS='$LIBS'"

	buildok=0
	arg=-r
	testwrap=0
	if test x"$where" = x"legacy"; then
		arg="$arg -L"
		tfn=lksh
	else
		tfn=mksh
		if test 0 = "$iscross"; then
			testwrap=1
		fi
	fi
	buildinfo=broken
	gotbin=0
	startdate=$(date -u)
	set -x
	CC="$CC" CFLAGS="$CFLAGS" CPPFLAGS="$CPPFLAGS" LDFLAGS="$LDFLAGS" \
	    LDSTATIC="$LDSTATIC" LIBS="$LIBS" sh ../../Build.sh $arg && \
	    test -f $tfn && gotbin=1
	set +x
	if test $gotbin = 0; then
		echo "Build attempt failed."
		cd ../..
		return
	fi
	if test $testwrap = 1; then
		echo "Got a binary, doing run-time checks."
		set -- $(md5sum ../../debian/rtchecks)
		test bd51f320dcb3970f0119db72f7ae6be4 = "$1" || {
			echo "Testsuite corrupt."
			exit 2
		}
		if ./$tfn ../../debian/rtchecks >rtchecks.out; then
			set -- $(md5sum rtchecks.out)
			test d5345290b8f3a343f6446710007d4e5a = "$1" || {
				echo "Input:"
				cat ../../debian/rtchecks
				echo "Output:"
				cat rtchecks.out
				echo "Checks failed."
				cd ../..
				return
			}
		else
			echo "Output:"
			cat rtchecks.out
			echo "Checks returned an error."
			cd ../..
			return
		fi
	fi
	if test $firstbuilt = 1; then
		echo "Got a binary, using first built."
		buildinfo=firstbuilt
		cd ../..
		buildok=1
		logbuildinfo $where
		return
	fi
	echo "Running simple checks on the binary."
	perl ../../check.pl -s ../../debian/mtest.t -p ./$tfn -v 2>&1 | \
	    tee mtest.log
	if grep '^Total failed: 0$' mtest.log >/dev/null; then
		echo "Simple tests okay."
	else
		echo "Simple tests failed."
		cd ../..
		return
	fi
	buildinfo=checked
	if test $nocheck = 0; then
		echo "Running mksh regression test suite."
		regressed=regressed
		if test x"$(uname -s)" = x"GNU"; then
			# script(1) fails on Debian GNU/Hurd in pbuilder
			test -e ../../../attended || regressed=unattended
			if test -e ../../../attended; then
				./test.sh -v
			else
				./test.sh -v -C regress:no-ctty
			fi 2>&1 | tee utest.log
		elif test -z "$(which script 2>/dev/null)"; then
			regressed=unattended
			echo "WARNING: script(1) not found, results inconclusive."
			./test.sh -v -C regress:no-ctty 2>&1 | tee utest.log
		else
			echo "This needs /dev/tty and /dev/ptmx in the chroot."
			echo >test.wait
			script -qc './test.sh -v 2>&1 | tee utest.log; x=$?; rm -f test.wait; exit $x'
			maxwait=0
			while test -e test.wait; do
				sleep 1
				maxwait=$(expr $maxwait + 1)
				test $maxwait -lt 900 || break
			done
		fi
		if grep '^pass .*:KSH_VERSION' utest.log >/dev/null 2>&1; then
			echo "Regression test suite run."
		else
			echo "Regression tests apparently not run."
			echo "Failing this build."
			cd ../..
			return
		fi
		buildinfo=$regressed
		resultest=$(grep -a '^[FPT]' utest.log 2>&1 | fgrep -a -v \
		    -e 'Trying Perl interpreter' \
		    -e 'Testing mksh for conformance' \
		    -e 'This shell is actually')
	fi
	cd ../..
	buildok=1
	logbuildinfo $where
}

# --- main code ---

LC_ALL=C; export LC_ALL
topdir=$(pwd)
# get maximum of hardening flags
DEB_BUILD_MAINT_OPTIONS="hardening=+all"
export DEB_BUILD_MAINT_OPTIONS

# pull environment configuration
eval $(dpkg-architecture -s)

rm -rf builddir
mkdir builddir builddir/full builddir/legacy \
    builddir/static-glibc builddir/static-dietlibc builddir/static-klibc
cp debian/printf.c builddir/legacy/

# oh puh-leaze!
DEB_BUILD_OPTIONS_reproducible=
for v in $DEB_BUILD_OPTIONS; do
	case $v in
	(parallel=*)
		;;
	(*)
		DEB_BUILD_OPTIONS_reproducible="$DEB_BUILD_OPTIONS_reproducible $v"
		;;
	esac
done
DEB_BUILD_OPTIONS_reproducible=${DEB_BUILD_OPTIONS_reproducible# }

echo "Building the package 'mksh' on '$DEB_BUILD_ARCH' for '$DEB_HOST_ARCH'" \
    "with DEB_BUILD_OPTIONS '$DEB_BUILD_OPTIONS_reproducible'"
echo "Values (not used) from environment: CFLAGS='$CFLAGS' CPPFLAGS='$CPPFLAGS' LDFLAGS='$LDFLAGS'"

# parse options
nocheck=0
noopt=0
nodbg=0
firstbuilt=0
small=1

if test x"$DEB_HOST_ARCH" = x"$DEB_BUILD_ARCH"; then
	iscross=0
else
	iscross=1
	echo Using first built binary because we are cross-compiling
	echo "from $DEB_BUILD_GNU_TYPE to $DEB_HOST_GNU_TYPE here."
	firstbuilt=1
fi

for i in $DEB_BUILD_OPTIONS; do
	case $i in
	(nocheck) nocheck=1 ;;
	(noopt) noopt=1 ;;
	(nodbg) nodbg=1 ;;
	(mksh-firstbuilt) firstbuilt=1 ;;
	esac
done

if test $firstbuilt = 1; then
	lkshlibs=glibc
else
	lkshlibs='klibc dietlibc glibc'
fi

# set default values for CC, CFLAGS, CPPFLAGS, LDFLAGS
case $iscross$CC in
0) dCC=gcc ;;
1) dCC=${DEB_HOST_GNU_TYPE}-gcc ;;
*) dCC=$CC ;;
esac
echo "Using compiler: '$dCC'"
if test $noopt = 1; then
	x=-O0
else
	x=-O2
fi
test $nodbg = 1 || x="$x -g"
dCFLAGS=$(dpkg-buildflags --get CFLAGS 2>/dev/null) || dCFLAGS=$x
dCPPFLAGS=$(dpkg-buildflags --get CPPFLAGS 2>/dev/null)
dLDFLAGS=$(dpkg-buildflags --get LDFLAGS 2>/dev/null)
echo "Values from dpkg-buildflags: CFLAGS='$dCFLAGS' CPPFLAGS='$dCPPFLAGS' LDFLAGS='$dLDFLAGS'"
dCFLAGS="$dCFLAGS -Wall -Wextra"
dLDFLAGS="$dLDFLAGS -Wl,--as-needed"
# Prevent the build system from adding this, as we provide it already
HAVE_CAN_WALL=0; export HAVE_CAN_WALL
# avoid needing a Build-Conflicts on libbsd-dev
HAVE_LIBUTIL_H=0; export HAVE_LIBUTIL_H

# avr32 currently has no locales
if test x"$DEB_HOST_ARCH" = x"avr32"; then
	HAVE_SETLOCALE_CTYPE=0; export HAVE_SETLOCALE_CTYPE
fi

# create a locale if we are to run the testsuite
test x"$HAVE_SETLOCALE_CTYPE" != x"0" && if test $nocheck = 0; then
	echo Creating locale for the regression tests
	mkdir builddir/tloc
	# cf. Debian #522776
	localedef -i en_US -c -f UTF-8 builddir/tloc/en_US.UTF-8 || \
	    echo Warning: additional testsuite failures may be shown
	LOCPATH=$topdir/builddir/tloc; export LOCPATH
fi

# build mksh-full
CC=$dCC
CFLAGS=$dCFLAGS
CPPFLAGS=$dCPPFLAGS
LDFLAGS=$dLDFLAGS
unset LDSTATIC
LIBS=
echo Building mksh-full...
trybuild full
if test $buildok = 0; then
	echo Build of mksh-full failed.
	exit 1
fi

bupkgs=
# no locale support in mksh-static or mksh-legacy needed
HAVE_SETLOCALE_CTYPE=0; export HAVE_SETLOCALE_CTYPE

# build mksh-static
sCC=$dCC
sCFLAGS=
# drop optimisation, debugging and PIC flags for mksh-static
for x in $dCFLAGS; do
	case $x in
	(-O*|-g*|-fPIE) ;;
	(*) sCFLAGS="$sCFLAGS $x" ;;
	esac
done
sCPPFLAGS="$dCPPFLAGS -DMKSH_BINSHPOSIX -DMKSH_BINSHREDUCED"
sLDFLAGS=
for x in $dLDFLAGS; do
	case $x in
	(-pie|-fPIE) ;;
	(*) sLDFLAGS="$sLDFLAGS $x" ;;
	esac
done
sLIBS=

have_static=0
# try klibc and dietlibc first unless cross-compiling
test $firstbuilt = 1 || for x in x; do
	echo Building mksh-static with klibc
	buildok=0
	if test -z "$(which klcc 2>/dev/null)"; then
		echo ... skipping, klcc not found
		break
	fi
	CC=klcc
	CFLAGS="$sCFLAGS -fno-stack-protector -Os"
	test $nodbg = 1 || CFLAGS="$CFLAGS -g"
	CPPFLAGS="$sCPPFLAGS -DMKSH_SMALL -DMKSH_SMALL_BUT_FAST"
	LDFLAGS=$sLDFLAGS
	LDSTATIC="-static"
	LIBS=$sLIBS
	HAVE_CAN_FSTACKPROTECTORSTRONG=0; export HAVE_CAN_FSTACKPROTECTORSTRONG
	HAVE_CAN_FSTACKPROTECTORALL=0; export HAVE_CAN_FSTACKPROTECTORALL
	trybuild static-klibc
	unset HAVE_CAN_FSTACKPROTECTORSTRONG HAVE_CAN_FSTACKPROTECTORALL
	# Collect gcc and klibc packages for Built-Using
	if test $buildok = 1; then
		x=$(dpkg -S "$(readlink -f "$($CC -print-libgcc-file-name)")")
		bupkgs="$bupkgs ${x%%: *} libklibc-dev linux-libc-dev"
		have_static=1
	fi
done
test $firstbuilt = 1 || for x in x; do
	echo Building mksh-static with dietlibc
	buildok=0
	if test -z "$(which diet 2>/dev/null)"; then
		echo ... skipping, diet not found
		break
	fi
	CC="diet -v -Os $sCC"
	CFLAGS=$sCFLAGS
	test $nodbg = 1 || CFLAGS="$CFLAGS -g"
	CPPFLAGS="$sCPPFLAGS -DMKSH_SMALL -DMKSH_SMALL_BUT_FAST"
	LDFLAGS=$sLDFLAGS
	LDSTATIC=" "
	LIBS=$sLIBS
	HAVE_CAN_FSTACKPROTECTORSTRONG=0; export HAVE_CAN_FSTACKPROTECTORSTRONG
	HAVE_CAN_FSTACKPROTECTORALL=0; export HAVE_CAN_FSTACKPROTECTORALL
	trybuild static-dietlibc
	unset HAVE_CAN_FSTACKPROTECTORSTRONG HAVE_CAN_FSTACKPROTECTORALL
	# Collect gcc and dietlibc packages for Built-Using
	if test $buildok = 1; then
		x=$(dpkg -S "$(readlink -f "$($CC -print-libgcc-file-name)")")
		bupkgs="$bupkgs ${x%%: *} dietlibc-dev"
		have_static=1
	fi
done

# only try glibc if cross-compiling or both others fail
if test $have_static = 0; then
	echo Building mksh-static with glibc
	buildok=0
	CC=$sCC
	CFLAGS="-Os $sCFLAGS"
	test $nodbg = 1 || CFLAGS="$CFLAGS -g"
	CPPFLAGS="$sCPPFLAGS -DMKSH_NOPWNAM"
	LDFLAGS=$sLDFLAGS
	LDSTATIC="-static"
	LIBS=$sLIBS
	trybuild static-glibc
	# Collect gcc and glibc packages for Built-Using
	if test $buildok = 1; then
		x=$(dpkg -S "$(readlink -f "$($CC -print-file-name=libgcc.a)")")
		bupkgs="$bupkgs ${x%%: *}"
		x=$(dpkg -S "$(readlink -f "$($CC -print-file-name=libc.a)")")
		bupkgs="$bupkgs ${x%%: *}"
	fi
fi

# fail if neither klibc nor dietlibc nor glibc were successful
if test $buildok$have_static = 00; then
	echo Build of mksh-static failed.
	exit 1
fi

# build mksh-legacy
lCC=$dCC
lCFLAGS=$dCFLAGS
# Debian #499139
lCPPFLAGS="$dCPPFLAGS -DMKSH_BINSHPOSIX -DMKSH_BINSHREDUCED"
lLDFLAGS=$dLDFLAGS
lLIBS=
# Debian #532343, #539158
USE_PRINTF_BUILTIN=1; export USE_PRINTF_BUILTIN
buildok=0
for x in $lkshlibs; do echo Building mksh-legacy with $x; case $x in
(klibc)
	if test -z "$(which klcc 2>/dev/null)"; then
		echo ... skipping, klcc not found
		continue
	fi
	CC=klcc
	CFLAGS=
	for x in $lCFLAGS; do
		case $x in
		(-O*|-g*|-fPIE) ;;
		(*) CFLAGS="$CFLAGS $x" ;;
		esac
	done
	CFLAGS="$CFLAGS -fno-stack-protector -Os"
	test $nodbg = 1 || CFLAGS="$CFLAGS -g"
	CPPFLAGS="$lCPPFLAGS"
	LDFLAGS=
	for x in $lLDFLAGS; do
		case $x in
		(-pie|-fPIE) ;;
		(*) LDFLAGS="$LDFLAGS $x" ;;
		esac
	done
	LDSTATIC="-static"
	LIBS=$lLIBS
	HAVE_CAN_FSTACKPROTECTORSTRONG=0; export HAVE_CAN_FSTACKPROTECTORSTRONG
	HAVE_CAN_FSTACKPROTECTORALL=0; export HAVE_CAN_FSTACKPROTECTORALL
	trybuild legacy
	unset HAVE_CAN_FSTACKPROTECTORSTRONG HAVE_CAN_FSTACKPROTECTORALL
	# Collect gcc and klibc packages for Built-Using
	if test $buildok = 1; then
		x=$(dpkg -S "$(readlink -f "$($CC -print-libgcc-file-name)")")
		bupkgs="$bupkgs ${x%%: *} libklibc-dev linux-libc-dev"
	fi
	;;
(dietlibc)
	if test -z "$(which diet 2>/dev/null)"; then
		echo ... skipping, diet not found
		continue
	fi
	CC="diet -v -Os $lCC"
	CFLAGS=
	for x in $lCFLAGS; do
		case $x in
		(-O*|-g*|-fPIE) ;;
		(*) CFLAGS="$CFLAGS $x" ;;
		esac
	done
	# debug builds are too fragile here, IIRC
	CPPFLAGS=$lCPPFLAGS
	LDFLAGS=
	for x in $lLDFLAGS; do
		case $x in
		(-pie|-fPIE) ;;
		(*) LDFLAGS="$LDFLAGS $x" ;;
		esac
	done
	LDSTATIC=" "
	LIBS=$lLIBS
	echo "Note: all warning: dereferencing pointer 'p' does break" \
	    "strict-aliasing rules come from dietlibc, not mksh!"
	HAVE_CAN_FSTACKPROTECTORSTRONG=0; export HAVE_CAN_FSTACKPROTECTORSTRONG
	HAVE_CAN_FSTACKPROTECTORALL=0; export HAVE_CAN_FSTACKPROTECTORALL
	trybuild legacy
	unset HAVE_CAN_FSTACKPROTECTORSTRONG HAVE_CAN_FSTACKPROTECTORALL
	# Collect gcc and dietlibc packages for Built-Using
	if test $buildok = 1; then
		x=$(dpkg -S "$(readlink -f "$($CC -print-libgcc-file-name)")")
		bupkgs="$bupkgs ${x%%: *} dietlibc-dev"
	fi
	;;
(glibc)
	CC=$lCC
	CFLAGS=$lCFLAGS
	CPPFLAGS=$lCPPFLAGS
	LDFLAGS=$lLDFLAGS
	LDSTATIC=
	LIBS=$lLIBS
	trybuild legacy
	;;
esac; test $buildok = 0 || break; done
if test $buildok = 0; then
	echo Build of mksh-legacy failed.
	exit 1
fi

echo Logging build information...
for x in $bupkgs; do
	dpkg-query -Wf '${source:Package} (= ${source:Version})\n' "$x"
done | sort -u | {
	buspkgs=
	while IFS= read -r x; do
		test -n "$x" || continue
		test x"$x" = x" (= )" && continue
		echo "Built Using: $x"
		test -z "$buspkgs" || buspkgs="$buspkgs, "
		buspkgs=$buspkgs$x
	done
	echo "mksh:B-U=$buspkgs" >builddir/substvars
}
pkgvsn=$(dpkg-parsechangelog -n1 | sed -n '/^Version: */s///p')
{
	cat debian/README.Debian
	echo Build information for mksh R${pkgvsn%%@(-|wtf)*([!-])}:
	for v in DEB_BUILD_GNU_TYPE DEB_HOST_GNU_TYPE DEB_BUILD_OPTIONS_reproducible; do
		eval x=\$$v
		echo "| $v='$x'"
	done
	echo Dependencies:
	dpkg-query -W $(for wasn in ./usr/lib/libc.so ./usr/lib/*/libc.so \
	    linux-libc-dev locales .=diet .=klcc .=as .=ld .=strip; do
		case $wasn in
		(.=*) wasn=.$(which ${wasn#.=} 2>/dev/null) ;;
		esac
		case $wasn in
		(./*)
			for wasn in ${wasn#.}; do
				test -e $wasn && dpkg -S $wasn | sed \
				    -e '/^diversion by/d' -e 's/: .*$//' \
				    -e 's/, /,/g' -e 'y/,/\n/'
			done
			;;
		(.*)	;;
		(*)
			echo $wasn
			;;
		esac
	done | sort -u) | column -t | sed 's/^/| /'
	for x in full static-glibc static-dietlibc static-klibc legacy; do
		test -e builddir/buildinfo.$x || continue
		echo ----
		cat builddir/buildinfo.$x
	done
	echo ----
	echo Version: $pkgvsn
	#date -R
} | gzip -n9 >builddir/README.Debian.gz
echo All builds complete.
exit 0
