#!/bin/bash
#
# usage: cut-release [-n] version-designation [make-args...]
#


dryrun=false
getantlr="./contrib/get-antlr-3.4"
dogetantlr=false
configantlr=""
version=""
cutrelease=`basename "$0"`

function isthatright {
  if $dryrun; then
    echo -n "[DRY RUN] "
  fi
  if [ -z "$1" ]; then
    echo -n "Does that look right? [y/n] "
  else
    echo -n "$1? [y/n] "
  fi
  while read yn; do
    if [ "$yn" = y -o "$yn" = Y -o "$yn" = yes -o "$yn" = YES -o "$yn" = Yes ]; then
      break
    elif [ "$yn" = n -o "$yn" = N -o "$yn" = no -o "$yn" = NO -o "$yn" = No ]; then
      echo "Aborting as per user request." >&2
      exit 1
    else
      echo -n "[y/n] "
    fi
  done
}

function printusage {
  echo "Usage: $cutrelease <options> version-designation [make-args...]" >&2
  echo 
  echo "Options:"
  echo
  echo "  -h, --help    print this message and exit"
  echo
  echo "  -n            do a dry run (do sanity checks and build "
  echo "                but don not touch the repository)"
  echo
  echo "  -a            get local antlr to use for compilation"
}

function error {
  echo "$cutrelease: ERROR: $1" >&2
}

function die {
  error "$1"
  exit 1
}

function internalerror {
  echo "$cutrelease: INTERNAL ERROR: $1" >&2
  exit 1
}

if ! [ -e src/expr/node.h -a -e .git ]; then
  die "You should run this from the top-level of a CVC4 git working directory" 
fi

while [[ $# -gt 0 ]]
do
  arg="$1"
  case $arg in
    -h | --help) 
      printusage
      exit 0
      ;;
    -n) 
      dryrun=true
      shift
      ;;
    -a)
      dogetantlr=true
      shift
      ;;
    -*)
      error "invalid option $arg"
      echo
      printusage
      exit 1
      ;;
    *)
      if [ "x$version" = "x" ]
      then
        version="$arg"
        shift
        if echo "$version" | grep '[^a-zA-Z0-9_.+(){}^%#-]' &>/dev/null
        then
          die "Version designation \`$version' contains illegal characters"
        fi
        break
      fi
  esac
done
makeargs="$@"

if [ "x$version" = "x" ] 
then
  error "missing version-designation"
  echo
  printusage
  exit 1
fi

if $dryrun
then
  echo
  echo '************************* DRY RUN *************************'
fi

eval "declare -a vs=($(echo "$version" | sed 's,^\([0-9]*\)\.\([0-9]*\)\(\.\([0-9]*\)\)\?\(.*\),[0]=\1 [1]=\2 [2]=\4 [3]=\5,'))"
major=${vs[0]}
minor=${vs[1]}; if [ -z "$minor" ]; then minor=0; fi
release=${vs[2]}; if [ -z "$release" ]; then release=0; fi
extra=${vs[3]}
echo
echo "Major  : $major"
echo "Minor  : $minor"
echo "Release: $release"
echo "Extra  : $extra"
echo
version="$major.$minor"
if [ "$release" != 0 ]; then
  version="$version.$release"
fi
version="$version$extra"
echo "Version: $version"
echo
isthatright

echo "Checking whether release \`$version' already exists..."
if [ -n "`git tag -l "$version"`" ]; then
  error "Git repo already contains a release \`$version'"
  $dryrun || exit 1
fi

echo "Checking working directory for local modifications..."
if $dryrun; then
  if [ -n "$(git status -s configure.ac)" ]; then
    die "In dry-run mode, cannot operate properly with local modifications to \"configure.ac\", sorry"
  fi
elif [ -n "$(git status -s | grep -v '^ *M *\(NEWS\|README\|AUTHORS\|RELEASE-NOTES\|INSTALL\|THANKS\|library_versions\|contrib/cut-release\)$')" ]; then
  die "\"git status\" indicates there are local modifications; please commit first"
fi

