#!/bin/bash
#
#   srcpac - A tool to rebuild official Arch Linux packages from source
#
#   Copyright (C) 2004-2009 Jason Chu <jason@archlinux.org>
#   Copyright (C) 2009-2014 Andrea Scarpino <andrea@archlinux.org>
#
# This program 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.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#

ver=0.10.10

# gettext initialization
export TEXTDOMAIN='srcpac'
export TEXTDOMAINDIR='/usr/share/locale'

declare -a args

source_file() {
  [ -f "${1}" ] && source "${1}"
}

source_file "/etc/abs.conf"
source_file "/etc/makepkg.conf"
source_file "~/.makepkg.conf"
source_file "/etc/srcpac.conf"
source_file "~/.srcpac.conf"

usage()
{
  printf "$(gettext "usage: %s <operation> [...]")\n" "$(basename $0)"
  printf "$(gettext "operations:")\n"
  printf "$(gettext "        %s {-h --help}")\n" "$(basename $0)"
  printf "$(gettext "        %s {-V --version}")\n" "$(basename $0)"
  printf "$(gettext "        %s {-Q --query}   [options] [<package(s)>]")\n" "$(basename $0)"
  printf "$(gettext "        %s {-R --remove}  [options] <package(s)>")\n" "$(basename $0)"
  printf "$(gettext "        %s {-S --sync}    [options] [package(s)]")\n" "$(basename $0)"
  printf "$(gettext "%s options are based on pacman, so check the pacman man page")\n" "$(basename $0)"
  printf "$(gettext "%s adds some option to %s:")\n" "$(basename $0)" "-S"
  printf "$(gettext "  -b, --build    builds the targets from source")\n"
  printf "$(gettext "  -bb            builds targets and their dependencies from source")\n"
  printf "$(gettext "  -bbb           builds targets, dependencies and their makedeps from source")\n"
  printf "$(gettext "  -m, --makedeps remove the makedepends after the build")\n"
  printf "$(gettext "  -o, --onlyconf applies the changes and displays the PKGBUILD without building")\n"
  printf "$(gettext "  -y, --refresh  download fresh package databases and syncronize the abs tree")\n" 
  printf "$(gettext "%s adds some option to %s:")\n" "$(basename $0)" "-Q"
  printf "$(gettext "  -b, --build    show all packages built from source")\n"
  printf "$(gettext "%s adds some option to %s:")\n" "$(basename $0)" "-R"
  printf "$(gettext "  -b, --build    remove build-flag only, not the package")\n"
  printf "$(gettext "  -o, --onlyconf remove the patchfile only")\n"

  exit 0
}

version()
{
  echo
  echo "                       $(basename $0) v${ver}"
  echo "                       Copyright (C) 2004-2009 Jason Chu"
  echo "                       Copyright (C) 2009-2014 Andrea Scarpino"
  echo
  echo "                       This program may be freely redistributed under"
  echo "                       the terms of the GNU General Public License."
  echo

  exit 0
}

##
# Applies changes of source build packages to PKGUILDs,
# if -o option is set we only display the PKGBUILD in a pager
##ost
apply_config()
{
  conf=/etc/srcpac.d/${1}
  if [ -f ${conf} ]; then
    if [ "${3}" = "noreplace" ]; then
      sed -f ${conf} ${2}/PKGBUILD | ${PAGER}
    else
      sed -i -f ${conf} ${2}/PKGBUILD
    fi
  fi
}

is_installed() {
  $PACMAN -Q $1 &> /dev/null
  return $?
}

