#!/bin/bash
# vim: set et sw=4 sts=4 :
# A bit of hackery to update everything that is humanly possible
# that maybe related to an older version of python. This script can
# be run as many times as you like.
#
# OLD_PY_VER      = old python version we are upgrading from
# NEW_PY_VER      = new python version we are upgrading to
# PKGS_EXCEPTIONS = packages that should NOT be re-emerged for any reason
# PKGS_MANUAL     = packages that should be re-emerged even if they don't
#                   fit the criteria (eg. ones that have python compiled
#                   statically)
#
# Runtime Variables:
#
# PKGS_TO_REMERGE = list of packages we deem to need re-emerging
# PKGS_OK         = list of packages that should be merged without any problems
# PKGS_MISSING    = list of packages that are installed, but cannot be merged
#                   because they have been pruned from portage
# PKGS_MASKED     = list of packages that are installed, but masked.
#

NEW_PY_VER=$(python -V 2>&1 | sed 's:Python ::' | cut -d. -f1-2)

PKGS_EXCEPTIONS="dev-lang/python sys-apps/portage"
PKGS_MANUAL="app-office/gnumeric app-office/dia dev-libs/boost x11-libs/vte"

# portage variables
PKG_DBDIR=/var/db/pkg
PORTDIR=`portageq portdir`
PORTDIR_OVERLAYS=`portageq portdir_overlay`

PRETEND=0
DIRECT_ONLY=0
IGNORE_VERSIONS=0
PKGS_TO_REMERGE=""
PKGS_COUNT_REMERGE=0
PORTAGE_PYTHON="/usr/bin/python"

SUPPORTED_PMS="portage pkgcore paludis"
PMS_COMMAND=( "emerge" "pmerge" "paludis" )
PMS_OPTIONS=( "-vD1" "-Do" "-i1" )

# load the gentoo-style info macros, but hack to get around
# it thinking this is an rc script
EBUILD="1"
source /etc/init.d/functions.sh

usage() {
    echo "usage: python-updater [-h|-p|-o X.X]"
    echo " --help, -h		help"
    echo " --pretend, -p		pretend (don't do anything)"
    echo " --old-version, -o  X.X	set old python version to upgrade from [default: ${OLD_PY_VER}]"
    echo " --ignore-versions, -i	ignore versions when remerging packages (still respects SLOTs)"
    echo " --direct-only		don't consider indirect aka manually added packages"
    echo " --package-manager, -P name	select between ${SUPPORTED_PMS} [default: portage]"
}

#
# Sanity check
#

if [ -z "${PORTDIR}" ]; then
    eerror "Unable to proceed. Can not find PORTDIR. Make sure the command:"
    eerror " "
    eerror "  portageq portdir"
    eerror " "
    eerror "returns a value. If it doesn't, make sure you have updated to"
    eerror "latest portage version."
    eerror " "
    eerror "Report bugs to http://bugs.gentoo.org/"
    exit 1
fi	

#
#
# Command Line Parsing
#
#
while [ -n "$1" ]; do
    case "$1" in
        -h|--help)
            usage
            exit 0
            ;;
        -p|--pretend)
            PRETEND=1
            ;;
        -o|--old-version)
            shift
            OLD_PY_VER="$1"
            ;;
        -i|--ignore-versions)
            IGNORE_VERSIONS=1
            ;;
        --direct-only)
            DIRECT_ONLY=1
            ;;
        -P|--package-manager)
            shift
            PACKAGE_MANAGER="$1"
            case "${PACKAGE_MANAGER}" in
                portage|pkgcore|paludis)
                    ;;
                *)
                    echo "unrecognised package manager selected. please select between ${SUPPORTED_PMS}"
                    exit
                    ;;
            esac
            
            # PMS_INDEX is used to select the right commands and options for the selected package manager
            PMS_INDEX=0
            for PM in ${SUPPORTED_PMS}; do
                [[ ${PM} == ${PACKAGE_MANAGER} ]] && break
                PMS_INDEX=$((${PMS_INDEX} + 1))
            done
            ;;
	*)
            usage
            echo "unrecognised option: $1"
            exit 0
            ;;
    esac
    shift
done

