#!/bin/sh
# Tazbb - SliTaz Build Bot.
# System wide config file: /etc/slitaz/tazbb.conf
#
# Tazbb is a tool to automate package building, it can be run manually
# or via a cron job. On SliTaz build host, tazbb is run in a chroot env.
#
# (c) 2009 SliTaz GNU/Linux project - GNU gpl v3
#

# Include config file or exit if no file found.
if [ -f "./tazbb.conf" ]; then
	. ./tazbb.conf
elif [ -f "/etc/slitaz/tazbb.conf" ]; then
	. /etc/slitaz/tazbb.conf
else
	echo -e "\nNo config file found: tazbb.conf...\n" && exit 0
fi
LOG_SUFFIX=""
case "$HG_WOK" in
*stable) LOG_SUFFIX="&stable=1";;
esac

# Tazbb is only for root.
if test $(id -u) != 0 ; then
   echo -e "\nYou must be root to run: `basename $0`.\n" && exit 0
fi

# Let tazbb finish is work and make sure needed files exist.
if [ -f $LOCK_FILE ]; then
	case $1 in
		usage|list-*|*block|check-receipt)
			continue ;;
		*)
			echo -e "\nTazbb is already running and locked...\n"
			exit 0 ;;
	esac
else
	mkdir -p $DB_DIR $LOG_DIR
	touch $LOCK_FILE $DB_DIR/blocked
fi

# Set KERNEL variable
if [ -s $BUILD_WOK/linux/receipt ]; then
	. $BUILD_WOK/linux/receipt
	KERNEL=$VERSION
fi

# Get revision
cd $HG_WOK
NEW_REV=`hg head --template '{rev}\n'`
cd - > /dev/null

