#!/bin/sh
# -*- coding: latin-1 -*-

# Copyright © 2014, 2017, 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.

# To work around a deficiency between portupgrade and the pkg package manager 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. Update list of outdated ports.
#     2c. Check to see if ports-mgmt/pkg is still out of date.
#     2d. Abort if ports-mgmt/pkg still needs to be updated.
#  3. Run portupgrade -ncfprv on ${OUTDATED_PORTS}.
#  4. Check to see if x11/xcb-proto is out of date.
#     4a. Run portupgrade -fprv x11/xcb-proto.
#     4b. Update list of outdated ports.
#     4c. Check to see if x11/xcb-proto is still out of date.
#     4d. Abort if x11/xcb-proto still needs to be updated.
#  5. Set ${OLD_LIST} to a dummy value, e.g. "dummy".
#  6. Set ${NEW_DEPENDENCIES} to the empty string.
#  7. While ${NEW_DEPENDENCIES} does not equal ${OLD_LIST}:
#     7a. Store ${NEW_DEPENDENCIES} in ${OLD_LIST}.
#     7b. Set ${NEW_DEPENDENCIES} to the empty string.
#     7c. For each of the outdated ports, gather an accumulative list of the origins of the new dependencies using make missing, store in ${NEW_DEPENDENCIES}.
#     7d. 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.)
#
#  8. If the length of ${NEW_DEPENDENCIES} is non-zero, run portupgrade -Nfprv on ${NEW_DEPENDENCIES}.
#  9. Check to see if graphics/poppler is out of date.
#     9a. Run portupgrade -fpv on graphics/poppler.
#     9b. Check to see if graphics/poppler is still out of date.
#     9c. Abort if graphics/poppler still needs to be updated.
#     9d. If print/tex-luatex or print/tex-xetex is installed.
#          9d1. Find the name of the previous libpoppler.so.NN library.
#          9d2. Link /usr/local/lib/compat/pkg/libpoppler.so.NN.* (hopefully one filename) to /usr/local/lib/compat/pkg/libpoppler.so.NN.
#          9d3. Forcefully upgrade print/tex-luatex and/or print/tex-xetex.
#     9e. Forcefully upgrade ports depending on graphics/poppler, excluding graphics/poppler, print/tex-luatex, and print/tex-xetex.
#     9f. Update list of outdated ports.
# 10. Check to see if devel/icu is out of date.
#     10a. Run portupgrade -fpv on devel/icu.
#     10b. Check to see if devel/icu is still out of date.
#     10c. Abort if devel/icu still needs to be updated.
#     10d. If print/tex-xetex is installed.
#          10d1. Find the name of the previous libicu*.so.NN libraries.
#          10d2. Link /usr/local/lib/compat/pkg/libicu*.so.NN.* (hopefully one filename) to /usr/local/lib/compat/pkg/libicu*.so.NN.
#          10d3. Forcefully upgrade print/tex-xetex.
#     10e. Forcefully upgrade ports depending on devel/icu, excluding devel/icu and print/tex-xetex.
#     10f. Update list of outdated ports.
# 11. Check to see if lang/rust or its dependencies are out of date.
#     11a. Gather lists of ports lang/rust depends on, and on ports depending on lang/rust.
#     11b. Run portupgrade -ncfprv on lang/rust and the lists gathered in 3a.
#     11c. Run pkg delete -y lang/rust.
#     11d. Update list of outdated ports.
# 12. Run portupgrade -fprv on ${OUTDATED_PORTS}.
# 13. Continue special handling of lang/rust and any ports depending on lang/rust.
#     13a. Readd lang/rust.
#     13b. Abort if unable to add lang/rust.
#     13c. Readd ports depending on lang/rust.
#
# Note: It also helps to keep portupgrade's ports database up to date.
#
#       Moving /usr/local/lib/compat/pkg/* to, say, /usr/local/lib-compat-pkg,
#       is recommended to avoid running out of disk space in the long run.
#       /usr/local/lib-compat-pkg should be emptied before moving said files.
#
# A bit of programmer's license has been applied below.

#set -x

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

if [ -x /usr/local/bin/ccache ]; then
  export PATH=/usr/local/libexec/ccache:${PATH};
  export CCACHE_PATH=/usr/bin:/usr/local/bin;
fi

PORTSDIR="/usr/ports"
LOGDIR="/var/log/portupgrade"