echo "Checking repo for unmerged updates..."
git fetch &>/dev/null
if git status -sb | grep -q '^## .* \[.*behind '; then
  error "This working directory isn't up to date"
  $dryrun || exit 1
fi

echo "Checking sources for broken public headers..."
suspect_files="\
$(grep -r --exclude-dir=.git '^[ \t]*#[ \t]*include[ \t]*"[^/]*"' src |
  grep -v '"cvc4parser_public\.h"' |
  grep -v '"cvc4_public\.h"' |
  grep -v '"cvc4_private\.h"' |
  grep -v '"cvc4autoconfig\.h"' |
  grep -v '"cvc4parser_private\.h"' |
  cut -f1 -d: |
  sort -u |
  xargs grep -l '^[ \t]*#[ \t]*include[ \t]*"cvc4.*public\.h"' |
  xargs echo)"
if [ -n "$suspect_files" ]; then
  error "The following publicly-installed headers appear"
  error "  to have relative #includes and may be broken"
  error "  after install: $suspect_files"
  $dryrun || exit 1
fi

for updatefile in NEWS README AUTHORS RELEASE-NOTES INSTALL THANKS; do
  echo "Checking $updatefile file for recent updates..."
  if [ -n "$(git status -s $updatefile)" ]; then
    echo "+ It's locally modified."
  else
    git log -n 1 --date=local --pretty=format:'+ Last changed on %ad (%ar)' $updatefile
    echo
    case $updatefile in
      NEWS)
        echo "You should make sure to put some notes in the NEWS file for"
        echo "release $version, indicating the most important changes since the"
        echo "last release."
        ;;
      AUTHORS)
        echo "You should make sure there are no new authors to list for $version."
        ;;
      *)
        echo "You should ensure that $updatefile is appropriately updated"
        echo "for $version before continuing.  (Maybe it even lists the previous"
        echo "version number!)"
        ;;
    esac
    echo
    isthatright "Continue without updating $updatefile"
  fi
done

echo "Adjusting version info lines in configure.ac..."
if ! grep '^m4_define(_CVC4_MAJOR,  *[0-9][0-9]* *)' configure.ac &>/dev/null ||
   ! grep '^m4_define(_CVC4_MINOR,  *[0-9][0-9]* *)' configure.ac &>/dev/null ||
   ! grep '^m4_define(_CVC4_RELEASE,  *[0-9][0-9]* *)' configure.ac &>/dev/null ||
   ! grep '^m4_define(_CVC4_EXTRAVERSION,  *\[.*\] *)' configure.ac &>/dev/null; then
  error "Cannot locate the version info lines of configure.ac"
  $dryrun || exit 1
fi
perl -pi -e 's/^m4_define\(_CVC4_MAJOR, ( *)[0-9]+( *)\)/m4_define(_CVC4_MAJOR, ${1}'"$major"'$2)/;
             s/^m4_define\(_CVC4_MINOR, ( *)[0-9]+( *)\)/m4_define(_CVC4_MINOR, ${1}'"$minor"'$2)/;
             s/^m4_define\(_CVC4_RELEASE, ( *)[0-9]+( *)\)/m4_define(_CVC4_RELEASE, ${1}'"$release"'$2)/;
             s/^m4_define\(_CVC4_EXTRAVERSION, ( *)\[.*\]( *)\)/m4_define(_CVC4_EXTRAVERSION, $1\['"$extra"'\]$2)/' configure.ac

trap 'echo; echo; echo "Aborting in error."; git checkout -- configure.ac; echo' EXIT

echo
echo 'Made the following change to configure.ac:'
echo
git diff configure.ac
echo
isthatright

if ! grep '^m4_define(_CVC4_MAJOR,  *'"$major"' *)' configure.ac &>/dev/null ||
   ! grep '^m4_define(_CVC4_MINOR,  *'"$minor"' *)' configure.ac &>/dev/null ||
   ! grep '^m4_define(_CVC4_RELEASE,  *'"$release"' *)' configure.ac &>/dev/null ||
   ! grep '^m4_define(_CVC4_EXTRAVERSION,  *\['"$extra"'\] *)' configure.ac &>/dev/null; then
  internalerror "Cannot find the modified version info lines in configure.ac, bailing..."
