#!/bin/bash

#  satisfydeps - it's pbuilder based script for astra to operate with deps


#   pbuilder -- personal Debian package builder
#   Copyright (C) 2001,2002,2003,2005-2007 Junichi Uekawa
#
#   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 2 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, write to the Free Software
#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
#
# module to satisfy build dependencies; default flavor

# The original pbuilder-satisfydepends code, which is now called 'classic'

set -e


package_versions() {
	local PACKAGE="$1"
	LC_ALL=C $CHROOTEXEC /usr/bin/apt-cache show "$PACKAGE" | sed -n 's/^Version: //p'
}

candidate_version() {
	local PACKAGE="$1"
	LC_ALL=C $CHROOTEXEC apt-cache policy "$PACKAGE" | sed -n 's/ *Candidate: //p'
}

checkbuilddep_versiondeps() {
    local PACKAGE="$1"
    local COMPARESTRING="$2"
    local DEPSVERSION="$3"
    local PACKAGEVERSIONS=$( package_versions "$PACKAGE" | xargs)
    # no versioned provides.
    if [ "${FORCEVERSION}" = "yes" ]; then
	return 0;
    fi
    for PACKAGEVERSION in $PACKAGEVERSIONS ; do
      if dpkg --compare-versions "$PACKAGEVERSION" "$COMPARESTRING" "$DEPSVERSION"; then
	# satisfies depends
	return 0;
      fi
    done
    echo "      Tried versions: $PACKAGEVERSIONS"
    # cannot satisfy depends
    return 1;
}

get_source_control_field() {
    local field="$1"

    sed -n -e "s/^$field://i" -e '
t store
/^-----BEGIN PGP SIGNED MESSAGE-----$/ {
    : pgploop
    n
    /^$/ d
    b pgploop
}
/^$/q
d
: store
H
: loop
n
/^#/ b loop
/^[ \t]/ b store
x
# output on single line
s/\n//g
# change series of tabs and spaces into a space
s/[\t ]\+/ /g
# normalize space before and after commas
s/ *, */, /g
# normalize space before and after pipes
s/ *| */ | /g
# normalize space before and after parentheses
s/ *( */ (/g
s/ *) */)/g
# normalize space before and after brackets
s/ *\[ */ [/g
s/ *\] */]/g
# normalize space after exclamation mark
s/! */!/g
# normalize space between operator and version
s/(\(>>\|>=\|>\|==\|=\|<=\|<<\|<\|!=\) *\([^)]*\))/(\1 \2)/g
# normalize space at beginning and end of line
s/^ *//
s/ *$//
p' \
        "$DEBIAN_CONTROL"
}

get_build_deps() {
    local output

    output="`get_source_control_field "Build-Depends"`"
    output="${output%, }"
    if [ "$BINARY_ARCH" = no ]; then
        output="${output:+$output, }`get_source_control_field "Build-Depends-Indep"`"
        output="${output%, }"
    fi
    echo "$output"
}

get_build_conflicts() {
    local output

    output="`get_source_control_field "Build-Conflicts"`"
    if [ "$BINARY_ARCH" = no ]; then
        output="${output:+$output, }`get_source_control_field "Build-Conflicts-Indep"`"
    fi
    echo "$output"
}

checkbuilddep_archdeps() {
    # returns FALSE on INSTALL
    local INSTALLPKG="$1"
    local ARCH="$2"
    # architectures listed between [ and ] for this dep
    local DEP_ARCHES="$(echo "$INSTALLPKG" | sed -e 's/.*\[\(.*\)\].*/\1/' -e 'y|/| |')"
    local PKG="$(echo "$INSTALLPKG" | cut -d ' ' -f 1)"
    local USE_IT
    local IGNORE_IT
    local INCLUDE
    # Use 'dpkg-architecture' to support architecture wildcards.
    for d in $DEP_ARCHES; do
        if echo "$d" | grep -q '!'; then
            d="$(echo $d | sed 's/!//')"
            if dpkg-architecture -a$ARCH -i$d; then
                IGNORE_IT="yes"
            fi
        else
            if dpkg-architecture -a$ARCH -i$d; then
                USE_IT="yes"
            fi
            INCLUDE="yes"
        fi
    done
    if [ $IGNORE_IT ] && [ $USE_IT ]; then
        printf "W: inconsistent arch restriction on $PKG: " >&2
        printf "$DEP_ARCHES depedency\n" >&2
    fi
    if [ $IGNORE_IT ] || ( [ $INCLUDE ] && [ ! $USE_IT ] ); then
        return 0
    fi
    return 1
}