# Determine old python version
if [[ -z "${OLD_PY_VER}" ]]; then
    for old in 2.5 2.4 2.3 2.2 2.1; do
        if [ "${old}" != "${NEW_PY_VER}" ]; then
            if [ -e /usr/bin/python${old} ] ; then
                OLD_PY_VER=${old}
                break;
            fi
        fi
    done
fi


if [ -z "${OLD_PY_VER}" ] ; then
    eerror "Can't determine any previous Python version(s)."
    exit 1
fi

#
# Test where portage is, in python2.2 or somewhere else?
#
for py in /usr/bin/python /usr/bin/python${OLD_PY_VER} /usr/bin/python${NEW_PY_VER}; do
    if ${py} -c "import portage" > /dev/null 2>&1; then
        PORTAGE_PYTHON=${py}
        break;
    fi
done

#
#
# Find all packages that have installed something in
# /usr/lib/python${OLD_PY_VER}
#
#
OLD_MODULES_DIRS="/usr/lib/python${OLD_PY_VER} /usr/lib32/python${OLD_PY_VER} /usr/lib64/python${OLD_PY_VER}"
OLD_INCLUDE_DIR=/usr/include/python${OLD_PY_VER}

einfo "Starting Python Updater from ${OLD_PY_VER} to ${NEW_PY_VER} :"
einfo "Searching for packages with files in ${OLD_MODULES_DIRS} .."

# iterate thru all the installed package's contents
for content in `find ${PKG_DBDIR} -name CONTENTS`; do
    # extract the category, package name and package version
    CATPKGVER=$(echo ${content} | sed "s:${PKG_DBDIR}/\(.*\)/CONTENTS:\1:")
    CATPKG="${CATPKGVER%%-[0-9]*}"

    # exclude packages that are an exception, like portage and python itself.
    exception=0
    for exp in ${PKGS_EXCEPTIONS}; do
        if [ -z "${CATPKG##${exp}}" ]; then
            exception=1
            break;
        fi
    done
    
    if [ ${exception} = 1 ]; then
        continue;
    fi
    
    # Check if package is in PKGS_MANUAL
    if [[ DIRECT_ONLY -ne 1 ]]; then
        for pkg in ${PKGS_MANUAL}; do
            if [ -z "${CATPKG##${pkg}}" ]; then
                exception=2
                break;
            fi
        done
    fi
    
    # replace version number by SLOT if IGNORE_VERSIONS != 0
    # Don't ignore versions when SLOT doesn't exist, bug 201848
    if [[ IGNORE_VERSIONS -ne 0 && -f "${content/CONTENTS/SLOT}" ]]; then
        SLOT=$(< ${content/CONTENTS/SLOT})
        CATPKGVER="${CATPKG}:${SLOT}"
    else
        CATPKGVER="=${CATPKGVER}"
    fi
    
    if [ ${exception} = 2 ]; then
        PKGS_TO_REMERGE="${PKGS_TO_REMERGE} ${CATPKGVER}"
        einfo "Adding to list (manually): ${CATPKGVER}"
        continue;
    fi
    
    for OLD_MODULES_DIR in ${OLD_MODULES_DIRS}; do
        if fgrep "${OLD_MODULES_DIR}" ${content} > /dev/null; then
            PKGS_TO_REMERGE="${PKGS_TO_REMERGE} ${CATPKGVER}"
            einfo "Adding to list: ${CATPKGVER}"
        elif fgrep "${OLD_INCLUDE_DIR}" ${content} > /dev/null; then
            PKGS_TO_REMERGE="${PKGS_TO_REMERGE} ${CATPKGVER}"
        fi
    done
done

# only pretending?
[[ PRETEND -eq 1 ]] && PMS_OPTIONS[${PMS_INDEX}]="${PMS_OPTIONS[${PMS_INDEX}]}p"

# (Pretend to) remerge packages
if [[ -n "${PKGS_TO_REMERGE}" ]]; then
    ${PMS_COMMAND[${PMS_INDEX}]} ${PMS_OPTIONS[${PMS_INDEX}]} ${PKGS_TO_REMERGE}
else
    einfo "No packages needs to be remerged."
fi