fi
if [ -z "$(git status -s configure.ac)" ]; then
  internalerror "\"git status\" indicates there are no local modifications to configure.ac; I expected the ones I just made!"
fi

echo "Building and checking distribution \`cvc4-$version'..."

if $dogetantlr 
then
  echo "Fetching and compiling antlr..."
  $getantlr > /dev/null 2>&1
  configantlr="--with-antlr-dir=$(pwd)/antlr-3.4 ANTLR=$(pwd)/antlr-3.4/bin/antlr3"
fi

if ! $SHELL -c '\
	version="'$version'"; \
	set -ex; \
	./autogen.sh || echo "autoconf failed; does library_versions have something to match $version?"; \
	mkdir "release-$version"; \
	cd "release-$version"; \
	../configure production-staticbinary --disable-shared --enable-unit-testing --with-portfolio --no-gpl '"$configantlr"'; \
	make dist '"$makeargs"'; \
	tar xf "cvc4-$version.tar.gz"; \
	cd "cvc4-$version"; \
	./configure production-staticbinary --disable-shared --enable-unit-testing --with-portfolio --no-gpl '"$configantlr"'; \
	make check '"$makeargs"'; \
	make distcheck '"$makeargs"'; \
'; then
  exit 1
fi

if ! [ -e release-$version/cvc4-$version.tar.gz ]; then
  internalerror "Cannot find the distribution tarball I just built"
fi
if ! [ -e release-$version/cvc4-$version/builds/src/main/cvc4 ]; then
  internalerror "Cannot find the binary I just built"
fi

echo
echo 'This release of CVC4 will identify itself as:'
echo
release-$version/cvc4-$version/builds/src/main/cvc4 --version
echo
isthatright

echo
echo 'This binary release of CVC4 will identify itself as being configured like this:'
echo
release-$version/cvc4-$version/builds/src/main/cvc4 --show-config
echo
isthatright

#echo
#echo "Signing tarball..."
#cp -p "release-$version/cvc4-$version.tar.gz" .
#gpg -b --armor "cvc4-$version.tar.gz"

#echo
#echo "Signing cvc4 binary..."
#cp -p "release-$version/cvc4-$version/builds/src/main/cvc4" "cvc4-$version"
#gpg -b --armor "cvc4-$version"

#echo
#echo "Signing pcvc4 binary..."
#cp -p "release-$version/src/main/pcvc4" "pcvc4-$version"
#gpg -b --armor "pcvc4-$version"

if $dryrun; then
  echo
  echo '************************* DRY RUN *************************'
fi

echo
echo "About to run: git commit -am \"Cutting release $version.\""
isthatright
$dryrun || git commit -am "Cutting release $version."

echo
echo "About to run: git tag \"$version\""
isthatright
$dryrun || git tag "$version"

echo
andbranch=
if git status -bs | grep -q '^## master\($\|  *\|\.\.\.\)'; then
  if [ "$release" = 0 ]; then
    echo "About to run: git branch \"$version.x\""
    isthatright
    $dryrun || git branch "$version.x"
    andbranch=", the $version.x branch,"
  else
    echo "Not branching, because this is a minor release (i.e., not a \"dot-oh\""
    echo "release), so I'm guessing you have a devel branch outside of master"
    echo "somewhere?  You can still make a branch manually, of course."
  fi
else
  echo "Not branching, because it appears there already is a branch"
  echo "to work with for version $version ..?  (You're not on master.)"
  echo "You can still make a branch manually, of course."
fi

echo
echo "Done.  Next steps are to:"
echo
echo "1. push to GitHub (don't forget to push master$andbranch and the $version tag)"
echo "2. upload source and binaries"
echo "3. advertise these packages"
echo "4. add a $version release and next-milestone to Bugzilla."
echo "5. change $version to pre-release in master$andbranch"
echo "   (in configure.ac) and update library_versions and NEWS."
echo

trap '' EXIT

