#!/bin/bash
# $Owl: Owl/build/buildworld.sh,v 1.54 2011/10/29 22:13:27 solar Exp $

NATIVE_DISTRIBUTION='Openwall GNU/*/Linux'
NATIVE_VENDOR='Openwall'

TIME=/usr/bin/time
test -x "$TIME" || TIME=

. buildworld.conf

export -n BRANCH PACKAGE
MAKEFLAGS=${MAKEFLAGS/PACKAGE=*}

PACKAGES=$BRANCH/packages

NATIVE=$HOME/native
SOURCES=$HOME/sources
FOREIGN=$HOME/foreign

RPMQ=rpm
RPMB=rpm

# Expand here %sparc, %alpha, etc. if you can build for it
ARCH_EXPAND='s/%ix86/i386 i486 i586 i686 pentium3 pentium4 athlon geode/g'

log()
{
	local MESSAGE

	MESSAGE="$1"

	echo "`date +%H:%M:%S`: $MESSAGE" | tee -a $HOME/logs/buildworld
}

spec()
{
	local PACKAGE DIR SPEC

	PACKAGE=$1

	DIR=$NATIVE/$PACKAGES/$PACKAGE

	SPEC=
	if [ -f $DIR/build.spec ]; then
		SPEC="`cat $DIR/build.spec`"
		test -n "$SPEC" && SPEC="$DIR/$SPEC"
	elif [ -f $DIR/$PACKAGE.spec ]; then
		SPEC=$DIR/$PACKAGE.spec
	fi

	if [ -n "$SPEC" ]; then
		ARCH_INC=`sed -nr "$ARCH_EXPAND; /^ExclusiveArch/Ip" "$SPEC"`
		if [ -n "$ARCH_INC" ]; then
			if ! echo "$ARCH_INC" | fgrep -q $ARCHITECTURE; then
				SPEC=
			fi
		fi
	fi

	if [ -n "$SPEC" ]; then
		ARCH_EXC=`sed -nr "$ARCH_EXPAND; /^ExcludeArch/Ip" "$SPEC"`
		if [ -n "$ARCH_EXC" ]; then
			if echo "$ARCH_EXC" | fgrep -q $ARCHITECTURE; then
				SPEC=
			fi
		fi
	fi

	echo "$SPEC"
}

