#!/usr/bin/env bash

# This script builds tar, rpm, and deb packages for a new release.  The
# packages are created in the release/ directory (which is created if it
# does not exist).
#
# There's only one command line option: VERSION.  It must be newer than
# the last version in the Changelog; see check_version().  Do not include
# a leading 'v', just `build-packages 1.0.8' for example.
#
# These environment variables control what the script does:
#   CHECK=0|1       - Do (not) check the branch, version, etc.
#   UPDATE=0|1      - Do (not) update changelogs, versions, etc.
#   BUILD=0|1       - Do (not) build any packages
#   BUILD_TAR=0|1   - Do (not) build the .tar.gz package
#   BUILD_RPM=0|1   - Do (not) build the .rpm package
#   BUILD_DEB=0|1   - Do (not) build the .deb package
#   BUILD_GO=0|1    - Do (not) build Go binaries
# All of these env vars are true by default.  If, for example, you just want
# to build the branch as-is: CHECK=0 UPDATE=0 build-packages VERSION
# Otherwise, this script is pretty strict and tries to ensure a good build.
#
# These environment variables are for special builds:
#   UPDATE_DOCS=0|1 - Do (not) update docs
#   BETA=1          - UPDATE_DOCS=0 BUILD_RPM=0 BUILD_DEB=0
#   QUIET=1         - Do (not) ask questions
#
# A few more things you should know:
#  * You'll need rpmbuild and dpkg-buildpackage to build the rpm and deb pkgs
#  * Output (STDOUT and STDERR) for some stuff is saved to files in tmpdir
#  * All dates/times are UTC
#  * No pkgs are signed (TODO)

# ############################################################################
# Standard startup, find the branch's root directory
# ############################################################################

set -ue # bail out on errors, be strict

exit_status=0

die() {
   echo "$1" >&2
   exit 1
}

warn() {
   echo "$1" >&2
   exit_status=1
}

cwd=$PWD
PERCONA_TOOLKIT_BRANCH=$(git rev-parse --show-toplevel)
if [ -n "$PERCONA_TOOLKIT_BRANCH" ]; then
   BRANCH=$PERCONA_TOOLKIT_BRANCH
   cd $BRANCH
else
   while [ ! -f Makefile.PL ] && [ $PWD != "/" ]; do
      cd ..
   done
   if [ ! -f Makefile.PL ]; then
      die "Cannot find the root directory of the Percona Toolkit branch"
      exit 1
   fi
   BRANCH=`pwd`
fi
cd $cwd

# ############################################################################
# Paths
# ############################################################################

DOCS_DIR=$BRANCH/docs
SPHINX_CONFIG_DIR=$BRANCH/config/sphinx-build
DEB_CONFIG_DIR=$BRANCH/config/deb
RPM_CONFIG_DIR=$BRANCH/config/rpm
RELEASE_DIR=$BRANCH/release
GO_SRC_DIR=$(git rev-parse --show-toplevel)/src/go

# ############################################################################
# Programs and their options
# ############################################################################

TAR=${TAR:-tar}

# ############################################################################
# Subroutines
# ############################################################################

check_branch() {
   echo -n "Checking branch... "
   local clean_branch=$(git status --porcelain|wc -l)
   if [ "$clean_branch" -gt 0 ]; then
      die "The branch has uncommitted changes or unknown files"
   fi
   echo "OK"
}

check_version() {
   echo -n "Checking new version $VERSION... "
   cd $BRANCH
   local current_version=$(expr `cat Makefile.PL | grep VERSION | awk '{print $3}'` : "'\([0-9.]*\)'")
   if ! [ "$VERSION" '>' "$current_version" ]; then
      die "New version $VERSION is not greater than current version $current_version"
   fi
   echo "OK"
}

check_changelog() {
   echo -n "Checking Changelog... "
   cd $BRANCH
   first_line=$(head -n 3 Changelog | tail -n 1)
   if [ $(expr "$first_line" : "v[0-9]") -gt 0 ]; then
      die "No changes since $first_line"
   fi
   if [ -n "$(grep "^v$VERSION" Changelog)" ]; then
      die "Entries for v$VERSION already exist"
   fi
   echo "OK"
}

check_rel_notes() {
   echo -n "Checking release_notes.rst... "
   cd $DOCS_DIR
   if [ -n "$(grep "^v$VERSION" release_notes.rst)" ]; then
      die "Entries for v$VERSION already exist"
   fi
   echo "OK"
}

