#!/bin/bash -e

. $(dirname $0)/mbd-common.sh
. "${MBD_REPCONFIGFILE}"

mbd_opt_init "Mini-buildd: Perform checks on a changes file."
mbd_opt_add "c:" "Absolute path to changes file."
mbd_opt_add "a:" "Run check(s) for that arch."
mbd_opt_add "r"  "Run check(s) remotely on the build host for arch (-a)."
mbd_opt_add "q:" "Single QA check to run. Logging to stdout and stderr, exit code of check.
         Used internally only; you may misuse it for debugging."
mbd_opt_add "Q:" "List of QA check to run. Logging to files in log dir given via -L."
mbd_opt_add "I:" "Build ID PACKAGE/VERSION/STAMP."
mbd_opt_add "P:" "Prefix for log files."

mbd_opt_parse "$@"

mbd_opt_get c >/dev/null
MBD_TMP_BUILDID=$(mbd_opt_get I)
mbdCheckUser mini-buildd

MBD_TMP_CFA="$(mbd_opt_get c)"
MBD_TMP_CF=$(basename "${MBD_TMP_CFA}")
MBD_TMP_DIR=$(dirname "${MBD_TMP_CFA}")
if mbd_opt_given a; then
	MBD_TMP_ARCH="$(mbd_opt_get a)"
	MBD_TMP_ARCHHOST="${MBD_TMP_ARCH}"
else
	MBD_TMP_ARCHHOST="rep"
fi
if ! mbd_opt_given r; then
	mbdCheckFile "${MBD_TMP_CFA}"
	cd "${MBD_TMP_DIR}"
	mbdParseCF "${MBD_TMP_CF}"
fi

MBD_TMP_SSH="ssh -o StrictHostKeyChecking=no -p ${mbd_sshport}"
MBD_TMP_SCP="scp -o StrictHostKeyChecking=no -P ${mbd_sshport}"

# Check funtions must be named mbdQACheck<CHECKNAME>.
#
# Pre-conditions:
#  - Variables MBD_TMP_CF[A], MBD_TMP_DIR, MBD_TMP_ARCH.
#  - All variables produced by mbdParseCF (on MBD_TMP_CFA).
# Output:
#  - All output (stdout or stderr) goes to the respective test's log file.
# Retval:
#  Check functions must deliver 0 (fine), 1 (warn) or 2 (fail) return status.
#  Note that all checks are run "set +e".

# Check mandatory version part
mbdQACheckVersion()
{
	local retval=0
	local mandatory=$(mbdGetMandatoryVersionPart "${mbdParseCF_dist}")
	local info="Mandatory version part \"${mandatory}\" for distribution \"${mbdParseCF_dist}\" in \"${mbdParseCF_version}\""

	if echo "${mbdParseCF_version}" | grep --quiet "${mandatory}"; then
		echo "I: ${info}: Included."
	else
		echo "I: ${info}: Missing."
		retval=2
	fi
	return ${retval}
}

# Check that source package is not in repo yet
mbdQACheckNew()
{
	local retval=0
	local info="${mbdParseCF_package}"

	# Always check normal and experimental dist
	local dist
	for dist in $(mbdExpandDists "${mbdParseCF_dist}"); do
		if [ -e "${MBD_HOME}/rep/${dist}/${MBD_TMP_CF}" ]; then
			echo "E: ${info}: Already installed in ${dist}."
			echo "I: Maybe you have accidentially re-uploaded the same package, or forgot to create a new debian changelog entry."
			retval=2
		else
			echo "I: ${info}: New for ${dist}."
		fi
	done
	return ${retval}
}

