#!/bin/sh

# to make sure that lines like "rm $tmp/*" doesn't remove root
set -u

PROGNAME=`basename $0`
CUSTOMTMP="no"
OUTDIR="./"
KEEP="no"
DEVELOP="no"
TIMERS=
ALLOWSRCMISMATCH=
VERBOSE=

usage() {
	echo "usage: $PROGNAME architecture Packages Sources" >&2
	echo >&2
	echo "Find the reasons why Debian is currently not bootstrappable." >&2
	echo "Assumes that Architecture:all packages need not be bootstrapped." >&2
	echo "Operates on the strong dependency graph." >&2
	echo >&2
	echo " -h, --help       Show this help message and exit" >&2
	echo " -k, --keep       Keep the temporary files" >&2
	echo " -T, --timers     time all program executions" >&2
	echo " -m, --allowsrcmismatch Allow binary packages to be associated with a source " >&2
	echo "                        package of a different version than themselves" >&2
	echo " -o, --output=DIR Output directory. Default is the current directory" >&2
	echo " -t, --tmp=DIR    Temporary directory. Default is created by mktemp(1). Implies --keep." >&2
	echo " -v, --verbose    Be more verbose" >&2
	echo " -d, --debug      Maximum verbosity" >&2
	echo " -D, --develop    Execute tools from the source checkout instead of \$PATH" >&2
}