update_version() {
   echo -n "Updating version in tools... "
   cd $BRANCH/bin
   for tool_file in *; do
      if [ $tool_file != "govendor" ]; then
         sed -i'.bak' -e "s/^$tool_file [0-9]\.[0-9][^ ]\+/$tool_file $VERSION/" $tool_file
         if [ $? -ne 0 ]; then
            die "Error updating version in $tool_file"
         fi
         rm "$tool_file.bak"
      fi
   done

   local new_versions=$(grep --no-filename '^pt-[^ ]\+ [0-9]\.' * | cut -d' ' -f2 | sort -u)
   if [ "$new_versions" != "$VERSION" ]; then
      die "The version in some tools did not update correctly"
   fi
   echo "OK"

   echo -n "Updating version in Makefile.PL... "
   cd $BRANCH
   sed -i'.bak' -e "s/'[0-9.]*'/'$VERSION'/" Makefile.PL
   if [ $? -ne 0 ]; then
      die "Error updating version in Makefile.PL"
   fi
   rm "Makefile.PL.bak"
   echo "OK"

   echo -n "Updating version in percona-toolkit.pod... "
   cd $DOCS_DIR
   sed -i'.bak' -e "s/^Percona Toolkit v.*/Percona Toolkit v$VERSION released $DATE/" percona-toolkit.pod
   if [ $? -ne 0 ]; then
      die "Error updating version in percona-toolkit.pod"
   fi
   rm "percona-toolkit.pod.bak"
   echo "OK"

   echo -n "Updating version in sphinx-build/conf.py... "
   # What Sphinx calls version, we call series; what it calls release we
   # call version.
   cd $SPHINX_CONFIG_DIR
   sed -i'.bak' -e "s/^version = .*/version = '$SERIES'/" conf.py
   if [ $? -ne 0 ]; then
      die "Error updating version in conf.py"
   fi
   rm "conf.py.bak"
   sed -i'.bak' -e "s/^release = .*/release = '$VERSION'/" conf.py
   if [ $? -ne 0 ]; then
      die "Error updating release in conf.py"
   fi
   rm "conf.py.bak"
   echo "OK"
}

update_copyright_year() {
   echo -n "Updating copyright year in tools to $YEAR ... "
   cd $BRANCH/bin
   for tool_file in *; do
      # Skip checking binary files (golang binaries)
      file bin/pt-pg-summary | grep -q "ELF"
      if [ $? -eq 0 ]; then
          continue
      fi
      local copyright="$(grep "[0-9] Percona LLC and/or its affiliates" $tool_file)"
      local new_copyright="$(../util/new-copyright-year "$YEAR" "$copyright")"
      if [ $? -ne 0 ]; then
         die "Error parsing copyright year in $tool_file"
      fi
      sed -i'.bak' -e "s#^$copyright#$new_copyright#" $tool_file
      if [ $? -ne 0 ]; then
         die "Error updating copyright year in $tool_file"
      fi
      rm "$tool_file.bak"
   done
   echo "OK"

   echo -n "Updating copyright year in percona-toolkit.pod... "
   local pod=$DOCS_DIR/percona-toolkit.pod
   local copyright="$(grep "[0-9] Percona LLC and/or its affiliates" $pod)"
   local new_copyright="$(../util/new-copyright-year "$YEAR" "$copyright")"
   if [ $? -ne 0 ]; then
      die "Error parsing copyright year in percona-toolkit.pod"
   fi
   sed -i'.bak' -e "s#^$copyright#$new_copyright#" $pod
   if [ $? -ne 0 ]; then
      die "Error updating copyright year in percona-toolkit.pod"
   fi
   rm $pod.bak
   echo "OK"
}

