#!/bin/bash

# Copyright (C) 2011-2015 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
#
# This programme is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This programme is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

export PATH="${HOME}/bin:${PATH}"

GIT_USER="gituser"
GIT_TRANSPORT="https"
GIT_HOSTNAME="git.mydomain.org"

DEBEMAIL="firstname.lastname@mydomain.org"
DEBFULLNAME="Firstname Lastname"
GPG_KEY=""
DEB_DISTS_SUPPORTED="debian ubuntu"
DEBIAN_DISTROS="jessie,stretch,buster,bullseye,sid"
UBUNTU_DISTROS="xenial,bionic,disco,eoan"

COMPONENT_RELEASE="release"
COMPONENT_NIGHTLY="nightly"
COMPONENT_BUNDLES="bundle-release1 bundle-release2"
REPOS_SERVER="packages.mydomain.org"
REPOS_BASE="/"
PACKAGES_WITHOUT_OTHERMIRROR="keyring"
GNUPGHOME="${HOME}/.gnupg"

TIMESTAMP=$(date +%s%N)

test -z "${1}" && { echo "usage: $(basename "${0}") [<subpath>/]<git-project> {release,release/<codename>,nightly,nightly/<codename>} [<git-checkout>]"; exit 1; }

PREFIX="$(cut -d"-" -f1 <<< "$(basename "${0}")")"
test -f "${HOME}/.buildscripts/${PREFIX}.conf" && . "${HOME}/.buildscripts/${PREFIX}.conf" || { echo "${0} has no valid context prefix..." >&2; exit 1; }

: ${FORCE_BUILD:="no"}
: ${DEB_BUILD_FOR:="debian:${DEBIAN_DISTROS} ubuntu:${UBUNTU_DISTROS}"}

# These parts are not user-serviceable.
typeset -ag temp_cleanup=""
# End of non-user-serviceable part.

set -ex

# Cleans up temporary directories and files.
# RFC SHOULD be called by trap handlers.
cleanup () {
	typeset temp_dir=""
	for temp_dir in "${temp_cleanup[@]}"; do
		if [ -d "${temp_dir}" ]; then
			rm -Rf -- "${temp_dir}"
		fi
	done
}

# Run cleanup() automatically.
trap cleanup ERR EXIT SIGTERM SIGINT SIGHUP SIGPIPE SIGALRM SIGUSR1 SIGUSR2

set_vars() {
	SBUILD="sbuild"
	TEMP_BASE="${HOME}/tmp/"
	mkdir -p -- "${TEMP_BASE}"
	chmod 2770 "${TEMP_BASE}"

	# first argv is the name of the Git project
	PROJECT_PATH="${1}"
	PROJECT_PATH="${PROJECT_PATH/%.git/}"
	PROJECT="$(basename "${PROJECT_PATH}")"
	DEBSRCPKG="${PROJECT}"

	# grab repository component area from command line (2nd argv) or guess it
	ARGV2_COMPONENT="$(cut -d"/" -f1 <<< "${2}/")"
	ARGV2_CODENAME="$(cut -d"/" -f2 <<< "${2}/")"
	COMPONENT="${ARGV2_COMPONENT:-${COMPONENT:-$COMPONENT_NIGHTLY}}"
	CODENAMES="${ARGV2_CODENAME:-${CODENAMES}}"
	[ -n "${ARGV2_CODENAME}" ] && FORCE_BUILD="yes" || true
	DATE="${DATE:-$(date +%Y%m%d)}"
	if [ "x${COMPONENT}" = "x${COMPONENT_RELEASE}" ]; then
		CHECKOUT="${3:-release-builds}"
	elif [ "x${COMPONENT}" = "x${COMPONENT_RELEASE}-test" ]; then
		CHECKOUT="${3:-release-testbuilds}"
	elif grep -qs "$COMPONENT" <<< "${COMPONENT_BUNDLES}"; then
		CHECKOUT="${3:-build-$COMPONENT}"
	elif [ "x${COMPONENT}" = "x${COMPONENT_NIGHTLY}" ]; then
		CHECKOUT="${3:-master}"
	else
		echo "error: no such package component area for this Git project. Aborting..."
		exit 1
	fi
	# the DATE might be given as ,,today'' from the command line
	[ "x${DATE}" = "xtoday" ] && DATE="$(date +%Y%m%d)"

	# setting paths
	PROJECT_DIR="${HOME}/build/${COMPONENT}/${TIMESTAMP}/${PROJECT}"
	temp_cleanup+=("${PROJECT_DIR}")
	temp_cleanup+=("$(dirname ${PROJECT_DIR})")

	PKGDIST="${HOME}/pkg-dist/${COMPONENT}/${PROJECT}"

	# creating paths
	mkdir -p -- "${PROJECT_DIR}"
	mkdir -p -- "${PKGDIST}"

	# by default we build for all current debian versions
	if [ "x${ARGV2_CODENAME}" != "x" ]; then
		if grep -qs "${ARGV2_CODENAME}" <<< "${DEBIAN_DISTROS}"; then
			DEB_BUILD_FOR="debian:${ARGV2_CODENAME}"
		elif grep -qs "${ARGV2_CODENAME}" <<< "${UBUNTU_DISTROS}"; then
			DEB_BUILD_FOR="ubuntu:${ARGV2_CODENAME}"
		fi
	fi

	return 0
}

