#!/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) local is_binary=$(file $tool_file | grep -c "ELF") if [ $is_binary -gt 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 $(find src/go); 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. " >> $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 $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 update_manifest for file in `cat MANIFEST`; do mkdir -p $RELEASE_DIR/$PKG/$(dirname $file) 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 sed -e "s/@@ARCHITECTURE@@/$ARCH/" $RPM_CONFIG_DIR/percona-toolkit.spec > $RELEASE_DIR/percona-toolkit.spec 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 $RELEASE_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/ sed -e "s/@@ARCHITECTURE@@/$DEB_ARCH/" $DEB_CONFIG_DIR/control > $RELEASE_DIR/$PKG/debian/control # 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} ONLY_UPDATE_COPYRIGHT_YEAR=${ONLY_UPDATE_COPYRIGHT_YEAR:-0} if [ $OS_ARCH != "linux-amd64" ] && [ $OS_ARCH != "linux-arm64" ] && [ $OS_ARCH != "linux-386" ] && [ $OS_ARCH != "darwin-amd64" ]; then die "Valid OS-ARCH combinations are: linux-amd64, linux-arm64, linux-386, darwin-amd64" fi if [ $OS_ARCH == "linux-amd64" ]; then ARCH="x86_64" DEB_ARCH="amd64" elif [ $OS_ARCH == "linux-arm64" ]; then ARCH="aarch64" DEB_ARCH="arm64" elif [ $OS_ARCH == "linux-386" ]; then ARCH="i386" DEB_ARCH="i386" else ARCH="noarch" DEB_ARCH="all" fi 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 if [ $ONLY_UPDATE_COPYRIGHT_YEAR -eq 1 ]; then update_copyright_year 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 < 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