#!/bin/sh
## 
## Copyright 2015-2020 The Regents of the University of California
## All rights reserved.
## 
## This file is part of Spoofer.
## 
## Spoofer is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## 
## Spoofer is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with Spoofer.  If not, see <http://www.gnu.org/licenses/>.
## 


stagedir=spoofer-stage
usage() {
    cat >&2 <<EOF
usage: $0 {-u|-s<dir>} [-r<cvsrev>] [-F] <version> {src|ubuntu|win32|mac}...

Exports <cvs_revision> of spoofer from CVS into ${stagedir} and builds
one or more distributions.  But if ${stagedir} already exists from a
previous run, you are given the option to reuse it, which will also preserve
any distributions already built that weren't requested in this run.

After the distributions are built, you will be given instructions on next
steps (including for any distributions remaining from a previous run).

Options:
-u            binary distributions will be unsigned
-s <dir>      binary distributions will be signed using files in <dir>
-r <cvsrev>   check out cvs revision "<cvsrev>" instead of "release-<version>"
              (with each '.' changed to '-').
-o <options>  additional options to pass to configure
-F            force version:  command line <version> overrides configure.ac
-t <tarball>  name of tarball file for "ubuntu" releases (defaults to tarball
              built by "src" argument)

<dist_type> is one or more of the following:
  src           source distribution
  ubuntu[-<N>]  Ubuntu PPA version N, for all current Ubuntu series.  If N is
                omitted, it defaults to 1, and the debian log message will be
                "New upstream release."; otherwise you will be prompted for a
                log message.  To limit release to specific ubuntu series, set an
                environment variable e.g. SPOOFER_UBUNTU_SERIES="bionic cosmic".
  win32         Windows binary distribution (requires a i686-w64-mingw32.static
                cross-compiling environment)
  mac           Mac OSX binary distribution (requires OSX)
EOF
}

assert() {
    local cmd="$*"
    echo "#  $cmd" >&2
    if test "$1" = "!"; then
	shift
	! "$@"
    else
	"$@"
    fi || { echo; echo "$0: assertion failed: $cmd"; exit 1; } >&2
}

finish() {
    local dist_type="$1"
    local prefix="$2"
    local version="$3"
    local suffix="$4"
    local file="$prefix$version$suffix"
    (
	case "$version" in
	*alpha*|*beta*)
	    ;;
	*)
	    assert cp "./$file" ../web/downloads
	    assert cd ../web
	    cvs status "downloads/$file" | egrep "Status: Locally Added" || \
		cvs add -kb "downloads/$file" || \
		{ echo; echo "$0: failed: cvs add -kb downloads/$file"; exit 1; } >&2

	    # replace filename in index.xml
	    assert perl -0777 -pe "s/(href=[\"']downloads\\/\\Q${prefix}\\E)[^\\s<>]*(\\Q${suffix}\\E[\"']>\\s*\\Q${prefix}\\E)[^\\s<>]*(\\Q${suffix}\\E\\s*<.resource>)/\${1}${version}\${2}${version}\${3}/s;" index.xml >index.xml.tmp
	    assert egrep -q "downloads/${file}" index.xml.tmp
	    assert mv index.xml.tmp index.xml

	    # add new release to recentspooferupdates.xinc and remove old release
	    assert perl -ne "if (/Recent Spoofer Updates/) { print \"\$_  <item href='/projects/spoofer/#software'>Spoofer ${version} released ($(date +%Y-%m))</item>\\n\"; } elsif (!/Spoofer [0-9.]* released/) { print; }" recentspooferupdates.xinc >recentspooferupdates.xinc.tmp
	    assert egrep -q "Spoofer ${version} released" recentspooferupdates.xinc.tmp
	    assert mv recentspooferupdates.xinc.tmp recentspooferupdates.xinc
	    ;;
	esac

    ) || exit $?
    echo "$built ${stagedir}/$(basename $(pwd))/$file" >>../NEXT.${dist_type}.built

    gen_db_update "$dist_type" "https://www.caida.org/projects/spoofer/downloads/${file}"
}