checkbuilddep_provides() {
    local PACKAGENAME="$1"
    # PROVIDED needs to be used outside of this function.
    PROVIDED=$($CHROOTEXEC /usr/bin/apt-cache showpkg $PACKAGENAME \
	| awk '{p=0}/^Reverse Provides:/,/^$/{p=1}{if(p && ($0 !~ "Reverse Provides:")){PACKAGE=$1}} END{print PACKAGE}')
}

# returns either "package=version", to append to an apt-get install line, or
# package
versioneddep_to_aptcmd() {
	local INSTALLPKG="$1"

	local PACKAGE
	local PACKAGE_WITHVERSION
	local PACKAGEVERSIONS
	local CANDIDATE_VERSION
	local COMPARESTRING
	local DEPSVERSION

	PACKAGE="$(echo "$INSTALLPKG" | sed -e 's/^[/]*//' -e 's/[[/(].*//')"
	PACKAGE_WITHVERSION="$PACKAGE"

	# if not versionned, we skip directly to outputting $PACKAGE
	if echo "$INSTALLPKG" | grep '[(]' > /dev/null; then
	    # package versions returned by APT, in reversed order
	    PACKAGEVERSIONS="$( package_versions "$PACKAGE" | tac | xargs )"
	    CANDIDATE_VERSION="$( candidate_version "$PACKAGE" )"

	    COMPARESTRING="$(echo "$INSTALLPKG" | tr "/" " " | sed 's/^.*( *\(<<\|<=\|>=\|=\|<\|>>\|>\) *\(.*\)).*$/\1/')"
	    DEPSVERSION="$(echo "$INSTALLPKG" | tr "/" " " | sed 's/^.*( *\(<<\|<=\|>=\|=\|<\|>>\|>\) *\(.*\)).*$/\2/')"
	    # if strictly versionned, we skip to outputting that version
	    if [ "=" = "$COMPARESTRING" ]; then
		PACKAGE_WITHVERSION="$PACKAGE=$DEPSVERSION"
	    else
		# try the candidate version, then all available versions (asc)
		for VERSION in $CANDIDATE_VERSION $PACKAGEVERSIONS; do
		    if dpkg --compare-versions "$VERSION" "$COMPARESTRING" "$DEPSVERSION"; then
			if [ $VERSION != $CANDIDATE_VERSION ]; then
			    PACKAGE_WITHVERSION="$PACKAGE=$VERSION"
			fi
			break;
		    fi
		done
	    fi
	fi

	echo "$PACKAGE_WITHVERSION"
}

print_help() {
    # print out help message
    cat <<EOF
satisfydeps -- satisfy dependencies

--help:        give help
--control:     specify control file (debian/control, *.dsc)
--chroot:      operate inside chroot
--binary-all:  include binary-all
--binary-arch: include binary-arch only
--echo:        echo mode, do nothing. (--force-version required for most operation)
--force-version: skip version check.
--continue-fail: continue even when failed.

EOF
}


# splits lines of deps (possibly with alternates) separated by commas into one
# dep (possibly with alternates) per line; also changes all spaces to slashes
# first as to iterate on deps with "for"; e.g. "foo, bar | baz", "foobar (>=
# 2)"  becomes "foo", "bar/|/baz", "foobar/(>=/2)"
split_deps() {
    sed 's#, *#\n#g; s# #/#g'
}
# and build-conflicts

# splits build-dep with alternates into one alternate per line; e.g. "foo|bar"
# becomes "foo", "bar"
split_alternates() {
    echo "$*" | sed 's/ *| */\n/g'
}

# filters package name out of a "/" escaped simple build-dep, possibly with
# architecture or version specifier; e.g. "/foo/(>=/2)/[i386/amd64]" becomes
# "foo"
get_pkg_name() {
    echo "$*" | sed 's#^/*##; s#[[/(].*##'
}