##
# Searches the ABS tree for a possible package candidates
# usage: get_candidates($pkgname, $is_target)
##
get_candidates()
{
  local found=0
  is_target=$2

  for pkgbuild in `find ${ABSROOT} -name PKGBUILD`; do
    unset pkgbase
    unset pkgname
    source "$pkgbuild" &> /dev/null
    if [ "${1}" = "${pkgbase}" ]; then
      found=1
      break
    else
      for _pkgname in "${pkgname[@]}"; do
        if [ "${1}" = "${_pkgname}" ]; then
          found=1
          break
        fi
      done
    fi

    if [ ${found} -eq 1 ]; then
      if [ $is_target -eq 1 ]; then
        # Backup target's makedepends array
       local _makedepends=${makedepends[@]}

        if [ ${BUILDDEPS} -eq 1 ]; then
          for _dep in ${depends[@]}; do
            is_installed ${_dep%%[<>=]*}
            installed=$?
            [ "${installed}" -eq 0 ] && continue

            get_candidates ${_dep%%[<>=]*} 0
          done
        fi
        if [ ${BUILDMAKEDEPS} -eq 1 ]; then
          for _makedep in ${_makedepends[@]}; do
            is_installed ${_makedep%%[<>=]*}
            installed=$?
            [ "${installed}" -eq 0 ] && continue

            get_candidates ${_makedep%%[<>=]*} 0
          done
        fi
      fi
      break
    fi
  done

  if [ ${found} -eq 1 ]; then
    candidate="${pkgbuild##${ABSROOT}}"
    candidates[${#candidates[@]}]="${candidate%%/PKGBUILD}"
  else
    printf "$(gettext "Error: Could not find %s under %s")\n" "${1}" "${ABSROOT}"
  fi
}

##
# Updates the ABS tree and the pacman repositories
##
refresh()
{
  printf "$(gettext "Starting ABS sync...")"
  abs &> /dev/null
  printf "$(gettext "done")\n"
  $PACMAN -Sy
  if [ ${#args[@]} -eq 0 -a $SYSUPGRADE -eq 0 ]; then
    exit 0
  fi
}

##
#  usage : get_full_version( $epoch, $pkgver, $pkgrel )
# return : full version spec, including epoch (if necessary), pkgver, pkgrel
##
get_full_version() {
  if [[ $1 -eq 0 ]]; then
    # zero epoch case, don't include it in version
    echo $2-$3
  else
    echo $1:$2-$3
  fi
}

get_arch() {
  if [[ "$1" = "any" ]]; then
    echo "any"
  else
    echo "${CARCH}"
  fi
}

##
# Build a package
##
build_package() {
  local action="build"
  if [ "$1" = "install" ]; then
    action="install"
  fi

  shift

  local pkg=${1##*/}
  local pkgdir="${ABSROOT}/${1}"
  local builddir="/var/srcpac/${pkg}"

  local succeed=0

  local MAKEPKGOPTS="-c -s -f"
  [ $NODEPS -eq 1 ] && MAKEPKGOPTS="${MAKEPKGOPTS} -d"
  [ $MAKEDEPS -eq 1 ] && MAKEPKGOPTS="${MAKEPKGOPTS} -r"
  [ $NOCONFIRM -eq 1 ] && MAKEPKGOPTS="${MAKEPKGOPTS} --noconfirm"

  local SPLITPKG=0
  [ "${pkg}" != "${pkgdir##*/}" ] && SPLITPKG=1
  [ $SPLITPKG -eq 1 ] && MAKEPKGOPTS="${MAKEPKGOPTS} --pkg ${pkg}"

  # create the build dir and apply configuration
  [[ -d ${builddir} ]] && rm -rf ${builddir}
  mkdir -p ${builddir}
  cp ${pkgdir}/* ${builddir}
  apply_config ${pkg} ${builddir}

  pushd ${builddir} &> /dev/null

  unset epoch
  unset pkgver
  unset pkgrel
  unset arch
  source PKGBUILD &> /dev/null

  if [ -z "${SUDO_USER}" ]; then
    chown -R nobody ${builddir}
    makepkg ${MAKEPKGOPTS} --asroot
    [ $? -eq 0 ] && succeed=1
  else
    chown -R ${SUDO_USER} ${builddir}
    sudo -u ${SUDO_USER} makepkg ${MAKEPKGOPTS}
    [ $? -eq 0 ] && succeed=1
  fi

  popd &> /dev/null

  if [ $succeed -ne 1 ]; then
    broken="${broken} ${pkg}"
  else
    local CACHEDIR=$( (grep -m 1 '^CacheDir' /etc/pacman.conf || echo 'CacheDir = /var/cache/pacman/pkg') | sed 's/CacheDir\s*=\s*//')
    local filename="${pkg}-$(get_full_version ${epoch:-0} ${pkgver} ${pkgrel})-$(get_arch ${arch}).pkg.tar.?z"
    cp -f ${builddir}/${filename} "${CACHEDIR}"
    rm -rf ${builddir}

    if [ "${action}" = "install" ]; then
      local PACARGS="-U"
      [ $NODEPS -eq 1 ] && PACARGS="${PACARGS}dd"
      [ $FORCE -eq 1 ] && PACARGS="${PACARGS}f"
      [ $ROOT -eq 1 ] && PACARGS="${PACARGS} -r $NEWROOT"
      [ $ASDEPS -eq 1 ] && PACARGS="${PACARGS} --asdeps"
      [ $ASEXPLICIT -eq 1 ] && PACARGS="${PACARGS} --asexplicit"
      [ $NOCONFIRM -eq 1 ] && PACARGS="${PACARGS} --noconfirm"

      $PACMAN $PACARGS "${CACHEDIR}"/${filename}
      [ $? -eq 0 ] && touch "/var/lib/srcpac/${pkg}"
    fi
  fi
}

check_args()
{
  # Options
  MAJOR=""
  ASDEPS=0
  ASEXPLICIT=0
  NODEPS=0
  FORCE=0
  INFO=0
  GROUP=0
  SYSUPGRADE=0
  DOWNLOAD=0
  REFRESH=0
  IGNORE=0
  IGNOREPKG=""
  NOCONFIRM=0
  ROOT=0
  NEWROOT=""
  BUILD=0
  ONLYCONF=0
  MAKEDEPS=1
  BUILDDEPS=0
  BUILDMAKEDEPS=0

  ARGLIST=$@
  ARGSANS=""

  while [ "$#" -ne "0" ]; do
    case $1 in
      --help) usage ;;
      --version) version ;;
      --query)
        MAJOR="query"
        ARGSANS="$ARGSANS $1"
        ;;
      --remove)
        MAJOR="remove"
        ARGSANS="$ARGSANS $1"
        ;;
      --sync)
        MAJOR="sync"
        ARGSANS="$ARGSANS $1"
        ;;
      --asdeps)
        ASDEPS=1
        ARGSANS="$ARGSANS $1"
        ;;
      --asexplicit)
        ASEXPLICIT=1
        ARGSANS="$ARGSANS $1"
        ;;
      --nodeps)
        NODEPS=1
        ARGSANS="$ARGSANS $1"
        ;;
      --force)
        FORCE=1
        ARGSANS="$ARGSANS $1"
        ;;
      --info)
        INFO=1
        ARGSANS="$ARGSANS $1"
        ;;
      --groups)
        GROUP=1
        ARGSANS="$ARGSANS $1"
        ;;
      --sysupgrade)
        SYSUPGRADE=1
        ARGSANS="$ARGSANS $1"
        ;;
      --downloadonly)
        DOWNLOAD=1
        ARGSANS="$ARGSANS $1"
        ;;
      --refresh)
        REFRESH=1
        ARGSANS="$ARGSANS $1"
        ;;
      --ignore)
        IGNORE=1
        IGNOREPKG="$IGNOREPKG $2"
        ARGSANS="$ARGSANS $1 $2"
        ;;
      --noconfirm)
        NOCONFIRM=1
        ARGSANS="$ARGSANS $1"
        ;;
      --root)
        ROOT=1
        NEWROOT="$2"
        ARGSANS="$ARGSANS $1 $2"
        shift
        ;;
      --build)
        BUILD=1
        ARGSANS="$ARGSANS $1"
        ;;
      --onlyconf)
        ONLYCONF=1
        ARGSANS="$ARGSANS $1"
        ;;
      --makedeps)
        MAKEDEPS=0
        ARGSANS="$ARGSANS $1"
        ;;
      --*)
        ARGSANS="$ARGSANS $1"
        ;;
      -*)
        ARGSANS="$ARGSANS $1"
        if [ $(echo $1 | grep r) ]; then
          OPTIONAL=$2
        fi
        OPTIND=1
        while getopts ":VQRSbdfigmuyr:ow" opt $1 $OPTIONAL; do
          case $opt in
            V) version ;;
            Q) MAJOR="query" ;;
            R) MAJOR="remove" ;;
            S) MAJOR="sync" ;;
            b) [ $BUILDDEPS -eq 1 ] && BUILDMAKEDEPS=1
               [ $BUILD -eq 1 ] && BUILDDEPS=1
                BUILD=1;;
            d) NODEPS=1 ;;
            f) FORCE=1 ;;
            i) INFO=1 ;;
            g) GROUP=1 ;;
            m) MAKEDEPS=0 ;;
            o) ONLYCONF=1 ;;
            r) ROOT=1
               NEWROOT="${OPTARG}"
               shift
               ;;
            u) SYSUPGRADE=1 ;;
            w) DOWNLOAD=1 ;;
            y) REFRESH=1 ;;
          esac
        done
        ;;
      *)
        args[${#args[@]}]=$1
        ;;
    esac
    shift
  done
}

##
# Remove the srcpac package file
##
remove()
{
  if [ $ONLYCONF -eq 1 ]; then
    [ -f /etc/srcpac.d/$1 ] && rm /etc/srcpac.d/$1
  else
    if [ -f /var/lib/srcpac/$1 ]; then
      printf "$(gettext "removing source reference %s...")" "${1}"
      rm /var/lib/srcpac/$1
      printf "$(gettext "done")\n"
    fi
  fi
}

##
# Check if a package has been built using srcpac
##
is_built() {
  ls /var/lib/srcpac/$1 &> /dev/null
  return $?
}

main()
{
  check_args $@

  if [ -z "${MAJOR}" ]; then
    usage
    exit 1
  fi

  if [ ${UID} -ne 0 -a -z "${SUDO_USER}" -a ${MAJOR} != "query" ]; then
    printf "$(gettext "Error: You need to use sudo or to be root")\n"
    exit 1
  fi

  if [ -f /run/srcpac.lock ]; then
    printf "$(gettext "\
Another instance of srcpac is already running.\n\
  If you're sure srcpac is not already running\n\
  you can remove /run/srcpac.lock")\n"
    exit 1
  fi

  if [ -z "${ABSROOT}" ]; then
    printf "$(gettext "Error: The ABSROOT environment variable is not defined")\n"
    exit 1
  fi

  if [ ! -d /var/lib/srcpac ]; then
    mkdir /var/lib/srcpac
    [ $? -eq 0 ] && exit 1
  fi

  if [ ! -d /etc/srcpac.d ]; then
    mkdir /etc/srcpac.d
    [ $? -eq 0 ] && exit 1
  fi

  if [ "${MAJOR}" = "remove" ]; then
    if [ $BUILD -eq 0 -a $ONLYCONF -eq 0 ]; then
      $PACMAN $ARGLIST
    fi

    for pkg in ${args[@]}; do
      remove $pkg
    done
    exit 0
  fi

  if [ "${MAJOR}" = "query" ]; then
    if [ $BUILD -eq 1 ]; then
      for pkg in `find /var/lib/srcpac/ -type f`; do
        $PACMAN -Q $(basename $pkg)
      done
    elif [ $GROUP -eq 1 ]; then
      $PACMAN -Qg ${args[@]}
    else
      for pkg in ${args[@]}; do
        local valid=1
        if [ $INFO -eq 1 ]; then
          $PACMAN -Qi $pkg && valid=0 | head -n -1
        else
          echo -n $($PACMAN -Q $pkg) && valid=0
        fi

        if [ $valid -eq 0 ]; then
          is_built $pkg
          built=$?
          if [ $INFO -eq 1 ]; then
            printf "$(gettext "Source         : ")"
            if [ $built -eq 0 ]; then
              printf "$(gettext "Yes")\n"
            else
              printf "$(gettext "No")\n"
            fi
            printf "$(gettext "Patchfile      : ")"
            if [ -f /etc/srcpac.d/$pkg ]; then
              printf "$(gettext "Yes")\n"
            else
              printf "$(gettext "No")\n"
            fi
            echo
          else
            if [ $built -eq 0 ]; then
              printf "$(gettext " [source]")\n"
            else
              echo
            fi
          fi
        fi
      done
    fi
    exit 0
  fi

  if [ "${MAJOR}" = "sync" ]; then
    if [ $BUILD -eq 0 -a $ONLYCONF -ne 1 ]; then
      if [ $SYSUPGRADE -eq 1 ]; then
        [ $REFRESH -eq 1 ] && refresh

        if [ $IGNORE -eq 1 ]; then
          local ignorestr=$(echo $IGNOREPKG | tr "," "|")
          local output=$($PACMAN -Qqu --noconfirm | grep -Ev ${ignorestr})
        else
          local output=$($PACMAN -Qqu --noconfirm)
        fi

        # regular packages, these have not been built with srcpac
        declare -a regpkg
        for pkg in ${output}; do
          is_built $pkg
          built=$?
          if [ $built -eq 0 ]; then
            packages="${packages} $pkg"
          else
            regpkg[${#regpkg[@]}]=$pkg
          fi
        done

        local PACARGS="-S"
        [ $GROUP -eq 1 ] && PACARGS="${PACARGS}g"
        [ $NODEPS -eq 1 ] && PACARGS="${PACARGS}dd"
        [ $FORCE -eq 1 ] && PACARGS="${PACARGS}f"
        [ $ROOT -eq 1 ] && PACARGS="${PACARGS} -r $NEWROOT"
        [ $IGNORE -eq 1 ] && PACARGS="${PACARGS} --ignore $IGNOREPKG"
        [ $NOCONFIRM -eq 1 ] && PACARGS="${PACARGS} --noconfirm"

        if [ -n "${regpkg[*]}" ]; then
          $PACMAN $PACARGS ${regpkg[@]}
        fi

        for pkg in $packages; do
          get_candidates $pkg 1
        done
      else
        [ $REFRESH -eq 1 ] && refresh
        $PACMAN $ARGLIST
        exit 0
      fi
    else
      [ $REFRESH -eq 1 ] && refresh

      if [ $SYSUPGRADE -eq 1 ]; then
        if [ $IGNORE -eq 1 ]; then
          local ignorestr=$(echo $IGNOREPKG | tr "," "|")
          local packages=$($PACMAN -Qqu --noconfirm | grep -Ev ${ignorestr})
        else
          local packages=$($PACMAN -Qqu --noconfirm)
        fi
      else
        local packages="${args[@]}"
      fi

      if [ $GROUP -eq 1 ]; then
        for pkg in $($PACMAN -Sqg $packages); do
          get_candidates $pkg 1
        done
      else
        for pkg in $packages; do
          get_candidates $pkg 1
        done
      fi

      if [ $DOWNLOAD -eq 1 ]; then
        for pkg in ${candidates[@]}; do
          build_package "build" $pkg
        done
        exit 0
      fi

      if [ $ONLYCONF -eq 1 ]; then
        for pkg in ${candidates[@]}; do
          apply_config "${pkg##*/}" "${ABSROOT}/${pkg}" noreplace
        done
        exit 0
      fi
    fi

    if [ -n "${candidates[*]}" ]; then

      touch /run/srcpac.lock

      echo
      printf "$(gettext "Source Targets:") "
      for pkg in ${candidates[@]}; do
        echo -n "${pkg##*/} "
      done
      echo
      if [ ${NOCONFIRM} -eq 0 ]; then
        printf "$(gettext "Proceed? [Y/n]") "
        read
        if [ "${REPLY}" != "$(gettext "y")" -a "${REPLY}" != "$(gettext "Y")" -a "${REPLY}" != "" ]; then
          rm /run/srcpac.lock &> /dev/null
          exit 0
        fi
      fi

      for pkg in ${candidates[@]}; do
        build_package "install" ${pkg}
      done

      rm /run/srcpac.lock &> /dev/null
    fi

    if [ -n "${broken}" ]; then
      printf "$(gettext "Build failed for:%s")\n" "${broken}"
    fi
  fi
}

main $@

exit 0
