#!/bin/sh

set -e
#set -x

if [ "${1}" = "-u" ] && [ -n "${2}" ] ; then
	ORIG_URL="${2}"
	shift
	shift
fi

if [ -z "${1}" ] ; then
	echo "This tool creates a template Debian package out of a crates.io crate."
	echo "Once this script has run, make sure it did what you expected, then use"
	echo "the newly created source package as a template. Make sure you correct"
	echo "the debian/copyright file before publishing any package."
	echo "It also reports which Rust dependencies are missing from Debian, so"
	echo "that they can be packaged (ex: pkgos-debcrate serde_json)."
	echo "If the upstream git repository is a Cargo workspace (monorepo), a"
	echo "single source package is generated, with one binary package per"
	echo "member crate, built with the dh-rust buildsystem."
	echo "Usage: ${0} [-u URL] crate-name [version]"
	echo "The salsa subgroup of the OpenStack team is guessed from the"
	echo "current directory (the path component after 'openstack')."
	echo "Non interactive environment overrides:"
	echo "  PKGOS_DEBCRATE_TEAM: salsa subgroup (ex: third-party)"
	echo "  PKGOS_DEBCRATE_LICENSE: license string (ex: 'MIT OR Apache-2.0')"
	echo "  PKGOS_DEBCRATE_SOURCE: source package name for workspaces"
	echo "                         (default: rust-<git repo basename>)"
	exit 1
fi

CRATE=${1}
VERSION=${2}
RUN_DIR=$(pwd)
WORK=$(mktemp -d "${TMPDIR:-/tmp}/pkgos-debcrate.XXXXXX")
trap 'rm -rf ${WORK}' EXIT
WARN_FILE=${WORK}/warnings
touch ${WARN_FILE}
WGET_UA="--user-agent=pkgos-debcrate (Debian openstack-pkg-tools)"

if ! echo "${CRATE}" | grep -qE '^[A-Za-z][A-Za-z0-9_-]*$' ; then
	echo "===> Invalid crate name: ${CRATE}"
	exit 1
fi

DASHED=$(echo ${CRATE} | tr '[:upper:]_' '[:lower:]-')
DEB_PKG_NAME=rust-${DASHED}
LIB_PKG_NAME=librust-${DASHED}-dev

if ! which jq >/dev/null 2>&1 ; then
	echo "===> Please install jq before using this tool."
	exit 1
fi

# The crates.io metadata is fetched from the API, unless a <crate>.json
# file is already present in the working directory (ex: previously
# downloaded, or prepared by a test suite).
API_JSON=${CRATE}.json
if ! [ -e ${API_JSON} ] ; then
	echo "===> Querying crates.io metadata for ${CRATE}"
	if ! wget -q "${WGET_UA}" "https://crates.io/api/v1/crates/${CRATE}" -O ${API_JSON} ; then
		if [ -n "${ORIG_URL}" ] ; then
			echo "===> Could not reach crates.io API, continuing anyway (empty metadata)."
			echo '{}' >${API_JSON}
		else
			echo "===> Could not reach crates.io API. Is the crate name correct?"
			exit 1
		fi
	fi
fi

if [ -z "${VERSION}" ] ; then
	VERSION=$(jq --raw-output '[.versions[] | select(.yanked==false)][0].num // ""' ${API_JSON})
	if [ -z "${VERSION}" ] ; then
		echo "===> Could not find a non yanked version for ${CRATE}."
		echo "===> Please pass the version as a second argument."
		exit 1
	fi
fi

DESC=$(jq --raw-output '.crate.description // ""' ${API_JSON})
REPO=$(jq --raw-output '.crate.repository // ""' ${API_JSON})
if [ -z "${REPO}" ] ; then
	REPO="https://crates.io/crates/${CRATE}"
fi

echo "===> Package name will be ${DEB_PKG_NAME} version ${VERSION}"

# Clone the upstream repository, reset it to the tag of the wanted
# version, and create the debian/unstable branch, like pkgos-debpypi
# does. The git tag tree is used as the source for the package. When the
# clone isn't possible, the crate archive is downloaded and imported
# into a fresh git repository instead. An existing clone is reused:
# when run from inside it, when it sits in the current directory, or in
# the parent directory.
echo "===> Cloning the upstream repository"
REPO_BASE_NAME=$(echo "${REPO}" | sed -e 's/\.git$//' -e 's#/$##' -e 's#.*/##')
UP_CLONED=no
CLONE_DIR=""
if [ -z "${ORIG_URL}" ] ; then
	if [ -d .git ] && [ "$(git remote get-url origin 2>/dev/null | sed -e 's/\.git$//' -e 's#/$##' -e 's#.*/##')" = "${REPO_BASE_NAME}" ] ; then
		# Running from inside the packaging clone.
		CLONE_DIR=$(pwd)
		UP_CLONED=yes
	elif [ -d ${DEB_PKG_NAME}/.git ] ; then
		cd ${DEB_PKG_NAME}
		CLONE_DIR=$(pwd)
		UP_CLONED=yes
	elif [ -d ../${DEB_PKG_NAME}/.git ] ; then
		cd ../${DEB_PKG_NAME}
		CLONE_DIR=$(pwd)
		UP_CLONED=yes
	elif ! echo "${REPO}" | grep -q '^https\?://crates\.io/crates/' && git clone "${REPO}" ${DEB_PKG_NAME} 2>>${WARN_FILE} ; then
		cd ${DEB_PKG_NAME}
		CLONE_DIR=$(pwd)
		UP_CLONED=yes
	fi
fi
if [ "${UP_CLONED}" = "yes" ] ; then
	UP_TAG=""
	for CAND in v${VERSION} ${VERSION} ; do
		if git show ${CAND} >/dev/null 2>&1 ; then
			UP_TAG=${CAND}
			break
		fi
	done
fi
if [ "${UP_CLONED}" = "yes" ] && [ -z "${UP_TAG}" ] ; then
	echo "===> The upstream repository has no tag for version ${VERSION} (looked for: v${VERSION}, ${VERSION})."