usage()
{
	echo -e "\nSliTaz developers and build host tool\n
\033[1mUsage: \033[0m `basename $0` [command] [--option]
\033[1mCommands: \033[0m\n
  usage           Print this short usage and command list.
  list-pkgs       List last cooked packages with date.
  report          Run in report mode and dont cook anything [--verbose].
  cook            Cook, install and log a single package build.
  cook-all        Cook all missing, modified or unbuilt packages.
  cook-commit     Cook all packages affected by a commit in the last update.
  test-pkgs       Execute a test suite on all packages [--verbose].
  [un]block       Block or unblock a package to skip or enable building.
  mail            Send mail to package maintainer with tazbbmail.
  check-depends   Verify DEPENDS value with library needs [--verbose].
  clean-up        Remove old packages [--verbose|--dry-run].
  clean-log       Remove all generated build log files.\n"
}

status()
{
	local CHECK=$?
	echo -en "\033[70G"
	if [ $CHECK = 0 ]; then
		echo "Done"
	else
		echo "Failed"
	fi
	return $CHECK
}

top_summary()
{
	cat > $DB_DIR/summary << _EOT_
Update   : `date`
Revision : $NEW_REV (<a href="$HG_URL/log/$NEW_REV">changelog</a>)
_EOT_
}

packages_summary()
{
	if ! grep -q "^Packages" $DB_DIR/summary; then
		cat >> $DB_DIR/summary << _EOT_
Packages : `ls $BUILD_WOK | wc -l` in the wok, `cat $DB_DIR/cooklist | wc -l` to cook, \
`cat $DB_DIR/genpkglist | wc -l` to repack, `cat $DB_DIR/blocked | wc -l` blocked, \
`cat $DB_DIR/corrupted | wc -l` corrupted
_EOT_
	fi
}

VERBOSE=""

packages_summary_update()
{
	sed -i s/"[0-9]* in the wok"/"`ls $BUILD_WOK | wc -l` in the wok"/ \
		$DB_DIR/summary
	sed -i s/"[0-9]* to cook"/"`cat $DB_DIR/cooklist | wc -l` to cook"/ \
		$DB_DIR/summary
	sed -i s/"[0-9]* to repack"/"`cat $DB_DIR/genpkglist | wc -l` to repack"/ \
		$DB_DIR/summary
	sed -i s/"[0-9]* blocked"/"`cat $DB_DIR/blocked | wc -l` blocked"/ \
		$DB_DIR/summary
	sed -i s/"[0-9]* corrupted"/"`cat $DB_DIR/corrupted | wc -l` corrupted"/ \
		$DB_DIR/summary
}

list_packages()
{
	cd $PACKAGES_REPOSITORY
	ls -1t *.tazpkg | head -20 | \
	while read file
	do
		echo -n $(stat -c '%y' $PACKAGES_REPOSITORY/$file | cut -d. -f1)
		echo "   $file"
	done
}

show_report()
{
	echo "Cooklist"
	echo "================================================================================"
	cat $DB_DIR/cooklist && echo ""
	echo "Genpkglist"
	echo "================================================================================"
	cat $DB_DIR/genpkglist && echo ""
	echo "Packlist"
	echo "================================================================================"
	cat $DB_DIR/packlist && echo ""
	echo "Blocked"
	echo "================================================================================"
	cat $DB_DIR/blocked && echo ""
	echo ""
}

# URL encoding
escape()
{
	echo $1 | sed -e 's/+/%2B/g' -e 's|/|%2F|g' -e 's/:/%3A/g'
}

update_wok()
{
	local forced
	forced=""
	echo ""
	echo "(updating flavors)" > $DB_DIR/running
	cd $HG_FLAVORS
	LAST_REV=`hg head --template '{rev}\n'`
	hg pull && hg update
	NEW_REV=`hg head --template '{rev}\n'`
	if [ "$NEW_REV" != "$LAST_REV" ]; then
		size=`du -sh $HG_FLAVORS | awk '{ print $1 }'`
		echo -n "Copying Hg flavors to the build flavors ($size)... "
		cp -a $HG_FLAVORS/* $BUILD_FLAVORS
		cp -a $HG_FLAVORS/.hg $BUILD_FLAVORS
		echo -e "Done\n"
		forced="yes"
	fi
	echo "(updating wok)" > $DB_DIR/running
	cd $HG_WOK
	LAST_REV=`hg head --template '{rev}\n'`
	hg pull && hg update
	NEW_REV=`hg head --template '{rev}\n'`
	# Gen a new summary and link last revision for the web interface.
	echo -e "\nHg wok    : $HG_WOK ($NEW_REV)"
	echo -e "Build wok : $BUILD_WOK ($LAST_REV)\n"
	top_summary
	# Copy Hg wok if new revision or exit to stop process since nothing
	# have change (--forced can be used).
	if [ "$NEW_REV" != "$LAST_REV" ]; then
		size=`du -sh $HG_WOK | awk '{ print $1 }'`
		echo -n "Copying Hg wok to the build wok ($size)... "
		#rsync -r -n -t $HG_WOK/ $BUILD_WOK/
		cp -a $HG_WOK/* $BUILD_WOK
		cp -a $HG_WOK/.hg $BUILD_WOK
		echo -e "Done\n"
	else
		if [ "$1" = "cook-all" ] || [ "$1" = "cook-commit" ]; then
			if [ "$2" != "--forced" -a -z "$forced" ]; then
				echo -e "Nothing to cook...\n"
				packages_summary
				rm -f $LOCK_FILE && exit 0
			fi
		fi
	fi
}

# Running 'tazbb report' should not pack anything and --verbose option
# can be used to display more messages.
check_flavors()
{
	# Clean up last results.
	rm -f $DB_DIR/packlist && touch $DB_DIR/packlist
	echo ""
	echo "Checking all files in: $HG_FLAVORS"
	echo "================================================================================"
	echo "(checking flavors)" > $DB_DIR/running
	for flavor in $(cd $HG_FLAVORS ; ls)
	do
		[ "$2" = "--verbose" ] && echo "Flavor : $flavor"
		if [ ! -s $PACKAGES_REPOSITORY/$flavor.flavor ]; then
			echo $flavor >> $DB_DIR/packlist
			[ "$1" = "report" ] && echo "Missing : $flavor"
			echo "Missing flavor : $flavor" >> $DB_DIR/report
			continue
		fi
		for i in $(find $HG_FLAVORS/$flavor -type f); do
			[ $PACKAGES_REPOSITORY/$flavor.flavor -nt \
			  $i ] && continue
			echo $flavor >> $DB_DIR/packlist
			[ "$1" = "report" ] && echo "Refresh : $flavor for $i"
			echo "Refresh flavor : $flavor" >> $DB_DIR/report
			continue 2
		done
		[ -s $HG_FLAVORS/$flavor/packages.list ] &&
		for i in $(cat $HG_FLAVORS/$flavor/packages.list); do
			if [ ! -d $BUILD_WOK/$i ]; then
				[ "$1" = "report" ] && 
				echo "Fix flavor for $i: $flavor"
				echo "Fix flavor for $i: $flavor" >> $DB_DIR/report
				continue
			fi
			[ $PACKAGES_REPOSITORY/$flavor.flavor -nt \
			  $BUILD_WOK/$i/taz ] && continue
			echo $flavor >> $DB_DIR/packlist
			[ "$1" = "report" ] && echo "Repack : $flavor for $i"
			echo "Repack flavor : $flavor" >> $DB_DIR/report
			continue 2
		done
	done

	# Check for meta flavors
	for flavor in $(cd $HG_FLAVORS ; ls)
	do
		grep -q ^ROOTFS_SELECTION $HG_FLAVORS/$flavor/receipt || continue
		. $HG_FLAVORS/$flavor/receipt
		set -- $ROOTFS_SELECTION
		if [ $PACKAGES_REPOSITORY/$2.flavor -nt \
		     $PACKAGES_REPOSITORY/$flavor.flavor ]; then
			echo $flavor >> $DB_DIR/packlist
			[ "$1" = "report" ] && echo "Refresh : $flavor for $2"
			echo "Refresh meta flavor : $flavor" >> $DB_DIR/report
			continue
		fi
		if grep -q ^$2$ $DB_DIR/packlist ; then
			echo $flavor >> $DB_DIR/packlist
			[ "$1" = "report" ] && echo "Repack : $flavor for $2"
			echo "Repack meta flavor : $flavor" >> $DB_DIR/report
			continue
		fi
	done
}

# Here we pack all flavors found in the packlist.
pack_flavors()
{
	[ -s $DB_DIR/packlist ] || return
	[ $PACKAGES_REPOSITORY/packages.list -nt /var/lib/tazpkg/packages.list ] &&
	cp -a $PACKAGES_REPOSITORY/packages.list /var/lib/tazpkg/packages.list
	cd $PACKAGES_REPOSITORY
	for flavor in $(cat $DB_DIR/packlist)
	do
		tazlito pack-flavor $flavor
		# Remove flavor from the packlist and empty lines for HTML <pre>.
		sed -i /"^$flavor$"/d $DB_DIR/packlist
		sed -i '/^$/d' $DB_DIR/packlist
	done
	cd - > /dev/null
}

# Running 'tazbb report' should not cook anything and --verbose option
# can be used to display more messages.
check_wok()
{
	# Clean up last results.
	rm -f $DB_DIR/cooklist && touch $DB_DIR/cooklist
	rm -f $DB_DIR/genpkglist && touch $DB_DIR/genpkglist
	rm -f $DB_DIR/report && touch $DB_DIR/report
	rm -f $DB_DIR/unbuilt && touch $DB_DIR/unbuilt
	echo "Checking all files in: $HG_WOK"
	echo "================================================================================"
	echo "(checking wok)" > $DB_DIR/running
	TOOLCHAIN="$(. $HG_WOK/slitaz-toolchain/receipt ; echo $DEPENDS)"
	TOOLCHAIN="$TOOLCHAIN glibc linux" # break cook loop
	for pkg in $HG_WOK/*
	do
		EXTRAVERSION=""
		WANTED=""
		BUILD_DEPENDS=""
		[ -s $pkg/receipt ] || continue
		. $pkg/receipt
		[ "$2" = "--verbose" ] && echo "Package : $PACKAGE"
		# Skip blocked packages.
		if grep -qs "^$PACKAGE$" $DB_DIR/blocked; then
			echo "Blocked : $PACKAGE ($VERSION)" && continue
		fi

		# Some packages may compute VERSION at cook time (bristuff)
		if grep -q ^get_version $pkg/receipt; then
			. $BUILD_WOK/$PACKAGE/taz/*/receipt
		fi

		# First check if package exit. Package naming _must_ be in the form of:
		# $PACKAGE-$VERSION or $PACKAGE-${VERSION}$EXTRAVERSION (Kernel string).
		if [ ! -f $PACKAGES_REPOSITORY/$PACKAGE-$VERSION.tazpkg ]; then
			[ -z "$EXTRAVERSION" ] && EXTRAVERSION="_$KERNEL"
			if [ ! -f $PACKAGES_REPOSITORY/$PACKAGE-${VERSION}$EXTRAVERSION.tazpkg ]; then
				[ "$1" = "report" ] && echo "Missing : $PACKAGE ($VERSION)"
				echo "Missing : $PACKAGE ($VERSION)" >> $DB_DIR/report
				echo "$PACKAGE" >> $DB_DIR/cooklist
			fi
		else
			# Check if package is up-to-date.
			PKG_YEAR=`date -u -r $PACKAGES_REPOSITORY/$PACKAGE-${VERSION}${EXTRAVERSION}.tazpkg '+%Y'`
			PKG_DATE=`date -u -r $PACKAGES_REPOSITORY/$PACKAGE-${VERSION}${EXTRAVERSION}.tazpkg '+%m%d%H%M'`
			for file in `find $pkg -type f`
			do
				FILE_YEAR=`date -u -r $file '+%Y'`
				FILE_DATE=`date -u -r $file '+%m%d%H%M'`
				[ "$2" = "--verbose" ] && echo " -> Checking: $file"
				if [ "$FILE_YEAR" -ge "$PKG_YEAR" -a "$FILE_DATE" -gt "$PKG_DATE" ] && ! grep -q $PACKAGE $DB_DIR/cooklist && ! grep -q $PACKAGE $DB_DIR/genpkglist; then
					[ "$1" = "report" ] && echo "Refresh : $PACKAGE ($VERSION)"
					echo "Refresh : $PACKAGE ($VERSION)" >> $DB_DIR/report
					echo "$PACKAGE" >> $DB_DIR/cooklist
				fi
			done
		fi
		
		# Desactivate this feature because it create infinite cooking loops.
		#if ! grep -q $PACKAGE $DB_DIR/cooklist; then
		#	case " $TOOLCHAIN " in
		#	*\ $PACKAGE\ *) continue;;
		#	esac
		#	case "$PACKAGE" in
		#	tazbb|tazwok|tazpkg) continue;;
		#	esac
		#	for dep in $BUILD_DEPENDS $TOOLCHAIN ; do
		#		[ $BUILD_WOK/$PACKAGE/taz -nt $BUILD_WOK/$dep/taz ] && continue
		#		[ "$1" = "report" ] && echo "Refresh : $PACKAGE (older than $dep)"
		#		echo "Refresh : $PACKAGE (older than $dep)" >> $DB_DIR/report
		#		echo "$PACKAGE" >> $DB_DIR/cooklist
		#		break
		#	done
		#fi
		# Rebuild unbuilt packages list with link to log file. This list
		# is also generated by cook_inslall to have real time stats.
		if [ ! -d $BUILD_WOK/$PACKAGE/taz ]; then
			echo "<a href=\"log.php?package=$(escape $PACKAGE)$LOG_SUFFIX\">$PACKAGE</a>" \
				>> $DB_DIR/unbuilt
		fi
	done
	packages_summary
}

# Create a new cooklist and summary (dont modify report) so 'tazbb cook-commit'
# can cook last changes.
check_commit()
{
	echo "(checking commit)" > $DB_DIR/running
	cd $HG_WOK
	# Clean up last results.
	rm -f $DB_DIR/cooklist && touch $DB_DIR/cooklist
	# Get the name of modified packages by the revision range. +1 last
	# commit was build by the previous build.
	LAST_REV=$(($LAST_REV+1))
	echo -e "Will cook from revision $LAST_REV to $NEW_REV\n"
	for rev in $(seq $LAST_REV $NEW_REV); do
		hg_log=$(hg log --rev=$rev --template "{files}\t{desc}\n")
		for file in $(echo "$hg_log" | cut -f 1); do
			pkg=`echo $file | cut -d "/" -f 1`
			if [ "$(echo $hg_log | grep -i '+build')" ]; then
				if ! grep -q ^$pkg$ $DB_DIR/genpkglist && ! grep -q ^$pkg$ $DB_DIR/cooklist; then
					if [ -f $pkg/receipt ]; then
						. $pkg/receipt
						echo "Commit  : $PACKAGE ($VERSION) - genpkg only" >> $DB_DIR/report
						echo "$PACKAGE" >> $DB_DIR/genpkglist
					fi
				fi
			else
				if ! grep -q ^$pkg$ $DB_DIR/cooklist; then
					if [ -f $pkg/receipt ]; then
						. $pkg/receipt
						echo "Commit  : $PACKAGE ($VERSION)" >> $DB_DIR/report
						sed "/^$PACKAGE$/d" -i $DB_DIR/genpkglist
						echo "$PACKAGE" >> $DB_DIR/cooklist
					fi
				fi
			fi
		done
	done
	packages_summary
}

# Genpkg : don't regen the package if source _pkg is missing, this
# function re-generate the package only if all is fine, else it
# does nothing and forget the package.
genpkg_package()
{
	EXTRAVERSION=""
	DEPENDS=""
	BUILD_DEPENDS=""
	SOURCE=""
	WANTED=""
	echo "(Repack <a href=\"log.php?package=$(escape $pkg)$LOG_SUFFIX\">$pkg</a>)" > $DB_DIR/running
	tazwok check-receipt $pkg && echo 'N' | tazwok genpkg $pkg
}

# Cook one package
cook_package()
{
	EXTRAVERSION=""
	DEPENDS=""
	BUILD_DEPENDS=""
	SOURCE=""
	WANTED=""
	echo "(cooking <a href=\"log.php?package=$(escape $pkg)$LOG_SUFFIX\">$pkg</a>)" > $DB_DIR/running
	tazwok clean $pkg
	script -c "tazbb check-receipt $pkg && echo 'install' | tazwok cook $pkg" $LOG_DIR/$pkg.log
	# Install new package (important for new shared libs). Note
	# that tests are done separatly with 'test_packages' and should
	# be done by tazwok.
	if [ -f $BUILD_WOK/$pkg/taz/*/receipt ]; then
		TAZBB_NO_INSTALL=""
		. $BUILD_WOK/$pkg/taz/*/receipt
		[ -n "$TAZBB_NO_INSTALL" ] && return 0
		echo "(installing $PACKAGE-${VERSION}$EXTRAVERSION.tazpkg)" \
			> $DB_DIR/running
		script -ac "yes | tazpkg install \
$PACKAGES_REPOSITORY/$PACKAGE-${VERSION}$EXTRAVERSION.tazpkg --forced" \
			$LOG_DIR/$pkg.log
		return 0
	fi
	return 1
}

# Sort list according WANTED and BUILD_DEPENDS
sort_cook_list()
{
	sort | while read pkg; do
		echo -n "$pkg"
		WANTED=""
		BUILD_DEPENDS=""
		. $BUILD_WOK/$pkg/receipt
		MISSING=""
		for i in $WANTED $BUILD_DEPENDS ; do
			# Verify that the dependancy exists and is older
			[ -f $BUILD_WOK/$i/taz/*/receipt ] &&
			[ $BUILD_WOK/$pkg/taz/*/receipt \
			  -nt $BUILD_WOK/$i/taz/*/receipt ] && continue
			WANTED=""
			[ -f $BUILD_WOK/$i/receipt ] &&
			. $BUILD_WOK/$i/receipt
			# This dependancy may be cooked 
			# by another package
			[ -n "$WANTED" ] && i=$WANTED
			case " $MISSING " in
			*\ $i\ *);;
			*) echo -n " $i";;
			esac
			MISSING="$MISSING $i"
		done
		echo ""
	done | awk '{ deps[$1] = $0; }
END {
	k=0;
	while (1) {
		skipped = 0;
		done = 0;
		for (entry in deps) {
			for (i = split(deps[entry], pkg, " "); i > 1; i--)
				if (deps[pkg[i]] != "") break;
			if (i == 1) {
				cook[k++] = pkg[1];
				deps[pkg[1]] = "";
				done++;
			}
			else if (i > 1) skipped++;
		}
		if (skipped == 0) break;
		if (done == 0) { # cross deps !!
			for (entry in deps) {
				if (split(deps[entry], pkg, " ") > 1)
					print pkg[1];
			}
			break;
		}
	}
	while (k > 0) print cook[--k];
}
'
}

# Here we cook all packages found in the cooklist.
cook_install()
{
	# First repack packages in genpkglist
	for pkg in `cat $DB_DIR/genpkglist`; do
		genpkg_package $pkg
		
		# Remove package from the genpkglist and empty lines for HTML <pre>.
		sed -i /"^$pkg$"/d $DB_DIR/genpkglist
		sed -i '/^$/d' $DB_DIR/genpkglist
		packages_summary_update
	done		
	
	echo "" > $DB_DIR/unbuilt
	for pkg in `cat $DB_DIR/cooklist | sort_cook_list`
	do
		if ! cook_package $pkg; then
			# Link to build log.
			echo "<a href=\"log.php?package=$(escape $pkg)$LOG_SUFFIX\">$pkg</a>" >> \
				$DB_DIR/unbuilt
		fi
		missing_depends="$(check_depends_pkg $pkg)"
		if [ -n "$missing_depends" ]; then
			cat >> $LOG_DIR/$pkg.log <<EOT

Update $pkg receipt for DEPENDS :
The package $pkg depends on packages $missing_depends

EOT
			# Unbuild package
			rm -rf $BUILD_WOK/$pkg/taz
			# Link to build log.
			echo "<a href=\"log.php?package=$(escape $pkg)$LOG_SUFFIX\">$pkg</a>" >> \
				$DB_DIR/unbuilt
		fi
		# Remove package from the cooklist and empty lines for HTML <pre>.
		sed -i /"^$pkg$"/d $DB_DIR/cooklist
		sed -i '/^$/d' $DB_DIR/cooklist
		packages_summary_update
	done
}

# Check for misc variables
check_variables()
{
	PACKAGE=""
	VERSION=""
	EXTRAVERSION=""
	CATEGORY=""
	SHORT_DESC=""
	MAINTAINER=""
	WEB_SITE=""
	PACKED_SIZE=""
	UNPACKED_SIZE=""
	. $BUILD_WOK/$1/receipt
	if [ "$PACKAGE" != "$1" ]; then
		echo "The PACKAGE variable should be $1"
		return 1
	fi
	if [ -z "$VERSION" ]; then
		echo "No VERSION in $1"
		return 1
	fi
	if [ -z "$SHORT_DESC" ]; then
		echo "No SHORT_DESC in $1"
		return 1
	fi
	case "$MAINTAINER" in
	'')	echo "No MAINTAINER in $1"
		return 1 ;;
	*\<*|*\>*)
		echo "Invalid MAINTAINER in $1"
		return 1 ;;
	*@*)	;;
	*)	echo "No email address for MAINTAINER in $1"
		return 1 ;;
	esac
	if [ -z "$WEB_SITE" ]; then
		echo "No WEB_SITE in $1"
		return 1
	fi
	if [ -n "$EXTRAVERSION" ]; then
		echo "Hardcoded EXTRAVERSION in $1"
		return 1
	fi
	if [ -n "$PACKED_SIZE" ]; then
		echo "Hardcoded PACKED_SIZE in $1"
		return 1
	fi
	if [ -n "$UNPACKED_SIZE" ]; then
		echo "Hardcoded UNPACKED_SIZE in $1"
		return 1
	fi
	case " base-system x-window utilities network graphics multimedia \
               office development system-tools security games misc meta \
               non-free " in
	*\ $CATEGORY\ *);;
	*) 	echo "Invalid CATEGORY in $1 : $CATEGORY"
		return 1;
	esac
	return 0
}

# Check for WANTED version
check_wanted_version()
{
	WANTED=""
	. $BUILD_WOK/$1/receipt
	if [ -n "$WANTED" ]; then
		expected=$VERSION
		VERSION=
		. $BUILD_WOK/$WANTED/receipt
		if [ "$VERSION" != "$expected" ]; then
			echo "$1: expected wanted version $expected, found $VERSION"
			return 1
		fi
	fi
	return 0
}

# Check for loop in BUILD_DEPENDS/WANTED
check_build_depends()
{
	local i
	BUILD_DEPENDS=""
	WANTED=""
	. $BUILD_WOK/$1/receipt
	for i in $BUILD_DEPENDS $WANTED ; do
		case " $2 " in
		*\ $i\ *)	echo "Loop in BUILD_DEPENDS/WANTED chain $2 $i"
				return 1 ;;
		*)		check_build_depends $i "$2 $1" || return 1 ;;
		esac
	done
	return 0
}

# Build depends_to_skip list with packages to remove from depends_to_add list
# These packages are already present in depends_to_add trees
scan_depends_to_skip()
{
	local i
	case " $depends_to_skip " in
	*\ $1\ *) return;;
	esac
	[ -d $BUILD_WOK/$1 ] || return
	DEPENDS=""
	. $BUILD_WOK/$1/receipt
	for i in $DEPENDS ; do
		case " $depends_to_add " in
		*\ $i\ *) depends_to_skip="$depends_to_skip $i";;
		esac
	done
	for i in $DEPENDS ; do
		scan_depends_to_skip $i
	done
}

# Reduce depends list by scanning nested depends
show_missing_depends()
{
	local i
	depends_to_add=""
	depends_to_skip="$2"
	for i in $1 ; do
		case " $depends_to_add " in
		*\ $i\ *) continue;;
		esac
		depends_to_add="$depends_to_add$i "
	done
	for i in $depends_to_add ; do
		scan_depends_to_skip $i
	done
	for i in $depends_to_add ; do
		case " $depends_to_skip " in
		*\ $i\ *) continue;;
		esac
		echo -n "$i "
	done
}

# Build all_depends variable
scan_dep()
{
	local i
	all_depends="$all_depends$PACKAGE "
	for i in $DEPENDS $SUGGESTED ; do
		case " $all_depends " in
		*\ $i\ *) continue;;
		esac
		[ -d $BUILD_WOK/$i ] || {
			all_depends="$all_depends$i "
			continue
		}
		DEPENDS=""
		SUGGESTED=""
		. $BUILD_WOK/$i/receipt
		scan_dep
	done
}

# Check for ELF file
is_elf()
{
	[ "$(dd if=$1 bs=1 skip=1 count=3 2> /dev/null)" = "ELF" ]
}

# Print shared library dependencies
ldd()
{
	LD_PRELOAD="" LD_TRACE_LOADED_OBJECTS=1 /lib/ld*.so $1 2> /dev/null
}

# scan a file for shared libraries and display according package names
check_depends_file()
{
	file=$1
	is_elf $file || continue
	case "$file" in
	*.o|*.ko|*.ko.gz) continue;;
	esac
	[ -s /tmp/files.list.tazbb$$ ] ||
	unlzma -c $PACKAGES_REPOSITORY/files.list.lzma >/tmp/files.list.tazbb$$
	ldd $file | while read lib rem; do
		case "$lib" in
		statically|linux-gate.so*|ld-*.so|*/ld-*.so)
			continue;;
		esac
		for dep in $(grep $lib /tmp/files.list.tazbb$$ | cut -d: -f1); do
			case " $all_depends " in
			*\ $dep\ *) continue 2;;
			esac
			for vdep in $(grep $dep $PACKAGES_REPOSITORY/packages.equiv | cut -d= -f1); do
				case " $all_depends " in
				*\ $vdep\ *) continue 3;;
				esac
			done
		done
		[ -n "$dep" ] || dep="UNKNOWN"
		all_depends="$all_depends $dep"
		if [ -n "$VERBOSE" ]; then
			echo "${file#*fs} depends on package $dep for the shared library $lib" 1>&2
		fi
		echo -n "$dep "
	done
}

DEFAULT_DEPENDS="glibc-base"

# scan a package for shared libraries and display missing package in DEPENDS
check_depends_pkg()
{
	pkg=$1
	echo "(checking depends for $pkg)" > $DB_DIR/running
	tmp=/tmp/tazbb$$
	mkdir $tmp
	package=$(basename $pkg)
	if ! cd ${package%%-*}*/taz/${package%.tazpkg}/.. 2> /dev/null; then
		cd $tmp
		tazpkg extract $pkg > /dev/null 2>&1
	fi
	. */receipt
	all_depends="$DEFAULT_DEPENDS "
	scan_dep
	toadd=$(find */fs -type f | while read file ; do
		check_depends_file $file
	done)
	. */receipt
	rm -rf */
	cd - > /dev/null
	rm -rf $tmp
	show_missing_depends "$toadd" "$DEPENDS $SUGGESTED"
}

check_depends_this_file()
{
	file=$1
	all_depends="$DEFAULT_DEPENDS "
	scan_dep
	check_depends_file $file
}

# Remove old packages in the build wok and clean pkgs repository. The
# Hg wok is copied into the build wok so packages removed by hg must be
# removed. To remove old packages in the repository we look into the
# build wok and dont remove unbuilt packages. Clean-up will also remove
# all corrupted packages.
clean_up()
{
	touch $DB_DIR/removed
	echo -e "\nCleaning the build wok, old and corrupted packages...\n"
	echo "(cleaning)" > $DB_DIR/running
	for pkg in `ls $BUILD_WOK`
	do
		if [ ! -d $HG_WOK/$pkg ]; then
			case $2 in
				--dry-run)
					echo "Removing directory : $pkg" ;;
				--verbose)
					echo "Removing directory : $pkg"
					rm -rf $BUILD_WOK/$pkg ;;
				*)
					rm -rf $BUILD_WOK/$pkg ;;
			esac
		fi
	done
	# Build a packages list with EXTRAVERSION so we can grep into it.
	rm -f $DB_DIR/packaged && touch $DB_DIR/packaged
	for receipt in $BUILD_WOK/*/taz/*/receipt
	do
		EXTRAVERSION=""
		. $receipt
		echo "$PACKAGE-${VERSION}$EXTRAVERSION.tazpkg" >> $DB_DIR/packaged
	done
	for pkg in `cd $PACKAGES_REPOSITORY && ls *.tazpkg`
	do
		if ! grep -q "^$pkg$" $DB_DIR/packaged; then
			case $2 in
				--dry-run)
					echo "Removing package : $pkg" ;;
				--verbose)
					echo "Removing package : $pkg"
					echo "$pkg" >> $DB_DIR/removed
					rm -f $PACKAGES_REPOSITORY/$pkg ;;
				*)
					echo "$pkg" >> $DB_DIR/removed
					rm -f $PACKAGES_REPOSITORY/$pkg ;;
			esac
		fi
	done
	# Remove all corrupted packages
	for pkg in `cat $DB_DIR/corrupted | awk '{ print $3 }'`
	do
		case $2 in
			--dry-run)
				echo "Removing corrupted: $pkg" ;;
			--verbose)
				echo "Removing corrupted: $pkg"
				echo "$pkg" >> $DB_DIR/removed
				rm -rf $PACKAGES_REPOSITORY/$pkg ;;
			*)
				echo "$pkg" >> $DB_DIR/removed
				rm -rf $PACKAGES_REPOSITORY/$pkg ;;
		esac
	done
	echo ""
	# Keep the 20 last removed packages list.
	cat $DB_DIR/removed | tail -n 20 > /tmp/removed.tail
	mv -f /tmp/removed.tail $DB_DIR/removed
	# Clean packages stuff/ directory
	echo -e "\nCleaning the build wok stuff/ directories...\n"
	for pkg in `ls $BUILD_WOK`
	do
		if [ -d "$BUILD_WOK/$pkg/stuff" ]; then
			cd $BUILD_WOK/$pkg
			for file in `find stuff -type f`
			do
				if [ ! -f "$HG_WOK/$pkg/$file" ]; then
					echo "Removing: $pkg/$file"
					rm $file
				fi
			done
		fi
	done
}

