#!/bin/sh
# Generates Built-Using after a successful cargo build.
# Run this in the package top-level directory where debian/ is.

set -e

DEB_HOST_RUST_TYPE="${DEB_HOST_RUST_TYPE:-$(printf "include /usr/share/rustc/architecture.mk\nall:\n\techo \$(DEB_HOST_RUST_TYPE)\n" | make --no-print-directory -sf -)}"
CARGO_REGISTRY="${CARGO_REGISTRY:-debian/cargo_registry}"
CARGO_CHANNEL="${CARGO_CHANNEL:-release}"
# useful for testing:
# CARGO_REGISTRY="$HOME/.cargo/registry/src/github.com-1ecc6299db9ec823" DEB_HOST_RUST_TYPE="." CARGO_CHANNEL=debug

CARGO_TARGET_DIR="target/$DEB_HOST_RUST_TYPE/$CARGO_CHANNEL"
CARGO_TARGET_DIR_ABS="$(readlink -f "$CARGO_TARGET_DIR")"

CPRIGHT_FORMAT="https://www.debian.org/doc/packaging-manuals/copyright-format/1.0"
SRCLEFT_LICENSES="$(echo GPL LGPL AGPL GFDL MPL CDDL CPL Artistic Perl QPL | tr ' ' '\n')"
pkg_has_srcleft_license() {
	local pkg="$1"
	local ver="$2"
	local f="/usr/share/doc/$pkg/copyright"
	if ! sed -nre 's	^Format: (.*)	\1	gp' "$f" | grep -qiw "$CPRIGHT_FORMAT"; then
		echo >&2 "$0: abort: Not in machine-readable format: $f"
		echo 2
	elif sed -nre 's	^X-Binary-Requires-Source: (.*)	\1	gp' "$f" | grep -qiw yes; then
		echo 1
	elif sed -nre 's	^License: (.*)	\1	gp' "$f" | grep -qiwF "$SRCLEFT_LICENSES"; then
		echo 1
	else
		echo 0
	fi
}

dep_files_to_pkgs() {
	xargs -r dpkg -S \
	| sed -nre 's	(.*): .*	\1	gp' \
	| xargs -r dpkg-query --show \
	| while read pkg ver; do echo "$pkg $ver $(pkg_has_srcleft_license "${pkg%:*}" "$ver")"; done
	# pkg_has_srcleft_license should be accurate for all rust crates, no need to give a $containing_crate
	# this is due to nature of crate copyright info, and the debian rust packaging policy
}

rust_dep_files() {
	cat "$CARGO_TARGET_DIR/deps"/*.d \
	| sed -nre 's	^\S*/('"$CARGO_REGISTRY"'/[^/]*)/.*	'"$(readlink -f "$PWD")/"'\1	gp' \
	| sort -u \
	| xargs -r readlink -f
}

rust_libs() {
	{ which rustc; rust_dep_files; } | dep_files_to_pkgs
}

gcc_default_searchdirs() {
	gcc -print-search-dirs \
	| sed -nre 's	^libraries: (.*)	\1	gp' \
	| tr ':' '\n' \
	| sed -e 's	^=	'"$(gcc -print-sysroot)"'	g' \
	| xargs readlink -m 2>/dev/null # suppress errors caused by early pipe closure
}

rust_search_lib() {
	local lib="$1"
	{
	cat
	# rust does not actually search normal paths when linking static libs
	# - see https://github.com/rust-lang/rust/issues/43118
	# - see also `fn link_rlib` in back/link.rs which calls
	#   `pub fn find_library` in back/archive.rs which generates the error message
	#gcc_default_searchdirs
	} | while read searchdir; do
		#echo >&2 "searching $searchdir for static lib $lib"
		local f="$(readlink -m "$searchdir/lib${lib}.a")"
		if test -f "$f"; then
			printf "%s\n" "$f"
			break
		fi
	done
}