gen_db_update() {
    local dist_type="$1"
    local file="$2"
    case $dist_type in
    src)
	local WHERE="Os IS NULL AND UpgradeKey IS NULL"
	;;
    *)
	local OS=$(sed -ne '/^#define OS "\(.*\)"/s//\1/p' config.h)
	local UPGRADE_KEY=$(sed -ne '/^#define UPGRADE_KEY "\(.*\)"/s//\1/p' config.h)
	test -n "$UPGRADE_KEY" || return; # no automatic upgrade, no db update
	assert test -n "$OS"
	local WHERE="Os='${OS}' AND UpgradeKey='${UPGRADE_KEY}'"
	;;
    esac
    NVERSION=$(sed -ne '/^#define NVERSION \(.*\)/s//\1/p' config.h)
    echo "    UPDATE ClientVersions SET CurVersion=${NVERSION}, VersionStr='${version}', file='${file}' WHERE ${WHERE};" >../NEXT.${dist_type}.db
}

CONFIG_OPTIONS='--enable-prober --enable-manager'
revision=""
cs_dir="."
signed="signed"
signprefix=""
tarball=""

while test "$1"; do
    option=""
    case "$1" in
    -[rsot]) option=$1; optarg=$2; shift; shift;;
    -[rsot]*) option=$(echo "$1" | sed -e 's/^\(-.\).*/\1/'); optarg=$(echo "$1" | sed -e 's/^-.//'); shift;;
    -u) signed="unsigned"; signprefix="unsigned-"; shift;;
    -F) forceversion="1"; shift;;
    -h) usage; exit 0;;
    --) shift; break;;
    -*) echo "unknown option $1" >&2; usage; exit 1;;
    *) break;;
    esac
    case "$option" in
    -r) revision=$optarg;;
    -s) cs_dir=$optarg;;
    -o) CONFIG_OPTIONS="$CONFIG_OPTIONS $optarg";;
    -t) tarball="$optarg";;
    esac
done

version="$1"

test $# -ge 1 || { usage; exit 1; }
test -n "$version" || { usage; exit 1; }

shift

if test -z "$revision"; then
    revision="release-$(echo $version | sed 's/\./-/g')"
fi

for dist_type in "$@"; do
    case $dist_type in
    src|ubuntu|ubuntu-*|win32|mac) ;;
    *)
	echo "Error: unknown distibution type \"$dist_type\"" >&2
	exit 1
	;;
    esac
done

# make cs_dir absolute
cs_dir=$(cd "$cs_dir"; pwd;)

echo "# release version: $version"
echo "# release revision: $revision"
echo "# release types: $*"

if test $(umask) != "0022"; then
    echo "WARNING: umask is $(umask); normally it should be 0022."
    echo "Press return to continue."
    read line
fi

reuse=false
exportdir="spoofer-$revision"
if test -d $stagedir/$exportdir; then
    echo "############## Found existing $stagedir/$exportdir"
    while true; do
	echo "\aType 'r' to reuse it, or 's' to start from scratch."
	read response
	case "$response" in
	    R|r) reuse=true; break;;
	    S|s) reuse=false; break;;
	    *) continue;;
	esac
    done
fi

if $reuse; then
    assert cd $stagedir/$exportdir
else
    echo "############## Exporting $stagedir/$exportdir"
    chmod -R u+rw $stagedir
    assert rm -rf $stagedir
    assert mkdir $stagedir
    assert cd $stagedir
    assert cvs export -r $revision -d $exportdir WIP/spoofer
    for d in analysis bgpsrv server web; do rm -r $exportdir/$d; done
    case "$version" in
    *alpha*|*beta*)
	# don't need web directory
	;;
    *)
	dir=$(pwd)
	cachedir=".spoofer-web-cache"
	if test -d "$HOME/$cachedir"; then
	    assert cd "$HOME/$cachedir"
	    assert cvs update
	else
	    assert cd "$HOME"
	    assert cvs checkout -d"$cachedir" www-caida-org/new-projects/spoofer
	    cat >$cachedir/cache-created-by-spoofer-release-script <<EOF
This cache directory was created by the Spoofer release script.
You may remove this directory, but do not otherwise modify it.
EOF
	fi
	cd "$dir"
	rm -rf web
	assert cp -a "$HOME/.spoofer-web-cache" web
	;;
    esac
    assert cd $exportdir
    case "$version" in
	*alpha*) cat README.alpha README >README.tmp && mv README.tmp README;;
    esac
fi

