#!/bin/bash

################################################################################
# To be used after running,
#
#	${ISSM_DIR}/jenkins/jenkins.sh ${ISSM_DIR}/jenkins/pine_island-mac-binaries-python
#
# in the context of a Jenkins project.
#
# When no runtime errors occur, performs the following:
# - Checks resulting executables and libraries against test suite.
# - Packages and compresses executables and libraries.
# - Commits compressed package to repository to be signed by JPL Cybersecurity.
# - Retrieves signed package and transmits it to ISSM Web site for 
#	distribution.
#
# Options:
# -c/--cleanup			Remove lock file from signed package repository (use if 
#						build is aborted to allow for subsequent fresh build)
# -n/--notarizeonly		Sign/notarize only (use if signing/notarization fails 
#						to skip tests/packaging)
# -s/--skiptests		Skip tests (use if this script fails for some reason 
#						after tests have successfully passed to save time)
# -t/--transferonly		Transfer package to ISSM Web site only (use if 
#						transfer fails for some reason to skip testing and
#						signing)
#
# Debugging:
# - Relies on a very tight handshake with project on remote JPL Cybersecurity 
#	Jenkins server. Debugging may be perfomed locally by running,
#
#		packagers/mac/sign-issm-mac-binaries-python.sh
#
#	with Apple Developer credentials.
# - Removing stdout/stderr redirections to null device (> /dev/null 2>&1) can 
#	help debug potential SVN issues.
#
# NOTE:
# - Assumes that 'issm-binaries-user' and 'issm-binaries-pass' are set up in 
#	the 'Bindings' section under a 'Username and password (separated)' binding 
#	(requires 'Credentials Binding Plugin').
# - For local debugging, the aformentioned credentials can be hardcoded into 
#	the 'USERNAME' and 'PASSWORD' constants below.
################################################################################

# Expand aliases within the context of this script
shopt -s expand_aliases

# From https://developer.apple.com/documentation/macos-release-notes/macos-catalina-10_15-release-notes,
#
#	Command line tool support for Subversion — including svn, git-svn, and 
#	related commands — is no longer provided by Xcode. (50266910)
#
# which results in,
#
#	svn: error: The subversion command line tools are no longer provided by 
#	Xcode.
#
# when calling svn, even when subversion is installed via Homebrew and its path 
# is available in PATH.
#
# NOTE: May be able to remove this after updating macOS.
#
alias svn='/usr/local/bin/svn'

## Override certain other aliases
#
alias grep=$(which grep)

## Constants
#
NOTARIZATION_LOGFILE="notarization.log"
PASSWORD=$env:issm-binaries-pass
PKG="ISSM-macOS-Python" # Name of directory to copy distributable files to
PYTHON_NROPTIONS="--benchmark all --exclude 125 126 234 235 418 420 435 444 445 701 702 703 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1201 1202 1203 1204 1205 1206 1207 1208 1301 1302 1303 1304 1401 1402 1601 1602 2006 2020 2021 2051 2052 2053 3001:3200 3201 3202 3300 3480 3481 4001 4002 4003" # NOTE: Combination of test suites from basic, Dakota, and Solid Earth builds, with tests that require a restart and those that require the JVM excluded
RETRIGGER_SIGNING_FILE="retrigger.txt"
SIGNED_REPO_COPY="./signed"
SIGNED_REPO_URL="https://issm.ess.uci.edu/svn/issm-binaries/mac/python/signed"
SIGNING_CHECK_PERIOD=60 # in seconds
SIGNING_LOCK_FILE="signing.lock"
UNSIGNED_REPO_COPY="./unsigned"
UNSIGNED_REPO_URL="https://issm.ess.uci.edu/svn/issm-binaries/mac/python/unsigned"
USERNAME=$env:issm-binaries-user

COMPRESSED_PKG="${PKG}.zip"

## Environment
#
export PATH="${ISSM_DIR}/bin:$(getconf PATH)" # Ensure that we pick up binaries from 'bin' directory rather than 'externalpackages'