PKGUPG_RESULTS="${LOGDIR}/pkgupg-results.log"
NEWDEP_CONFIG_RESULTS="${LOGDIR}/newdep-config-results.log"
NEWDEP_RESULTS="${LOGDIR}/newdep-results.log"
OUTDATED_PORTS_CONFIG_RESULTS="${LOGDIR}/outdated-ports-config-results.log"
XCB_PROTO_RESULTS="${LOGDIR}/xcb-proto-results.log"
POPPLER_RESULTS="${LOGDIR}/poppler-results.log"
POPPLER_DEPS_RESULTS="${LOGDIR}/poppler-deps-results.log"
LUATEX_RESULTS="${LOGDIR}/luatex-results.log"
ICU_RESULTS="${LOGDIR}/icu-results.log"
ICU_DEPS_RESULTS="${LOGDIR}/icu-deps-results.log"
XETEX_RESULTS="${LOGDIR}/xetex-results.log"
OUTDATED_PORTS_RESULTS="${LOGDIR}/outdated-ports-results.log"
LANG_RUST_RESULTS="${LOGDIR}/lang-rust-results.log"
PORTS_DEPENDING_ON_LANG_RUST_RESULTS="${LOGDIR}/ports-depending-on-lang-rust-results.log"

EXTRA_PORTS=""

if [ ! -d ${LOGDIR} ]; then
  mkdir -p ${LOGDIR};

  if [ ! -d ${LOGDIR} ]; then
    echo "${0}: unable to create directory ${LOGDIR}" >/dev/stderr;
    exit 69;
  fi;
fi

cd ${PORTSDIR} || exit 69

echo "${0}: gathering list of outdated ports"

# These lines don't work well with stuff like WITHOUT_NEW_XORG=yes, etc.
# Also, the downloaded index might be slightly out of date for a short period when this script executes.
#make fetchindex || exit 69
#OUTDATED_PORTS=`pkg version -ovIL= | awk '{print $1}'`

# Instead go for the slower, but accurate way using the ports tree.
OUTDATED_PORTS=`pkg version -ovPL= | awk '{print $1}'`

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

# Special handling of the package manager ports-mgmt/pkg.
# Too much breaks while it's out of date.
for o in ${OUTDATED_PORTS}; do
  if [ "${o}" = "ports-mgmt/pkg" ]; then
    echo "${0}: upgrading ports-mgmt/pkg";
    portupgrade -fpv -l ${PKGUPG_RESULTS} ports-mgmt/pkg;

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

    for oo in ${OUTDATED_PORTS}; do
      if [ "${oo}" = "ports-mgmt/pkg" ]; then
        echo "${0}: unable to upgrade ports-mgmt/pkg, bailing out" >/dev/stderr;
        exit 69;
      fi;
    done;

    break;
  fi;
done

unset o
unset oo

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

# Give the user a chance to (re)configure any of the outdated ports.
portupgrade -ncfprv -l ${OUTDATED_PORTS_CONFIG_RESULTS} ${@} ${OUTDATED_PORTS} || exit;

# Special handling of x11/xcb-proto.
for o in ${OUTDATED_PORTS}; do
  if [ "${o}" = "x11/xcb-proto" ]; then
    echo "${0}: upgrading x11/xcb-proto and any ports depending on it";
    portupgrade -fprv -l ${XCB_PROTO_RESULTS} x11/xcb-proto;

    echo "${0}: checking to see if x11/xcb-proto is still outdated";
    OUTDATED_PORTS=`pkg version -ovPL= | awk '{print $1}'`;

    for oo in ${OUTDATED_PORTS}; do
      if [ "${oo}" = "x11/xcb-proto" ]; then
        echo "${0}: unable to upgrade x11/xcb-proto, bailing out" >/dev/stderr;
        exit 69;
      fi;
    done;

    break;
  fi;
done

unset o
unset oo

# Catch any missing new dependencies.
# The list may grow or shrink as the user configures each new dependency.
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}";
    # This approach is safer in cases of removed ports or ports with changed origins.
    cd ${PORTSDIR}/${p} && NEW_DEPENDENCIES="${NEW_DEPENDENCIES} `make missing`";
  done;
  cd /tmp;

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

  if [ -n "${NEW_DEPENDENCIES}" ]; then
    # Give the user a chance to configure any of the new dependencies.
    portupgrade -ncNfpv -l ${NEWDEP_CONFIG_RESULTS} ${NEW_DEPENDENCIES} || exit;
  else
    break;
  fi;
done

