#!/bin/bash determine_new_version() { grep "version = " Cargo.toml | sed -Ee 's/version = "(.*)"/\1/' | head -1 } determine_crate_name() { grep "name = " Cargo.toml | sed -Ee 's/name = "(.*)"/\1/' | head -1 } check_published_version() { cirrus_agent="${CIRRUS_CI/?*/(Cirrus-CI)}" final_agent="Release-Script/1.0 ${cirrus_agent:-(local)} (for $crate) (author:HeroicKatora)" echo $final_agent # Does the api information start with: '{"errors":' [[ $(wget -U "$final_agent" --content-on-error "https://crates.io/api/v1/crates/$crate/$new_version" -qO -) == "{\"errors\":"* ]] || { published_commit=$(wget -U "$final_agent" "https://crates.io/api/v1/crates/$crate/$new_version/download" -qO - | tar xOzf - "$crate-$new_version/.cargo_vcs_info.json") published_commit=$(sed 's/\"sha1\": \"\(.*\)\"/\1/;T0;p;:0;d' <<< "$published_commit") echo "Already published at" "$published_commit" } } git_considered_clean() { [[ -z $(git status -s) ]] } count_wip_marker() { # WIP alone is not a marker [[ -z $(grep "\[WIP\]" Changes.md Readme.md) ]] } check_release_changes() { [[ -z $(grep "# v$new_version" Changes.md) ]] } make_git_tag() { tag_edit_msg="../.git/TAG_MSG_$(uuidgen)" touch "$tag_edit_msg" function cleanup() { rm "$tag_edit_msg" } trap cleanup EXIT # Extract the verion specific section from Changes.md # Delete lines until $new_version header # Delete lines starting from the next header # Delete all empty lines at the start # Use as the initial message for a signed tag, but open edit anyways echo $(pwd) sed -e '0,/'"$new_version"'/d;/\#/,$d;/./,$!d' Changes.md >> "$tag_edit_msg" echo >> "$tag_edit_msg" # Sign the package content by including its hash in the tag sha256sum "../target/package/${crate}-${new_version}.crate" >> "$tag_edit_msg" git tag -s -F "$tag_edit_msg" -e "${crate}-v${new_version}" $published_commit } is_force="" do_tag="" for param in $@ do case "$param" in -f) is_force="-f";; --tag) do_tag="yes";; --help) ;& -h) { cat << EOF usage: release [-f] [-h|--help] Automates checks and tagging of new releases. Encourages a workflow where planned changes are integrated into readme and migration documentation early, with WIP markers to help produce complete logs. -f Force usage of version, even if such a tag already exists. -h, --help Display this help A semantic version number matching [0-9a-zA-Z.-]* EOF exit 1; } ;; esac done new_version="$(determine_new_version)" crate="$(determine_crate_name)" # check it is a sane version number [[ -z $(grep -vE '[0-9a-zA-Z.-]*' <<< "$new_version" ) ]] || { echo "Fail: Check version number: ${new_version}"; exit 1; } # check we identified the crate [[ -z "$crate" ]] && { echo "Couldn't determine crate name"; exit 1; } # Check that the working dir is clean. May comment this out if it produces problems. git_considered_clean || { echo "Fail: Working directory is not clean"; exit 1; } # Check that for every author, we have at least name or mail recorded in Contributors.txt # For each commit author, checks that either his/her name or respective mail # address appears in the contributors file. Note that a .mailcap file could be # introduced to canonicalize these names in the output of git-shortlog already. # Since this needs GNU parallel, the check is optional. if [[ -f Contributors.txt ]]; then if which parallel 2>/dev/null; then { (git shortlog -se | parallel -C '\t|<' grep -Fq -e '{2}' -e '\<{3}' Contributors.txt) || { echo "Fail: contributor not listed"; exit 1; }; } else { echo "Checking contributors needs GNU parallel, please make sure manually." 1>&2; } fi fi check_published_version || { echo "Version $new_version appears already published"; exit 1; } # Check there are no more [WIP] markers in Migrate and Readme count_wip_marker || { echo "Fail: Work in progress in documentation"; exit 1; } # Find a matching header in the changelog check_release_changes && { echo "Fail: No changelog regarding this release"; exit 1; } # Packaging works. Note: does not publish the version. cargo package || { echo "Fail: cargo could not package successfully"; exit 1; } [[ -z $do_tag ]] || make_git_tag