# example:  m4_define([SPOOFER_VERSION], [1.4.0-beta1])
cfgversion=$(sed -ne 's/^m4_define(\[SPOOFER_VERSION\], *\[\([^]]*\)\].*/\1/p' configure.ac)
if test "$forceversion" = "1"; then
    sed -e 's/^\(m4_define(\[SPOOFER_VERSION\], *\)\[\([^]]*\)\]/\1['"$version"']/' configure.ac >configure-forced.ac || exit $?
    echo "Forced version:"
    diff configure.ac configure-forced.ac
    mv configure-forced.ac configure.ac
elif test "x$cfgversion" != "x$version"; then
    echo "ERROR: configure.ac version '$cfgversion' does not match requested version '$version'."
    exit 1
fi


### sanity checks before we start the slow process of building releases

# sanity check: copyrights
for f in prober/spoofer-prober.cc manager/scheduler/main.cpp mac-scripts/apps/postinstall.in; do
    egrep -q "Copyright.*$(date +%Y) The Regents of the University of California" $f || \
	{ echo "Error: missing/old copyright in $f" >&2; exit 1; }
done

# sanity check: debian/copyright
egrep "The Regents of the University of California" debian/copyright>reg
assert egrep -q "$(date +%Y) The Regents" reg
egrep -v "$(date +%Y) The Regents" reg && \
    { echo "Error: out of date debian/copyright?" >&2; exit 1; }
rm reg


assert ./bootstrap
assert cd .. ; # ${stagedir}

for dist_type in "$@"; do
    assert rm -f "NEXT.${dist_type}."*
    assert rm -rf ${dist_type}
    assert mkdir ${dist_type}
done