## Parse options
#
if [ $# -gt 1 ]; then
	echo "Can use only one option at a time"
	exit 1
fi

cleanup=0
notarize_only=0
skip_tests=0
transfer_only=0
while [ $# -gt 0 ]; do
    case $1 in
        -c|--cleanup) cleanup=1; shift ;;
        -n|--notarizeonly) notarize_only=1; shift ;;
        -s|--skiptests) skip_tests=1; shift ;;
        -t|--transferonly) transfer_only=1; shift ;;
        *) echo "Unknown parameter passed: $1"; exit 1 ;;
    esac
    shift
done

if [ ${cleanup} -eq 1 ]; then
	# Remove signing lock file from signed package repository so that a new 
	# build can run
	echo "Removing lock file from repository for signed packages"
	svn co \
		--username ${USERNAME} \
		--password ${PASSWORD} \
		--depth empty \
		${SIGNED_REPO_URL} \
		${SIGNED_REPO_COPY} > /dev/null 2>&1
	svn up ${SIGNED_REPO_COPY}/${SIGNING_LOCK_FILE} > /dev/null 2>&1
	svn delete ${SIGNED_REPO_COPY}/${SIGNING_LOCK_FILE} > /dev/null 2>&1
	svn commit --message "DEL: Removing lock file after failed build" ${SIGNED_REPO_COPY} > /dev/null 2>&1
	svn cleanup ${SIGNED_REPO_COPY} > /dev/null 2>&1
	exit 1
fi