# Check that no conflicting orig tarball is uploaded
mbdQACheckOrig()
{
	local retval=0
	local info="Orig tarball: ${mbdParseCF_orig_tarball}"

	local dist
	if [ -e "${mbdParseCF_orig_tarball}" ]; then
		# Always check normal and experimental dist
		local dist
		for dist in $(mbdExpandDists "${mbdParseCF_dist}"); do
			local repOrig="${MBD_HOME}/rep/${dist}/${mbdParseCF_orig_tarball}"
			if [ -e "${repOrig}" ]; then
				if cmp "${mbdParseCF_orig_tarball}" "${repOrig}"; then
					echo "W: ${info}: Already uploaded to ${dist}."
					retval=1
				else
					echo "E: ${info}: Re-upload with a different file in ${dist} (uploader should be punished hard)."
					retval=2
					break
				fi
			else
				echo "I: Upload with NEW orig tarball for ${dist}: ${mbdParseCF_orig_tarball}."
			fi
		done
	else
		echo "I: Upload without orig tarball: ${mbdParseCF_orig_tarball}."
	fi
	return ${retval}
}

# Do and check DSP upload to all build hosts
mbdQACheckUploadDSP()
{
	local retval=0
	local repOrig="${MBD_HOME}/rep/${mbdParseCF_dist}/${mbdParseCF_orig_tarball}"

	for arch in $(mbdD2SList "${mbd_archs}"); do
		mbdParseArch "${arch}"
		local buildDir="bld/builds/$(mbdBId2BDir "${mbdParseArch_arch}" "${MBD_TMP_BUILDID}")"

		echo "I: Uploading for ${mbdParseArch_host}/${mbdParseArch_arch}."

		if ! (${MBD_TMP_SSH} "${mbdParseArch_host}" mkdir -p -v "${buildDir}" && ${MBD_TMP_SCP} -p ${mbdParseCF_files} "${mbdParseArch_host}:${buildDir}/"); then
			echo "E: Error uploading DSP to build host ${mbdParseArch_host}."
			retval=2
		else
			echo "I: Files from changes uploaded: ${mbdParseCF_files}."
			if [ ! -e "${mbdParseCF_orig_tarball}" -a -e "${repOrig}" ]; then
				if ! ${MBD_TMP_SCP} -p "${repOrig}" "${mbdParseArch_host}:${buildDir}/"; then
					echo "E: Error uploading orig tarball from repo."
					retval=2
				else
					echo "I: Orig tarball from repo uploaded: ${mbdParseCF_orig_tarball}"
				fi
			fi
		fi
	done
	return ${retval}
}

# Check for lintian errors
mbdQACheckLintian()
{
	local lf="${MBD_TMP_CF}.lintian"

	${MBD_LIB}/mbd-lintian -C "mbd-${mbdParseCF_dist}-${MBD_TMP_ARCH}" -c "${MBD_TMP_CF}" >"${lf}" || true

	# @hack for lintian: Some lintian tests need to be ignored (and -X does not seem to work):
	#  - "bad-distribution-in-changes-file": As our dists are not supported by lintian.
	#  - "bad-version" (all bad-version* errors): As in lintian/sarge, Tilde-Versioning results in errors.
	#  - "no-description-in-changes-file": since dpkg 1.4.16: Changes files in source only uploads have no description field any more.
	#  - "menu-item-creates-new-root-section": for lintian <= etch, this is an ERROR, but imho should be a WARNING only, and not let builds fail.
	#  - "changelog-should-mention-nmu": Does not make sense for mini-buildd.
	#  - "source-nmu-has-incorrect-version-number": Does not make sense for mini-buildd.
	grep --invert-match "\( bad-.*distribution-in-changes-file\| bad-version\| no-description-in-changes-file\| menu-item-creates-new-root-section\| changelog-should-mention-nmu\| source-nmu-has-incorrect-version-number\| maintainer-upload-has-incorrect-version-number\| binary-nmu-debian-revision-in-source\| binary-nmu-uses-old-version-style \)" "${lf}" >"${lf}.mbd" || true

	# Check if we have errors ans/or warnings; at the same time output
	# error lines via grep.
	local errors=false
	local warnings=false
	! grep '^E: .*$' "${lf}.mbd" || errors=true
	! grep '^W: .*$' "${lf}.mbd" || warnings=true

	# Compute the retval
	local retval=0
	if ${errors}; then
		if ${mbdParseCF_mbd_backport_mode}; then
			echo "I: mini-buildd: Backport mode: Treating above lintian errors as warnings."
			retval=1
		else
			retval=2
		fi
	elif ${warnings}; then
		retval=1
	fi

	echo
	echo "I: ----------------------------------------------------------------------"
	echo "I: Verbose lintian descriptions (issues not shown above are ignored):"
	echo "I: ----------------------------------------------------------------------"
	cat "${lf}"
	return ${retval}
}