# Install any new dependencies.
if [ -n "${NEW_DEPENDENCIES}" ]; then
  portupgrade -Nfpv -l ${NEWDEP_RESULTS} ${NEW_DEPENDENCIES} || exit;
  unset NEW_DEPENDENCIES;
fi

# Special handling of graphics/poppler, print/tex-luatex, and print/tex-xetex.
for o in ${OUTDATED_PORTS}; do
  if [ "${o}" = "graphics/poppler" ]; then
    # The real issue is that /usr/local/bin/{luatex,xetex} is still linked to
    # the old poppler library, and luatex from /usr/local/bin is run by fmtutil
    # during (re)build of print/tex-luatex. This would be a lot easier if the
    # rebuild always referred to luatex in the stage dir.
    #
    # Linking /usr/local/lib/compat/pkg/libpoppler.so.NN.0.0 to
    # /usr/local/lib/compat/pkg/libpoppler.so.NN lets the installed luatex and
    # xetex continue being executable. What a mess.
    if pkg info -e print/tex-luatex || pkg info -e print/tex-xetex; then
      echo "${0}: upgrading graphics/poppler";
      portupgrade -fpv -l ${POPPLER_RESULTS} graphics/poppler;

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

      for oo in ${OUTDATED_PORTS}; do
        if [ "${oo}" = "graphics/poppler" ]; then
          echo "${0}: unable to upgrade graphics/poppler, bailing out" >/dev/stderr;
          exit 69;
        fi;
      done;
      unset oo;

      OLD_POPPLER_LIB=`ldd /usr/local/bin/luatex | grep libpoppler | awk '{print $1}'`;

      # Assuming that /usr/local/lib/compat/pkg/${OLD_POPPLER_LIB}.* refers to a single filename.
      echo "${0}: linking /usr/local/lib/compat/pkg/${OLD_POPPLER_LIB}.* to /usr/local/lib/compat/pkg/${OLD_POPPLER_LIB}";
      ln /usr/local/lib/compat/pkg/${OLD_POPPLER_LIB}.* /usr/local/lib/compat/pkg/${OLD_POPPLER_LIB};
      unset OLD_POPPLER_LIB;

      if pkg info -e print/tex-luatex; then
        echo "${0}: upgrading print/tex-luatex early simply as a precaution";
        portupgrade -fpv -l ${LUATEX_RESULTS} print/tex-luatex;
      fi;

      if pkg info -e print/tex-xetex; then
        echo "${0}: upgrading print/tex-xetex early simply as a precaution";
        portupgrade -fpv -l ${XETEX_RESULTS} print/tex-xetex;
      fi;

      #echo "${0}: upgrading ports depending on graphics/poppler, excluding graphics/poppler, print/tex-luatex, and, print/tex-xetex";
      #portupgrade -fprv -l ${POPPLER_DEPS_RESULTS} -x graphics/poppler -x print/tex-luatex -x print/tex-xetex graphics/poppler;

      # Propagate the remaining ports depending on graphics/poppler to the main action of this script.
      EXTRA_PORTS="${EXTRA_PORTS} `pkg query %ro graphics/poppler | grep -v print/tex-luatex | grep -v print/tex-xetex`"

      echo "${0}: updating list of outdated ports";
      OUTDATED_PORTS=`pkg version -ovPL= | awk '{print $1}'`;
    fi;

    break;
  fi;
done

unset o

