#!/usr/bin/env bash
#
# Run "cargo-semver-checks" on each of our crates, with the "full" features
# set, comparing against a provided arti version.
#
# We can't just run "cargo-semver-checks" directly; it runs with
# --all-features unconditionally, and therefore would complain about
# breakage in our experimental features.
#
# Note that cargo-semver-checks has a fairly high false negative rate.
# If it tells you that something is broken, you probably need to bump
# a major or minor version; but if it _doesn't_ tell you something is
# broken, you are not in the clear.

set -euo pipefail

if [ -z "${1-}" ]; then
	echo "Usage: $0 [git-tag]"
	echo "Script will run cargo-semver-checks on changes since [git-tag]."
	exit 1
fi
LAST_VERSION="$1"

if test "$(command -v cargo-semver-checks 2>/dev/null)" = ""; then
    echo "cargo-semver-checks appears not to be installed."
    echo "Try 'cargo install cargo-semver-checks.'"
    exit 1
fi

TEMPDIR=$(mktemp -d -t arti_semver_checks."XX""XX""XX")
trap 'rm -rf "$TEMPDIR"' 0

MAINT_PATH="$(dirname "$0")"

# Setting this envvar is extremely naughty, but it is what
# cargo-semver-checks does.  It lets us use unstable options on stable
# rust.
#
# Using nightly is not a reliable option; cargo-semver-checks does not
# always accept the json rustdoc format that nightly produces.
export RUSTC_BOOTSTRAP=1

# These are the same flags that cargo-semver-checks uses.
export RUSTDOCFLAGS="\
  -Z unstable-options \
  --document-private-items \
  --document-hidden-items \
  --output-format=json \
  --cap-lints allow"

# This is the crate that we document in order ot document all of our
# other crates.
#
# Note that this won't give quite correct results with arti <=1.1.4;
# "full" in one crate didn't imply "full" in all crates until 1.1.5
# when fixup-features was merged and applied.
TOPLEVEL_CRATE=arti

run_rustdoc () {
    # TODO: This is not a great way to do this.  It leaves the
    # worktree around after the script is done.
    if test ! -d sc_tmp; then
	git worktree add sc_tmp "$LAST_VERSION"
    fi

    # Note: We document a top-level crate directly, whereas
    # cargo-semver-checks instead creates a temporary crate and documents
    # that crate instead.
    #
    # It says that it does so in order to work around issues like
    # https://github.com/obi1kenobi/cargo-semver-checks/issues/167#issuecomment-1382367128
    cargo doc --package "${TOPLEVEL_CRATE}" --features=full

    cd sc_tmp
    git checkout "$LAST_VERSION"
    cargo doc --package "${TOPLEVEL_CRATE}" --features=full
    cd ..
}

run_semver_checks () {
    BREAKING=()

    for package in $("$MAINT_PATH"/list_crates); do
	echo "==== $package" 1>&2
	if grep "^publish *= *false" "crates/$package/Cargo.toml" >/dev/null; then
	    echo "...publish=false; skipping." 1>&2
	    continue
	fi
	fname="${package//-/_}"
	if ! test -f "target/doc/$fname.json" ; then
	    echo "...no documentation found; skipping." 1>&2
	    continue
	fi
	tmpfile="$TEMPDIR/output.txt"
	rm -f "$tmpfile"
	x=ok
	# We use "script" here to capture the output without
	# overriding the terminal settings, as would happen if we used
	# tee.
	script -efq "$tmpfile" -c "cargo semver-checks check-release \
	     -p \"$package\" \
	     --baseline-rustdoc \"sc_tmp/target/doc/$fname.json\" \
	     --current-rustdoc \"target/doc/$fname.json\"" || x=failed
	if test "$x" = "failed"; then
	    ISSUE=$(grep "Final.*semver requires " "$tmpfile" || true)
	    ISSUE=${ISSUE/*:/}
	    BREAKING+=("$(printf "%-24s %s" "$package" "$ISSUE")")
	fi
    done

    if test "${#BREAKING[@]}" -ne "0"; then
	echo "semver-checks reported errors:"
	for br in "${BREAKING[@]}"; do
	    echo "    $br"
	done
    fi
}

# TODO: Add a flag to skip the run_rustdoc step: it can be kinda slow.
run_rustdoc
run_semver_checks