update_manifest() {
   echo -n "Updating MANIFEST... "
   cd $BRANCH
   echo -n > MANIFEST
   for file in * bin/* docs/*.pod; do
      if [ -f $file ]; then
         echo $file >> MANIFEST
      fi
   done
   echo "OK"
}

update_percona_toolkit_pod() {
   echo -n "Updating TOOLS section in percona-toolkit.pod... "
   cd $BRANCH/bin
   local pod=$DOCS_DIR/percona-toolkit.pod
   local tool_list=/tmp/percona-tool-list.pod

   echo "=head1 TOOLS

This release of Percona Toolkit includes the following tools:

=over
" > $tool_list

   for tool in *; do
      desc=$(grep -A 2 '^=head1 NAME' $tool | tail -n 1 | sed 's/ - /:/' | cut -d':' -f2)
      echo "=item $tool

$desc
" >> $tool_list
   done

   echo "=back

For more free, open-source software developed Percona, visit
L<http://www.percona.com/software/>.
" >> $tool_list

   cat $pod | ../util/replace-text -v from='^=head1 TOOLS' -v file=$tool_list -v to='^=head1' > $pod.tmp
   rm $tool_list

   if [ -z "$(podchecker $pod.tmp 2>&1 | grep -i 'pod syntax OK')" ]; then
      die "POD syntax errors; run podchecker $pod.tmp"
   fi

   mv $pod.tmp $pod
   echo "OK"
}

update_changelog() {
   echo -n "Updating Changelog... "
   cd $BRANCH
   head -n 2 Changelog > /tmp/changelog.tmp
   echo "v$VERSION released $DATE" >> /tmp/changelog.tmp
   echo >> /tmp/changelog.tmp
   n_lines=$(wc -l Changelog | awk '{print $1}')
   tail -n $((n_lines - 2)) Changelog >> /tmp/changelog.tmp
   mv /tmp/changelog.tmp $BRANCH/Changelog
   echo "OK"

   echo -n "Updating Debian changelog... "
   cd $DEB_CONFIG_DIR
   echo "percona-toolkit ($VERSION-1) unstable; urgency=low
" > /tmp/changelog.tmp

   cat $BRANCH/Changelog | $BRANCH/util/log-entries $VERSION >> /tmp/changelog.tmp
   echo >> /tmp/changelog.tmp
   echo " -- Percona Toolkit Developers <toolkit-dev@percona.com>  $DEB_DATE
" >> /tmp/changelog.tmp
   cat changelog >> /tmp/changelog.tmp
   mv /tmp/changelog.tmp changelog
   echo "OK"
}

update_rel_notes() {
   echo -n "Updating release_notes.rst... "
   cd $DOCS_DIR

   head -n 3 release_notes.rst > /tmp/release_notes.tmp

   local line="v$VERSION released $DATE"
   local len=${#line}
   local ul="$(printf "%${len}s" | tr [:space:] '=')"
   echo "$line"                           >> /tmp/release_notes.tmp
   echo "$ul"                             >> /tmp/release_notes.tmp
   echo                                   >> /tmp/release_notes.tmp
   (cd $cwd && cat $REL_NOTES             >> /tmp/release_notes.tmp)
   echo                                   >> /tmp/release_notes.tmp
   echo "Changelog"                       >> /tmp/release_notes.tmp
   echo "---------"                       >> /tmp/release_notes.tmp
   echo                                   >> /tmp/release_notes.tmp
   cat $BRANCH/Changelog | $BRANCH/util/log-entries $VERSION \
                         | sed -e 's/^ *//g'                 \
                                          >> /tmp/release_notes.tmp
   echo                                   >> /tmp/release_notes.tmp

   tail -n +4 release_notes.rst >> /tmp/release_notes.tmp

   mv /tmp/release_notes.tmp release_notes.rst

   echo "OK"
}

