#!/bin/bash # 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 dependency. set -e function package_versions() { local PACKAGE="$1" ( $CHROOTEXEC /usr/bin/apt-cache show "$PACKAGE" ) | sed -n 's/^Version: \(.*\)$/\1/p' } function candidate_version() { local PACKAGE="$1" LC_ALL=C $CHROOTEXEC apt-cache policy "$PACKAGE" | sed -n 's/ *Candidate: *\(.*\)/\1/p' } function 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; } function expand_arch () { local ARCH="$1" local EXPANDED_ARCH # just keep the original behavior. echo "$ARCH" return # the following may be used if dpkg change is set to stone. if echo "$ARCH" | grep "-" > /dev/null; then EXPANDED_ARCH=$ARCH else EXPANDED_ARCH="linux-$ARCH" fi local WC1=$(echo $EXPANDED_ARCH | sed 's/^[^-]*/any/') local WC2=$(echo $EXPANDED_ARCH | sed 's/[^-]*$/any/') echo "$ARCH\\|$EXPANDED_ARCH\\|$WC1\\|$WC2" } function checkbuilddep_archdeps () { # returns FALSE on INSTALL local INSTALLPKG="$1" local ARCH="$2" if echo "$INSTALLPKG" | sed 's/.*\(\[.*\]\)/\1/' | grep "[[/][!]\($(expand_arch $ARCH)\)[]/]" > /dev/null; then # if !$ARCH exists in there, ERROR. return 0; fi if ! echo "$INSTALLPKG" | sed 's/.*\(\[.*\]\)/\1/' | grep "[!]" > /dev/null; then if ! echo "$INSTALLPKG" | sed 's/.*\(\[.*\]\)/\1/' | grep "[[/]\($(expand_arch $ARCH)\)[]/]" > /dev/null; then # if $ARCH does not exist, ERROR. return 0; fi fi return 1; } function 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 function 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" } function checkbuilddep_internal () { # Use this function to fulfill the dependency (almost) local ARCH=$(dpkg-architecture -qDEB_HOST_ARCH) local INSTALLPKG local INSTALLPKGLIST local INSTALLPKGMULTI local CURRENTREALPKGNAME local SATISFIED local PACKAGEVERSIONS local CANDIDATE_VERSION echo " -> Attempting to parse the build-deps $Id$" for INSTALLPKGMULTI in $(cat ${DEBIAN_CONTROL} | \ awk ' BEGIN{source=1} /^$/ {source=0} /^Source:/ {source=1} /^[^ ]*:/ {p=0} tolower($0) ~ /^'"${BD_REGEXP}"':/ {p=1} {if(p && source) {print $0}}' | \ sed 's/^[^: ]*://' | \ tr " " "/" | \ awk 'BEGIN{RS=","} {print}'); do echo " -> Considering build-dep$(echo "$INSTALLPKGMULTI" | tr "/" " " )" SATISFIED="no" for INSTALLPKG in $(echo "$INSTALLPKGMULTI" | \ awk 'BEGIN{RS="|"} {print}'); do CURRENTREALPKGNAME=$(echo "$INSTALLPKG" | sed -e 's/^[/]*//' -e 's/[[/(].*//') if echo "$INSTALLPKG" | grep '\[' > /dev/null ; then if checkbuilddep_archdeps "$INSTALLPKG" "$ARCH"; then SATISFIED="yes" echo " -> This package is not for this architecture" continue; fi fi CURRENT_APT_COMMAND="$(versioneddep_to_aptcmd "$INSTALLPKG")" while [ "$SATISFIED" = "no" ]; do echo " -> Trying to add ${CURRENT_APT_COMMAND}" if APT_OUTPUT="$( exec 2>&1; LC_ALL=C $CHROOTEXEC /usr/bin/apt-get -s install ${INSTALLPKGLIST} ${CURRENT_APT_COMMAND} )"; then # success, we're done SATISFIED="yes" INSTALLPKGLIST="${INSTALLPKGLIST} ${CURRENT_APT_COMMAND}" break fi # try to parse APT's output to recognize lines such as: # libfoo-dev: Depends: bar (>= xyz) but www is to be installed DEP_INSTALLPKG="$(echo "$APT_OUTPUT" | \ sed -n \ -e "s/^ *.*: *Depends: *\(.*\) but.*is to be installed\$/\\1/p" \ -e "s/^ *.*: *Depends: *\(.*\) but it is not going to be installed\$/\\1/p" \ -e "s/^ *.*: *Depends: *\(.*\)\$/\\1/p" | \ head -1 | \ tr " " "/")" APT_ADD_COMMAND="$(versioneddep_to_aptcmd "$DEP_INSTALLPKG")" if echo "$CURRENT_APT_COMMAND" | grep -q "$APT_ADD_COMMAND"; then # loop detected, give up with real packages echo " -> Loop detected, last APT error was: ======" echo "$APT_OUTPUT" echo " -> =========================================" echo " -> (not adding $APT_ADD_COMMAND to $CURRENT_APT_COMMAND)" break fi CURRENT_APT_COMMAND="$CURRENT_APT_COMMAND $APT_ADD_COMMAND" done if [ "$SATISFIED" = "yes" ]; then break; fi echo " -> Cannot install ${CURRENT_APT_COMMAND}; apt errors follow:" if $CHROOTEXEC /usr/bin/apt-get -s install ${INSTALLPKGLIST} "${CURRENT_APT_COMMAND}"; 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 $CHROOTEXEC /usr/bin/apt-get -s install ${INSTALLPKGLIST} ${PROVIDED} >& /dev/null; then SATISFIED="yes"; INSTALLPKGLIST="${INSTALLPKGLIST} ${PROVIDED}" else # show the error for diagnostic purposes echo " -> Cannot install $PROVIDED; apt errors follow:" if $CHROOTEXEC /usr/bin/apt-get -s install ${INSTALLPKGLIST} ${PROVIDED}; then : 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 --force-yes install ${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 --force-yes install ${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 $(cat "${DEBIAN_CONTROL}" | \ awk 'BEGIN{source=1} /^$/ {source=0} /^Source:/ {source=1} /^[^ ]*:/{p=0} tolower($0) ~ /^'"${BC_REGEXP}"':/ {p=1} {if(p && source) {print $0}}' | \ sed 's/^[^: ]*://' | \ tr " " "/" | \ awk 'BEGIN{RS=","} {print}'); do CURRENTREALPKGNAME=$(echo "$INSTALLPKG" | sed -e 's/^[/]*//' -e 's/[[/(].*//') echo " -> Considering ${CURRENTREALPKGNAME}" if echo "$INSTALLPKG" | grep '\[' > /dev/null ; then # this package has arch-conflicts. if checkbuilddep_archdeps "$INSTALLPKG" "$ARCH"; then echo "I: Ignoring other-arch" continue; fi fi if echo "$INSTALLPKG" | grep '[(]' > /dev/null ; then # this package has version-conflicts if ! checkbuilddep_versiondeps ${CURRENTREALPKGNAME} \ $(echo "$INSTALLPKG" | tr "/" " " | sed 's/^.*([ ]*\(<<\|<=\|>=\|=\|<\|>>\|>\)[ ]*\(.*\)).*$/\1/') \ $(echo "$INSTALLPKG" | tr "/" " " | sed 's/^.*([ ]*\(<<\|<=\|>=\|=\|<\|>>\|>\)[ ]*\(.*\)).*$/\2/'); then echo "I: Satisfies version, not trying" continue; fi fi # if package exists, remove it. if $CHROOTEXEC /usr/bin/dpkg -s $(echo "$INSTALLPKG" | tr "/" " " | awk '{print $1}') 2>&1 | grep ^Package: > /dev/null; 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" } function print_help () { # print out help message cat < --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 CHROOTEXEC="" BD_REGEXP="build-(depends|depends-indep)" BC_REGEXP="build-(conflicts|conflicts-indep)" FORCEVERSION="" CONTINUE_FAIL="no" while [ -n "$1" ]; do case "$1" in --control|-c) DEBIAN_CONTROL="$2" shift; shift ;; --chroot) CHROOTEXEC="chroot $2 " shift; shift ;; --internal-chrootexec) CHROOTEXEC="$2" shift; shift ;; --binary-all) BD_REGEXP='build-(depends|depends-indep)' BC_REGEXP='build-(conflicts|conflicts-indep)' shift ;; --binary-arch) BD_REGEXP='build-depends' BC_REGEXP='build-conflicts' shift ;; --echo) CHROOTEXEC="echo $CHROOTEXEC" shift ;; --continue-fail) CONTINUE_FAIL="yes" shift ;; --force-version) FORCEVERSION="yes" shift; ;; --help|-h|*) print_help exit 1 ;; esac done checkbuilddep_internal