blocked_urls()
{
	rm -f $DB_DIR/blocked.urls
	for pkg in `cat $DB_DIR/blocked`
	do
		if [ -f $LOG_DIR/$pkg.log ]; then
			echo "<a href=\"log.php?package=$(escape $pkg)$LOG_SUFFIX\">$pkg</a>" >> \
				$DB_DIR/blocked.urls
		else
			echo "$pkg" >> $DB_DIR/blocked.urls
		fi
	done
}

# 4k, not a meta or a get-* package and no files = buggy package
test_packages()
{
	echo -e "\nTesting all packages in: $PACKAGES_REPOSITORY"
	echo "================================================================================"
	echo "(testing packages)" > $DB_DIR/running
	rm -f $DB_DIR/corrupted && touch $DB_DIR/corrupted
	for pkg in $PACKAGES_REPOSITORY/*.tazpkg
	do
		tmp=/tmp/bb-test.$$
		CATEGORY=""
		if du $pkg | grep -qw '^4' && ! echo `basename $pkg` | grep -q '^get-'; then
			mkdir -p $tmp && cd $tmp
			cpio -i receipt >/dev/null 2>&1 < $pkg
			. ./receipt
			if [ "$CATEGORY" != "meta" ]; then
				[ "$2" = "--verbose" ] && echo "Testing: $PACKAGE"
				cpio -i fs.cpio.gz >/dev/null 2>&1 < $pkg
				cpio -i fs.cpio.lzma >/dev/null 2>&1 < $pkg
				if [ ! -f fs.cpio.gz -a ! -f fs.cpio.lzma ]; then
					echo "Missing filesystem    `basename $pkg`"
					if [ -f $LOG_DIR/$PACKAGE.log ];then
						echo "Missing filesystem    `basename $pkg` <a href=\"log.php?package=$(escape $PACKAGE)$LOG_SUFFIX\">Log</a>" \
							>> $DB_DIR/corrupted
					else
						echo "Missing filesystem    `basename $pkg`" \
							>> $DB_DIR/corrupted
					fi
				else
					( zcat fs.cpio.gz 2> /dev/null || \
					  unlzma -c fs.cpio.lzma ) | \
					cpio -id >/dev/null 2>&1
					files=`find fs -type f -o -type l`
					if [ -z "$files" ]; then
						echo "Empty filesystem      `basename $pkg`"
						if [ -f $LOG_DIR/$PACKAGE.log ]; then
							echo "Empty filesystem      `basename $pkg` <a href=\"log.php?package=$(escape $PACKAGE)$LOG_SUFFIX\">Log</a>" \
								>> $DB_DIR/corrupted
						else
							echo "Empty filesystem      `basename $pkg`" \
							>> $DB_DIR/corrupted
						fi
					fi
				fi
			fi
			cd .. && rm -rf $tmp
		fi
	done
	packages_summary_update
	echo ""
}

# Generate flavor list
gen_flavor_list()
{
	cd $PACKAGES_REPOSITORY
	noheader=""
	for i in *.flavor; do
		tazlito show-flavor $i --brief $noheader
		noheader="--noheader"
	done > flavors.list
	cd - > /dev/null
}

case "$1" in
	list-pkgs)
		# List last cooked packages.
		list_packages ;;
	report)
		# Run in report mode. If an update is done we must cook-all to
		# rebuild all updated packages.
		[ "$2" == "--update" ] && update_wok $@ || echo ""
		check_wok $@
		check_flavors $@
		test_packages $@
		show_report ;;
	cook)
		# Cook, install and log a single package build.
		if [ -z $2 ]; then
			echo "Please specify a package on the command line."
			rm -f $LOCK_FILE && exit 0
		fi
		pkg=$2
		echo "Starting to cook and install: $pkg"
		if ! cook_package $pkg; then
			echo "Unable to install: $pkg"
		fi ;;
	cook-all)
		# Update wok, gen report (with cooklist), cook all packages, test,
		# clean, gen new report and lists.
		update_wok $@
		check_wok $@
		cook_install
		test_packages $@
		check_flavors $@
		pack_flavors
		clean_up $@
		check_wok $@
		echo "(generating lists)" > $DB_DIR/running
		tazwok gen-list --text
		check_flavors $@
		gen_flavor_list
		echo "" ;;
	cook-commit)
		# Cook all packages affected by the last commits in the wok.
		# Clean up is done only by cook-all to avoid rebuild of corrupted
		# packages on each commit.
		update_wok $@
		check_commit
		cook_install
		test_packages $@
		check_flavors $@
		pack_flavors
		check_wok $@
		check_flavors $@
		echo "(generating lists)" > $DB_DIR/running
		tazwok gen-list --text
		gen_flavor_list
		echo "" ;;
	block)
		# Add a pkg name to the list of blocked packages.
		echo ""
		if grep -qs "^$2$" $DB_DIR/blocked; then
			echo -e "$2 is already in the blocked packages list."
		else
			echo -n "Adding $2 to     : $DB_DIR/blocked... "
			echo "$2" >> $DB_DIR/blocked && echo "Done"
			if grep -q "^$2$" $DB_DIR/cooklist; then
				echo -n "Removing $2 from : $DB_DIR/cooklist... "
				sed -i /"^$2$"/d $DB_DIR/cooklist && echo "Done"
				packages_summary_update
			fi
		fi
		blocked_urls
		echo "" ;;
	unblock)
		# Remove a pkg name from the list of blocked packages.
		echo ""
		if grep -qs "^$2$" $DB_DIR/blocked; then
			echo -n "Removing $2 from : $DB_DIR/blocked... "
			sed -i /"^$2$"/d $DB_DIR/blocked
			sed -i '/^$/d' $DB_DIR/blocked && echo "Done"
			echo -n "Adding $2 to     : $DB_DIR/cooklist... "
			echo "$2" >> $DB_DIR/cooklist && echo "Done"
			packages_summary_update
		else
			echo -e "$2 is not in the blocked packages list."
		fi
		blocked_urls
		echo "" ;;
	test-pkgs)
		# Start a test suite on all builded packages.
		test_packages $@ ;;
	test-suite)
		# Start a test suite on all builded package and the wok using
		# the great 'tazwok check'.
		#
		# test_packages > $LOG_DIR/test-suite.log
		# tazwok check >> $LOG_DIR/test-suite.log
		#
		test_packages $@
		script -c "tazwok check" $LOG_DIR/test-suite.log ;;
	mail)
		# Tazbbmail Pythom script wrapper.
		PACKAGE=$2
		tazbbmail $PACKAGE ;;
	clean-up)
		# Remove old packages and generate new packages lists.
		update_wok $@
		clean_up $@
		packages_summary_update
		[ "$2" != "--dry-run" ] && tazwok gen-list --text ;;
	clean-log)
		logs=`ls $LOG_DIR | wc -l`
		echo -n "Cleaning: $LOG_DIR... "
		rm -rf $LOG_DIR/*
		echo "$logs log removed" ;;
	check-receipt)
		check_variables $2 &&
		check_wanted_version $2 &&
		check_build_depends $2 ""
		exit $? ;;
	check-depends)
		case "$2" in
		wok)
			for pkg in $PACKAGES_REPOSITORY/*.tazpkg ; do
				missing_depends="$(check_depends_pkg $pkg)"
				[ -n "$missing_depends" ] && 
				echo "The package $pkg depends on $missing_depends."
			done ;;
		package)
			pkg=$3
			VERBOSE=$4
			missing_depends="$(check_depends_pkg $pkg)"
			[ -n "$missing_depends" ] && 
			echo "The package $pkg depends on $missing_depends."
			;;
		file)
			file=3
			VERBOSE=$4
			missing_depends="$(check_depends_this_file $file)"
			[ -n "$missing_depends" ] && 
			echo "The file $file depends on $missing_depends."
			;;
		*)	cat <<EOT
check-depends wok		check every package in wok.
check-depends package <pkg>     check one package.
check-depends file <filename>   check one file only.
EOT
			;;
		esac ;;
	*)
		usage ;;
esac

echo "" > $DB_DIR/running
rm -f $LOCK_FILE /tmp/files.list.tazbb$$

exit 0