binaries()
{
	local SPEC SOURCE TOTAL SUB

	SPEC=$1
	SOURCE=$2

	TOTAL="`grep -cE '^%files([[:space:]]|$)' $SPEC`"
	test $? -gt 1 && return 1
	SUB="`grep -cEf - $SPEC << EOF
^%files[[:space:]]+-n
^%files[[:space:]].*[[:space:]]-n
^%files[[:space:]]+[^[:space:]-]
^%files[[:space:]].*[[:space:]][^[:space:]-][^[:space:]]*[[:space:]]+[^[:space:]-]
EOF`"
	test $? -gt 1 && return 1

	if [ $TOTAL -gt $SUB ]; then
		$RPMQ -q --specfile $SPEC
	else
		$RPMQ -q --specfile $SPEC | grep -v "^${SOURCE}-[^-]*-[^-]*\$"
	fi
}

built()
{
	local SPEC SOURCE BINARY REGEX

	SPEC=$1
	SOURCE=$2

	binaries $SPEC $SOURCE |
	while read BINARY; do
		REGEX="^${BINARY}\.[^.]*\.rpm\$"
		if [ -z "`ls $HOME/RPMS/ | grep "$REGEX"`" ]; then
			cat > /dev/null
			return 1
		fi
	done
}

build_native()
{
	local NUMBER PACKAGE WORK NAME VERSION ARCHIVE FLAGS WITH_WITHOUT_TEST

	NUMBER=$1
	PACKAGE=$2

	WORK=$HOME/rpm-work-$NUMBER

	log "#$NUMBER: Building $PACKAGE"
	cd $WORK/SOURCES/ || exit 1
	if [ -f $NATIVE/$PACKAGES/$PACKAGE/build.archive ]; then
		while read NAME VERSION; do
			ARCHIVE=${NAME}-${VERSION}
			ln -sf $NATIVE/$PACKAGES/$PACKAGE/$NAME $ARCHIVE
			tar czhf $ARCHIVE.tar.gz \
				--exclude CVS \
				--owner=root --group=root --mode=go-rwx \
				$ARCHIVE
			if [ $? -eq 0 -a -d $HOME/archives ]; then
				mv -f $ARCHIVE.tar.gz $HOME/archives/
				ln -s $HOME/archives/$ARCHIVE.tar.gz .
			fi
			rm $ARCHIVE
		done < $NATIVE/$PACKAGES/$PACKAGE/build.archive
	fi
	ls $SOURCES/$PACKAGES/$PACKAGE/*.src.rpm 2>/dev/null | \
		xargs -n 1 -i sh -c 'rpm2cpio {} | cpio -i --quiet'
	ln -sf $SOURCES/$PACKAGES/$PACKAGE/* .
	rm -f '*' || :
	ln -sf $NATIVE/$PACKAGES/$PACKAGE/* .
	test -e $PACKAGE.spec || ln -s "`spec $PACKAGE`" $PACKAGE.spec
	if [ "$BUILDSOURCE" = yes ]; then
		FLAGS=-ba
	else
		FLAGS=-bb
	fi
	case "${RUN_TESTS-}" in
		yes) WITH_WITHOUT_TEST='--with test' ;;
		no) WITH_WITHOUT_TEST='--without test' ;;
		*) WITH_WITHOUT_TEST= ;;
	esac
	if $TIME $PERSONALITY $RPMB $FLAGS $PACKAGE.spec \
		$TARGET \
		--define "distribution $NATIVE_DISTRIBUTION" \
		--define "vendor $NATIVE_VENDOR" \
		--define "buildarch $BUILDARCH" \
		--define "buildhost $BUILDHOST" \
		--define "home $HOME" \
		--define "number $NUMBER" \
		$WITH_WITHOUT_TEST \
		&> $HOME/logs/$PACKAGE < /dev/null;
	then
		mv $WORK/RPMS/*/* $HOME/RPMS/
		test "$BUILDSOURCE" = yes && mv $WORK/SRPMS/* $HOME/SRPMS/
	else
		rm -rf $WORK/RPMS/*
		test "$BUILDSOURCE" = yes && rm -rf $WORK/SRPMS/*
		log "#$NUMBER: Failed $PACKAGE"
		touch $HOME/native-work/failed
	fi
	rm -rf $WORK/buildroot
	rm -rf $WORK/BUILD/*
	rm $WORK/SOURCES/*
	cd $HOME/native-work || exit 1
}

build_foreign()
{
	local NUMBER PACKAGE WORK

	NUMBER=$1
	PACKAGE=$2

	WORK=$HOME/rpm-work-$NUMBER

	log "#$NUMBER: Building $PACKAGE"
	if $TIME $PERSONALITY $RPMB --rebuild $FOREIGN/${PACKAGE}.src.rpm \
		$TARGET \
		--define "buildarch $BUILDARCH" \
		--define "buildhost $BUILDHOST" \
		--define "home $HOME" \
		--define "number $NUMBER" \
		--define "_unpackaged_files_terminate_build 0" \
		--define "_missing_doc_files_terminate_build 0" \
		&> $HOME/logs/$PACKAGE < /dev/null;
	then
		mv $WORK/RPMS/*/* $HOME/RPMS/
	else
		rm -rf $WORK/RPMS/*
		log "#$NUMBER: Failed $PACKAGE"
	fi
	rm -rf $WORK/BUILD/*
}

builder()
{
	local NUMBER SOURCE SPEC

	NUMBER=$1

	log "#$NUMBER: Scanning native"

	cd $HOME/native-work || exit 1

	ls $NATIVE/$PACKAGES/ | grep -v '^CVS$' |
	while read SOURCE; do
		SPEC="`spec $SOURCE`"
		test -n "$SPEC" || continue
		mkdir .${SOURCE} &> /dev/null || continue
		touch .${SOURCE}/$NUMBER
		if [ -n "$PACKAGE" ]; then
			if [ "$SOURCE" = "$PACKAGE" ]; then
				touch $HOME/rpm-work-$NUMBER/built
				build_native $NUMBER $SOURCE
			fi
		elif built $SPEC $SOURCE; then
			log "#$NUMBER: Skipping $SOURCE"
		else
			build_native $NUMBER $SOURCE
		fi
	done
	if [ -n "$PACKAGE" -a ! -e "$HOME/rpm-work-$NUMBER/built" ]; then
		log "#$NUMBER: Failed $PACKAGE (no such package)"
		touch $HOME/native-work/failed
	fi

	log "#$NUMBER: Scanning foreign"

	cd $HOME/foreign-work || exit 1

	test -d $FOREIGN &&
	ls $FOREIGN/ |
	while read SOURCE; do
		SOURCE=`basename $SOURCE .src.rpm`
		mkdir .${SOURCE} &> /dev/null || continue
		cd .${SOURCE} || exit 1
		touch $NUMBER
		if [ -n "$PACKAGE" ]; then
			cd $HOME/foreign-work || exit 1
			if [ "$SOURCE" = "$PACKAGE" ]; then
				build_foreign $NUMBER $SOURCE
			fi
			continue
		fi
		rpm2cpio $FOREIGN/${SOURCE}.src.rpm | cpio -i --quiet '*.spec'
		cd $HOME/foreign-work || exit 1
		SPEC=$HOME/foreign-work/.${SOURCE}/*.spec
		if built $SPEC $SOURCE; then
			log "#$NUMBER: Skipping $SOURCE"
		else
			build_foreign $NUMBER $SOURCE
		fi
	done

	cd $HOME || exit 1

	log "#$NUMBER: Removing temporary files"
	rm -rf rpm-work-$NUMBER

	log "#$NUMBER: Finished"
	exit 0
}

detect_arch()
{
	local MACHINE

	MACHINE=`uname -m` || MACHINE="unknown"

	case "$MACHINE" in
	*86)
		ARCHITECTURE=i686
		;;
	x86_64)
		ARCHITECTURE=x86_64
		;;
	sparc*)
		ARCHITECTURE=sparc
		;;
	alpha)
		ARCHITECTURE=alpha
		;;
	esac
}

detect_proc()
{
	case "$ARCHITECTURE" in
	sparc*)
		PROCESSORS="`sed -n \
			's/^ncpus active[[:space:]]\+: \([0-9]\+\)$/\1/p' \
			/proc/cpuinfo`"
		;;
	alpha*)
		PROCESSORS="`sed -n \
			's/^cpus detected[[:space:]]\+: \([0-9]\+\)$/\1/p' \
			/proc/cpuinfo`"
		;;
	*)
		PROCESSORS="`grep -cw ^processor /proc/cpuinfo`"
		;;
	esac

	test -n "$PROCESSORS" || PROCESSORS=1
	test "$PROCESSORS" -ge 1 || PROCESSORS=1
}

detect()
{
	test -n "$ARCHITECTURE" || detect_arch
	test -n "$PROCESSORS" || detect_proc
	if [ -n "$PACKAGE" ]; then
		test -n "$MAKEFLAGS" || MAKEFLAGS="-j $PROCESSORS"
		PROCESSORS=1
	fi
	test -n "$BUILDHOST" || BUILDHOST="`hostname -f`"
	test -n "$BUILDHOST" || BUILDHOST="localhost.localdomain"

	if [ -n "$ARCHITECTURE" ]; then
		TARGET="--target ${ARCHITECTURE}-unknown-linux"
		BUILDARCH="$ARCHITECTURE"
	else
		TARGET=
		BUILDARCH="%_arch"
	fi

	if [ -z "$PERSONALITY" ]; then
		case "$ARCHITECTURE" in
		sparc|sparcv9)
			PERSONALITY=sparc32
			;;
		*86)
			PERSONALITY=i386
			;;
		x86_64)
			PERSONALITY=x86_64
			;;
		*)
			PERSONALITY=
			;;
		esac
	fi

	PERSONALITY=$(type -P $PERSONALITY 2>/dev/null)
	test -x "$PERSONALITY" || PERSONALITY=
}

check_includes()
{
	test `find /usr/include/linux/ /usr/include/asm/ \
		-type f ! -perm +004 -print 2>&1 | wc -c` = '0'
}

sanity_check()
{
	log "Sanity check"

	check_includes || {
		log "Kernel headers are not world-readable - they should be"
		exit 1
	}

	test -r /usr/include/linux/version.h || {
		log "No /usr/include/linux/version.h (kernel never configured?)"
		exit 1
	}
}

clean_death()
{
	kill 0

	cd $HOME || exit 1
	rm -rf tmp-work rpm-work-[1-9]* native-work foreign-work

	echo "`date '+%Y %b %e %H:%M:%S'`: Interrupted" >> logs/buildworld
	exit 1
}

setup_rpm()
{
	local RPM_VERSION

	log "Detecting version of RPM"
	RPM_VERSION="`rpm --version | cut -f3 -d' '`"
	if [ -z "$RPM_VERSION" ]; then
		log "Cannot find RPM version, perhaps 'rpm' is not in PATH?"
		exit 1
	fi

	case "$RPM_VERSION" in
	4.*)
		log "It's RPM4, using different binaries for different tasks"
		RPMQ=rpmquery
		RPMB=rpmbuild
		;;
	3.*)
		log "It's RPM3, using 'rpm' for everything"
		;;
	*)
		log "Unknown version of RPM, using defaults"
		;;
	esac
}

if [ "`id -u`" = "0" -o ! -O $HOME ]; then
	echo >&2 "Run this as the owner of $HOME (typically, as user \"build\")"
	exit 1
fi

unset LANG LANGUAGE LINGUAS
unset LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_TIME

umask $UMASK
cd $HOME || exit 1

mkdir -p logs archives RPMS
test "$BUILDSOURCE" = yes && mkdir -p SRPMS

echo "`date '+%Y %b %e %H:%M:%S'`: Started" >> logs/buildworld

setup_rpm

log "Removing stale temporary files"
rm -rf tmp-work rpm-work-[1-9]* native-work foreign-work

trap clean_death HUP INT TERM

detect
sanity_check

NUMBER=1
while [ $NUMBER -le $PROCESSORS ]; do
	mkdir -p rpm-work-$NUMBER/{BUILD,SOURCES,SPECS,SRPMS}
	mkdir -p rpm-work-$NUMBER/RPMS/{noarch,i386,i686,x86_64,sparc,sparcv9,alpha}
	NUMBER=$[$NUMBER + 1]
done

mkdir tmp-work native-work foreign-work || exit 1

export TMPDIR=$HOME/tmp-work
export TMP=$HOME/tmp-work

NUMBER=1
while [ $NUMBER -le $PROCESSORS ]; do
	builder $NUMBER &
	NUMBER=$[$NUMBER + 1]
done

cd $HOME || exit 1

wait

STATUS=0
if [ -e $HOME/native-work/failed ]; then
	STATUS=1
	log "Some packages failed to build"
fi

log "Removing temporary files"
rm -rf tmp-work native-work foreign-work

echo "`date '+%Y %b %e %H:%M:%S'`: Finished" >> logs/buildworld
exit $STATUS