prepare_workspace() {
	# make sure our local working copy is up to date...

	my_DIST="$1"
	my_CODENAME="$2"
	my_WORKDIR="$(dirname ${PROJECT_DIR})"

	cd ${my_WORKDIR}
	if [ ! -d ${PROJECT}/.git ]; then
		git clone --no-hardlinks --no-local "${GIT_TRANSPORT}://${GIT_HOSTNAME}/${PROJECT_PATH}.git"
		cd "${PROJECT}"
		git fetch origin "${CHECKOUT}" || true
		git checkout --force "${CHECKOUT}" || git checkout --force -b "${CHECKOUT}"

		GIT_OBJECT_ID="$(git rev-parse --verify HEAD)"
	fi

	cd "${PROJECT_DIR}"

	# extract Debian source package name from debian/changelog
	if [ -e debian/changelog ]; then
		DEBSRCPKG="$(dpkg-parsechangelog -S Source)"
	fi

	return 0
}

clear_pkgdist() {
	# pkgdist directory cleanup

	# Do NOT spawn a subshell here.
	# Allow changing global variables in the main process.
	typeset -a deb_build_for_arr
	typeset OLDIFS="${IFS}"
	IFS=" "
	read -a deb_build_for_arr <<< "${DEB_BUILD_FOR}"
	IFS="${OLDIFS}"

	typeset line=""
	for line in "${deb_build_for_arr[@]}"; do
		l_DIST="$(cut -d":" -f1 <<< "${line/: /:}" | tr [:upper:] [:lower:])"
		l_CODENAMES="${CODENAMES:-$(cut -d":" -f2- <<< "${line/: /:}" | sed -e 's/,/ /g' | tr [:upper:] [:lower:])}"
		grep -qs "${l_DIST}" <<< "${DEB_DISTS_SUPPORTED}" && {
			for l_CODENAME in ${l_CODENAMES}; do

				# in case we build a special CODENAME (squeeze, wheezy, lucid, ...) do skip
				# the wrong distribution here...
				test -z "${CODENAMES}" || grep "${CODENAMES}" <<< "${line}" || break

				for l_ARCH in amd64 i386; do
					[ "x${SKIP_ARCH}" != "x${l_ARCH}" ] && {
						mkdir -p -- "${PKGDIST}/${l_DIST}/${l_CODENAME}/${l_ARCH}"
						rm -f -- "${PKGDIST}/${l_DIST}/${l_CODENAME}/${l_ARCH}/dupload.conf"
						rm -f -- "${PKGDIST}/${l_DIST}/${l_CODENAME}/${l_ARCH}/"*_*.changes
						rm -f -- "${PKGDIST}/${l_DIST}/${l_CODENAME}/${l_ARCH}/"*_*.upload
						rm -f -- "${PKGDIST}/${l_DIST}/${l_CODENAME}/${l_ARCH}/"*_*.build
						rm -f -- "${PKGDIST}/${l_DIST}/${l_CODENAME}/${l_ARCH}/"*_*.dsc
						rm -f -- "${PKGDIST}/${l_DIST}/${l_CODENAME}/${l_ARCH}/"*_*.tar.gz
						rm -f -- "${PKGDIST}/${l_DIST}/${l_CODENAME}/${l_ARCH}/"*.deb
					}
				done
			done
		}
	done
	return 0
}