fi
if [ "${UP_CLONED}" = "yes" ] && [ -n "${UP_TAG}" ] ; then
	echo "===> Configuring the Debian repo from the upstream git (${UP_TAG})"
	git reset --hard ${UP_TAG}
	# gen-orig-xz archives the tag named after the Debian upstream
	# version (with ~ replaced by _): tag the wanted version on top of
	# the upstream tag, like pkgos-debpypi does.
	GIT_TAG=$(echo ${VERSION} | sed 's/~/_/g')
	if [ "${GIT_TAG}" != "${UP_TAG}" ] && ! git show ${GIT_TAG} >/dev/null 2>&1 ; then
		git tag ${GIT_TAG} ${UP_TAG}
	fi
	GIT_CUR_BRANCH=$(git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
	git checkout -q -B debian/unstable
	if [ -n "${GIT_CUR_BRANCH}" ] && [ "${GIT_CUR_BRANCH}" != "debian/unstable" ] ; then
		git branch -D ${GIT_CUR_BRANCH} 2>/dev/null || true
	fi
	echo "  The upstream git tag tree is used as the source: it may differ from the crates.io archive." >>${WARN_FILE}
	SOURCE_DIR=$(pwd)
else
	if [ "${UP_CLONED}" = "yes" ] ; then
		echo "===> No usable upstream tag: importing the crate archive into the upstream repository."
	else
		echo "===> Could not clone the upstream repository: importing the crate archive into git instead."
	fi
	# The crate archive is downloaded and extracted in the directory the
	# tool was started from.
	cd ${RUN_DIR}
	ORIG_URL_DEFAULT="https://static.crates.io/crates/${CRATE}/${CRATE}-${VERSION}.crate"
	CRATE_FILE=${CRATE}-${VERSION}.crate
	if [ -n "${ORIG_URL}" ] ; then
		echo "===> Downloading ${ORIG_URL}"
		wget -nv "${WGET_UA}" "${ORIG_URL}" -O ${CRATE_FILE}
	else
		if [ ! -e ${CRATE_FILE} ] ; then
			echo "===> Downloading ${ORIG_URL_DEFAULT}"
			wget -nv "${WGET_UA}" "${ORIG_URL_DEFAULT}" -O ${CRATE_FILE}
		fi
	fi
	if [ -e ${DEB_PKG_NAME}-${VERSION} ] ; then
		echo "===> Folder ${DEB_PKG_NAME}-${VERSION} already exists, please remove it first."
		exit 1
	fi
	echo "===> Extracting ${CRATE_FILE}"
	tar -xzf ${CRATE_FILE}
	if [ -d ${CRATE}-${VERSION} ] ; then
		mv ${CRATE}-${VERSION} ${DEB_PKG_NAME}-${VERSION}
	elif [ -d $(echo ${CRATE} | tr '-' '_')-${VERSION} ] ; then
		mv $(echo ${CRATE} | tr '-' '_')-${VERSION} ${DEB_PKG_NAME}-${VERSION}
	else
		echo "===> Could not find the top folder of the crate archive."
		exit 1
	fi
	if [ -n "${CLONE_DIR}" ] ; then
		# The clone exists but has no usable tag: replace its tree with
		# the crate content on an upstream-import branch, keeping the
		# upstream history on its own branch. The packaging then
		# happens in the clone, as usual.
		cd ${CLONE_DIR}
		git checkout -q -B upstream-import
		find . -mindepth 1 -maxdepth 1 ! -name .git ! -name ${DEB_PKG_NAME}-${VERSION} -exec rm -rf {} +
		cp -a ${RUN_DIR}/${DEB_PKG_NAME}-${VERSION}/. .
		rm -rf ${RUN_DIR}/${DEB_PKG_NAME}-${VERSION}
		git add -A
		GIT_AUTHOR_NAME="Upstream authors" GIT_AUTHOR_EMAIL=debian-openstack@lists.debian.org \
		GIT_COMMITTER_NAME="Thomas Goirand" GIT_COMMITTER_EMAIL=zigo@debian.org \
		git commit -q -m "Imported upstream version ${VERSION}" || true
		GIT_TAG=$(echo ${VERSION} | sed 's/~/_/g')
		git tag -f ${GIT_TAG}
	else
		cd ${DEB_PKG_NAME}-${VERSION}
		if ! [ -d .git ] ; then
			git init -q
		fi
		git add -A
		git reset -q -- debian 2>/dev/null || true
		GIT_AUTHOR_NAME="Upstream authors" GIT_AUTHOR_EMAIL=debian-openstack@lists.debian.org \
		GIT_COMMITTER_NAME="Thomas Goirand" GIT_COMMITTER_EMAIL=zigo@debian.org \
		git commit -q -m "Imported upstream version ${VERSION}" || true
		GIT_TAG=$(echo ${VERSION} | sed 's/~/_/g')
		git tag ${GIT_TAG} 2>/dev/null || true
	fi
	git checkout -q -B debian/unstable
	SOURCE_DIR=$(pwd)
fi

# This parses a Cargo.toml with a small awk state machine, and outputs:
#  DEP <TAB> section <TAB> name <TAB> realname <TAB> req <TAB> optional <TAB> source <TAB> features <TAB> target-cfg
#  FEATURE <TAB> feature-name
#  BIN
#  INFO <TAB> edition|msrv|license|authors|name|version|description|repository <TAB> value
#  WSFIELD <TAB> version|authors|license|edition|rust-version|description|repository <TAB> value
#  WSDEP <TAB> name <TAB> req <TAB> features
#  MEMBER <TAB> member-path
# Unparseable lines are pushed to the warnings file. Fields inherited
# from a workspace (ex: version.workspace = true) are reported as
# the "@workspace@" value.
parse_cargo_toml () {
	awk '
	BEGIN { sec="" ; tgtsec="" ; dep="" ; dreal="" ; dreq="" ; dopt="no" ; dsrc="crates" ; dfeat="" ; buf="" ; depth=0 ; inmembers=0 }

	function emit_dep () {
		if (dep !~ /^[A-Za-z][A-Za-z0-9_-]*$/) {
			print "WARN\tunparseable dependency name: " dep > "/dev/stderr"
		} else {
			print "DEP\t" sec "\t" dep "\t" dreal "\t" dreq "\t" dopt "\t" dsrc "\t" dfeat "\t" tgtsec
		}
		dep="" ; dreal="" ; dreq="" ; dopt="no" ; dsrc="crates" ; dfeat=""
	}

	/^[[:space:]]*#/ { next }
	/^[[:space:]]*$/ { next }

	# Multiline inline table accumulation.
	{
		line=$0
		if (depth>0) {
			buf=buf" "line
			depth=gsub(/\{/,"{",buf)-gsub(/\}/,"}",buf)
			if (depth>0) next
			line=buf ; buf="" ; depth=0
		} else if (line ~ /= *\{[^}]*$/) {
			buf=line
			depth=gsub(/\{/,"{",buf)-gsub(/\}/,"}",buf)
			if (depth>0) next
			line=buf ; buf=""
		}
	}

	# Section headers.
	/^\[\[/ {
		if (dep!="") { emit_dep() ; dep="" }
		if ($0 ~ /^\[\[[Bb]in\]\]/) print "BIN"
		sec="other" ; next
	}
	/^\[/ {
		if (dep!="") { emit_dep() ; dep="" }
		s=$0 ; gsub(/[\[\]]/,"",s) ; gsub(/"/,"",s) ; gsub(sprintf("%c",39),"",s)
		if (s ~ /^target\..*\.(dev-)?(build-)?dependencies$/) { sec="target" ; tgtsec=s ; next }
		if (s ~ /^(dev-)?(build-)?dependencies$/) { sec=s ; next }
		if (s ~ /^target\..*\.(dev-)?(build-)?dependencies\./) {
			dep=s ; sub(/^.*\./,"",dep)
			dreal=dep ; dreq="" ; dopt="no" ; dsrc="crates" ; dfeat=""
			sec="targetblock" ; tgtsec=s ; next
		}
		if (s ~ /^dev-dependencies\./) {
			dep=s ; sub(/^.*\./,"",dep)
			dreal=dep ; dreq="" ; dopt="no" ; dsrc="crates" ; dfeat=""
			sec="depblock-dev" ; next
		}
		if (s ~ /^build-dependencies\./) {
			dep=s ; sub(/^.*\./,"",dep)
			dreal=dep ; dreq="" ; dopt="no" ; dsrc="crates" ; dfeat=""
			sec="depblock-build" ; next
		}
		if (s ~ /^dependencies\./) {
			dep=s ; sub(/^.*\./,"",dep)
			dreal=dep ; dreq="" ; dopt="no" ; dsrc="crates" ; dfeat=""
			sec="depblock" ; next
		}
		if (s=="features") { sec="features" ; next }
		if (s=="package") { sec="package" ; next }
		if (s=="workspace.package") { sec="wspackage" ; next }
		if (s=="workspace.dependencies") { sec="wsdeps" ; next }
		if (s=="workspace") { sec="workspace" ; next }
		sec="other" ; next
	}

	sec=="workspace" {
		if (inmembers) {
			l=$0
			if (l ~ /\]/) { inmembers=0 ; gsub(/\].*/,"",l) }
			gsub(/"/,"",l) ; gsub(/,/," ",l) ; gsub(/^[[:space:]]+|[[:space:]]+$/,"",l)
			n=split(l, parts, /[[:space:]]+/)
			for (i=1 ; i<=n ; i++) if (parts[i] != "") print "MEMBER\t" parts[i]
			next
		}
		if ($0 ~ /(^|[[:space:]])members[[:space:]]*=[[:space:]]*\[/) {
			l=$0 ; sub(/.*\[/,"",l)
			if (l ~ /\]/) {
				gsub(/\].*/,"",l) ; gsub(/"/,"",l) ; gsub(/,/," ",l)
				n=split(l, parts, /[[:space:]]+/)
				for (i=1 ; i<=n ; i++) if (parts[i] != "") print "MEMBER\t" parts[i]
			} else {
				inmembers=1
				gsub(/"/,"",l) ; gsub(/,/," ",l)
				n=split(l, parts, /[[:space:]]+/)
				for (i=1 ; i<=n ; i++) if (parts[i] != "") print "MEMBER\t" parts[i]
			}
		}
		next
	}

	sec=="wspackage" && /=/ {
		if ($0 ~ /(^|[[:space:]])version[[:space:]]*=/) { v=$0 ; sub(/.*version[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "WSFIELD\tversion\t" v }
		if ($0 ~ /(^|[[:space:]])edition[[:space:]]*=/) { v=$0 ; sub(/.*edition[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "WSFIELD\tedition\t" v }
		if ($0 ~ /(^|[[:space:]])rust-version[[:space:]]*=/) { v=$0 ; sub(/.*rust-version[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "WSFIELD\trust-version\t" v }
		if ($0 ~ /(^|[[:space:]])license[[:space:]]*=/) { v=$0 ; sub(/.*license[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "WSFIELD\tlicense\t" v }
		if ($0 ~ /(^|[[:space:]])description[[:space:]]*=/) { v=$0 ; sub(/.*description[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "WSFIELD\tdescription\t" v }
		if ($0 ~ /(^|[[:space:]])repository[[:space:]]*=/) { v=$0 ; sub(/.*repository[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "WSFIELD\trepository\t" v }
		if ($0 ~ /(^|[[:space:]])authors[[:space:]]*=/) { v=$0 ; sub(/.*authors[[:space:]]*=[[:space:]]*\[/,"",v) ; sub(/\].*/,"",v) ; gsub(/"/,"",v) ; print "WSFIELD\tauthors\t" v }
		next
	}

	sec=="wsdeps" && /=/ {
		dep=$1 ; sub(/=.*/,"",dep) ; sub(/[[:space:]]+$/,"",dep)
		rest=$0 ; sub(/^[^=]*=/,"",rest) ; sub(/^[[:space:]]*/,"",rest)
		dreq="" ; dfeat="" ; dpath=""
		if (rest ~ /^\{/) {
			if (rest ~ /version[[:space:]]*=/) { dreq=rest ; sub(/.*version[[:space:]]*=[[:space:]]*"/,"",dreq) ; sub(/".*/,"",dreq) }
			if (rest ~ /path[[:space:]]*=/) { dpath=rest ; sub(/.*path[[:space:]]*=[[:space:]]*"/,"",dpath) ; sub(/".*/,"",dpath) }
			if (rest ~ /features[[:space:]]*=/) { dfeat=rest ; sub(/.*features[[:space:]]*=[[:space:]]*\[/,"",dfeat) ; sub(/\].*/,"",dfeat) ; gsub(/"/,"",dfeat) ; gsub(/[[:space:]]/,"",dfeat) }
		} else {
			dreq=rest ; gsub(/"/,"",dreq) ; gsub(/[[:space:]]/,"",dreq)
		}
		if (dep ~ /^[A-Za-z][A-Za-z0-9_-]*$/) print "WSDEP\t" dep "\t" dreq "\t" dfeat "\t" dpath
		next
	}

	sec=="features" && /=/ {
		f=$1 ; sub(/=.*/,"",f)
		if (f ~ /^[A-Za-z][A-Za-z0-9_-]*$/) print "FEATURE\t" f
		next
	}

	sec=="package" && /=/ {
		if ($0 ~ /(^|[[:space:]])edition\.workspace[[:space:]]*=/) { print "INFO\tedition\t@workspace@" }
		else if ($0 ~ /(^|[[:space:]])edition[[:space:]]*=/) { v=$0 ; sub(/.*edition[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "INFO\tedition\t" v }
		if ($0 ~ /(^|[[:space:]])rust-version\.workspace[[:space:]]*=/) { print "INFO\tmsrv\t@workspace@" }
		else if ($0 ~ /(^|[[:space:]])rust-version[[:space:]]*=/) { v=$0 ; sub(/.*rust-version[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "INFO\tmsrv\t" v }
		if ($0 ~ /(^|[[:space:]])license\.workspace[[:space:]]*=/) { print "INFO\tlicense\t@workspace@" }
		else if ($0 ~ /(^|[[:space:]])license[[:space:]]*=/) { v=$0 ; sub(/.*license[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "INFO\tlicense\t" v }
		if ($0 ~ /(^|[[:space:]])authors\.workspace[[:space:]]*=/) { print "INFO\tauthors\t@workspace@" }
		else if ($0 ~ /(^|[[:space:]])authors[[:space:]]*=/) { v=$0 ; sub(/.*authors[[:space:]]*=[[:space:]]*\[/,"",v) ; sub(/\].*/,"",v) ; gsub(/"/,"",v) ; print "INFO\tauthors\t" v }
		if ($0 ~ /(^|[[:space:]])name[[:space:]]*=/) { v=$0 ; sub(/.*name[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "INFO\tname\t" v }
		if ($0 ~ /(^|[[:space:]])version\.workspace[[:space:]]*=/) { print "INFO\tversion\t@workspace@" }
		else if ($0 ~ /(^|[[:space:]])version[[:space:]]*=/) { v=$0 ; sub(/.*version[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "INFO\tversion\t" v }
		if ($0 ~ /(^|[[:space:]])description\.workspace[[:space:]]*=/) { print "INFO\tdescription\t@workspace@" }
		else if ($0 ~ /(^|[[:space:]])description[[:space:]]*=/) { v=$0 ; sub(/.*description[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "INFO\tdescription\t" v }
		if ($0 ~ /(^|[[:space:]])repository\.workspace[[:space:]]*=/) { print "INFO\trepository\t@workspace@" }
		else if ($0 ~ /(^|[[:space:]])repository[[:space:]]*=/) { v=$0 ; sub(/.*repository[[:space:]]*=[[:space:]]*"/,"",v) ; sub(/".*/,"",v) ; print "INFO\trepository\t" v }
		next
	}

	(sec=="dependencies" || sec=="dev-dependencies" || sec=="build-dependencies" || sec=="target" || sec=="targetblock" || sec=="depblock" || sec=="depblock-dev" || sec=="depblock-build") && /=/ {
		if (sec=="depblock" || sec=="targetblock" || sec=="depblock-dev" || sec=="depblock-build") {
			if ($0 ~ /(^|[[:space:]])version[[:space:]]*=/) { dreq=$0 ; sub(/.*version[[:space:]]*=[[:space:]]*"/,"",dreq) ; sub(/".*/,"",dreq) }
			if ($0 ~ /(^|[[:space:]])optional[[:space:]]*=[[:space:]]*true/) dopt="yes"
			if ($0 ~ /(^|[[:space:]])package[[:space:]]*=/) { dreal=$0 ; sub(/.*package[[:space:]]*=[[:space:]]*"/,"",dreal) ; sub(/".*/,"",dreal) }
			if ($0 ~ /(^|[[:space:]])(git|path)[[:space:]]*=/) dsrc="gitpath"
			if ($0 ~ /(^|[[:space:]])features[[:space:]]*=/) { dfeat=$0 ; sub(/.*features[[:space:]]*=[[:space:]]*\[/,"",dfeat) ; sub(/\].*/,"",dfeat) ; gsub(/"/,"",dfeat) ; gsub(/[[:space:]]/,"",dfeat) }
			if ($0 ~ /(^|[[:space:]])workspace[[:space:]]*=[[:space:]]*true/) dsrc="workspace"
			next
		}
		dep=$1 ; sub(/=.*/,"",dep) ; sub(/[[:space:]]+$/,"",dep)
		rest=$0 ; sub(/^[^=]*=/,"",rest) ; sub(/^[[:space:]]*/,"",rest)
		dreal=dep ; dreq="" ; dopt="no" ; dsrc="crates" ; dfeat=""
		if (rest ~ /^\{/) {
			if (rest ~ /package[[:space:]]*=/) { dreal=rest ; sub(/.*package[[:space:]]*=[[:space:]]*"/,"",dreal) ; sub(/".*/,"",dreal) }
			if (rest ~ /version[[:space:]]*=/) { dreq=rest ; sub(/.*version[[:space:]]*=[[:space:]]*"/,"",dreq) ; sub(/".*/,"",dreq) }
			if (rest ~ /optional[[:space:]]*=[[:space:]]*true/) dopt="yes"
			if (rest ~ /(^|[[:space:]])(git|path)[[:space:]]*=/) dsrc="gitpath"
			if (rest ~ /workspace[[:space:]]*=[[:space:]]*true/) dsrc="workspace"
			if (rest ~ /features[[:space:]]*=/) { dfeat=rest ; sub(/.*features[[:space:]]*=[[:space:]]*\[/,"",dfeat) ; sub(/\].*/,"",dfeat) ; gsub(/"/,"",dfeat) ; gsub(/[[:space:]]/,"",dfeat) }
		} else {
			dreq=rest ; gsub(/"/,"",dreq) ; gsub(/[[:space:]]/,"",dreq)
		}
		emit_dep()
		next
	}

	END { if (dep!="") emit_dep() }
	' ${1:-Cargo.toml}
}

# Small hardcoded map of -sys crates which need system build dependencies.
sys_build_dep () {
	SYS_NAME=${1}
	SYS_FEATURES=${2}
	case ${SYS_NAME} in
	openssl-sys)
		if ! echo "${SYS_FEATURES}" | grep -q 'vendored' ; then echo libssl-dev ; fi ;;
	libz-sys)
		if ! echo "${SYS_FEATURES}" | grep -q 'static' ; then echo zlib1g-dev ; fi ;;
	libsqlite3-sys)
		if ! echo "${SYS_FEATURES}" | grep -q 'bundled' ; then echo libsqlite3-dev ; fi ;;
	onig-sys)
		echo libonig-dev ;;
	krb5-sys)
		echo libkrb5-dev ;;
	rdkafka-sys)
		if echo "${SYS_FEATURES}" | grep -q 'cmake_build' ; then echo cmake ; fi ;;
	esac
}

first_number () {
	echo "${1}" | sed -n 's/^[^0-9]*\([0-9][0-9]*\(\.[0-9][0-9]*\)*\).*/\1/p'
}

# Compares a cargo version requirement against the version packaged in Debian.
# Prints OK, BUMP (the crate needs a newer major than Debian, a version bump
# of the Debian package is needed) or OLDER (the crate needs an older major
# than Debian, a parallel package is needed).
check_dep_compat () {
	REQ=${1}
	DEBVER=${2}
	if [ -z "${REQ}" ] || [ "${DEBVER}" = "NONE" ] ; then
		echo OK
		return
	fi
	RNUM=$(first_number "${REQ}")
	if [ -z "${RNUM}" ] ; then
		echo OK
		return
	fi
	RMAJ=$(echo ${RNUM} | cut -d. -f1)
	RMIN=$(echo ${RNUM} | cut -s -d. -f2)
	DMAJ=$(echo ${DEBVER} | cut -d. -f1)
	DMIN=$(echo ${DEBVER} | cut -s -d. -f2)
	if [ "${RMAJ}" != "${DMAJ}" ] ; then
		if [ "${RMAJ}" -gt "${DMAJ}" ] 2>/dev/null ; then
			echo BUMP
		else
			echo OLDER
		fi
		return
	fi
	if [ "${RMAJ}" = "0" ] && [ -n "${RMIN}" ] && [ -n "${DMIN}" ] && [ "${RMIN}" != "${DMIN}" ] ; then
		if [ "${RMIN}" -gt "${DMIN}" ] 2>/dev/null ; then
			echo BUMP
		else
			echo OLDER
		fi
		return
	fi
	echo OK
}

get_apt_list () {
	if [ -z "${APT_LIST_FILE}" ] ; then
		APT_LIST_FILE=${WORK}/aptlist
		apt-cache pkgnames librust- 2>/dev/null >${APT_LIST_FILE} || touch ${APT_LIST_FILE}
	fi
}

# Prints the Debian librust binary package name matching a crate, or nothing.
debian_librust_name () {
	get_apt_list
	D=$(echo ${1} | tr '[:upper:]_' '[:lower:]-')
	grep -E "^librust-${D}(-[0-9][0-9.]+)?-dev$" ${APT_LIST_FILE} | sort | head -n1
}

# Prints the upstream version of a crate as packaged in Debian, or NONE.
debian_upstream_version () {
	get_apt_list
	PKG=$(debian_librust_name ${1})
	if [ -z "${PKG}" ] ; then
		echo NONE
		return
	fi
	apt-cache policy ${PKG} 2>/dev/null | sed -n 's/^ *Candidate: //p' | head -n1 | sed -e 's/-[^-]*$//' -e 's/+.*$//'
}

# Debian API version (major, or 0.<minor> for pre-1.0 releases) of a
# version string.
deb_apiv () {
	MAJ=$(echo ${1} | cut -d. -f1)
	MIN=$(echo ${1} | cut -s -d. -f2)
	if [ "${MAJ}" = "0" ] && [ -n "${MIN}" ] ; then
		echo "0.${MIN}"
	else
		echo "${MAJ}"
	fi
}

MISSING=${WORK}/missing
BUMPS=${WORK}/bumps
OLDER=${WORK}/older
TARGET_FILE=${WORK}/target_deps
BD_ALL=${WORK}/bd_all
DEV_ALL=${WORK}/dev_deps
SYS_ALL=${WORK}/sys_deps
touch ${MISSING} ${BUMPS} ${OLDER} ${TARGET_FILE} ${BD_ALL} ${DEV_ALL} ${SYS_ALL}

# Adds a dependency to the global Build-Depends set. $1 = dep line.
add_bd () {
	if ! grep -qx "${1}" ${BD_ALL} ; then
		echo "${1}" >>${BD_ALL}
	fi
}

# Parses a member manifest and resolves its package fields, honoring
# workspace inheritance.
# $1 = member directory (relative to the source root), $2 = member index.
# Emits the member data files: m<idx>.name/.version/.description/.license/
# .authors/.edition/.msrv/.repository/.features/.bin
parse_member_fields () {
	MEMBER_DIR=${1}
	MIDX=${2}
	parse_cargo_toml "${SOURCE_DIR}/${MEMBER_DIR}/Cargo.toml" >${WORK}/m${MIDX}.tsv 2>>${WARN_FILE}

	# Resolved package fields, with workspace inheritance.
	for FIELD in name version description license authors edition msrv repository ; do
		VAL=$(awk -F'\t' -v F=${FIELD} '$1=="INFO" && $2==F {print $3; exit}' ${WORK}/m${MIDX}.tsv)
		if [ "${VAL}" = "@workspace@" ] ; then
			VAL=$(awk -F'\t' -v F=${FIELD} '$1=="WSFIELD" && $2==F {print $3; exit}' ${WORK}/root.tsv)
		fi
		echo "${VAL}" >${WORK}/m${MIDX}.${FIELD}
	done
	if [ -z "$(cat ${WORK}/m${MIDX}.name)" ] ; then
		echo "$(basename ${MEMBER_DIR})" >${WORK}/m${MIDX}.name
	fi

	awk -F'\t' '$1=="FEATURE" {print $2}' ${WORK}/m${MIDX}.tsv | sort -u >${WORK}/m${MIDX}.features
	if awk -F'\t' '$1=="BIN" {found=1; exit} END {exit !found}' ${WORK}/m${MIDX}.tsv ; then
		echo yes >${WORK}/m${MIDX}.bin
	else
		echo no >${WORK}/m${MIDX}.bin
	fi
	touch ${WORK}/m${MIDX}.deps

	MNAME=$(cat ${WORK}/m${MIDX}.name)
	MVERSION=$(cat ${WORK}/m${MIDX}.version)
	if [ -n "${MVERSION}" ] && [ "${MVERSION}" != "${VERSION}" ] ; then
		echo "  ${MNAME}: crate version ${MVERSION} differs from the packaged version ${VERSION}." >>${WARN_FILE}
	fi
}

# Processes the dependencies of a member. The resulting librust-<dep>-dev
# lines are added to the member Depends (m<idx>.deps) for normal and
# build dependencies, and to the global Build-Depends set, with the
# <!nocheck> profile for dev dependencies.
# $1 = member directory, $2 = member index.
process_member_deps () {
	MEMBER_DIR=${1}
	MIDX=${2}
	MNAME=$(cat ${WORK}/m${MIDX}.name)

	while IFS=	read -r LINE ; do
		KIND=$(echo "${LINE}" | cut -f1)
		case ${KIND} in
		DEP)
			SEC=$(echo "${LINE}" | cut -f2)
			DEP=$(echo "${LINE}" | cut -f3)
			REAL=$(echo "${LINE}" | cut -f4)
			REQ=$(echo "${LINE}" | cut -f5)
			OPT=$(echo "${LINE}" | cut -f6)
			SRC=$(echo "${LINE}" | cut -f7)
			FEAT=$(echo "${LINE}" | cut -f8)
			TGT=$(echo "${LINE}" | cut -f9)
			# Workspace inherited dependencies are resolved from
			# the root [workspace.dependencies] table. When that
			# entry is a path dependency pointing at a member of
			# this workspace, it is internal to the source
			# package.
			if [ "${SRC}" = "workspace" ] ; then
				WSLINE=$(awk -F'\t' -v D=${DEP} '$1=="WSDEP" && $2==D {print; exit}' ${WORK}/root.tsv)
				if [ -n "${WSLINE}" ] ; then
					REQ=$(echo "${WSLINE}" | cut -f3)
					WSFEAT=$(echo "${WSLINE}" | cut -f4)
					WSPATH=$(echo "${WSLINE}" | cut -f5)
					if [ -n "${FEAT}" ] ; then
						FEAT="${WSFEAT},${FEAT}"
					else
						FEAT=${WSFEAT}
					fi
					WSPATH=${WSPATH#./}
					if [ -n "${WSPATH}" ] && echo "${WS_MEMBERS}" | tr ' ' '\n' | grep -qx "${WSPATH}" ; then
						SRC=sibling
					else
						SRC=crates
					fi
				fi
			fi
			# Path dependencies pointing to sibling members are
			# internal to this source package.
			if [ "${SRC}" = "gitpath" ] ; then
				if echo "${MEMBER_NAMES}" | tr ' ' '\n' | grep -qx "${REAL}" ; then
					SRC=sibling
				else
					echo "  ${MNAME}: dependency ${DEP} is pulled from git or a path, not from crates.io" >>${WARN_FILE}
					continue
				fi
			fi
			case ${SEC} in
			target|targetblock)
				case ${TGT} in
				*unix*|*linux*)
					emit_dep_line "${MIDX}" "${MNAME}" "dependencies" "${REAL}" "${REQ}" "${FEAT}" "${SRC}" ;;
				*)
					if ! grep -qx "  ${MNAME}: target specific dependency ${DEP} (${TGT}), not added to debian/control" ${TARGET_FILE} ; then
						echo "  ${MNAME}: target specific dependency ${DEP} (${TGT}), not added to debian/control" >>${TARGET_FILE}
					fi ;;
				esac
				;;
			*)
				emit_dep_line "${MIDX}" "${MNAME}" "${SEC}" "${REAL}" "${REQ}" "${FEAT}" "${SRC}" ;;
			esac
			;;
		esac
	done <${WORK}/m${MIDX}.tsv
}

# Processes one dependency of a member. The resulting librust-<dep>-dev
# line is added to the member Depends (m<idx>.deps) for normal and build
# dependencies, and to the global Build-Depends set, with the <!nocheck>
# profile for dev dependencies.
emit_dep_line () {
	MIDX=${1}
	MNAME=${2}
	SEC=${3}
	REAL=${4}
	REQ=${5}
	FEAT=${6}
	SRC=${7}
	DEBDASH=$(echo ${REAL} | tr '[:upper:]_' '[:lower:]-')
	SYSDEP=$(sys_build_dep "${REAL}" "${FEAT}")
	if [ -n "${SYSDEP}" ] ; then
		if ! grep -qx "${SYSDEP}" ${SYS_ALL} ; then
			echo ${SYSDEP} >>${SYS_ALL}
			add_bd "${SYSDEP}"
		fi
	fi
	# Internal dependency between members of this source package: the
	# workspace resolves it locally, so it must never appear in the
	# Build-Depends (that would be a self dependency). It only shows
	# up in the member's binary Depends, for consumers, except for
	# dev dependencies which aren't runtime dependencies at all.
	if [ "${SRC}" = "sibling" ] ; then
		case ${SEC} in
		dev-dependencies|depblock-dev)
			;;
		*)
			DEPLINE="librust-${DEBDASH}-dev"
			if ! grep -qx "${DEPLINE}" ${WORK}/m${MIDX}.deps ; then
				echo "${DEPLINE}" >>${WORK}/m${MIDX}.deps
			fi ;;
		esac
		return
	fi
	DEBVER=$(debian_upstream_version ${REAL})
	COMPAT=$(check_dep_compat "${REQ}" "${DEBVER}")
	case ${COMPAT} in
	BUMP)
		echo "  ${DEBPKG_NAME}: ${MNAME} needs ${REAL} version ${REQ}, Debian has ${DEBVER} (needs a Debian version bump)" >>${BUMPS} ;;
	OLDER)
		# The dependency needs an older major than Debian: pin the
		# API version so that the parallel package is used.
		APIV=$(deb_apiv "$(first_number "${REQ}")")
		echo "  librust-${DEBDASH}-dev: ${MNAME} needs ${REAL} ${REQ}, Debian has ${DEBVER} (package an older major as librust-${DEBDASH}-${APIV}-dev)" >>${OLDER} ;;
	esac
	if [ "${COMPAT}" = "OLDER" ] ; then
		APIV=$(deb_apiv "$(first_number "${REQ}")")
		DEPLINE="librust-${DEBDASH}-${APIV}-dev"
	else
		DEPLINE="librust-${DEBDASH}-dev"
	fi
	if [ -z "$(debian_librust_name ${REAL})" ] && [ "${COMPAT}" != "OLDER" ] ; then
		if ! grep -qx "  librust-${DEBDASH}-dev" ${MISSING} ; then
			echo "  librust-${DEBDASH}-dev" >>${MISSING}
		fi
	fi
	case ${SEC} in
	dev-dependencies|depblock-dev)
		if ! grep -qx "${DEPLINE} <!nocheck>" ${DEV_ALL} ; then
			echo "${DEPLINE} <!nocheck>" >>${DEV_ALL}
		fi ;;
	*)
		if ! grep -qx "${DEPLINE}" ${WORK}/m${MIDX}.deps ; then
			echo "${DEPLINE}" >>${WORK}/m${MIDX}.deps
		fi
		add_bd "${DEPLINE}" ;;
	esac
}

echo "===> Parsing Cargo.toml"
parse_cargo_toml "${SOURCE_DIR}/Cargo.toml" >${WORK}/root.tsv 2>>${WARN_FILE}

# Detect a Cargo workspace: the root manifest declares members. The root
# package itself (when the root isn't virtual) is also a member.
WS_MEMBERS=$(awk -F'\t' '$1=="MEMBER" {print $2}' ${WORK}/root.tsv)
ROOT_HAS_PKG=$(awk -F'\t' '$1=="INFO" && $2=="name" {print $3; exit}' ${WORK}/root.tsv)

if [ -n "${WS_MEMBERS}" ] ; then
	# Expand member globs against the filesystem, keeping only dirs
	# holding a Cargo.toml.
	EXPANDED=""
	for M in ${WS_MEMBERS} ; do
		case ${M} in
		*[*?]*)
			for D in ${SOURCE_DIR}/${M} ; do
				if [ -e "${D}/Cargo.toml" ] ; then
					EXPANDED="${EXPANDED} ${D#${SOURCE_DIR}/}"
				fi
			done ;;
		*)
			if [ -e "${SOURCE_DIR}/${M}/Cargo.toml" ] ; then
				EXPANDED="${EXPANDED} ${M}"
			else
				echo "  Workspace member ${M} has no Cargo.toml, skipping." >>${WARN_FILE}
			fi ;;
		esac
	done
	if [ -n "${ROOT_HAS_PKG}" ] ; then
		EXPANDED=". ${EXPANDED}"
	fi
	# A single member workspace is processed as a plain single crate.
	NB=$(echo ${EXPANDED} | wc -w)
	if [ "${NB}" -le 1 ] ; then
		WS_MEMBERS=""
	else
		WS_MEMBERS=$(echo ${EXPANDED} | sed 's/^ *//;s/ *$//')
	fi
fi

if [ -n "${WS_MEMBERS}" ] ; then
	echo "===> Cargo workspace detected with $(echo ${WS_MEMBERS} | wc -w) members"
	# The source package is named after the git repository, like
	# upstream, unless overridden.
	if [ -n "${PKGOS_DEBCRATE_SOURCE}" ] ; then
		SRC_PKG=${PKGOS_DEBCRATE_SOURCE}
	else
		REPO_BASE=$(echo "${REPO}" | sed -e 's/\.git$//' -e 's#/$##' -e 's#.*/##')
		SRC_PKG=rust-${REPO_BASE}
	fi
else
	WS_MEMBERS="."
	SRC_PKG=${DEB_PKG_NAME}
fi

# Name of the source package (used in the warnings below).
DEBPKG_NAME=${SRC_PKG}

echo "===> Processing $(echo ${WS_MEMBERS} | wc -w) crate(s)"
MIDX=0
MEMBER_NAMES=""
for M in ${WS_MEMBERS} ; do
	parse_member_fields "${M}" "${MIDX}"
	MEMBER_NAMES="${MEMBER_NAMES} $(cat ${WORK}/m${MIDX}.name)"
	MIDX=$((MIDX + 1))
done

# The requested crate must be part of the source package.
FOUND=no
MIDX=0
for M in ${WS_MEMBERS} ; do
	if [ "$(cat ${WORK}/m${MIDX}.name)" = "${CRATE}" ] ; then
		FOUND=yes
		MAIN_IDX=${MIDX}
	fi
	MIDX=$((MIDX + 1))
done
if [ "${FOUND}" != "yes" ] ; then
	echo "===> The crate ${CRATE} isn't part of this source package. Members are:"
	MIDX=0
	for M in ${WS_MEMBERS} ; do
		echo "     $(cat ${WORK}/m${MIDX}.name) ($(cat ${WORK}/m${MIDX}.version))"
		MIDX=$((MIDX + 1))
	done
	exit 1
fi
echo "===> Main crate: ${CRATE} (source package: ${SRC_PKG})"

# Second pass: dependencies, now that all member names are known.
MIDX=0
for M in ${WS_MEMBERS} ; do
	process_member_deps "${M}" "${MIDX}"
	MIDX=$((MIDX + 1))
done

MAIN_DESC=$(cat ${WORK}/m${MAIN_IDX}.description)
if [ -n "${MAIN_DESC}" ] ; then
	DESC=${MAIN_DESC}
fi
if [ -z "${DESC}" ] ; then
	DESC="TODO: add a description for the ${CRATE} crate"
fi

# License handling. Uses the crates.io license string when it can be parsed.
normalize_license () {
	echo "${1}" | sed -e 's/ WITH [A-Za-z0-9_.-]*//' -e 's/[[:space:]]*\(OR\|\/\)[[:space:]]*/ /g' | tr '[:lower:]_' '[:upper:]-'
}

map_license () {
	case ${1} in
	MIT) echo Expat ;;
	APACHE-2.0|APACHE-2|APACHE) echo Apache-2.0 ;;
	BSD-2-CLAUSE|BSD-2) echo BSD-2-clause ;;
	BSD-3-CLAUSE|BSD-3) echo BSD-3-clause ;;
	ISC) echo ISC ;;
	ZLIB) echo Zlib ;;
	MPL-2.0|MPL2.0) echo MPL-2.0 ;;
	GPL-2.0|GPL-2.0-ONLY|GPL-2) echo GPL-2+ ;;
	GPL-3.0|GPL-3.0-ONLY|GPL-3) echo GPL-3+ ;;
	LGPL-2.1|LGPL-2.1-ONLY) echo LGPL-2.1 ;;
	LGPL-3.0|LGPL-3.0-ONLY|LGPL-3) echo LGPL-3+ ;;
	*) echo TODO ;;
	esac
}

# Resolves the Debian license titles of a member: from the environment
# override, then from the license string of the manifest (or the crates.io
# metadata for the requested crate of a single-crate package). Prints the
# titles.
member_license_titles () {
	MLIDX=${1}
	MLICENSE=$(cat ${WORK}/m${MLIDX}.license)
	if [ "${MLIDX}" = "${MAIN_IDX}" ] && [ -n "${PKGOS_DEBCRATE_LICENSE}" ] ; then
		MLICENSE=${PKGOS_DEBCRATE_LICENSE}
	elif [ "${MLIDX}" = "${MAIN_IDX}" ] && [ -z "${MLICENSE}" ] && [ "$(echo ${WS_MEMBERS} | wc -w)" = "1" ] ; then
		MLICENSE=$(jq --raw-output "[.versions[] | select(.num==\"${VERSION}\")][0].license // \"\"" ${API_JSON} 2>/dev/null)
	fi
	TITLES=""
	if [ -n "${MLICENSE}" ] ; then
		for TOKEN in $(normalize_license "${MLICENSE}") ; do
			MAPPED=$(map_license ${TOKEN})
			if [ "${MAPPED}" = "TODO" ] ; then
				TITLES=""
				break
			fi
			TITLES="${TITLES} ${MAPPED}"
		done
		TITLES=$(echo ${TITLES} | sed 's/^ *//;s/ *$//')
	fi
	if [ -z "${TITLES}" ] ; then
		if [ -n "${PKGOS_DEBCRATE_LICENSE}" ] ; then
			TITLES=TODO
		elif [ "${MLIDX}" = "${MAIN_IDX}" ] ; then
			echo "===> Could not detect the license of this crate (Cargo.toml says: ${MLICENSE:-unknown})"
			CHOSEN=$(pkgos-choose "What is the license of this crate?" "Apache-2.0" "BSD-2-clause" "BSD-3-clause" "Expat" "GPL-2+" "GPL-3+" "LGPL-2.1" "LGPL-3+" "Other")
			case ${CHOSEN} in
			Other) TITLES=TODO ;;
			*) TITLES=${CHOSEN} ;;
			esac
		else
			echo "  $(cat ${WORK}/m${MLIDX}.name): could not detect the license (Cargo.toml says: ${MLICENSE:-unknown}), set to TODO." >>${WARN_FILE}
			TITLES=TODO
		fi
	fi
	echo "${TITLES}"
}

echo "===> Detecting licenses"
MIDX=0
for M in ${WS_MEMBERS} ; do
	member_license_titles ${MIDX} >${WORK}/m${MIDX}.titles
	if [ -s ${WORK}/m${MIDX}.titles ] ; then
		echo "     $(cat ${WORK}/m${MIDX}.name): $(cat ${WORK}/m${MIDX}.titles)"
	fi
	MIDX=$((MIDX + 1))
done
LICENSE_TITLES=$(cat ${WORK}/m${MAIN_IDX}.titles)
echo "===> License: ${LICENSE_TITLES}"

# Salsa location. The subgroup is double-guessed from the directory the
# script was started from: when working under .../openstack/<subgroup>/,
# the crate is packaged for that subgroup. PKGOS_DEBCRATE_TEAM overrides
# the guess, and if nothing can be guessed, the user is prompted.
if [ -n "${PKGOS_DEBCRATE_TEAM}" ] ; then
	SALSA_SUBGROUP=${PKGOS_DEBCRATE_TEAM}
else
	SALSA_SUBGROUP=$(echo "${RUN_DIR}" | awk -F/ '{for(i=1;i<NF;i++) if ($i=="openstack") {print $(i+1); exit}}')
	if [ -n "${SALSA_SUBGROUP}" ] ; then
		echo "===> Guessing the salsa subgroup from the current directory: ${SALSA_SUBGROUP}"
	else
		echo "===> For which salsa subgroup of the OpenStack team should this be packaged?"
		echo -n "Subgroup [third-party]: "
		read SALSA_SUBGROUP
		if [ -z "${SALSA_SUBGROUP}" ] ; then
			SALSA_SUBGROUP=third-party
		fi
	fi
fi
VCS_URL=https://salsa.debian.org/openstack-team/${SALSA_SUBGROUP}/${SRC_PKG}

echo "===> Creating debian folder for ${SRC_PKG}"
if ! [ -d debian/source ] ; then
	mkdir -p debian/source
fi

# debian/control, built with the dh-rust buildsystem, one binary package
# per member crate.
echo "===> Creating debian/control"
BD_SORTED=$(sort ${BD_ALL} | tr '\n' ',')
BD_SORTED=$(echo "${BD_SORTED}" | sed 's/,$//')
DEV_SORTED=$(sort ${DEV_ALL} | tr '\n' ',')
DEV_SORTED=$(echo "${DEV_SORTED}" | sed 's/,$//')
SYS_SORTED=$(sort ${SYS_ALL} | tr '\n' ',')
SYS_SORTED=$(echo "${SYS_SORTED}" | sed 's/,$//')
{
	echo "Source: ${SRC_PKG}"
	echo "Section: rust"
	echo "Maintainer: Debian OpenStack <team+openstack@tracker.debian.org>"
	echo "Uploaders:"
	echo " Thomas Goirand <zigo@debian.org>,"
	echo "Build-Depends:"
	echo " debhelper-compat (= 14),"
	echo " dh-sequence-rust,"
	if [ -n "${SYS_SORTED}" ] ; then
		echo " ${SYS_SORTED},"
	fi
	if [ -n "${BD_SORTED}" ] ; then
		echo " ${BD_SORTED},"
	fi
	if [ -n "${DEV_SORTED}" ] ; then
		echo " ${DEV_SORTED},"
	fi
	echo " openstack-pkg-tools,"
	echo "Standards-Version: 4.7.4"
	echo "Vcs-Browser: ${VCS_URL}"
	echo "Vcs-Git: ${VCS_URL}.git"
	echo "Homepage: ${REPO}"
	echo "Rules-Requires-Root: no"
} >debian/control

MIDX=0
for M in ${WS_MEMBERS} ; do
	MNAME=$(cat ${WORK}/m${MIDX}.name)
	MVERSION=$(cat ${WORK}/m${MIDX}.version)
	MDESC=$(cat ${WORK}/m${MIDX}.description)
	if [ -z "${MDESC}" ] ; then
		# The manifest description may be missing, or a multiline
		# TOML string the parser can't extract: use the crates.io
		# description when available.
		MDESC=${DESC}
	fi
	if [ -z "${MDESC}" ] ; then
		MDESC="TODO: add a description for the ${MNAME} crate"
	fi
	MDASHED=$(echo ${MNAME} | tr '[:upper:]_' '[:lower:]-')
	MDEPS=$(sort ${WORK}/m${MIDX}.deps | tr '\n' ',')
	MDEPS=$(echo "${MDEPS}" | sed 's/,$//')
	APIV=$(deb_apiv "${MVERSION:-${VERSION}}")
	{
		echo ""
		echo "Package: librust-${MDASHED}-dev"
		echo "Architecture: all"
		echo "Depends:"
		if [ -n "${MDEPS}" ] ; then
			echo " ${MDEPS},"
		fi
		echo " \${misc:Depends},"
		echo "Provides:"
		echo " librust-${MDASHED}-${APIV}-dev (= \${binary:Version}),"
		if [ "${APIV}" != "${MVERSION}" ] && [ -n "${MVERSION}" ] ; then
			echo " librust-${MDASHED}-${MVERSION}-dev (= \${binary:Version}),"
		fi
		if echo "${APIV}" | grep -q '^0\.' ; then
			echo " librust-${MDASHED}-0-dev (= \${binary:Version}),"
		fi
		for FEAT in $(cat ${WORK}/m${MIDX}.features) ; do
			echo " librust-${MDASHED}-${APIV}+${FEAT}-dev (= \${binary:Version}),"
			if echo "${APIV}" | grep -q '^0\.' ; then
				echo " librust-${MDASHED}-0+${FEAT}-dev (= \${binary:Version}),"
			fi
		done
		echo "Description: ${MDESC} - Rust source code"
		echo " ${MDESC}"
		echo " ."
		echo " This package contains the Rust source code for the ${MNAME} crate."
	} >>debian/control
	if [ "$(cat ${WORK}/m${MIDX}.bin)" = "yes" ] ; then
		{
			echo ""
			echo "Package: ${MDASHED}"
			echo "Section: utils"
			echo "Architecture: any"
			echo "X-Cargo-Crates: ${MNAME}"
			echo "Depends:"
			echo " \${misc:Depends},"
			echo " \${shlibs:Depends},"
			echo "Description: ${MDESC}"
			echo " ${MDESC}"
			echo " ."
			echo " This package contains the ${MNAME} binary."
		} >>debian/control
		echo "  ${MNAME}: this crate ships binaries: review the runtime dependencies of the ${MDASHED} package." >>${WARN_FILE}
	fi
	MIDX=$((MIDX + 1))
done

# debian/rules, using the dh-rust buildsystem, with a self-contained
# gen-orig-xz target (pkgos.make's dh overrides are Python oriented).
echo "===> Creating debian/rules"
NEED_TEST_DISABLE=""
if [ -s ${DEV_ALL} ] && [ -s ${MISSING} ] ; then
	while IFS= read -r MISSING_LINE ; do
		MISSING_CRATE=$(echo ${MISSING_LINE} | awk '{print $1}' | sed 's/^librust-//;s/-dev$//')
		if grep -q "^librust-${MISSING_CRATE}-dev <!nocheck>$" ${DEV_ALL} ; then
			NEED_TEST_DISABLE=yes
			NEED_TEST_LIST="${NEED_TEST_LIST}${MISSING_CRATE}, "
		fi
	done <${MISSING}
	if [ "${NEED_TEST_DISABLE}" = "yes" ] ; then
		NEED_TEST_LIST=$(echo ${NEED_TEST_LIST} | sed 's/, $//')
		TEST_OVERRIDE="override_dh_auto_test:
	# Disabled because ${NEED_TEST_LIST} isn't in Debian
	echo \"Disabled tests.\"
"
	fi
fi

cat >debian/rules <<RULES_EOF
#!/usr/bin/make -f

UPSTREAM_GIT := ${REPO}
include /usr/share/dpkg/pkg-info.mk

DEB_TAG = \$(shell echo '\$(DEB_VERSION_UPSTREAM)' | sed -e 's/~/_/g')

%:
	dh \$@

${TEST_OVERRIDE}
gen-orig-xz:
	if ! [ -f ../\$(DEB_SOURCE)_\$(DEB_VERSION_UPSTREAM).orig.tar.xz ] ; then \
		git archive --prefix=\$(DEB_SOURCE)-\$(DEB_VERSION_UPSTREAM)/ \$(DEB_TAG) | xz > ../\$(DEB_SOURCE)_\$(DEB_VERSION_UPSTREAM).orig.tar.xz ; \
	fi
	mkdir -p ../build-area
	cp -a ../\$(DEB_SOURCE)_\$(DEB_VERSION_UPSTREAM).orig.tar.xz ../build-area/
RULES_EOF
chmod +x debian/rules

# debian/copyright, with one section per member in workspace mode.
echo "===> Creating debian/copyright"
license_text () {
	case ${1} in
	Apache-2.0)
		cat <<'LICEOF'
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 .
    http://www.apache.org/licenses/LICENSE-2.0
 .
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 .
 On Debian-based systems the full text of the Apache version 2.0 license
 can be found in /usr/share/common-licenses/Apache-2.0.
LICEOF
		;;
	Expat)
		cat <<'LICEOF'
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 .
 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.
 .
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 DAMAGE.
LICEOF
		;;
	BSD-2-clause)
		cat <<'LICEOF'
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 1. Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
 .
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
LICEOF
		;;
	BSD-3-clause)
		cat <<'LICEOF'
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 1. Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
 3. Neither the name of the copyright holder nor the names of its contributors
    may be used to endorse or promote products derived from this software
    without specific prior written permission.
 .
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
LICEOF
		;;
	ISC)
		cat <<'LICEOF'
 Permission to use, copy, modify, and/or distribute this software for any
 purpose with or without fee is hereby granted, provided that the above
 copyright notice and this permission notice appear in all copies.
 .
 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF THE USE OR
 PERFORMANCE OF THIS SOFTWARE.
LICEOF
		;;
	Zlib)
		cat <<'LICEOF'
 This software is provided 'as-is', without any express or implied warranty.
 In no event will the authors be held liable for any damages arising from the
 use of this software.
 .
 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it
 freely, subject to the following restrictions:
 .
 1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software in a
    product, an acknowledgment in the product documentation would be
    appreciated but is not required.
 2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.
 3. This notice may not be removed or altered from any source distribution.
LICEOF
		;;
	MPL-2.0)
		cat <<'LICEOF'
 This Source Code Form is subject to the terms of the Mozilla Public License,
 v. 2.0. If a copy of the MPL was not distributed with this file, You can
 obtain one at http://mozilla.org/MPL/2.0/.
 .
 TODO: inline the complete text of the MPL-2.0 license, taken from the
 upstream LICENSE file.
LICEOF
		;;
	GPL-2+)
		cat <<'LICEOF'
 This program 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 2 of the License, or (at your option) any later
 version.
 .
 On Debian systems, the complete text of the GNU General Public License v2
 can be found in /usr/share/common-licenses/GPL-2.
LICEOF
		;;
	GPL-3+)
		cat <<'LICEOF'
 This program 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.
 .
 On Debian systems, the complete text of the GNU General Public License v3
 can be found in /usr/share/common-licenses/GPL-3.
LICEOF
		;;
	LGPL-2.1)
		cat <<'LICEOF'
 This library is free software; you can redistribute it and/or modify it under
 the terms of the GNU Lesser General Public License as published by the Free
 Software Foundation; version 2.1 of the License.
 .
 On Debian systems, the complete text of the GNU Lesser General Public License
 version 2.1 can be found in /usr/share/common-licenses/LGPL-2.1.
LICEOF
		;;
	LGPL-3+)
		cat <<'LICEOF'
 This library is free software; you can redistribute it and/or modify it under
 the terms of the GNU Lesser General Public License as published by the Free
 Software Foundation; either version 3 of the License, or (at your option) any
 later version.
 .
 On Debian systems, the complete text of the GNU Lesser General Public License
 version 3 can be found in /usr/share/common-licenses/LGPL-3.
LICEOF
		;;
	*)
		cat <<'LICEOF'
 TODO: add the full license text here.
LICEOF
		;;
	esac
}