# Special handling of devel/icu and print/tex-xetex.
for o in ${OUTDATED_PORTS}; do
  if [ "${o}" = "devel/icu" ]; then
    # The real issue is that /usr/local/bin/xetex is still linked to the old
    # icu libraries, and xetex from /usr/local/bin is run by fmtutil during
    # (re)build of print/tex-xetex. This would be a lot easier if the rebuild
    # always referred to xetex in the stage dir.
    #
    # Linking /usr/local/lib/compat/pkg/libicu*.so.NN.* to
    # /usr/local/lib/compat/pkg/libicu*.so.NN lets the installed xetex
    # continue being executable. Another mess.
    if pkg info -e print/tex-xetex; then
      echo "${0}: upgrading devel/icu";
      portupgrade -fpv -l ${ICU_RESULTS} devel/icu;

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

      for oo in ${OUTDATED_PORTS}; do
        if [ "${oo}" = "devel/icu" ]; then
          echo "${0}: unable to upgrade devel/icu, bailing out" >/dev/stderr;
          exit 69;
        fi;
      done;

      OLD_ICU_LIBS=`ldd /usr/local/bin/xetex | grep libicu | awk '{print $1}'`;

      # Assuming that each occurrence of /usr/local/lib/compat/pkg/${OLD_ICU_LIBS}.* refers to a single filename.
      for oo in ${OLD_ICU_LIBS}; do
        echo "${0}: linking /usr/local/lib/compat/pkg/${oo}.* to /usr/local/lib/compat/pkg/${oo}";
        ln /usr/local/lib/compat/pkg/${oo}.* /usr/local/lib/compat/pkg/${oo};
      done;
      unset OLD_ICU_LIBS;
      unset oo;

      echo "${0}: upgrading print/tex-xetex early simply as a precaution";
      portupgrade -fpv -l ${XETEX_RESULTS} print/tex-xetex;

      #echo "${0}: upgrading ports depending on devel/icu, excluding devel/icu and print/tex-xetex";
      #portupgrade -fprv -l ${ICU_DEPS_RESULTS} -x devel/icu -x print/tex-xetex devel/icu;

      # Propagate the remaining ports depending on devel/icu to the main action of this script.
      EXTRA_PORTS="${EXTRA_PORTS} `pkg query %ro devel/icu | grep -v print/tex-xetex`"

      echo "${0}: updating list of outdated ports";
      OUTDATED_PORTS=`pkg version -ovPL= | awk '{print $1}'`;
    fi;

    break;
  fi;
done

unset o

# Special handling of lang/rust, its dependencies, and any ports depending on lang/rust. Part 1 of 2.
if pkg info -e lang/rust; then
  echo "${0}: gathering list of ports that lang/rust depends on";
  PORTS_RUST_DEPENDS_ON=`pkg query %do lang/rust`;

  for o in ${OUTDATED_PORTS}; do
    for oo in lang/rust ${PORTS_RUST_DEPENDS_ON}; do
      if [ "${oo}" = "${o}" ]; then
        RUST_NEEDS_UPDATE="yes";

        echo "${0}: gathering list of ports depending on lang/rust";
        PORTS_DEPENDING_ON_RUST=`pkg query %ro lang/rust`;

        # lang/rust cannot be upgraded while it's installed.
        # The same applies if any of its dependencies are outdated.
        # lang/rust and any ports depending on it will be restored later by this script.
        echo "${0}: removing lang/rust and any ports depending on it: ${PORTS_DEPENDING_ON_RUST}";
        pkg delete -y lang/rust;

        echo "${0}: updating list of outdated ports";
        OUTDATED_PORTS=`pkg version -ovPL= | awk '{print $1}'`;

        break 2; # Break out of the two nested for loops.
      fi;
    done;
  done;

  unset o;
  unset oo;
fi

# Upgrade the outdated ports. This is the main action of this script.
# Everything else is to ensure this step goes as smooth as possible.
portupgrade -fprv -l ${OUTDATED_PORTS_RESULTS} ${@} ${OUTDATED_PORTS} ${EXTRA_PORTS} || exit

# Continue special handling of lang/rust, and any ports depending on lang/rust. Part 2 of 2.
if [ -n "${RUST_NEEDS_UPDATE}" ]; then
  echo "${0}: I might need to add lang/rust and afterwards add: ${PORTS_DEPENDING_ON_RUST}";

  if [ "`pkg query %o lang/rust`" != "lang/rust" ]; then
    echo "${0}: adding lang/rust";
    portupgrade -Nfpv -l ${LANG_RUST_RESULTS} lang/rust || exit;
  else
    echo "${0}: lang/rust is already in place";
  fi;

  if [ -n "${PORTS_DEPENDING_ON_RUST}" ]; then
    echo "${0}: adding removed ports depending on lang/rust: ${PORTS_DEPENDING_ON_RUST}";
    for p in ${PORTS_DEPENDING_ON_RUST}; do
      if [ "`pkg query %o ${p}`" != "${p}" ]; then
        portupgrade -Nfpv -l ${PORTS_DEPENDING_ON_LANG_RUST_RESULTS} ${p} || exit;
      fi;
    done;
  fi;

#  if [ -n "${PORTS_DEPENDING_ON_RUST}" ]; then
#    echo "${0}: adding removed ports depending on lang/rust: ${PORTS_DEPENDING_ON_RUST}";
#    portupgrade -Nfpv -l ${PORTS_DEPENDING_ON_LANG_RUST_RESULTS} ${PORTS_DEPENDING_ON_RUST} || exit;
#  fi;
fi

# EOF