# filter operator and version of a "/" escaped versioned build-dep; e.g.
# "foo/(>=/2)" becomes ">= 2"
get_dep_op_and_ver() {
    echo "$*" | sed -n 's#/# #g; s/.*( *\(<<\|<=\|>=\|=\|<\|>>\|>\) *\([^ )]*\) *).*/\1 \2/p'
}

# call apt-get in chroot in simulate mode
apt_sim() {
    $CHROOTEXEC /usr/bin/apt-get -s install "$@"
}

# Use this function to fulfill the dependency (almost)
checkbuilddep_internal() {
    local ARCH=$($CHROOTEXEC dpkg-architecture -qDEB_HOST_ARCH)
    local INSTALLPKG
    local INSTALLPKGLIST
    local INSTALLPKGMULTI
    local CURRENTREALPKGNAME
    local SATISFIED
    local APTFLAG="-o APT::Install-Recommends=false"
    echo " -> Attempting to parse the build-deps "
    for INSTALLPKGMULTI in $(get_build_deps | split_deps); do
      echo " -> Considering build-dep $(echo "$INSTALLPKGMULTI" | tr "/" " ")"
      SATISFIED="no"
      for INSTALLPKG in $(split_alternates "$INSTALLPKGMULTI"); do
	CURRENTREALPKGNAME=$(get_pkg_name "$INSTALLPKG")
	if echo "$INSTALLPKG" | grep -q '\['; then
	    if checkbuilddep_archdeps "$INSTALLPKG" "$ARCH"; then
		SATISFIED="yes"
		echo "   -> This package is not for this architecture"
		continue
	    fi
	fi
	if echo "$INSTALLPKG" | grep -q '('; then
	    #echo "Debug: $INSTALLPKG"
	    if ! checkbuilddep_versiondeps "$CURRENTREALPKGNAME" `get_dep_op_and_ver "$INSTALLPKG"`; then
	      echo "   -> Does not satisfy version, not trying"
	      continue
	    fi
	fi
	echo "   -> Trying $CURRENTREALPKGNAME"

	if apt_sim $APTFLAG $INSTALLPKGLIST $CURRENTREALPKGNAME >& /dev/null; then
	    SATISFIED="yes"
	    INSTALLPKGLIST="$INSTALLPKGLIST $CURRENTREALPKGNAME"
	else
	    echo "       -> Cannot install $CURRENTREALPKGNAME; apt errors follow:"
	    if apt_sim $APTFLAG $INSTALLPKGLIST "$CURRENTREALPKGNAME"; then
		:
	    fi
	    # package could not be found. -- looking for alternative.
	    PROVIDED=""
	    checkbuilddep_provides "$CURRENTREALPKGNAME"
	    if [ -n "$PROVIDED" ]; then
		# something provides this package
		echo "     -> Considering $PROVIDED to satisfy the dependency "
		if apt_sim $APTFLAG $INSTALLPKGLIST $PROVIDED >& /dev/null; then
		    SATISFIED="yes";
		    INSTALLPKGLIST="$INSTALLPKGLIST $PROVIDED"
		else
		    # show the error for diagnostic purposes
		    echo "       -> Cannot install $PROVIDED; apt errors follow:"
		    apt_sim $APTFLAG $INSTALLPKGLIST $PROVIDED || true
		fi
	    fi
	fi
	if [ "$SATISFIED" = "yes" ]; then
	    break;
	fi
      done;
      if [ "$SATISFIED" = "no" ]; then
	  echo "E: Could not satisfy build-dependency." >&2
	  if [ "$CONTINUE_FAIL" != "yes" ]; then
	      exit 2
	  fi
      fi
    done;
    
    # now actually install the packages
    echo " -> Installing $INSTALLPKGLIST"
    if ! $CHROOTEXEC apt-get -y  "${APTGETOPT[@]}" install $APTFLAG $INSTALLPKGLIST; then
	echo " -> Trying to fix apt error"
	# Work around an apt bug which causes configure to fail.
	if $CHROOTEXEC dpkg --configure --pending && $CHROOTEXEC apt-get -y "${APTGETOPT[@]}" install $APTFLAG $INSTALLPKGLIST; then
	    echo " -> Apt bug workaround succeeded"
	elif [ "$CONTINUE_FAIL" != "yes" ]; then
	    echo "E: Unrecoverable error installing build-dependencies." >&2
	    exit 1
	fi
    fi

    # start processing build-conflicts.
    for INSTALLPKG in $(get_build_conflicts | split_deps); do
      CURRENTREALPKGNAME=$(get_pkg_name "$INSTALLPKG")
      echo " -> Considering $CURRENTREALPKGNAME"
      
      if echo "$INSTALLPKG" | grep -q '\['; then
	  # this package has arch-conflicts.
	  if checkbuilddep_archdeps "$INSTALLPKG" "$ARCH"; then
	      echo "I: Ignoring other-arch"
	      continue
	  fi
      fi
      if echo "$INSTALLPKG" | grep -q '('; then
	  # this package has version-conflicts
	  if ! checkbuilddep_versiondeps "$CURRENTREALPKGNAME" `get_dep_op_and_ver "$INSTALLPKG"`; then
	      echo "I: Satisfies version, not trying"
	      continue
	  fi
      fi

      # if package exists, remove it.
      if $CHROOTEXEC /usr/bin/dpkg -s "$CURRENTREALPKGNAME" 2>&1 | grep -q ^Package:; then
	  if ! $CHROOTEXEC /usr/bin/apt-get -y remove $CURRENTREALPKGNAME ; then
	      echo "E: Could not satisfy build-conflicts" >&2
	      exit 1
	  fi
      else
	  echo "I: $CURRENTREALPKGNAME package is not installed, no need to remove"
      fi
    done
    echo " -> Finished parsing the build-deps"
}