build_packages() {
	# use pbuilder for building all variants of this package

	# Do NOT spawn a subshell here.
	# Allow changing global variables in the main process.
	typeset -a deb_build_for_arr
	typeset OLDIFS="${IFS}"
	IFS=" "
	read -a deb_build_for_arr <<< "${DEB_BUILD_FOR}"
	IFS="${OLDIFS}"

	typeset line=""
	for line in "${deb_build_for_arr[@]}"; do
		l_DIST="$(cut -d":" -f1 <<< "${line/: /:}" | tr [:upper:] [:lower:])"
		l_CODENAMES="${CODENAMES:-$(cut -d":" -f2- <<< "${line/: /:}" | sed -e 's/,/ /g' | tr [:upper:] [:lower:])}"
		grep -qs "${l_DIST}" <<< "${DEB_DISTS_SUPPORTED}" && {
			for l_CODENAME in ${l_CODENAMES}; do

				# prepare workspace
				prepare_workspace "${l_DIST}" "${l_CODENAME}"

				# in case we build a special CODENAME (squeeze, wheezy, lucid, ...) do skip
				# the wrong distribution here...
				test -z "${CODENAMES}" || grep "${CODENAMES}" <<< "${line}" || break

				TEMP_DIR="$(mktemp -d --tmpdir=${TEMP_BASE})"
				temp_cleanup+=("${TEMP_DIR}")

				mkdir -p -- "${TEMP_DIR}/${PROJECT}"
				chmod 2770 -Rf -- "${TEMP_DIR}"

				cd "${PROJECT_DIR}"
				git clone --no-hardlinks --no-local "${PROJECT_DIR}" "${TEMP_DIR}/${PROJECT}/"
				cd "${TEMP_DIR}/${PROJECT}"
				git checkout "${CHECKOUT}" || git checkout master
				if [ -e .gitmodules ]; then
					git submodule init
					git submodule update --force --checkout --recursive
				fi

				find "${PROJECT_DIR}/../" -maxdepth 0 -mindepth 0 -type f | grep -qs "${PROJECT}_"*.orig.tar.gz && cp -- "${PROJECT_DIR}/../${PROJECT}_"*.orig.tar.gz ..
				GITREV="$(gitrevno)"

				# we always build native packages for our repos
				SA_OPTION=""
				test -f "debian/source/format" && grep -Eqs '^3.0.*\(quilt\)$' "debian/source/format" && {
					git fetch origin upstream:upstream
					UPSTREAM_VERSION="$(dpkg-parsechangelog | grep "Version:" | cut -d " " -f2 | sed -e 's/-.*//' -e 's/^.*://')"
					REVISION="$(dpkg-parsechangelog | grep "Version:" | cut -d " " -f2 | sed -e 's/.*-//')"
					git archive --prefix="${PROJECT}-${UPSTREAM_VERSION}/" -o "../${PROJECT}_${UPSTREAM_VERSION}.orig.tar.gz" "upstream/${UPSTREAM_VERSION}" && {
						SA_OPTION="--debbuildopts=\"-sa\""
					} || echo "1.0" > "debian/source/format"
				}

				# for Ubuntu version is the codename of the distribution release
				if [ -n "${BASH_VERSINFO[0]}" ] && [ "${BASH_VERSINFO[0]}" -gt 3 ]; then
					typeset -l codename="${l_CODENAME}"
				else
					typeset codename="$(tr '[:upper:]' '[:lower:]' <<< "${l_CODENAME}")"
				fi

				# translate the version name for Debian releases
				[ "x${l_CODENAME}" = "xsid" ] && codename="unstable"
				#[ "x$l_CODENAME" = "xjessie" ] && codename=testing
				#[ "x$l_CODENAME" = "xwheezy" ] && codename=stable
				#[ "x$l_CODENAME" = "xoldstable" ] && codename=oldstable

				typeset numerical_version=""
				typeset -i tmp_ret="1"
				typeset pretty_dist=""

				if [ -n "${l_DIST}" ] && [ "${l_DIST}" = "debian" ]; then
					pretty_dist="Debian"
					numerical_version="$("debian-codename-to-version.sh" "${codename}")"
					tmp_ret="${?}"
				fi

				if [ -n "${l_DIST}" ] && [ "${l_DIST}" = "ubuntu" ]; then
					pretty_dist="Ubuntu"
					numerical_version="$("ubuntu-codename-to-version.sh" "${codename}")"
					tmp_ret="${?}"
				fi

				if [ "${tmp_ret}" -ne "0" ] || [ -z "${numerical_version}" ]; then
					echo "Error: unable to map code name \"${codename}\" to Debian or Ubuntu numerical versions. Unknown code name or not applicable to distribution \"${dist_pretty}\"? Aborting." >&2
					exit 1
				fi

				# modify the section for non-release package builds
				[ "x${COMPONENT}" != "xrelease" ] && {
					mv -- "debian/control" "debian/control.tmp"
					sed "s,Section:[\ ]*\(.*\),Section: ${COMPONENT}/\1,g" debian/control.tmp > debian/control
				}

				# modify changelog for this build
				if [ "${COMPONENT}" != "${COMPONENT_NIGHTLY}" ]; then
					dch --distribution "${codename}" --force-distribution -l "+git${DATE}.${GITREV}+${numerical_version}.${COMPONENT}." "Auto-built ${pretty_dist} ${l_CODENAME} package for ${REPOS_SERVER} repository (Git commit: ${GIT_OBJECT_ID})."
				else
					dch --distribution "${codename}" --force-distribution -l "+git${DATE}.${GITREV}+${numerical_version}.${COMPONENT}." "Development-Snapshot!!! Auto-built ${pretty_dist} ${l_CODENAME} package for ${REPOS_SERVER} repository (Git commit: ${GIT_OBJECT_ID})."
				fi
				mkdir -p -- "${PKGDIST}/${l_DIST}/${l_CODENAME}/"{amd64,i386}
				OTHERMIRROR=""
				if [ "x${COMPONENT}" = "x${COMPONENT_NIGHTLY}" ]; then
					grep -qs "${PROJECT}" <<< "${PACKAGE_WITHOUT_OTHERMIRROR}" || OTHERMIRROR="deb http://${REPOS_SERVER}${REPOS_BASE}${l_DIST}-nightly ${l_CODENAME} main"
				elif echo "${COMPONENT_BUNDLES}" | grep -q "${COMPONENT}" 1>/dev/null; then
					grep -qs "${PROJECT}" <<< "${PACKAGE_WITHOUT_OTHERMIRROR}" || OTHERMIRROR="deb http://${REPOS_SERVER}${REPOS_BASE}${l_DIST}-${COMPONENT} ${l_CODENAME} main"
				else
					grep "${PROJECT}" <<< "${PACKAGE_WITHOUT_OTHERMIRROR}" || OTHERMIRROR="deb http://${REPOS_SERVER}${REPOS_BASE}${l_DIST} ${l_CODENAME} main"
				fi
				# create git changelog immediately prior to building the package
				git --no-pager log --since "2 years ago" --format="%ai %aN (%h) %n%n%x09*%w(68,0,10) %s%d%n" > ChangeLog

				# build the source package
				dpkg-buildpackage -uc -us -S -d
				cd ..
				DSCFILE="$(pwd)/$(ls -1 "${DEBSRCPKG}_"*.dsc | head -n1)"

				SBUILD_OPTIONS="-n -j2 -sAd ${codename} -k ${GPG_KEY} --build-dep-resolver=aptitude"

				curl http://${REPOS_SERVER}${REPOS_BASE}/archive.key 2>/dev/null 1> ${TEMP_DIR}/extra-archive.key
				if head -n1 ${TEMP_DIR}/extra-archive.key | grep -q -E '\-----BEGIN PGP PUBLIC KEY BLOCK-----' &&
				   tail -n1 ${TEMP_DIR}/extra-archive.key | grep -q -E '\-----END PGP PUBLIC KEY BLOCK-----'; then
					SBUILD_OPTIONS="${SBUILD_OPTIONS} --extra-repository-key=${TEMP_DIR}/extra-archive.key"
				fi

				SBUILD_OPTIONS_64="${SBUILD_OPTIONS} -c ${PREFIX}-${l_CODENAME}"
				SBUILD_OPTIONS_32="${SBUILD_OPTIONS} -c ${PREFIX}-${l_CODENAME}-i386 --arch=i386  --debbuildopts=-B"
				if [ -n "${SA_OPTION}" ]; then
					SBUILD_OPTIONS_64=${SBUILD_OPTIONS}" ${SA_OPTION}"
				fi

				[ "x${SKIP_ARCH}" != "xamd64" ] && grep -Eqs 'Architecture.*(all|any|amd64)' "${TEMP_DIR}/${PROJECT}/debian/control" && {
					cd "${PKGDIST}/${l_DIST}/${l_CODENAME}/amd64"
					tac ${DSCFILE} | while read line; do
						if echo $line | grep -E "^Files:" 1>/dev/null; then break; fi
						# each line contains a file that is part of the src:package
						filename="$(echo $line | cut -d" " -f3-)"
						if [ -n "$filename" ]; then cp "${TEMP_DIR}/${filename}" .; fi
					done
					if [ -z "${OTHERMIRROR}" ]; then
						nice ${SBUILD} ${SBUILD_OPTIONS_64} "${DSCFILE}"
					else
						nice ${SBUILD} ${SBUILD_OPTIONS_64} --extra-repository="${OTHERMIRROR}" "${DSCFILE}"
					fi
				}
				[ "x${SKIP_ARCH}" != "xi386" ] && grep -Eqs 'Architecture.*(any|i386)' "${TEMP_DIR}/${PROJECT}/debian/control" && {
					cd "${PKGDIST}/${l_DIST}/${l_CODENAME}/i386"
					tac ${DSCFILE} | while read line; do
						if echo $line | grep -E "^Files:" 1>/dev/null; then break; fi
						# each line contains a file that is part of the src:package
						filename="$(echo $line | cut -d" " -f3-)"
						if [ -n "$filename" ]; then cp "${TEMP_DIR}/${filename}" .; fi
					done
					if [ -z "${OTHERMIRROR}" ]; then
						nice ${SBUILD} ${SBUILD_OPTIONS_32} "${DSCFILE}"
					else
						nice ${SBUILD} ${SBUILD_OPTIONS_32} --extra-repository="${OTHERMIRROR}" "${DSCFILE}"
					fi
				}
			done
		}
	done
	return 0
}