# Check if it builds from source
mbdQACheckBuild()
{
	local retval=0
	local lf="${MBD_TMP_CF}.build"
	local chroot="mbd-${mbdParseCF_dist}-${MBD_TMP_ARCH}"
	mbdParseArch "${MBD_TMP_ARCH}"

	# Run sbuild. Notes:
	# * sbuild_mode is set to "user"; sbuild failure means build failure,
	# or nothing to build for that arch.
	# * DEB_BUILD_OPTIONS are configured per build host.
	# * We give CCACHE_DIR explicitely here as build tools may change
	# $HOME for whatver reasons (e.g., package "subversion"'s test suite
	# does), effectively disabling gcc.
	if DEB_BUILD_OPTIONS="${mbdParseArch_debopts}" CCACHE_DIR="${HOME}/.ccache" \
		sbuild --verbose --nolog --dist="${mbdParseCF_dist}" --arch="${MBD_TMP_ARCH}" --chroot="${chroot}" ${mbdParseArch_sbuildopts} "${mbdParseCF_package}.dsc" >"${lf}" 2>&1; then
		echo "I: Built successful for arch=${MBD_TMP_ARCH} [${mbdParseArch_sbuildopts}]."
	else
		# @hack: If this package has no packages to be built for this arch, this is ok, and we get:
		if grep -i "${MBD_TMP_ARCH}.*not in arch list.*skipping" ${lf}; then
			retval=0
			echo "I: No packages to build for arch=${MBD_TMP_ARCH}."
		else
			retval=2
			echo "E: FTBFS for ${MBD_TMP_ARCH}."
		fi
	fi
	echo "I: ----------------------------------------------------------------------"
	echo "I: Build log:"
	echo "I: ----------------------------------------------------------------------"
	cat "${lf}"
	return ${retval}
}