update_user_docs() {
   echo -n "Updating user docs... "
   $BRANCH/util/write-user-docs $BRANCH/bin/* > /tmp/sphinx-build.output 2>&1
   if [ $? -ne 0 ]; then
      warn "Error updating user docs:"
      cat /tmp/sphinx-build.output >&2
      exit 1
   fi
   rm /tmp/sphinx-build.output
   echo "OK"
}

prep_release_dir() {
   echo -n "Preparing release directory... "
   cd $BRANCH
   if [ ! -d $RELEASE_DIR ]; then
      mkdir $RELEASE_DIR
   elif [ -d $RELEASE_DIR/$PKG ]; then
      rm -rf $RELEASE_DIR/$PKG
   fi
   (
      cd $RELEASE_DIR
      mkdir -p $PKG $PKG/bin $PKG/docs $PKG/lib
   )
   update_manifest
   for file in `cat MANIFEST`; do
      cp $file $RELEASE_DIR/$PKG/$file
   done
   echo "OK"
}

build_tar() {
   echo -n "Building $PKG.tar.gz... "
   cd $RELEASE_DIR
   $TAR czf "$PKG.tar.gz" $PKG
   echo "OK"
}

build_rpm() {
   echo -n "Building $PKG-1.$ARCH.rpm... "
   cd $RELEASE_DIR
   if [ ! -f "$PKG.tar.gz" ]; then
      die "Cannot build RPM because $PKG.tar.gz does not exist"
   fi

   mkdir -p rpm rpm/BUILD rpm/SOURCES rpm/RPMS rpm/SRPMS
   mkdir -p RPM
   cd rpm
   local topdir=`pwd`
   # Build RPM package from the tarball.
   rpmbuild -bb --clean $RPM_CONFIG_DIR/percona-toolkit.spec \
      --quiet                            \
      --define "_topdir $PWD"            \
      --define "_sourcedir $RELEASE_DIR" \
      --define "version $VERSION"        \
      --target=$ARCH \
      --define "release 1" > $tmpdir/rpmbuild 2>&1
   if [ $? -ne 0 ]; then
      warn "rpmbuild has warnings; see $tmpdir/rpmbuild"
   fi

   if [ ! -f "RPMS/$ARCH/$PKG-1.$ARCH.rpm" ]; then
      die "RPMS/$ARCH/$PKG-1.$ARCH.rpm did not build"
   fi
   mv "RPMS/$ARCH/$PKG-1.$ARCH.rpm" $RELEASE_DIR/RPM
   rm -rf $RELEASE_DIR/rpm

   echo "OK"
}

build_deb() {
   local deb_pkg="percona-toolkit_$VERSION-1_$DEB_ARCH.deb"
   echo -n "Building $deb_pkg... "

   cd $RELEASE_DIR
   if [ ! -f "$PKG.tar.gz" ]; then
      die "Cannot build deb because $PKG.tar.gz does not exist"
   fi

   # Copy debian pkg files.
   if [ ! -d "$RELEASE_DIR/$PKG/debian" ]; then
      mkdir $RELEASE_DIR/$PKG/debian
   else
      rm -rf * $RELEASE_DIR/$PKG/debian
   fi
   cp $BRANCH/config/deb/* $RELEASE_DIR/$PKG/debian/

   # Build Debian binary and source packages.
   cd $RELEASE_DIR/$PKG
   dpkg-buildpackage -us -uc -a$DEB_ARCH >$tmpdir/dpkg-buildpackage 2>&1
   if [ $? -ne 0 ]; then
      warn "dpkg-buildpackage has warnings; see $tmpdir/dpkg-buildpackage"
   fi

   rm -rf debian/ build-stamp >/dev/null
   make distclean >/dev/null
   cd $RELEASE_DIR
   rm -rf *.changes >/dev/null

   mkdir -p deb
   mv percona-toolkit_* deb/

   echo "OK"
}

build_go_binaries() {
   BASE_DIR=$(git rev-parse --show-toplevel)
   GITHUB_DIR="${GOPATH}/src/github.com"
   PERCONA_DIR="${GITHUB_DIR}/percona"
   TOOLKIT_DIR="${PERCONA_DIR}/percona-toolkit"
   GO_TOOLS_DIR="${TOOLKIT_DIR}/src/go"
   CUR_DIR=$(pwd)
   UNLINK=0
   export PATH=${GOPATH}/bin:${PATH}

   echo "GOPATH: ${GOPATH}"
   echo "Github directory: ${GITHUB_DIR}"
   echo "Percona directory: ${PERCONA_DIR}"
   echo "Toolkit directory: ${TOOLKIT_DIR}"
   echo "Current directory: ${CUR_DIR}"

   if [[ ${BASE_DIR} != ${TOOLKIT_DIR} ]]
   then
       echo "not in gopath"
       if [ -e ${TOOLKIT_DIR} ]; then
           echo "Cannot link current directory into GOPATH. Already exists" >&2
           die "Please check/remove the ${TOOLKIT_DIR} directory"
       else
           mkdir -p ${PERCONA_DIR}
           ln -s ${BASE_DIR} ${PERCONA_DIR}
           UNLINK=1
       fi
   fi
   echo "Building Go tools for $OS_ARCH ..."
   cd $GO_TOOLS_DIR
   make $OS_ARCH
   cd $CUR_DIR

   if [ $UNLINK -eq 1 ]; then
       rm ${TOOLKIT_DIR}
   fi

   echo "OK"
}

# ############################################################################
# Script starts here
# ############################################################################

if [ $# -lt 2 ]; then
   echo "Usage: $0 VERSION RELEASE_NOTES OS-ARCH"
   echo "Example: $0 3.1 docs/release_notes.rst linux-amd64"
   echo "Valid OS-ARCH combinations are: linux-amd64, linux-386, darwin-amd64"
   die "Please try again with different parameters"
fi
VERSION=$1
REL_NOTES=$2
OS_ARCH=${3:-linux-amd64}

ONLY_UPDATE_VERSION=${ONLY_UPDATE_VERSION:-0}

if [ $OS_ARCH != "linux-amd64" ] && [ $OS_ARCH != "linux-386" ] && [ $OS_ARCH != "darwin-amd64" ]; then
   die "Valid OS-ARCH combinations are: linux-amd64, linux-386, darwin-amd64"
fi

if [ $OS_ARCH == "linux-amd64" ]; then
   ARCH="x86_64"
   DEB_ARCH="amd64"
elif [ $OS_ARCH == "linux-386" ]; then
   ARCH="i386"
   DEB_ARCH="i386"
else
   ARCH="noarch"
   DEB_ARCH="all"
fi

sed -i'.bak' -e "s/@@ARHITECTURE@@/$ARCH/" $RPM_CONFIG_DIR/percona-toolkit.spec
sed -i'.bak' -e "s/@@ARHITECTURE@@/$DEB_ARCH/" $DEB_CONFIG_DIR/control

if [ ! -f $REL_NOTES ]; then
   die "$REL_NOTES does not exist"
fi

# My machine's language is not English.  This affects DEB_DATE because
# date %a is language-sensitive.  We want abbreviated day names in English.
# Setting LANG as such works for me; hopefully it works for you, too.
LANG='en_US.UTF-8'

SERIES=$(echo $VERSION | sed 's/\.[0-9][0-9]*$//')
YEAR=$(date -u +'%Y')                      # for updating copyright year
DATE=$(date -u +'%F')                      # for updating release date
DEB_DATE=$(date -u +'%a, %d %b %Y %T %z')  # for updating deb/changelog
PKG="percona-toolkit-$VERSION"             # what we're building

if [ $ONLY_UPDATE_VERSION -eq 1 ]; then
   update_version
   exit
fi

# mktemp -d doesn't work on Mac OS X, so we'll do it the old-fashioned way.
tmpdir="/tmp/build-percona-toolkit-$VERSION"
rm -rf $tmpdir >/dev/null 2>&1
mkdir $tmpdir

BETA=${BETA:-0}
if [ $BETA -eq 1 ]; then
   UPDATE_DOCS=0
   BUILD_RPM=0
   BUILD_DEB=0
fi

# This script does not check that you've done pre-release tasks like running
# the test suite, updating Changelog entries, etc.  You're responsible for
# that.  These checks are for the sanity of package building.
CHECK=${CHECK:-1}
if [ $CHECK -eq 1 ]; then
   check_branch
   check_version
   check_changelog
   check_rel_notes
fi

BUILD_GO=${BUILD_GO:-1}
if [ $BUILD_GO -eq 1 ]; then
    build_go_binaries
fi

# These items need to be updated automatically for each release.
UPDATE=${UPDATE:-1}
if [ $UPDATE -eq 1 ]; then
   update_version
   update_copyright_year
   update_manifest

   UPDATE_DOCS=${UPDATE_DOCS:-1}
   if [ $UPDATE_DOCS -eq 1 ]; then
      update_percona_toolkit_pod
      update_changelog
      update_rel_notes
      update_user_docs
   fi
fi


# Now that those ^ items are updated, you need to commit and push one more
# time before the release packages are built.  This script can't do that
# because your branch could non-standard.
BUILD=${BUILD:-1}
QUIET=${QUIET:-0}
if [ $BUILD -eq 1 ]; then
   if [ $QUIET -eq 0 ]; then
      cat <<MSG

Branch verified and updated; ready to build $PKG,
but first you must:

  1. git diff and review the changes (Changelog, percon-toolkit.pod, etc.)
  2. git add . (from root dir of project)
  3. git commit -m "Build $PKG"
  4. git push origin <release-branch>

Press any key to continue... (or Ctrl-C to abort)
MSG

      read
   fi

   prep_release_dir

   BUILD_TAR=${BUILD_TAR:-1}
   if [ $BUILD_TAR -eq 1 ]; then
      build_tar
   fi

   BUILD_RPM=${BUILD_RPM:-1}
   if [ $BUILD_RPM -eq 1 ]; then
      build_rpm
   fi

   BUILD_DEB=${BUILD_DEB:-1}
   if [ $BUILD_DEB -eq 1 ]; then
      build_deb
   fi

   if [ -d $RELEASE_DIR/$PKG ]; then
      rm -rf  $RELEASE_DIR/$PKG
   fi

   echo "Done building $PKG.  Packages are in $RELEASE_DIR"
fi

exit $exit_status