if [ ${transfer_only} -eq 0 ]; then
	if [ ${notarize_only} -eq 0 ]; then
		# Clean up from previous packaging
		echo "Cleaning up existing assets"
		cd ${ISSM_DIR}
		rm -rf ${PKG} ${COMPRESSED_PKG} ${SIGNED_REPO_COPY} ${UNSIGNED_REPO_COPY}
		mkdir ${PKG}

		# Check out copy of SVN repository for signed packages
		#
		# NOTE: Get empty copy because we do not want to have to check out package 
		#		from previous signing.
		#
		echo "Checking out copy of repository for signed packages"
		svn co \
			--username ${USERNAME} \
			--password ${PASSWORD} \
			--depth empty \
			${SIGNED_REPO_URL} \
			${SIGNED_REPO_COPY} > /dev/null 2>&1

		# If lock file exists, a signing build is still in process by JPL Cybersecurity
		svn up ${SIGNED_REPO_COPY}/${SIGNING_LOCK_FILE} > /dev/null 2>&1
		if [[ -f ${SIGNED_REPO_COPY}/${SIGNING_LOCK_FILE} ]]; then
			echo "Previous signing job still in process by JPL Cybersecurity. Please try again later."
			exit 1
		fi

		# Add required binaries and libraries to package and modify them where needed
		cd ${ISSM_DIR}/bin

		echo "Modify generic"
		cat generic_static.py | sed -e "s/generic_static/generic/g" > generic.py

		echo "Moving MPICH binaries to bin/"
		if [ -f ${ISSM_DIR}/externalpackages/petsc/install/bin/mpiexec ]; then
			cp ${ISSM_DIR}/externalpackages/petsc/install/bin/mpiexec .
			cp ${ISSM_DIR}/externalpackages/petsc/install/bin/hydra_pmi_proxy .
		elif [ -f ${ISSM_DIR}/externalpackages/mpich/install/bin/mpiexec ]; then
			cp ${ISSM_DIR}/externalpackages/mpich/install/bin/mpiexec .
			cp ${ISSM_DIR}/externalpackages/mpich/install/bin/hydra_pmi_proxy .
		else
			echo "MPICH not found"
			exit 1
		fi

		echo "Moving GDAL binaries to bin/"
		if [ -f ${ISSM_DIR}/externalpackages/gdal/install/bin/gdal-config ]; then
			cp ${ISSM_DIR}/externalpackages/gdal/install/bin/gdalsrsinfo .
			cp ${ISSM_DIR}/externalpackages/gdal/install/bin/gdaltransform .
		else
			echo "GDAL not found"
			exit 1
		fi

		echo "Moving GMT binaries to bin/"
		if [ -f ${ISSM_DIR}/externalpackages/gmt/install/bin/gmt-config ]; then
			cp ${ISSM_DIR}/externalpackages/gmt/install/bin/gmt .
			cp ${ISSM_DIR}/externalpackages/gmt/install/bin/gmtselect .
		else
			echo "GMT not found"
			exit 1
		fi

		echo "Moving Gmsh binaries to bin/"
		if [ -f ${ISSM_DIR}/externalpackages/gmsh/install/bin/gmsh ]; then
			cp ${ISSM_DIR}/externalpackages/gmsh/install/bin/gmsh .
		else
			echo "Gmsh not found"
			exit 1
		fi

		# Run tests
		if [ ${skip_tests} -eq 0 ]; then
			echo "Running tests"

			cd ${ISSM_DIR}/test/NightlyRun
			rm python.log 2> /dev/null

			# Set Python environment
			export PYTHONPATH="${ISSM_DIR}/src/m/dev"
			export PYTHONSTARTUP="${PYTHONPATH}/devpath.py"
			export PYTHONUNBUFFERED=1 # We don't want Python to buffer output, otherwise issm.exe output is not captured

			# Run tests, redirecting output to logfile and suppressing output to console
			./runme.py ${PYTHON_NROPTIONS} &> python.log 2>&1

			# Check that Python did not exit in error
			pythonExitCode=`echo $?`
			pythonExitedInError=`grep -E "Error|Standard exception|Traceback|bad interpreter" python.log | wc -l`

			if [[ ${pythonExitCode} -ne 0 || ${pythonExitedInError} -ne 0 ]]; then
				echo "----------Python exited in error!----------"
				cat python.log
				echo "-----------End of python.log-----------"

				# Clean up execution directory
				rm -rf ${ISSM_DIR}/execution/*

				exit 1
			fi

			# Check that all tests passed
			numTestsFailed=`cat python.log | grep -c -e "FAILED|ERROR"`

			if [[ ${numTestsFailed} -ne 0 ]]; then
				echo "One or more tests FAILED"
				exit 1
			else
				echo "All tests PASSED"
			fi
		else
			echo "Skipping tests"
		fi

		# Create package
		cd ${ISSM_DIR}
		svn cleanup --remove-ignored --remove-unversioned test # Clean up test directory (before copying to package)
		echo "Copying assets to package: ${PKG}"
		cp -rf bin examples lib scripts test ${PKG}/
		mkdir ${PKG}/execution
		cp packagers/mac/issm-executable_entitlements.plist ${PKG}/bin/entitlements.plist
		${ISSM_DIR}/scripts/py_to_pyc.sh ${PKG}/bin # Compile Python source files
		echo "Cleaning up unneeded/unwanted files"
		rm -f ${PKG}/bin/*.py # Remove all Python scripts
		rm -f ${PKG}/bin/generic_static.* # Remove static versions of generic cluster classes
		rm -f ${PKG}/lib/*.a # Remove static libraries from package
		rm -f ${PKG}/lib/*.la # Remove libtool libraries from package
		rm -rf ${PKG}/test/SandBox # Remove testing sandbox from package

		# Compress package
		echo "Compressing package"
		ditto -ck --sequesterRsrc --keepParent ${PKG} ${COMPRESSED_PKG}
	fi

	# Commit lock file to repository for signed packages
	echo "Committing lock file to repository for signed packages"
	touch ${SIGNED_REPO_COPY}/${SIGNING_LOCK_FILE}
	svn add ${SIGNED_REPO_COPY}/${SIGNING_LOCK_FILE} > /dev/null 2>&1
	svn commit --message "ADD: New lock file" ${SIGNED_REPO_COPY} > /dev/null 2>&1

	# Save current working copy revision number
	svn up ${SIGNED_REPO_COPY} > /dev/null 2>&1
	CURRENT_REV=$(svn info --show-item last-changed-revision ${SIGNED_REPO_COPY})

	# Check out copy of SVN repository for unsigned packages
	echo "Checking out copy of repository for unsigned packages"
	svn co \
		--username ${USERNAME} \
		--password ${PASSWORD} \
		${UNSIGNED_REPO_URL} \
		${UNSIGNED_REPO_COPY} > /dev/null 2>&1

	if [ ${notarize_only} -eq 0 ]; then
		# Commit new compressed package to repository for unsigned binaries
		#
		# NOTE: This will not work if, for any reason, the checksum on the compressed 
		#		package is unchanged.
		#
		echo "Committing package to repository for unsigned packages"
		cp ${COMPRESSED_PKG} ${UNSIGNED_REPO_COPY}
		svn add ${UNSIGNED_REPO_COPY}/${COMPRESSED_PKG} > /dev/null 2>&1
		svn commit --message "CHG: New unsigned package" ${UNSIGNED_REPO_COPY} > /dev/null 2>&1
	else
		# NOTE: If notarize_only == 1, we commit a dummy file as the signing 
		#		build on the remote JPL Cybersecurity Jenkins server is 
		#		triggered by polling SCM.
		#
		echo "Attempting to sign existing package again"
		touch ${UNSIGNED_REPO_COPY}/${RETRIGGER_SIGNING_FILE}
		svn add ${UNSIGNED_REPO_COPY}/${RETRIGGER_SIGNING_FILE} > /dev/null 2>&1
		svn commit --message "ADD: Retriggering signing with same package (previous attempt failed)" ${UNSIGNED_REPO_COPY} > /dev/null 2>&1
	fi

	# Check status of signing
	echo "Checking progress of signing..."
	IN_PROCESS=1
	SUCCESS=0

	while [ ${IN_PROCESS} -eq 1 ]; do
		echo "...in progress still; checking again in ${SIGNING_CHECK_PERIOD} seconds"
		sleep ${SIGNING_CHECK_PERIOD}
		svn up ${SIGNED_REPO_COPY} > /dev/null 2>&1
		NEW_REV=$(svn info --show-item last-changed-revision ${SIGNED_REPO_COPY})

		if [ ${NEW_REV} -ne ${CURRENT_REV} ]; then
			IN_PROCESS=0

			svn up ${SIGNED_REPO_COPY}/${NOTARIZATION_LOGFILE} > /dev/null 2>&1
			svn up ${SIGNED_REPO_COPY}/${COMPRESSED_PKG} > /dev/null 2>&1

			# No error, so check status
			STATUS=$(grep 'Status:' ${SIGNED_REPO_COPY}/${NOTARIZATION_LOGFILE} | sed -e 's/[[:space:]]*Status: //')
			if [[ "${STATUS}" == "success" ]]; then
				echo "Notarization successful!"
				
				# Set flag indicating notarization was successful
				SUCCESS=1
			else
				echo "Notarization failed!"
			fi
		fi
	done
else
	# Assume that previous build resulted in successful signing of package but 
	# that transfer to ISSM Web site failed and user built this project again 
	# with -t/--transferonly option.
	SUCCESS=1
fi

# Handle result of signing
if [ ${SUCCESS} -eq 1 ]; then
	# Transfer signed package to ISSM Web site
	echo "Transferring signed package to ISSM Web site"
	scp -i ~/.ssh/pine_island_to_ross ${SIGNED_REPO_COPY}/${COMPRESSED_PKG} jenkins@ross.ics.uci.edu:/var/www/html/${COMPRESSED_PKG}

	if [ $? -ne 0 ]; then
		echo "Transfer failed! Verify connection then build this project again (with -t/--transferonly option to skip testing and signing)."
		exit 1
	fi
else
	echo "----------------------- Contents of notarization logfile -----------------------"
	cat ${SIGNED_REPO_COPY}/${NOTARIZATION_LOGFILE}
	echo "--------------------------------------------------------------------------------"

	exit 1
fi