upload_packages() {
	# dupload the new packages to the reprepro repository

	# Do NOT spawn a subshell here.
	# Allow changing global variables in the main process.
	typeset -a deb_build_for_arr
	typeset OLDIFS="${IFS}"
	IFS=" "
	read -a deb_build_for_arr <<< "${DEB_BUILD_FOR}"
	IFS="${OLDIFS}"

	typeset line=""
	for line in "${deb_build_for_arr[@]}"; do
		l_DIST="$(cut -d":" -f1 <<< "${line/: /:}" | tr [:upper:] [:lower:])"
		l_CODENAMES="${CODENAMES:-$(cut -d":" -f2- <<< "${line/: /:}" | sed -e 's/,/ /g' | tr [:upper:] [:lower:])}"
		for l_CODENAME in ${l_CODENAMES}; do

			# in case we build a special CODENAME (squeeze, wheezy, lucid, ...) do skip
			# the wrong distribution here...
			test -z "${CODENAMES}" || grep "${CODENAMES}" <<< "${line}" || break

			for l_ARCH in amd64 i386; do
				[ "x${SKIP_ARCH}" != "x${l_ARCH}" ] && {
					cd "${PKGDIST}/${l_DIST}/${l_CODENAME}/${l_ARCH}"
					test -f "./dupload.conf" || ln -s -- "${HOME}/.dupload.conf.${PREFIX}" "./dupload.conf" && true
					ls -- "${DEBSRCPKG}_"*.changes >/dev/null 2>&1 && dupload --to "${PREFIX}-${l_DIST}-${COMPONENT}" "${DEBSRCPKG}_"*.changes 0<&-
				}
			done
		done
	done
	return 0
}

### MAIN ###
set_vars "$@" && {
	if [ "x$(basename "${0}")" = "x${PREFIX}-build-deb-package" ] || [ "x$(basename "${0}")" = "x${PREFIX}-build+upload-deb-package" ]; then
									# Treat any value other than "no" and "0" as true.
		cd "${PROJECT_DIR}" && pkgneedsbuild "${CHECKOUT}" || ( [ "x${FORCE_BUILD}" != "xno" ] && [ "x${FORCE_BUILD}" != "x0" ] ) && {
			clear_pkgdist
			build_packages
		}
	fi
	if [ "x$(basename "${0}")" = "x${PREFIX}-upload-deb-package" ] || [ "x$(basename "${0}")" = "x${PREFIX}-build+upload-deb-package" ]; then
		upload_packages
	fi
}