die() {
	echo " + error $@"
	if [ "$KEEP" = "yes" ]; then
		echo " + temporary files stored in $tmp" >&2
	else
		rm -f "$tmp"/*
		rm -fd $tmp
	fi
	exit 1
}

run() {
	if [ -n "$TIMERS" ]; then
		time --format "%e seconds" "$@"
	else
		"$@"
	fi
}

# clean up when sighup, sigint or sigterm is received
trap "die \"got signal\"" 1 2 15

getopt -T > /dev/null && exitcode=0 || exitcode=$?
if [ $exitcode -eq 4 ]; then
	# GNU enhanced getopt is available
	ARGS=`getopt --long help,keep,timers,allowsrcmismatch,output:,tmp:,verbose,debug,develop --options hkTmo:t:vdD -- "$@"`
else
	# Original getopt is available
	ARGS=`getopt hkTmo:t:vdD "$@"`
fi

if [ $? -ne 0 ]; then
	exit 1
fi

eval set -- $ARGS

while [ $# -gt 0 ]; do
	case "$1" in
		-h | --help)     usage; exit 0;;
		-k | --keep)     KEEP="yes";;
		-T | --timers)   TIMERS="yes";;
		-m | --allowsrcmismatch) ALLOWSRCMISMATCH=--allowsrcmismatch;;
		-o | --output)   OUTDIR="$2"; shift;;
		-t | --tmp)      CUSTOMTMP="yes"; KEEP="yes"; tmp="$2"; shift;;
		-v | --verbose)  VERBOSE=--verbose;;
		-d | --debug)    set -x;;
		-D | --develop)  DEVELOP="yes";;
		--)              shift; break;;
	esac
	shift
done

getocamlexec() {
	execname="$1"
	for ext in "native" "p.native" "byte" "d.byte"; do
		path="./$execname.$ext"
		if [ -f "$path" ]; then
			echo "$path"
			return
		fi
	done
	die "cannot find executable for $execname"
}

bin_buildcheck=dose-builddebcheck
if [ "$DEVELOP" = "yes" ]; then
	bin_bin2src=$(getocamlexec bin2src)
	bin_create_graph=$(getocamlexec create-graph)
	bin_latest_version=./tools/latest-version.py
	bin_buildgraph2packages=./tools/buildgraph2packages.py
	bin_graph_shortest_path=./tools/graph-shortest-path.py
	bin_graphml2dot=./tools/graphml2dot.py
else
	bin_bin2src=botch-bin2src
	bin_create_graph=botch-create-graph
	bin_selfcycles=botch-selfcycles
	bin_latest_version=botch-latest-version
	bin_buildgraph2packages=botch-buildgraph2packages
	bin_graph_shortest_path=botch-graph-shortest-path
	bin_graphml2dot=botch-graphml2dot
fi

if [ $# -ne 3 ]; then
	usage
	exit 1
fi

arch="$1"
packages="$2"
sources="$3"

if [ $CUSTOMTMP = "yes" ]; then
	mkdir -p "$tmp" || die "cannot mkdir $tmp"
else
	tmp=`mktemp --directory`
fi

mkdir -p "$OUTDIR" || die "cannot mkdir $OUTDIR"

if [ "$KEEP" != "yes" ] && [ -n "$(ls -A $tmp)" ]; then
	echo "$tmp is not empty and you did not specify --keep" >&2
	echo "refusing to run and delete that directory" >&2
	exit 1
fi

zcat -f "$packages" | grep-dctrl --exact-match -F Architecture all > "$tmp/bs-available-noall"

zcat -f "$packages" | grep-dctrl --exact-match --field Package build-essential \
	| run $bin_latest_version - - \
	| run $bin_bin2src $ALLOWSRCMISMATCH --deb-native-arch="$arch" - "$sources" \
	| run $bin_create_graph $VERBOSE --progress $ALLOWSRCMISMATCH \
		--deb-native-arch="$arch" --strongtype \
		--available "$tmp/bs-available-noall" --bg "$sources" "$packages" - \
	> "$tmp/bs-buildgraph.xml"

$bin_buildgraph2packages "$tmp/bs-buildgraph.xml" "$packages" \
	| grep-dctrl --not --exact-match -F Architecture all \
	| $bin_bin2src $ALLOWSRCMISMATCH --deb-native-arch="$arch" - "$sources" \
	| grep-dctrl --no-field-names --show-field=Package,Version '' \
	| paste --serial --delimiter="  \n" \
	| sed 's/\s\+$//; /^$/d' \
	| sort \
	> "$tmp/bs-buildgraph.list"

[ -f "$tmp/bs-buildgraph.list" -a ! -s "$tmp/bs-buildgraph.list" ] && die "created graph is empty"

$bin_buildcheck --deb-native-arch="$arch" -f -m "$packages" "$sources" \
	| sed -n 's/^\s\+failure: src:\([^ ]\+\) (= \([^)]\+\))/\1 \2/p' \
	| sort \
	> "$tmp/bs-failures.list"

[ -f "$tmp/bs-failures.list" -a ! -s "$tmp/bs-failures.list" ] && die "all packages compile"

reasons=""

comm -12 "$tmp/bs-buildgraph.list" "$tmp/bs-failures.list" \
	| while read name version; do \
		fname="${name}_$version"
		run $bin_graph_shortest_path "$tmp/bs-buildgraph.xml" ${tmp}/${fname}.xml --source type:bin --source name:build-essential --target type:src --target name:$name --target version:$version || die "botch-shortest-path failed"
		run $bin_graphml2dot ${tmp}/${fname}.xml ${OUTDIR}/${fname}.dot || die "botch-graphml2dot failed"
		run dot -Tsvg ${OUTDIR}/${fname}.dot > ${OUTDIR}/${fname}.svg || die "dot failed"
		# execute graph-easy but do not fail if it doesn't exist or doesn't work as advertised
		graph-easy --input ${OUTDIR}/${fname}.dot --as_ascii >&2 || true
		echo "stored explanation graph for $name (= $version) in ${OUTDIR}/${fname}.dot and a rendering in ${fname}.svg" >&2
		reasons="$reasons $name"
	done

if [ -n "$reasons" ]; then
	echo "failure reasons: $reasons" >&2
fi

if [ "$KEEP" = "yes" ]; then
	echo " + temporary files stored in $tmp" >&2
else
	rm -f "$tmp"/*
	rm -fd $tmp
fi

if [ -n "$reasons" ]; then
	exit 1
else
	exit 0
fi