CROPYEAR=$(date +%Y)
DUAL_LIST=$(echo ${LICENSE_TITLES} | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')
{
	echo "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/"
	echo "Upstream-Name: ${CRATE}"
	echo "Source: ${REPO}"
	echo ""
	if [ "$(echo ${WS_MEMBERS} | wc -w)" -gt 1 ] ; then
		# One section per member of the workspace.
		MIDX=0
		for M in ${WS_MEMBERS} ; do
			MNAME=$(cat ${WORK}/m${MIDX}.name)
			MAUTHORS=$(cat ${WORK}/m${MIDX}.authors)
			if [ -z "${MAUTHORS}" ] ; then
				MAUTHORS="Upstream authors"
			fi
			if [ "${M}" = "." ] ; then
				MFILES="*"
			else
				MFILES="${M}/*"
			fi
			echo "Files: ${MFILES}"
			echo "Copyright: ${CROPYEAR}, ${MAUTHORS}"
			echo "License: $(echo $(cat ${WORK}/m${MIDX}.titles) | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/^ *//;s/ *$//')"
			echo ""
			MIDX=$((MIDX + 1))
		done
	else
		echo "Files: *"
		echo "Copyright: ${CROPYEAR}, $(cat ${WORK}/m${MAIN_IDX}.authors)"
		NB=$(echo ${DUAL_LIST} | wc -w)
		if [ "${NB}" = "1" ] ; then
			echo "License: ${DUAL_LIST}"
			echo ""
			license_text ${DUAL_LIST}
		else
			FIRST=$(echo ${DUAL_LIST} | cut -d' ' -f1)
			REST=$(echo ${DUAL_LIST} | cut -sd' ' -f2-)
			echo "License: ${FIRST}-or-$(echo ${REST} | tr ' ' '-')"
			echo ""
			echo " This package is under a dual license. See the ${FIRST} or $(echo ${REST} | tr ' ' '-') section below."
			echo " ."
			echo " Debian user's choice: they may redistribute under either license."
			echo ""
			license_text ${FIRST}
			echo ""
			echo "License: $(echo ${REST} | tr ' ' '-')"
			echo ""
			license_text ${REST}
		fi
	fi
	echo ""
	echo "Files: debian/*"
	echo "Copyright: ${CROPYEAR}, Thomas Goirand <zigo@debian.org>"
	echo "License: $(echo ${DUAL_LIST} | cut -d' ' -f1)"
	echo ""
	license_text $(echo ${DUAL_LIST} | cut -d' ' -f1)
} >debian/copyright

# debian/watch
echo "===> Creating debian/watch"
cat >debian/watch <<WATCH_EOF
version=4
opts=filenamemangle=s/.*\\/(.*)\\/download/${CRATE}-\$1.tar.gz/g,\\
uversionmangle=s/(\\d)[_\\.\\-\\+]?((RC|rc|pre|dev|beta|alpha)\\.?\\d*)\$/\$1~\$2/ \\
https://qa.debian.org/cgi-bin/fakeupstream.cgi?upstream=crates.io/${CRATE} .*/crates/${CRATE}/@ANY_VERSION@/download
WATCH_EOF

echo "3.0 (quilt)" >debian/source/format
cat >debian/source/options <<OPTS_EOF
extend-diff-ignore = "(^|/)Cargo\\.lock$"
OPTS_EOF

echo "===> Creating changelog"
EDITOR=touch dch --create --package ${SRC_PKG} --distribution unstable --urgency medium -v ${VERSION}-1
rm -f +1

if which wrap-and-sort >/dev/null 2>&1 ; then
	wrap-and-sort -bastk
fi
git add debian
GIT_AUTHOR_NAME="Thomas Goirand" GIT_AUTHOR_EMAIL=zigo@debian.org \
GIT_COMMITTER_NAME="Thomas Goirand" GIT_COMMITTER_EMAIL=zigo@debian.org \
git commit -q -a -m "Initial packaging" || true

echo "===> Regenerating orig tarball"
rm -f ../*.orig.tar.* ../build-area/${SRC_PKG}_*.orig.tar.*
./debian/rules gen-orig-xz

echo "===> Writing ITP text to ${RUN_DIR}/itp.txt"
cat >${RUN_DIR}/itp.txt <<ITPEOF
* Package name    : ${SRC_PKG}
  Version         : ${VERSION}
  Upstream Author : $(cat ${WORK}/m${MAIN_IDX}.authors)
* URL             : ${REPO}
* License         : ${LICENSE_TITLES}
  Programming Lang: Rust
  Description     : ${DESC}
ITPEOF

echo "===> Launching reportbug"
if which pkgos-reportbug >/dev/null 2>&1 && [ -z "${PKGOS_DEBPIPY_NO_REPORTBUG}" ] ; then
	pkgos-reportbug ${RUN_DIR}/itp.txt
else
	echo "===> Not launching reportbug (not installed, or PKGOS_DEBPIPY_NO_REPORTBUG is set)."
	echo "===> Please run the following command, then review and send the ITP:"
	echo "pkgos-reportbug ${RUN_DIR}/itp.txt"
fi

echo "===> Warnings and manual attention needed:"
if [ -s ${WARN_FILE} ] ; then
	cat ${WARN_FILE}
fi
if [ -s ${TARGET_FILE} ] ; then
	echo "  Target specific dependencies not added to debian/control:"
	cat ${TARGET_FILE}
fi
if [ -s ${OLDER} ] ; then
	echo "  Dependencies needing an older major than Debian (parallel package needed):"
	cat ${OLDER}
fi
if [ -s ${BUMPS} ] ; then
	echo "  Dependencies needing a Debian version bump:"
	cat ${BUMPS}
fi
if [ -s ${MISSING} ] ; then
	echo "  Dependencies missing from Debian (package them first, in this order):"
	cat ${MISSING}
fi
MIDX=0
for M in ${WS_MEMBERS} ; do
	EDITION=$(cat ${WORK}/m${MIDX}.edition)
	MSRV=$(cat ${WORK}/m${MIDX}.msrv)
	MNAME=$(cat ${WORK}/m${MIDX}.name)
	if [ -n "${EDITION}" ] && [ "${EDITION}" = "2024" ] ; then
		echo "  ${MNAME} uses edition 2024: it needs rustc (>= 1.85)."
	fi
	if [ -n "${MSRV}" ] && [ "${MSRV}" != "@workspace@" ] ; then
		echo "  ${MNAME} declares a minimum supported rust version of ${MSRV}."
	fi
	MIDX=$((MIDX + 1))
done
echo "===> All done. Review debian/control, debian/copyright, then commit."