for dist_type in "$@"; do
    case $dist_type in
    src)
	echo; echo "############## building source distribution"
	assert cd src
	if test -z "$QMAKE"; then
	    for f in qmake "$HOME/Qt/5.3/gcc/bin/qmake"; do
		if $f --version >/dev/null 2>&1; then QMAKE="$f"; break; fi
	    done
	fi
	if test -z "$PROTOC"; then
	    for f in protoc "$HOME/proto3/bin/protoc"; do
		if $f --version >/dev/null 2>&1; then PROTOC="$f"; break; fi
	    done
	fi
	assert ../$exportdir/configure ${CONFIG_OPTIONS}
	# During "make dist", disable unneeded tools to catch unintended use.
	assert make dist CXX='bad_CXX' PROTOC='bad_PROTOC' QMAKE='bad_QMAKE'
	assert make distcheck DISTCHECK_CONFIGURE_FLAGS="${CONFIG_OPTIONS} \
	    PROTOC='$PROTOC' QMAKE='$QMAKE'"
	assert finish $dist_type spoofer- "$version" .tar.gz
	assert cd ..; # ${stagedir}

	ver=$(echo "${version}" | sed -e 's/\.//g')
	set $(sha256sum src/spoofer-${version}.tar.gz)
	SUM="$1"
        echo "$SUM" | egrep -q '^[0-9a-f]{64}$' || \
            SUM="(###### FILL IN SHA256SUM OF spoofer-${version}.tar.gz ######)"
	cat >>NEXT.${dist_type}.local <<-EOF
	    in '~/openwrt/openwrt-packages'
	        # see https://openwrt.org/submitting-patches
	        git fetch upstream
	        git checkout master
	        git merge upstream/master    ;# sync my copy with upstream
	        git push origin master       ;# and push to my fork on github
	        git checkout -b spoofer-${ver}  ;# create new branch
	        in net/spoofer/Makefile:
	            Copyright $(date +%Y)
	            PKG_VERSION:=${version}
	            PKG_RELEASE:=1
	            PKG_HASH:=${SUM}
	            # REMOVE "include vpath.mk"
	        # make any other necessary changes
	        git add net/spoofer/Makefile
	        git commit --signoff
	            spoofer: Update to ${version}
	        git push --all
	        # make a pull request
	EOF

	;;

    ubuntu*)
	# See: https://help.launchpad.net/Packaging/PPA/BuildingASourcePackage
	echo; echo "############## building ubuntu distribution"

	assert test "$(lsb_release -si)" = "Ubuntu"
	assert cd ubuntu

	case "$tarball" in
	    '') tarball=../src/spoofer-$version.tar.gz;;
	    /*) : nop ;;
	    *) tarball=../../$tarball;;
	esac
	assert tar zxf "$tarball"

	assert cd spoofer-$version
	if test -n "$SPOOFER_DEBIAN"; then
	    cp -a "$SPOOFER_DEBIAN" debian; # XXX
	else
	    assert cvs checkout -r $revision -d debian WIP/spoofer/debian
	fi

	cat >>../../NEXT.${dist_type}.local <<-EOF
	    in '$stagedir/ubuntu'
	        copy spoofer-$version/debian/changelog to a source tree and commit it
	EOF

	if test -n "$SPOOFER_RELEASE_BINARY"; then
	    all_series=$(lsb_release -sc)
	elif test -n "$SPOOFER_UBUNTU_SERIES"; then
	    all_series="$SPOOFER_UBUNTU_SERIES"
        else
            ymd=$(date +"%Y-%m-%d")
            inrange() { ! test $1 '<' $2 && ! test $1 '>' $3; }
            all_series=$(echo $(
                inrange $ymd 2016-04-21 2021-04-99 && echo xenial; # LTS
                inrange $ymd 2018-04-26 2023-04-99 && echo bionic; # LTS
                inrange $ymd 2018-10-18 2019-07-18 && echo cosmic
                inrange $ymd 2019-04-18 2020-01-23 && echo disco
                inrange $ymd 2019-10-17 2020-07-17 && echo eoan
                inrange $ymd 2020-04-23 2025-04-99 && echo focal; # LTS
                inrange $ymd 2020-10-22 2021-07-99 && echo groovy
            ))
            if ! test $ymd '<' 2021-04; then
		echo "WARNING: don't know name of current Ubuntu series" >&2
            fi
	fi
	prev_series=""
	for series in $all_series; do
	    case $series in
		xenial) ubuntu="ubuntu-16.04.1";;
		bionic) ubuntu="ubuntu-18.04.1";;
		cosmic) ubuntu="ubuntu-18.10.1";;
		disco)  ubuntu="ubuntu-19.04.1";;
		eoan)   ubuntu="ubuntu-19.10.1";;
		focal)  ubuntu="ubuntu-20.04.1";;
		groovy) ubuntu="ubuntu-20.10.1";;
		*) echo "unknown ubuntu series '$series'" >&2; exit 1;;
	    esac

	    if test -n "$prev_series"; then
		# Use the same debian/changelog, just change the series name
                # (Launchpad requires the correct series name in the changelog;
                # an override in dput.cf isn't sufficient.)
		sed -i -e "1s/~ubuntu.*) $prev_series;/~$ubuntu) $series;/" \
		    debian/changelog
            elif test "${dist_type}" = "ubuntu"; then
		# Use a default log message
		assert debchange -p -v "$version-1~$ubuntu" -D "$series" "New upstream release."
            else # "ubuntu-N"
		# Prompt for a new log message
                N=${dist_type#ubuntu-}
		assert debchange -p -v "$version-${N}~$ubuntu" -D "$series"
	    fi
	    PPA_VERSION=$(sed -ne '1s/spoofer (\(.*\)).*/\1/p' debian/changelog)
	    ORIG_VERSION=$(echo $PPA_VERSION | sed -e 's/-[0-9.]*$//')
	    if ! test -L ../spoofer_${ORIG_VERSION}.orig.tar.gz; then
		assert ln -s "$tarball" ../spoofer_${ORIG_VERSION}.orig.tar.gz
	    fi

	    # Note: to check dependencies, build in a clean chroot environment
	    # with: `pdebuild --buildresult ..`
            # Note: if running remotely without a GUI, but debuild is timing
            # out in gpg, it may be giving a GUI prompt on another screen
            # where you can't answer.  Try forcing it to use tty:
            # > sudo apt install pinentry-tty
            # > sudo update-alternatives --config pinentry
            #   (then select pinentry-tty)
	    if test -n "$SPOOFER_RELEASE_BINARY"; then
		assert debuild -i -us -uc -b
	    else
		assert debuild -S
	    fi

	    echo "$built ${stagedir}/ubuntu/spoofer_${PPA_VERSION}"'*' >>../../NEXT.${dist_type}.built
	    cat >>../../NEXT.${dist_type}.local <<-EOF
	        dput spoofer-$series spoofer_${PPA_VERSION}_source.changes
		EOF
	    prev_series="$series"
	done

	case "$version" in
	*alpha*|*beta*)
	    ;;
	*)
            assert sed -i \
                -e "s/\(Spoofer-\)[^ ]*\( PPA\)/Spoofer-$version PPA/" \
                -e "s/Ubuntu.*Package.*(signed)/Ubuntu Packages for: $all_series (signed)/" \
                ../../web/index.xml
            ;;
        esac

	cd ../..; # $stagedir
	;;

    win32)
	echo; echo "############## building $signed Windows binary distribution"
	assert cd win32
	assert cp $HOME/WIP/spoofer/win-bin/WinPcap_4_1_3.exe ../$exportdir/win-bin
	assert cp $HOME/WIP/spoofer/win-bin/vc2012u4redist_x86.exe ../$exportdir/win-bin
	(
	    PATH=/opt/mxe/usr/bin:$PATH
	    assert ../$exportdir/configure ${CONFIG_OPTIONS} \
		--host=i686-w64-mingw32.static \
		UPGRADE_KEY="https://www.caida.org/" \
		CXXFLAGS="-O2 -DNDEBUG" \
		PROTOC='/opt/mxe/usr/x86_64-pc-linux-gnu/bin/protoc'
	    assert make CS_DIR="$cs_dir" windist-${signed}
	) || exit $?
	assert finish $dist_type ${signprefix}Spoofer- "$version" -win32.exe
	assert cd ..; # ${stagedir}
	;;

    mac)
	echo; echo "############## building $signed Mac OSX binary distribution"
	assert test $(uname) = "Darwin"
	assert cd mac
	(
	    export MACOSX_DEPLOYMENT_TARGET=10.10
	    # where to find headers/libraries/tools/etc for deployment target
	    TARGETROOT="$HOME/macos-$MACOSX_DEPLOYMENT_TARGET"
	    # set PATH for protoc, productsign, qmake
	    PATH="$TARGETROOT/bin:$PATH"
	    assert ../$exportdir/configure \
		--with-openssl="$TARGETROOT" \
		${CONFIG_OPTIONS} \
		UPGRADE_KEY="https://www.caida.org/" \
		CXX='clang++ -stdlib=libc++' \
		CXXFLAGS="-arch x86_64 -O2 -I$TARGETROOT/include -DNDEBUG" \
		LDFLAGS="-arch x86_64 -L$TARGETROOT/lib"
		# PKG_CONFIG_PATH="$TARGETROOT/lib/pkgconfig" # not needed
	    assert make macdist-${signed}
	) || exit $?
	assert finish $dist_type ${signprefix}Spoofer- "$version" -macosx.pkg
	assert cd ..; # ${stagedir}
	;;

    *)
	# shouldn't happen
	echo "############## unknown distibution type \"$dist_type\"" >&2
	exit 1
	;;
    esac
