Coping with portupgrade’s inability to handle new dependencies

I grew tired of handholding portupgrade because of the latter’s inability to handle new dependencies. The script below is the result of my frustration. The script is available for downloading at http://ximalas.info/~trond/create-zfs/canmount/upgrade-outdated-ports.sh.

#!/bin/sh

# Copyright © 2014, Trond Endrestøl <Trond.Endrestol@ximalas.info>
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#set -x

# To work around a deficiency between portupgrade and the pkg* tools when dealing with new dependencies:
#
# 1. Gather a list of origins for the outdated installed ports, store in ${OUTDATED_PORTS}.
# 2. Check to see if ports-mgmt/pkg is out of date.
#    2a. Run portupgrade -fpv ports-mgmt/pkg.
#    2b. Check to see if ports-mgmt/pkg is still out of date.
#    2c. Abort if ports-mgmt/pkg still needs to be updated.
# 3. Run portupgrade -ncfprv on ${OUTDATED_PORTS}.
# 4. Set ${OLD_LIST} to a dummy value, e.g. "dummy".
# 5. Set ${NEW_DEPENDENCIES} to the empty string.
# 6. While ${NEW_DEPENDENCIES} does not equal ${OLD_LIST}:
#    6a. Store ${NEW_DEPENDENCIES} in ${OLD_LIST}.
#    6b. Set ${NEW_DEPENDENCIES} to the empty string.
#    6c. For each of the outdated ports, gather an accumulative list of the origins of the new dependencies using make missing, store in ${NEW_DEPENDENCIES}.
#    6d. If the length of ${NEW_DEPENDENCIES} is non-zero, run portupgrade -ncNfprv on ${NEW_DEPENDENCIES}, otherwise break out of the loop.
# (Hopefully, no further interactivity will be required of the user beyond this point.)
# 7. If the length of ${NEW_DEPENDENCIES} is non-zero, run portupgrade -Nfprv on ${NEW_DEPENDENCIES}.
# 8. Run portupgrade -fprv on ${OUTDATED_PORTS}.
#
# A bit of programmer's license has been applied below.

PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin

PKG_VERSION="pkg version"

cd /usr/ports || exit 69

# These lines don't work well with stuff like WITHOUT_NEW_XORG=yes, etc.
#make fetchindex || exit 69
#OUTDATED_PORTS="`${PKG_VERSION} -IovL= | awk '{print $1}'`"

# Go for the slower, but accurate way.
OUTDATED_PORTS="`${PKG_VERSION} -ovPL= | awk '{print $1}'`"

if [ -z "${OUTDATED_PORTS}" -a "${#}" -eq 0 ]; then
  echo "${0}: no outdated ports found, try svn up /usr/ports" >/dev/stderr;
  exit 69;
fi

if echo "${OUTDATED_PORTS}" | grep -q "ports-mgmt/pkg"; then
  echo "${0}: upgrading ports-mgmt/pkg";
  portupgrade -fpv ports-mgmt/pkg;

  echo "${0}: checking to see if ports-mgmt/pkg is still outdated";
  OUTDATED_PORTS="`${PKG_VERSION} -ovPL= | awk '{print $1}'`";

  if echo "${OUTDATED_PORTS}" | grep -q "ports-mgmt/pkg"; then
    echo "${0}: unable to upgrade ports-mgmt/pkg, bailing out" >/dev/stderr;
    exit 69;
  fi;
fi

portupgrade -ncfprv ${OUTDATED_PORTS} "${@}" || exit;

OLD_LIST="dummy"
NEW_DEPENDENCIES=""

while [ "${NEW_DEPENDENCIES}" != "${OLD_LIST}" ]; do
  OLD_LIST="${NEW_DEPENDENCIES}";
  NEW_DEPENDENCIES="";

  for p in ${OUTDATED_PORTS} "${@}"; do
    echo "${0}: running make missing in ${p}";
    cd /usr/ports/${p} && NEW_DEPENDENCIES="${NEW_DEPENDENCIES} `make missing`";
  done;
  cd /tmp;

  NEW_DEPENDENCIES=`echo ${NEW_DEPENDENCIES} | sort | uniq`;

  if [ -n "${NEW_DEPENDENCIES}" ]; then
    portupgrade -ncNfprv ${NEW_DEPENDENCIES} || exit;
  else
    break;
  fi;
done

if [ -n "${NEW_DEPENDENCIES}" ]; then
  portupgrade -Nfprv ${NEW_DEPENDENCIES} || exit;
fi

portupgrade -fprv ${OUTDATED_PORTS} "${@}" || exit

# EOF