mbdQACheckBackports()
{
	local retval=0

	local chroot="mbd-${mbdParseCF_dist}-${MBD_TMP_ARCH}"
	mbdParseArch "${MBD_TMP_ARCH}"

	echo "I: ----------------------------------------------------------------------"
	echo "I: Backports requested for:" ${mbdParseCF_mbd_auto_backports}
	echo "I: ----------------------------------------------------------------------"

	# We only run on arch=all architecture
	if [ "${mbdParseArch_arch}" != "${mbd_archall}" ]; then
		echo "I: ${mbdParseArch_arch}: Auto-Backports are done on ${mbd_archall} only, skipping."
	else
		local d
		for d in ${mbdParseCF_mbd_auto_backports}; do
			echo -e "\n==> Auto-backport requested for: ${d}"

			if [ "${mbdParseCF_dist}" = "${d}" ]; then
				echo "W: Ignoring backport request for ${d}: Same distribution as the original upload."
				retval=1
				break
			fi

			(
				# Work in dist subdir to avoid collisions, unpack original DSP and enter DST
				mkdir -v "${d}"
				cd "${d}"
				dpkg-source -x "$(dirname "${MBD_TMP_CFA}")/${mbdParseCF_package}.dsc" DST
				cd "DST"

				# Taint DST (CL/Version only): Use original version, replace manadatory version parts only.
				local bpoVersion=${mbdParseCF_version/$(mbdGetMandatoryVersionPart "${mbdParseCF_dist}" norevision)/$(mbdGetMandatoryVersionPart "${d}" norevision)}
				local bpoVersion_noepoch=${bpoVersion#*:}
				(
					export DEBFULLNAME="${MBD_AUTOBUILD_MAINTAINER}"
					export DEBEMAIL="mini-buildd@$(hostname -f)"
					debchange \
						--force-bad-version --newversion "${bpoVersion}" \
						--force-distribution --distribution="${d}" \
						"Auto-Backport (no changes) for ${d} from ${mbdParseCF_version}"
					debchange --append "MINI_BUILDD: BACKPORT_MODE"
				)
				[ $? -eq 0 ] || return 1

				# Build source package

				# @note: sbuild can't be used on a DST directly afaics, so we
				# can't use it here -- i.e., we can't build the source package
				# in a dedicated chroot with dependency handling. So we use -d
				# here; packages that need the source-deps even for -S will
				# fail.
				# @note: We add -v option with third version in CL, so we also
				# get the changes from the original version in the changes file;
				# the version number must exist exactly as given in cl, so we
				# can't just guess a lower version here. See Debian Bug #477638.
				local changesAfterVersion=$(dpkg-parsechangelog --offset 2 --count 1 | grep "^Version" | cut -d" " -f2-)
				dpkg-buildpackage -rfakeroot -kmini-buildd -d -S -sa $([ -z "${changesAfterVersion}" ] || echo -n "-v${changesAfterVersion}")

				# Go up from DST, and upload backport back to repository bpo spool
				cd ..
				local bpoCF="${mbdParseCF_source}_${bpoVersion_noepoch}_source.changes"
				echo "Source package build, uploading: ${bpoCF}"
				# Use subshell as we run "global" parse
				(
					mbdParseCF "${bpoCF}"
					UPDIR="${MBD_INCOMING_BPO}/${d}"
					${MBD_TMP_SSH} "${mbd_rephost}" mkdir -v -p ${UPDIR}
					${MBD_TMP_SCP} -p ${mbdParseCF_files} "${mbd_rephost}:${UPDIR}/"
				)
			)
			if [ $? -ne 0 ]; then
				echo "W: Backport for ${d} FAILED (see log above)."
				retval=1
			fi
		done
	fi
	return ${retval}
}

# Download DBP via dput
mbdQACheckDownloadDBP()
{
	local retval=0
	if dput -f -u mini-buildd-${mbd_id} ${mbdParseCF_package}_${MBD_TMP_ARCH}.changes; then
		echo "I: Uploaded for ${MBD_TMP_ARCH}."
	else
		echo "W: No DBP from ${MBD_TMP_ARCH} - skipping. This is ok if there are no packages to build for this arch."
		retval=1
	fi
	return ${retval}
}


# Run one check, either here (rep host) or remotely (bld host)
mbdRunCheck()
{
	if mbd_opt_given r; then
		mbdParseArch "${MBD_TMP_ARCH}"
		${MBD_TMP_SSH} ${mbdParseArch_host} ${MBD_LIB}/mbd-qa-check -c "$(mbd_opt_get c)" -a "${MBD_TMP_ARCH}" -q "${check}" -I "$(mbd_opt_get I)"
	else
		mbdQACheck${1}
	fi
	return $?
}

# Run either one check (no logging) or a list of checks (logging)
if mbd_opt_given q; then
	mbdRunCheck "$(mbd_opt_get q)"
else
	MBD_TMP_COUNT=0

	for check in $(mbd_opt_get Q); do
		MBD_TMP_LOGDIR="${MBD_HOME}/log/${MBD_TMP_BUILDID}"
		MBD_TMP_LOGBASE="${MBD_TMP_LOGDIR}/$(mbd_opt_get P).$(printf "%02d" ${MBD_TMP_COUNT})_${check}"
		MBD_TMP_LOGPRE="I: $(basename "${MBD_TMP_CF}")@${MBD_TMP_ARCHHOST}: \"${check}\""

		${MBD_LOG} -s "${MBD_TMP_LOGPRE}: Checking..."

		set +e
		mbdRunCheck "${check}" >"${MBD_TMP_LOGBASE}.log" 2>&1
		MBD_TMP_RETVAL=$?
		set -e

		MBD_TMP_STATUS=$(mbdRetval2Status QACHECK ${MBD_TMP_RETVAL})
		echo -n "${MBD_TMP_STATUS}" >"${MBD_TMP_LOGBASE}.status"

		${MBD_LOG} -s "${MBD_TMP_LOGPRE}: Checked: ${MBD_TMP_STATUS}."
		if [ ${MBD_TMP_RETVAL} -gt 1 ]; then
			exit 1
		fi

		MBD_TMP_COUNT=$((MBD_TMP_COUNT+1))
	done
fi