done

case "$version" in
*alpha*|*beta*)
    ;;
*)
    assert cp $exportdir/CHANGES web/downloads/changelog.txt
    assert cd web
    echo; echo
    cvs status 2>&1 | egrep '^File:|^cvs status: Examining' | egrep -v 'Status: Up-to-date'
    cd ..
    ;;
esac
echo; echo

assert ls NEXT.*.built >/dev/null 2>&1

echo "Successfully built releases:"
cat $(ls -tr NEXT.*.built)

echo
case "$version" in
*alpha*|*beta*)
    echo 'If this were a production release, the next steps would be:'
    ;;
*)
    echo 'Next steps:'
    ;;
esac

echo '* locally:'
echo "    in '$stagedir/web'"
echo "        cvs commit"
if ls NEXT.*.local >/dev/null 2>&1; then
    cat $(ls -tr NEXT.*.local)
fi
echo '* on web server (cider):'
echo "    cd ~/private_xml/www-caida-org/projects/spoofer"
echo "    cvs update"
echo "    # check https://www-xml.caida.org/~${USER}/www-caida-org/projects/spoofer"
echo "    wcs pub"
if ls NEXT.*.db >/dev/null 2>&1; then
    echo '* in "spoofer_test" and "spoofer" databases:'
    cat $(ls -tr NEXT.*.db)
fi