print_help() {
    # print out help message
    cat <<EOF
satisfydeps -- satisfy dependencies

--help:        give help
--control:     specify control file (debian/control, *.dsc)
--chroot:      operate inside chroot
--binary-all:  include binary-all
--binary-arch: include binary-arch only
--echo:        echo mode, do nothing. (--force-version required for most operation)
--force-version: skip version check.
--continue-fail: continue even when failed.

EOF
}


DEBIAN_CONTROL=debian/control
CHROOT=""
CHROOTEXEC=""
BINARY_ARCH="no"
FORCEVERSION=""
CONTINUE_FAIL="no"
CHROOTEXEC_AFTER_INTERNAL_CHROOTEXEC=no
ALLOWUNTRUSTED=no

while [ -n "$1" ]; do
    case "$1" in
	--control|-c)
	    DEBIAN_CONTROL="$2"
	    shift; shift
	    ;;

	# --chroot option and --internal-chrootexec options and --echo options somewhat conflict with each other.

	--chroot)
	    CHROOT="$2"
	    CHROOTEXEC="chroot $2 "
	    if [ ${CHROOTEXEC_AFTER_INTERNAL_CHROOTEXEC} = maybe ]; then
		echo '--chroot specified after --internal-chrootexec' >&2
		exit 1
	    fi
	    shift; shift
	    ;;
	--internal-chrootexec)
	    CHROOTEXEC="$2"
	    CHROOTEXEC_AFTER_INTERNAL_CHROOTEXEC=maybe
	    shift; shift 
	    ;;
	--echo)
	    CHROOTEXEC="echo $CHROOTEXEC"
	    CHROOTEXEC_AFTER_INTERNAL_CHROOTEXEC=maybe
	    shift
	    ;;

	--binary-all)
	    BINARY_ARCH="no"
	    shift
	    ;;
	--binary-arch)
	    BINARY_ARCH="yes"
	    shift
	    ;;
	--continue-fail)
	    CONTINUE_FAIL="yes"
	    shift
	    ;;
	--force-version)
	    FORCEVERSION="yes"
	    shift;
	    ;;
	--check-key)
	    ALLOWUNTRUSTED=no
	    shift;
	    ;;
	--allow-untrusted)
	    ALLOWUNTRUSTED=yes
	    shift;
	    ;;
	--help|-h|*)
	    print_help
	    exit 1
	    ;;
    esac
done

if [ $ALLOWUNTRUSTED = yes ]; then
	# Also duplicated in pbuilder-checkparams!
	# apt flag to accept untrusted packages
	APTGETOPT[${#APTGETOPT[@]}]='--force-yes'
	# aptitude flag to accept untrusted packages
	APTITUDEOPT[${#APTITUDEOPT[@]}]='-o'
	APTITUDEOPT[${#APTITUDEOPT[@]}]='Aptitude::CmdLine::Ignore-Trust-Violations=true'
fi

checkbuilddep_internal
