otter 0.7.1

Otter game system; common infrastructure Rust crate.
Documentation
#!/bin/bash
#
# usage:
#    ./make-release --dry-run|--real <branch>
# eg
#    ./make-release --dry-run main


# Overall release steps:
#
#  - update dependencies (cargo update, cargo upgrades)
#  - update our Nightly rust and do a test build, note in build.rst
#  - edit CHANGELOG.md
#  - update versions
#  - ensure pretest == tested == main
#  - make deploy and test that chiark still works
#  - make-release
#  - release announcement to mailing list
#  - blog post

#---------- argument parsing and options ----------

set -e
set -o pipefail

fail () { echo >&2 "${0##*/}: error: $*"; exit 12; }

dryrun=x-dry-run-unset
cargo_dryrun=--not-a-cargo-option-please-crash

case "$#.$1" in
2.--real)    dryrun=x     ; cargo_dryrun=''         ; ;;
2.--dry-run) dryrun=dryrun; cargo_dryrun='--dry-run'; ;;
*)           fail "bad usage" ;;
esac

keyid=0x559AE46C2D6B6D3265E7CBA1E3E3392348B50D39

cratesio_raw_url=\
https://raw.githubusercontent.com/rust-lang/crates.io-index/master

branch="$2"

dryrun () { echo "WOULD  $*"; }
x () { echo >&2 "+ $*"; "$@"; }

trouble=false
trouble () { echo >&2 "***TROUBLE***: $*"; trouble=true; }

#---------- checks ----------

version=$(perl <Cargo.toml -ne '
    next unless m{^version\s*=\s*\"([0-9.]+)\"\s*$};
    print "$1\n" or die $!;
    exit 0;
')

case "$version" in
'') fail "no version?" ;;
esac

echo "version $version"

equals () {
    diff <(git rev-parse refs/heads/$1) <(git rev-parse HEAD) \
	|| trouble "HEAD not equal to $1"
}

equals $branch
equals tested

bad=$(git status --porcelain)
if [ "x$bad" != x ]; then
    printf >&2 '%s\n' "$bad"
    trouble 'tree is dirty'
fi

tag="otter-$version"
tag_exists=$(git for-each-ref "[r]efs/tags/$tag")
if [ "x$tag_exists" != x ]; then trouble "tag $tag already exists"; fi

head -1 CHANGELOG.md | grep "^Version $version" \
|| trouble "CHANGELOG.md not updated"

cargo_order='base . cli daemon wasm apitest wdriver'
missing=(git ls-files :\*/Cargo.toml :Cargo.toml)
for x in $cargo_order; do missing+=(:!$x/Cargo.toml); done
missing=$( "${missing[@]}" )
if [ "x$missing" != x ]; then trouble "missing cargo package(s) $missing"; fi

#---------- end of checks ----------

if $trouble; then
    $dryrun fail "trouble! checks failed!"
else
    echo 'checks passed'
fi

#---------- actually do the work ----------

$dryrun git push chiark $branch
$dryrun git push origin $branch

#---------- non-idempotent things ----------

$dryrun make -j12 publish

$dryrun git tag -s -u "$keyid" -m "Otter v$version" $tag
$dryrun git push chiark $tag
$dryrun git push origin $tag

#---------- oh woe cargo ----------

# https://github.com/rust-lang/cargo/issues/9507
wait_for_crates_io () {
    local p=$1
    local delay=1
    local url="$cratesio_raw_url/${p:0:2}/${p:2:2}/$p"
    printf >&2 "waiting for upload of %s to take effect" "$p"
    while sleep $delay; do
	printf >&2 .
	local got=$(
	    curl -sS "$url" | jq '.vers | select(. == "'"$version"'")'
	)
	if [ "x$got" != x ]; then break; fi
	delay=$(( $delay * 11 / 10 + 1 ))
    done
    echo >&2 'done'
}

for cargo_dir in $cargo_order; do
    $dryrun_no_more_cargo \
    nailing-cargo --no-nail --linkfarm=git --- \
        sh -xec "
          find . ! -type l ! -type d ! -path './target/*' -print0 \
              | xargs -0r rm --
          cd $cargo_dir; cargo publish $cargo_dryrun
        "

    cargo_package=$(
	sed -n '/^name *=/ { s/^name *= *"\(.*\)" *$/\1/; p; q }' \
	    <$cargo_dir/Cargo.toml
    )

    wait_for_crates_io "$cargo_package"

    dryrun_no_more_cargo=$dryrun

done

#---------- finish ----------

$dryrun cat <<END


Successfully released to
  - git tags
  - all git branches
  - cargo publish

Consider
  - make deploy

You need to write release announcements
  - sgo-software-announce
  - blog

END