native_libs() {
	ls -1d "$CARGO_TARGET_DIR/build"/*/output 2>/dev/null | while read output; do
		sed -nre 's	^cargo:rustc-link-lib=static=(.*)	\1 '"$output"'	gp' "$output"
	done | while read lib output; do
		local containing_crate="$(basename "$(dirname "$output")")"
		test -n "$lib" || continue
		local libfile="$(sed -nre 's	^cargo:rustc-link-search=native=(.*)	\1	gp' "$output" | rust_search_lib "$lib")"
		local srcleft=""
		test -n "$libfile" || { echo >&2 "$0: abort: could not find static lib '$lib'; rustc should have failed already?"; exit 1; }
		echo >&2 "$0: found static lib $lib at $libfile"
		if [ "${libfile#$CARGO_TARGET_DIR_ABS/}" != "$libfile" ]; then
			# static library source code embedded in crate
			local srcstat="$(sed -nre 's	^dh-cargo:deb-built-using='"$lib"'=([01]=.*)	\1	gp' "$output")"
			case "$srcstat" in
			0=*|1=*)
				srcleft="${srcstat%%=*}"
				libfile="${srcstat#*=}"
				if [ "$(readlink -f "$libfile")" = "$(readlink -f "$PWD")" ]; then
					# Note that this exception only applies in the case that where you are building
					# the Debian package for $containing_crate itself. In the case where you are
					# building a Debian package for crate X depending on $containing_crate, the
					# latter still has to output the dh-cargo:deb-built-using in their build.rs so
					# that the Debian package for crate X can correctly set Built-Using themselves.
					echo >&2 "$0: static library derived from $libfile which is the top-level crate being built, no need to add Built-Using"
					continue
				fi
				;;
			*)
				echo >&2 "$0: abort: could not determine source-distribution conditions of ${libfile#$CARGO_TARGET_DIR_ABS/}."
				echo >&2 "You must patch build.rs of ${containing_crate%-*} to output 'println!(\"dh-cargo:deb-built-using=$lib=\$s={}\", env::var(\"CARGO_MANIFEST_DIR\").unwrap());' where:"
				echo >&2 "- \$s is 1 if the license(s) of the included static libs require source distribution alongside binaries, otherwise 0"
				exit 1
				;;
			esac
		fi
		local wpkg="$(dpkg -S "$(readlink -f "$libfile")")"
		test -n "$wpkg" || { echo >&2 "$0: abort: could not find Debian package for file $libfile"; exit 1; }
		local pkgstat="$(echo "$wpkg" | sed -nre 's	(.*): .*	\1	gp' | xargs -r dpkg-query --show)"
		local pkg="$(echo "$pkgstat" | cut -f1)"
		local ver="$(echo "$pkgstat" | cut -f2)"
		# static library source code embedded in crate (from earlier)
		if [ -n "$srcleft" ]; then
			echo "$pkg $ver $srcleft"
		# static libraries from another Debian package
		elif sed -nre 's	^dh-cargo:deb-built-using='"$lib"'=0~=(.*)	\1	gp' "$output" | { echo "${pkg%:*} $ver" | grep -qExf /dev/fd/3; } 3<&0; then
			echo "$pkg $ver 0"
		elif sed -nre 's	^dh-cargo:deb-built-using='"$lib"'=1~=(.*)	\1	gp' "$output" | { echo "${pkg%:*} $ver" | grep -qExf /dev/fd/3; } 3<&0; then
			echo "$pkg $ver 1"
		else
			# guess the conditions based on the whole d/copyright file
			# this loses granularity, e.g. gcc is mostly distributed as GPL-3 but the libbacktrace portion is BSD-3
			# to retain granularity the crate package maintainer should patch build.rs as suggested
			echo >&2 "$0: warning: guessing source-distribution conditions of $libfile, this may be inaccurate."
			echo >&2 "$0: warning: patch build.rs to suppress the above warning"
			srcleft="$(pkg_has_srcleft_license "${pkg%:*}" "$ver")"
			if [ "$srcleft" -gt 1 ]; then
				echo >&2 "You must patch build.rs of ${containing_crate%-*} to output 'dh-cargo:deb-built-using=$lib=\$s~=\$PAT' where:"
				echo >&2 "- \$s is 1 if the license(s) of the included static libs require source distribution alongside binaries, otherwise 0"
				echo >&2 "- \$PAT is an egrep pattern matching the \"\$pkg \$ver\" combinations that satisfy \$s"
				echo >&2 "  for example '$pkg .*' matches the currently-relevant package, $pkg $ver"
				exit 1
			fi
			echo "$pkg $ver $srcleft"
		fi
	done
}

output() {
	local binpkg="$1"
	if [ -z "$binpkg" ]; then
		cat
	else
		local built_using=""
		local built_using_x=""
		while read pkg ver srcleft; do
			local src="$(dpkg-query -f '${source:Package}' --show "$pkg")"
			local srcver="$(dpkg-query -f '${source:Version}' --show "$pkg")"
			case "$srcleft" in
			2)	exit 1;;
			1)	built_using="${built_using}$src (= $srcver), ";;
			esac
			built_using_x="${built_using_x}$src (= $srcver), "
		done
		echo "cargo:Built-Using=${built_using%, }" >> "debian/$binpkg.substvars"
		echo "cargo:X-Cargo-Built-Using=${built_using_x%, }" >> "debian/$binpkg.substvars"
	fi
}

native_libs="$(native_libs)" # capture output outside of pipe so set -e works
{
rust_libs
test -z "$native_libs" || echo "$native_libs"
} | LC_COLLATE=C.utf-8 sort -u | output "$@"
