[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[PATCH] New Dpkg::Deps module to replace parsedep() and showdep()



Hello everybody,

last week-end and today I worked on creating a new Dpkg::Deps module which
is able to parse dependencies, simplify them. I based my work on lintian's
lib/Dep.pm, expanded it and created an object-oriented interface for it.

The attached patch-set is the result of this work and represents my local
branch ready to be merged. It adds the new module and some non-regression
tests, and modify the various scripts that were using parsedep() and
showdep(). It also removes those functions and add the changelog entries.

I'm running this code and up to now it seems to work fine. During the
development, I also discovered a number of little bugs and oddities that I
took the liberty to commit directly on the master branch.

Please review and run it. I think it can be merged soon, just tell me to
go ahead when you're confident. It would be nice to include it in the
upcoming release as it automatically removes duplicate dependencies
potentially introduced by dpkg-shlibdeps (and thus renders my wishlist bug
#443973 useless).

I think that adding some POD documentation to the module would help. Feel
free to jump in. There's also two "die" functions that should probably by
replaced some Dpkg::ErrorHandling functions. But as they are errors for
programmers only (i.e. not the kind of error that an end-user will see), I
left them as-is for the moment.

(I have the feeling we already require translation of too many strings
that users won't never see)

Cheers,
-- 
Raphaël Hertzog

Premier livre français sur Debian GNU/Linux :
http://www.ouaza.com/livre/admin-debian/
>From 68e0d3b4599cc855316a50f13f8f4d697c3f2a04 Mon Sep 17 00:00:00 2001
From: Raphael Hertzog <hertzog@debian.org>
Date: Sun, 14 Oct 2007 23:27:13 +0200
Subject: [PATCH] Add new module Dpkg::Deps to handle dependencies and some corresponding tests

The Dpkg::Deps::* modules allows various operations on dependencies such
as testing for implication, evaluating them given some packages known to be
installed, etc. They also allow the simplification of the dependencies.
---
 ChangeLog                 |    3 +
 scripts/Dpkg/Deps.pm      |  750 +++++++++++++++++++++++++++++++++++++++++++++
 scripts/t/400_Dpkg_Deps.t |   51 +++
 3 files changed, 804 insertions(+), 0 deletions(-)
 create mode 100644 scripts/Dpkg/Deps.pm
 create mode 100644 scripts/t/400_Dpkg_Deps.t

diff --git a/ChangeLog b/ChangeLog
index 1eb1e32..469b248 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -11,6 +11,9 @@
 2007-10-14  Raphael Hertzog  <hertzog@debian.org>
 
 	* scripts/Dpkg/Version.pm: mark compare_versions as exportable.
+	* scripts/Dpkg/Deps.pm: new module to handle dependencies, to
+	evaluate them and check implications between them.
+	* scripts/t/400_Dpkg_Deps.t: non-regression tests for Dpkg::Deps.
 
 2007-10-13  Guillem Jover  <guillem@debian.org>
 
diff --git a/scripts/Dpkg/Deps.pm b/scripts/Dpkg/Deps.pm
new file mode 100644
index 0000000..2ef917f
--- /dev/null
+++ b/scripts/Dpkg/Deps.pm
@@ -0,0 +1,750 @@
+# Several parts are inspired by lib/Dep.pm from lintian
+
+package Dpkg::Deps;
+
+use strict;
+use warnings;
+
+use Dpkg::Version qw(compare_versions);
+use Dpkg::Arch qw(get_host_arch);
+use Dpkg::ErrorHandling qw(warning);
+use Dpkg::Gettext;
+textdomain("dpkg-dev");
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(@pkg_dep_fields @src_dep_fields %dep_field_type);
+
+# Some generic variables
+our @pkg_dep_fields = qw(Pre-Depends Depends Recommends Suggests Enhances
+                         Conflicts Breaks Replaces Provides);
+our @src_dep_fields = qw(Build-Depends Build-Depends-Indep
+                         Build-Conflicts Build-Conflicts-Indep);
+our %dep_field_type = (
+	'Pre-Depends' => 'normal',
+	'Depends' => 'normal',
+	'Recommends' => 'normal',
+	'Suggests' => 'normal',
+	'Enhances' => 'union',
+	'Conflicts' => 'union',
+	'Breaks' => 'union',
+	'Replaces' => 'union',
+	'Provides' => 'union',
+	'Build-Depends' => 'normal',
+	'Build-Depends-Indep' => 'normal',
+	'Build-Conflicts' => 'union',
+	'Build-Conflicts-Indep' => 'union',
+);
+
+# Some factorized function
+
+# Returns true if the arch list $p is a superset of arch list $q
+sub arch_is_superset {
+    my ($p, $q) = @_;
+    my $p_arch_neg = defined($p) && $p->[0] =~ /^!/;
+    my $q_arch_neg = defined($q) && $q->[0] =~ /^!/;
+
+    # If "a" has no arches, it is a superset of q and we should fall through
+    # to the version check.
+    if (not defined $p) {
+	return 1;
+    }
+
+    # If q has no arches, it is a superset of p and there are no useful
+    # implications.
+    elsif (not defined $q) {
+	return 0;
+    }
+
+    # Both have arches.  If neither are negated, we know nothing useful
+    # unless q is a subset of p.
+    elsif (not $p_arch_neg and not $q_arch_neg) {
+	my %p_arches = map { $_ => 1 } @{$p};
+	my $subset = 1;
+	for my $arch (@{$q}) {
+	    $subset = 0 unless $p_arches{$arch};
+	}
+	return 0 unless $subset;
+    }
+
+    # If both are negated, we know nothing useful unless p is a subset of
+    # q (and therefore has fewer things excluded, and therefore is more
+    # general).
+    elsif ($p_arch_neg and $q_arch_neg) {
+	my %q_arches = map { $_ => 1 } @{$q};
+	my $subset = 1;
+	for my $arch (@{$p}) {
+	    $subset = 0 unless $q_arches{$arch};
+	}
+	return 0 unless $subset;
+    }
+
+    # If q is negated and p isn't, we'd need to know the full list of
+    # arches to know if there's any relationship, so bail.
+    elsif (not $p_arch_neg and $q_arch_neg) {
+	return 0;
+    }
+
+    # If p is negated and q isn't, q is a subset of p if none of the
+    # negated arches in p are present in q.
+    elsif ($p_arch_neg and not $q_arch_neg) {
+	my %q_arches = map { $_ => 1 } @{$q};
+	my $subset = 1;
+	for my $arch (@{$p}) {
+	    $subset = 0 if $q_arches{substr($arch, 1)};
+	}
+	return 0 unless $subset;
+    }
+    return 1;
+}
+
+sub version_implies {
+    my ($rel_p, $v_p, $rel_q, $v_q) = @_;
+
+    # q wants an exact version, so p must provide that exact version.  p
+    # disproves q if q's version is outside the range enforced by p.
+    if ($rel_q eq '=') {
+        if ($rel_p eq '<<') {
+            return compare_versions($v_p, '<=', $v_q) ? 0 : undef;
+        } elsif ($rel_p eq '<=') {
+            return compare_versions($v_p, '<<', $v_q) ? 0 : undef;
+        } elsif ($rel_p eq '>>') {
+            return compare_versions($v_p, '>=', $v_q) ? 0 : undef;
+        } elsif ($rel_p eq '>=') {
+            return compare_versions($v_p, '>>', $v_q) ? 0 : undef;
+        } elsif ($rel_p eq '=') {
+            return compare_versions($v_p, '=', $v_q);
+        }
+    }
+
+    # A greater than clause may disprove a less than clause. An equal
+    # cause might as well.  Otherwise, if
+    # p's clause is <<, <=, or =, the version must be <= q's to imply q.
+    if ($rel_q eq '<=') {
+        if ($rel_p eq '>>') {
+            return compare_versions($v_p, '>=', $v_q) ? 0 : undef;
+        } elsif ($rel_p eq '>=') {
+            return compare_versions($v_p, '>>', $v_q) ? 0 : undef;
+	} elsif ($rel_p eq '=') {
+            return compare_versions($v_p, '<=', $v_q) ? 1 : 0;
+        } else { # <<, <=
+            return compare_versions($v_p, '<=', $v_q) ? 1 : undef;
+        }
+    }
+
+    # Similar, but << is stronger than <= so p's version must be << q's
+    # version if the p relation is <= or =.
+    if ($rel_q eq '<<') {
+        if ($rel_p eq '>>' or $rel_p eq '>=') {
+            return compare_versions($v_p, '>=', $v_p) ? 0 : undef;
+        } elsif ($rel_p eq '<<') {
+            return compare_versions($v_p, '<=', $v_q) ? 1 : undef;
+	} elsif ($rel_p eq '=') {
+            return compare_versions($v_p, '<<', $v_q) ? 1 : 0;
+        } else { # <<, <=
+            return compare_versions($v_p, '<<', $v_q) ? 1 : undef;
+        }
+    }
+
+    # Same logic as above, only inverted.
+    if ($rel_q eq '>=') {
+        if ($rel_p eq '<<') {
+            return compare_versions($v_p, '<=', $v_q) ? 0 : undef;
+        } elsif ($rel_p eq '<=') {
+            return compare_versions($v_p, '<<', $v_q) ? 0 : undef;
+	} elsif ($rel_p eq '=') {
+            return compare_versions($v_p, '>=', $v_q) ? 1 : 0;
+        } else { # >>, >=
+            return compare_versions($v_p, '>=', $v_q) ? 1 : undef;
+        }
+    }
+    if ($rel_q eq '>>') {
+        if ($rel_p eq '<<' or $rel_p eq '<=') {
+            return compare_versions($v_p, '<=', $v_q) ? 0 : undef;
+        } elsif ($rel_p eq '>>') {
+            return compare_versions($v_p, '>=', $v_q) ? 1 : undef;
+	} elsif ($rel_p eq '=') {
+            return compare_versions($v_p, '>>', $v_q) ? 1 : 0;
+        } else {
+            return compare_versions($v_p, '>>', $v_q) ? 1 : undef;
+        }
+    }
+
+    return undef;
+}
+
+sub parse {
+    my $dep_line = shift;
+    my %options = (@_);
+    $options{use_arch} = 1 if not exists $options{use_arch};
+    $options{reduce_arch} = 0 if not exists $options{reduce_arch};
+    $options{host_arch} = get_host_arch() if not exists $options{host_arch};
+    $options{union} = 0 if not exists $options{union};
+
+    my @dep_list;
+    foreach my $dep_and (split(/\s*,\s*/m, $dep_line)) {
+        my @or_list = ();
+        foreach my $dep_or (split(/\s*\|\s*/m, $dep_and)) {
+	    my $dep_simple = Dpkg::Deps::Simple->new($dep_or);
+	    if (not defined $dep_simple->{package}) {
+		warning(sprintf(_g("can't parse dependency %s"), $dep_and));
+		return undef;
+	    }
+	    $dep_simple->{arches} = undef if not $options{use_arch};
+            if ($options{reduce_arch}) {
+		$dep_simple->reduce_arch($options{host_arch});
+		next if not $dep_simple->arch_is_concerned($options{host_arch});
+	    }
+	    push @or_list, $dep_simple;
+        }
+	next if not @or_list;
+	if (scalar @or_list == 1) {
+	    push @dep_list, $or_list[0];
+	} else {
+	    my $dep_or = Dpkg::Deps::OR->new();
+	    $dep_or->add($_) foreach (@or_list);
+	    push @dep_list, $dep_or;
+	}
+    }
+    return undef if not @dep_list;
+    my $dep_and;
+    if ($options{union}) {
+	$dep_and = Dpkg::Deps::Union->new();
+    } else {
+	$dep_and = Dpkg::Deps::AND->new();
+    }
+    $dep_and->add($_) foreach (@dep_list);
+    return $dep_and;
+}
+
+package Dpkg::Deps::Simple;
+
+use strict;
+use warnings;
+
+use Dpkg::Arch qw(debarch_is);
+use Dpkg::Version qw(compare_versions);
+
+sub new {
+    my ($this, $arg) = @_;
+    my $class = ref($this) || $this;
+    my $self = {
+	'package' => undef,
+	'relation' => undef,
+	'version' => undef,
+	'arches' => undef,
+    };
+    bless $self, $class;
+    $self->parse($arg) if defined($arg);
+    return $self;
+}
+
+sub parse {
+    my ($self, $dep) = @_;
+    return if not $dep =~
+            /^\s*                           # skip leading whitespace
+              ([a-zA-Z0-9][a-zA-Z0-9+.-]+)  # package name
+              (?:                           # start of optional part
+                \s* \(                      # open parenthesis for version part
+                \s* (<<|<=|=|>=|>>|<|>)     # relation part
+                \s* (.*?)                   # do not attempt to parse version
+                \s* \)                      # closing parenthesis
+              )?                            # end of optional part
+              (?:                           # start of optional architecture
+                \s* \[                      # open bracket for architecture
+                \s* (.*?)                   # don't parse architectures now
+                \s* \]                      # closing bracket
+              )?                            # end of optional architecture
+	      \s*$			    # trailing spaces at end
+            /mx;
+    $self->{package} = $1;
+    $self->{relation} = $2;
+    $self->{version} = $3;
+    if (defined($4)) {
+	$self->{arches} = [ split(/\s+/, $4) ];
+    }
+    # Standardize relation field
+    if (defined($self->{relation})) {
+	$self->{relation} = '<<' if ($self->{relation} eq '<');
+	$self->{relation} = '>>' if ($self->{relation} eq '>');
+    }
+}
+
+sub dump {
+    my $self = shift;
+    my $res = $self->{package};
+    if (defined($self->{relation})) {
+	$res .= " (" . $self->{relation} . " " . $self->{version} .  ")";
+    }
+    if (defined($self->{'arches'})) {
+	$res .= " [" . join(" ", @{$self->{arches}}) . "]";
+    }
+    return $res;
+}
+
+# Returns true if the dependency in parameter can deduced from the current
+# dependency. Returns false if it can be negated. Returns undef if nothing
+# can be concluded.
+sub implies {
+    my ($self, $o) = @_;
+    if ($o->isa('Dpkg::Deps::Simple')) {
+	# An implication is only possible on the same package
+	return undef if $self->{package} ne $o->{package};
+
+	# Our architecture set must be a superset of the architectures for
+	# o, otherwise we can't conclude anything.
+	return undef unless Dpkg::Deps::arch_is_superset($self->{arches}, $o->{arches});
+
+	# If o has no version clause, then our dependency is stronger
+	return 1 if not defined $o->{relation};
+	# If o has a version clause, we must also have one, otherwise there
+	# can't be an implication
+	return undef if not defined $self->{relation};
+
+	return Dpkg::Deps::version_implies($self->{relation}, $self->{version},
+		$o->{relation}, $o->{version});
+
+    } elsif ($o->isa('Dpkg::Deps::AND')) {
+	# TRUE: Need to imply all individual elements
+	# FALSE: Need to NOT imply at least one individual element
+	my $res = 1;
+	foreach my $dep ($o->get_deps()) {
+	    my $implication = $self->implies($dep);
+	    unless (defined($implication) && $implication == 1) {
+		$res = $implication;
+		last if defined $res;
+	    }
+	}
+	return $res;
+    } elsif ($o->isa('Dpkg::Deps::OR')) {
+	# TRUE: Need to imply at least one individual element
+	# FALSE: Need to not apply all individual elements
+	# UNDEF: The rest
+	my $res = undef;
+	foreach my $dep ($o->get_deps()) {
+	    my $implication = $self->implies($dep);
+	    if (defined($implication)) {
+		if (not defined $res) {
+		    $res = $implication;
+		} else {
+		    if ($implication) {
+			$res = 1;
+		    } else {
+			$res = 0;
+		    }
+		}
+		last if defined($res) && $res == 1;
+	    }
+	}
+	return $res;
+    } else {
+	die "Can't handle unknown dependency type!\n";
+    }
+}
+
+sub get_deps {
+    my $self = shift;
+    return $self;
+}
+
+sub sort {
+    # Nothing to sort
+}
+
+sub arch_is_concerned {
+    my ($self, $host_arch) = @_;
+
+    return 0 if not defined $self->{package}; # Empty dep
+    return 1 if not defined $self->{arches};  # Dep without arch spec
+
+    my $seen_arch = 0;
+    foreach my $arch (@{$self->{arches}}) {
+	$arch=lc($arch);
+
+	if ($arch =~ /^!/) {
+	    my $not_arch = $arch;
+	    $not_arch =~ s/^!//;
+
+	    if (debarch_is($host_arch, $not_arch)) {
+		$seen_arch = 0;
+		last;
+	    } else {
+		# !arch includes by default all other arches
+		# unless they also appear in a !otherarch
+		$seen_arch = 1;
+	    }
+	} elsif (debarch_is($host_arch, $arch)) {
+	    $seen_arch = 1;
+	    last;
+	}
+    }
+    return $seen_arch;
+}
+
+sub reduce_arch {
+    my ($self, $host_arch) = @_;
+    if (not $self->arch_is_concerned($host_arch)) {
+	$self->{package} = undef;
+	$self->{relation} = undef;
+	$self->{version} = undef;
+	$self->{arches} = undef;
+    } else {
+	$self->{arches} = undef;
+    }
+}
+
+sub get_evaluation {
+    my ($self, $facts) = @_;
+    return undef if not defined $self->{package};
+    my ($check, $param) = $facts->check_package($self->{package});
+    if ($check) {
+	if (defined $self->{relation}) {
+	    if (ref($param)) {
+		# Provided packages
+		# XXX: Once support for versioned provides is in place,
+		# this part must be adapted
+		return 0;
+	    } else {
+		if (defined($param)) {
+		    if (compare_versions($param, $self->{relation}, $self->{version})) {
+			return 1;
+		    } else {
+			return 0;
+		    }
+		} else {
+		    return undef;
+		}
+	    }
+	} else {
+	    return 1;
+	}
+    }
+    return undef;
+}
+
+sub simplify_deps {
+    my ($self, $facts) = @_;
+    my $eval = $self->get_evaluation($facts);
+    if (defined($eval) and $eval == 1) {
+	$self->{package} = undef;
+	$self->{relation} = undef;
+	$self->{version} = undef;
+	$self->{arches} = undef;
+    }
+}
+
+sub is_empty {
+    my $self = shift;
+    return not defined $self->{package};
+}
+
+sub merge_union {
+    my ($self, $o) = @_;
+    return 0 if not $o->isa('Dpkg::Deps::Simple');
+    return 0 if $self->is_empty() or $o->is_empty();
+    return 0 if $self->{package} ne $o->{package};
+    return 0 if defined $self->{arches} or defined $o->{arches};
+
+    if (not defined $o->{relation} and defined $self->{relation}) {
+	# Union is the non-versioned dependency
+	$self->{relation} = undef;
+	$self->{version} = undef;
+	return 1;
+    }
+
+    my $implication = $self->implies($o);
+    my $rev_implication = $o->implies($self);
+    if (defined($implication)) {
+	if ($implication) {
+	    $self->{relation} = $o->{relation};
+	    $self->{version} = $o->{version};
+	    return 1;
+	} else {
+	    return 0;
+	}
+    }
+    if (defined($rev_implication)) {
+	if ($rev_implication) {
+	    # Already merged...
+	    return 1;
+	} else {
+	    return 0;
+	}
+    }
+    return 0;
+}
+
+package Dpkg::Deps::Multiple;
+
+use strict;
+use warnings;
+
+sub new {
+    my $this = shift;
+    my $class = ref($this) || $this;
+    my $self = { 'list' => [ @_ ] };
+    bless $self, $class;
+    return $self;
+}
+
+sub add {
+    my $self = shift;
+    push @{$self->{list}}, @_;
+}
+
+sub get_deps {
+    my $self = shift;
+    return @{$self->{list}};
+}
+
+sub sort {
+    my $self = shift;
+    my @res = ();
+    @res = sort { $a->dump() cmp $b->dump() } @{$self->{list}};
+    $self->{list} = [ @res ];
+}
+
+sub arch_is_concerned {
+    my ($self, $host_arch) = @_;
+    my $res = 0;
+    foreach my $dep (@{$self->{list}}) {
+	$res = 1 if $dep->arch_is_concerned($host_arch);
+    }
+    return $res;
+}
+
+sub reduce_arch {
+    my ($self, $host_arch) = @_;
+    my @new;
+    foreach my $dep (@{$self->{list}}) {
+	$dep->reduce_arch($host_arch);
+	push @new, $dep if $dep->arch_is_concerned($host_arch);
+    }
+    $self->{list} = [ @new ];
+}
+
+sub is_empty {
+    my $self = shift;
+    return scalar @{$self->{list}} == 0;
+}
+
+sub merge_union {
+    die "merge_union is only valid for Dpkg::Deps::Simple\n";
+}
+
+package Dpkg::Deps::AND;
+
+use strict;
+use warnings;
+
+our @ISA = qw(Dpkg::Deps::Multiple);
+
+sub dump {
+    my $self = shift;
+    return join(", ", map { $_->dump() } grep { not $_->is_empty() } $self->get_deps());
+}
+
+sub implies {
+    my ($self, $o) = @_;
+    # If any individual member can imply $o or NOT $o, we're fine
+    foreach my $dep ($self->get_deps()) {
+	my $implication = $dep->implies($o);
+	return 1 if defined($implication) && $implication == 1;
+	return 0 if defined($implication) && $implication == 0;
+    }
+    # If o is an AND, we might have an implication, if we find an
+    # implication within us for each predicate in o
+    if ($o->isa('Dpkg::Deps::AND')) {
+	my $subset = 1;
+	foreach my $odep ($o->get_deps()) {
+	    my $found = 0;
+	    foreach my $dep ($self->get_deps()) {
+		$found = 1 if $dep->implies($odep);
+	    }
+	    $subset = 0 if not $found;
+	}
+	return 1 if $subset;
+    }
+    return undef;
+}
+
+sub get_evaluation {
+    my ($self, $facts) = @_;
+    my $result = 1;
+    foreach my $dep ($self->get_deps()) {
+	my $eval = $dep->get_evaluation($facts);
+	if (not defined $eval) {
+	    $result = undef;
+	    last;
+	} elsif ($eval == 0) {
+	    $result = 0;
+	    last;
+	} elsif ($eval == 1) {
+	    # Still possible
+	}
+    }
+    return $result;
+}
+
+sub simplify_deps {
+    my ($self, $facts, @knowndeps) = @_;
+    my @new;
+
+WHILELOOP:
+    while (@{$self->{list}}) {
+	my $dep = shift @{$self->{list}};
+	my $eval = $dep->get_evaluation($facts);
+	next if defined($eval) and $eval == 1;
+	foreach my $odep (@knowndeps, @new, @{$self->{list}}) {
+	    next WHILELOOP if $odep->implies($dep);
+	}
+	push @new, $dep;
+    }
+    $self->{list} = [ @new ];
+}
+
+
+package Dpkg::Deps::OR;
+
+use strict;
+use warnings;
+
+our @ISA = qw(Dpkg::Deps::Multiple);
+
+sub dump {
+    my $self = shift;
+    return join(" | ", map { $_->dump() } grep { not $_->is_empty() } $self->get_deps());
+}
+
+sub implies {
+    my ($self, $o) = @_;
+    # In general, an OR dependency can't imply anything except an even
+    # bigger list of alternatives
+    if ($o->isa('Dpkg::Deps::OR')) {
+	my $subset = 1;
+	foreach my $odep ($o->get_deps()) {
+	    my $found = 0;
+	    foreach my $dep ($self->get_deps()) {
+		$found = 1 if $dep->implies($odep);
+	    }
+	    $subset = 0 if not $found;
+	}
+	return 1 if $subset;
+    }
+    return undef;
+}
+
+sub get_evaluation {
+    my ($self, $facts) = @_;
+    my $result = 0;
+    foreach my $dep ($self->get_deps()) {
+	my $eval = $dep->get_evaluation($facts);
+	if (not defined $eval) {
+	    $result = undef;
+	    last;
+	} elsif ($eval == 1) {
+	    $result = 1;
+	    last;
+	} elsif ($eval == 0) {
+	    # Still possible to have a false evaluation
+	}
+    }
+    return $result;
+}
+
+sub simplify_deps {
+    my ($self, $facts) = @_;
+
+    # Nothing to simplify
+    my @new;
+
+WHILELOOP:
+    while (@{$self->{list}}) {
+	my $dep = shift @{$self->{list}};
+	my $eval = $dep->get_evaluation($facts);
+	if (defined($eval) and $eval == 1) {
+	    $self->{list} = [];
+	    return;
+	}
+	foreach my $odep (@new, @{$self->{list}}) {
+	    next WHILELOOP if $odep->implies($dep);
+	}
+	push @new, $dep;
+    }
+    $self->{list} = [ @new ];
+}
+
+package Dpkg::Deps::Union;
+
+use strict;
+use warnings;
+
+our @ISA = qw(Dpkg::Deps::Multiple);
+
+sub dump {
+    my $self = shift;
+    return join(", ", map { $_->dump() } grep { not $_->is_empty() } $self->get_deps());
+}
+
+sub implies {
+    # Implication test are not useful on Union
+    return undef;
+}
+
+sub get_evaluation {
+    # Evaluation are not useful on Union
+    return undef;
+}
+
+sub simplify_deps {
+    my ($self, $facts) = @_;
+    my @new;
+
+WHILELOOP:
+    while (@{$self->{list}}) {
+	my $dep = shift @{$self->{list}};
+	foreach my $odep (@new) {
+	    next WHILELOOP if $dep->merge_union($odep);
+	}
+	push @new, $dep;
+    }
+    $self->{list} = [ @new ];
+}
+
+package Dpkg::Deps::KnownFacts;
+
+use strict;
+use warnings;
+
+sub new {
+    my $this = shift;
+    my $class = ref($this) || $this;
+    my $self = { 'pkg' => {}, 'virtualpkg' => {} };
+    bless $self, $class;
+    return $self;
+}
+
+sub add_installed_package {
+    my ($self, $pkg, $ver) = @_;
+    $self->{pkg}{$pkg} = $ver;
+}
+
+sub add_provided_package {
+    my ($self, $pkg, $rel, $ver, $by) = @_;
+    if (not exists $self->{virtualpkg}{$pkg}) {
+	$self->{virtualpkg}{$pkg} = [];
+    }
+    push @{$self->{virtualpkg}{$pkg}}, [ $by, $rel, $ver ];
+}
+
+sub check_package {
+    my ($self, $pkg) = @_;
+    if (exists $self->{pkg}{$pkg}) {
+	return (1, $self->{pkg}{$pkg});
+    }
+    if (exists $self->{virtualpkg}{$pkg}) {
+	return (1, $self->{virtualpkg}{$pkg});
+    }
+    return (0, undef);
+}
+
+1;
diff --git a/scripts/t/400_Dpkg_Deps.t b/scripts/t/400_Dpkg_Deps.t
new file mode 100644
index 0000000..ca9b489
--- /dev/null
+++ b/scripts/t/400_Dpkg_Deps.t
@@ -0,0 +1,51 @@
+# -*- mode: cperl;-*-
+
+use Test::More tests => 10;
+
+use strict;
+use warnings;
+
+use_ok('Dpkg::Deps');
+
+my $field_multiline = "libgtk2.0-common (= 2.10.13-1)  , libatk1.0-0 (>=
+1.13.2), libc6 (>= 2.5-5), libcairo2 (>= 1.4.0), libcupsys2 (>= 1.2.7),
+libfontconfig1 (>= 2.4.0), libglib2.0-0  (  >= 2.12.9), libgnutls13 (>=
+1.6.3-0), libjpeg62, python (<< 2.5)";
+my $field_multiline_sorted = "libatk1.0-0 (>= 1.13.2), libc6 (>= 2.5-5), libcairo2 (>= 1.4.0), libcupsys2 (>= 1.2.7), libfontconfig1 (>= 2.4.0), libglib2.0-0 (>= 2.12.9), libgnutls13 (>= 1.6.3-0), libgtk2.0-common (= 2.10.13-1), libjpeg62, python (<< 2.5)";
+
+my $dep_multiline = Dpkg::Deps::parse($field_multiline);
+$dep_multiline->sort();
+is($dep_multiline->dump(), $field_multiline_sorted, "Parse, sort and dump");
+
+my $dep_subset = Dpkg::Deps::parse("libatk1.0-0 (>> 1.10), libc6, libcairo2");
+is($dep_multiline->implies($dep_subset), 1, "Dep implies subset of itself");
+is($dep_subset->implies($dep_multiline), undef, "Subset doesn't imply superset");
+my $dep_opposite = Dpkg::Deps::parse("python (>= 2.5)");
+is($dep_opposite->implies($dep_multiline), 0, "Opposite condition implies NOT the depends");
+
+my $field_arch = "libc6 (>= 2.5) [!alpha !hurd-i386], libc6.1 [alpha], libc0.1 [hurd-i386]";
+my $dep_i386 = Dpkg::Deps::parse($field_arch, reduce_arch => 1, host_arch => 'i386');
+my $dep_alpha = Dpkg::Deps::parse($field_arch, reduce_arch => 1, host_arch => 'alpha');
+my $dep_hurd = Dpkg::Deps::parse($field_arch, reduce_arch => 1, host_arch => 'hurd-i386');
+is($dep_i386->dump(), "libc6 (>= 2.5)", "Arch reduce 1/3");
+is($dep_alpha->dump(), "libc6.1", "Arch reduce 2/3");
+is($dep_hurd->dump(), "libc0.1", "Arch reduce 3/3");
+
+
+my $facts = Dpkg::Deps::KnownFacts->new();
+$facts->add_installed_package("mypackage", "1.3.4-1");
+$facts->add_provided_package("myvirtual", undef, undef, "mypackage");
+
+my $field_duplicate = "libc6 (>= 2.3), libc6 (>= 2.6-1), mypackage (>=
+1.3), myvirtual | something, python (>= 2.5)";
+my $dep_dup = Dpkg::Deps::parse($field_duplicate);
+$dep_dup->simplify_deps($facts, $dep_opposite);
+is($dep_dup->dump(), "libc6 (>= 2.6-1)", "Simplify deps");
+
+my $field_dup_union = "libc6 (>> 2.3), libc6 (>= 2.6-1), fake (<< 2.0),
+fake(>> 3.0), fake (= 2.5), python (<< 2.5), python (= 2.4)";
+my $dep_dup_union = Dpkg::Deps::parse($field_dup_union, union => 1);
+$dep_dup_union->simplify_deps($facts);
+is($dep_dup_union->dump(), "libc6 (>> 2.3), fake (<< 2.0), fake (>> 3.0), fake (= 2.5), python (<< 2.5)", "Simplify union deps");
+
+
-- 
1.5.3.4

>From fbe07a79101a4c3ab9e45992669dc6c1811f123c Mon Sep 17 00:00:00 2001
From: Raphael Hertzog <hertzog@debian.org>
Date: Mon, 15 Oct 2007 13:59:18 +0200
Subject: [PATCH] Include the new scripts/Dpkg/Deps.pm in the dpkg-dev package

---
 debian/dpkg-dev.install |   13 +++++++------
 scripts/Makefile.am     |    9 +++++----
 2 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/debian/dpkg-dev.install b/debian/dpkg-dev.install
index bdee88d..8124ae5 100644
--- a/debian/dpkg-dev.install
+++ b/debian/dpkg-dev.install
@@ -17,12 +17,6 @@ usr/bin/dpkg-shlibdeps
 usr/bin/dpkg-source
 usr/lib/dpkg/controllib.pl
 usr/lib/dpkg/parsechangelog
-usr/share/perl5/Dpkg/Arch.pm
-usr/share/perl5/Dpkg/Path.pm
-usr/share/perl5/Dpkg/Version.pm
-usr/share/perl5/Dpkg/ErrorHandling.pm
-usr/share/perl5/Dpkg/Shlibs.pm
-usr/share/perl5/Dpkg/Shlibs
 usr/share/locale/*/LC_MESSAGES/dpkg-dev.mo
 usr/share/man/*/*/822-date.1
 usr/share/man/*/822-date.1
@@ -66,4 +60,11 @@ usr/share/man/*/*/dpkg-shlibdeps.1
 usr/share/man/*/dpkg-shlibdeps.1
 usr/share/man/*/*/dpkg-source.1
 usr/share/man/*/dpkg-source.1
+usr/share/perl5/Dpkg/Arch.pm
 usr/share/perl5/Dpkg/BuildOptions.pm
+usr/share/perl5/Dpkg/ErrorHandling.pm
+usr/share/perl5/Dpkg/Deps.pm
+usr/share/perl5/Dpkg/Path.pm
+usr/share/perl5/Dpkg/Shlibs
+usr/share/perl5/Dpkg/Shlibs.pm
+usr/share/perl5/Dpkg/Version.pm
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
index 9663fdb..b18b513 100644
--- a/scripts/Makefile.am
+++ b/scripts/Makefile.am
@@ -73,13 +73,14 @@ CLEANFILES = \
 perllibdir = $(PERL_LIBDIR)
 nobase_dist_perllib_DATA = \
 	Dpkg/Arch.pm \
-	Dpkg/Shlibs.pm \
-	Dpkg/Shlibs/Objdump.pm \
-	Dpkg/Shlibs/SymbolFile.pm \
-	Dpkg/ErrorHandling.pm \
 	Dpkg/BuildOptions.pm \
+	Dpkg/ErrorHandling.pm \
+	Dpkg/Deps.pm \
 	Dpkg/Gettext.pm \
 	Dpkg/Path.pm \
+	Dpkg/Shlibs.pm \
+	Dpkg/Shlibs/Objdump.pm \
+	Dpkg/Shlibs/SymbolFile.pm \
 	Dpkg/Version.pm \
 	Dpkg.pm
 
-- 
1.5.3.4

>From be01cb1c9ebdb325c3818cfe83dff5380e8075bc Mon Sep 17 00:00:00 2001
From: Raphael Hertzog <hertzog@debian.org>
Date: Mon, 15 Oct 2007 12:19:59 +0200
Subject: [PATCH] dpkg-gencontrol: use the new Dpkg::Deps module to rewrite the dependencies

Normal dependency fields are rewritten by order of importance and simplified
by taking into account the dependencies already parsed. The dependencies are
also sorted to have a more predictable output (which is easier to compare in
a debdiff output).

The other relation (non-dependency) fields are simplified as unions
instead of intersections.
---
 scripts/dpkg-gencontrol.pl |   47 ++++++++++++++++++++++++++++++++++++-------
 1 files changed, 39 insertions(+), 8 deletions(-)

diff --git a/scripts/dpkg-gencontrol.pl b/scripts/dpkg-gencontrol.pl
index 4cae8e7..ce8b683 100755
--- a/scripts/dpkg-gencontrol.pl
+++ b/scripts/dpkg-gencontrol.pl
@@ -14,10 +14,13 @@ use Dpkg::Arch qw(get_host_arch debarch_eq debarch_is);
 push(@INC,$dpkglibdir);
 require 'controllib.pl';
 
+use Dpkg::Deps qw(@pkg_dep_fields %dep_field_type);
+
 our %substvar;
 our (%f, %fi);
 our %p2i;
 our @pkg_dep_fields;
+our %dep_field_type;
 our $sourcepackage;
 
 textdomain("dpkg-dev");
@@ -226,15 +229,43 @@ init_substvar_arch();
 # Process dependency fields in a second pass, now that substvars have been
 # initialized.
 
-for $_ (keys %fi) {
-    my $v = $fi{$_};
+my $facts = Dpkg::Deps::KnownFacts->new();
+$facts->add_installed_package($f{'Package'}, $f{'Version'});
+if (exists $fi{"C$myindex Provides"}) {
+    my $provides = Dpkg::Deps::parse(substvars($fi{"C$myindex Provides"}),
+				     reduce_arch => 1, union => 1);
+    if (defined $provides) {
+	foreach my $subdep ($provides->get_deps()) {
+	    if ($subdep->isa('Dpkg::Deps::Simple')) {
+		$facts->add_provided_package($subdep->{package},
+			$subdep->{relation}, $subdep->{version},
+			$f{'Package'});
+	    }
+	}
+    }
+}
 
-    if (s/^C$myindex //) {
-        if (exists($pkg_dep_fields{$_})) {
-           my $dep = parsedep(substvars($v), 1, 1);
-           &error(sprintf(_g("error occurred while parsing %s"), $_)) unless defined $dep;
-            $f{$_}= showdep($dep, 0);
-        }
+my (@seen_deps);
+foreach my $field (@pkg_dep_fields) {
+    my $key = "C$myindex $field";
+    if (exists $fi{$key}) {
+	my $dep;
+	if ($dep_field_type{$field} eq 'normal') {
+	    $dep = Dpkg::Deps::parse(substvars($fi{$key}), use_arch => 1,
+				     reduce_arch => 1);
+	    &error(sprintf(_g("error occurred while parsing %s"), $_)) unless defined $dep;
+	    $dep->simplify_deps($facts, @seen_deps);
+	    # Remember normal deps to simplify even further weaker deps
+	    push @seen_deps, $dep if $dep_field_type{$field} eq 'normal';
+	} else {
+	    $dep = Dpkg::Deps::parse(substvars($fi{$key}), use_arch => 1,
+				     reduce_arch => 1, union => 1);
+	    &error(sprintf(_g("error occurred while parsing %s"), $_)) unless defined $dep;
+	    $dep->simplify_deps($facts);
+	}
+	$dep->sort();
+	$f{$field} = $dep->dump();
+	delete $f{$field} unless $f{$field}; # Delete empty field
     }
 }
 
-- 
1.5.3.4

>From f04e7c9e5cb680a3ec1433f6fcd30d772d91b2b1 Mon Sep 17 00:00:00 2001
From: Raphael Hertzog <hertzog@debian.org>
Date: Mon, 15 Oct 2007 14:27:27 +0200
Subject: [PATCH] dpkg-source: use the new Dpkg::Deps module instead of controllib's parsedep

---
 scripts/dpkg-source.pl |   17 +++++++++++++++--
 1 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/scripts/dpkg-source.pl b/scripts/dpkg-source.pl
index 95baed5..92b3ec7 100755
--- a/scripts/dpkg-source.pl
+++ b/scripts/dpkg-source.pl
@@ -9,6 +9,7 @@ use Dpkg::ErrorHandling qw(warning warnerror error failure unknown
                            internerr syserr subprocerr usageerr
                            $warnable_error $quiet_warnings);
 use Dpkg::Arch qw(debarch_eq);
+use Dpkg::Deps qw(@src_dep_fields %dep_field_type);
 
 my @filesinarchive;
 my %dirincluded;
@@ -315,9 +316,21 @@ if ($opmode eq 'build') {
 	    }
 	    elsif (m/^Uploaders$/i) { ($f{$_}= $v) =~ s/[\r\n]//g; }
 	    elsif (m/^Build-(Depends|Conflicts)(-Indep)?$/i) {
-		my $dep = parsedep($v, 1);
+		my $dep;
+		# XXX: Should use %dep_field_type to decide if we parse
+		# as union or not but since case-insensitive matching is
+		# used, I can't rely on $_ to have the very same
+		# capitalization...
+		if (lc($1) eq "depends") {
+		    $dep = Dpkg::Deps::parse($v);
+		} else {
+		    $dep = Dpkg::Deps::parse($v, union => 1);
+		}
 		&error(sprintf(_g("error occurred while parsing %s"), $_)) unless defined $dep;
-		$f{$_}= showdep($dep, 1);
+		my $facts = Dpkg::Deps::KnownFacts->new();
+		$dep->simplify_deps($facts);
+		$dep->sort();
+		$f{$_}= $dep->dump();
 	    }
             elsif (s/^X[BC]*S[BC]*-//i) { $f{$_}= $v; }
             elsif (m/^(Section|Priority|Files|Bugs)$/i || m/^X[BC]+-/i) { }
-- 
1.5.3.4

>From 43b32199bad06a38d384fb9bf619dcbdcf1d5b39 Mon Sep 17 00:00:00 2001
From: Raphael Hertzog <hertzog@debian.org>
Date: Mon, 15 Oct 2007 14:59:31 +0200
Subject: [PATCH] dpkg-checkbuilddeps: modify to use the new Dpkg::Deps module

Use Dpkg::Deps::KnownFacts instead of home-made structure to
store the information taken from the status file.
---
 scripts/dpkg-checkbuilddeps.pl |  104 +++++++++++++++------------------------
 1 files changed, 40 insertions(+), 64 deletions(-)

diff --git a/scripts/dpkg-checkbuilddeps.pl b/scripts/dpkg-checkbuilddeps.pl
index db28b10..6431bca 100755
--- a/scripts/dpkg-checkbuilddeps.pl
+++ b/scripts/dpkg-checkbuilddeps.pl
@@ -9,6 +9,7 @@ use Dpkg;
 use Dpkg::Gettext;
 use Dpkg::ErrorHandling qw(error);
 use Dpkg::Arch qw(get_host_arch);
+use Dpkg::Deps;
 
 push(@INC,$dpkglibdir);
 require 'controllib.pl';
@@ -47,67 +48,71 @@ my $controlfile = shift || "debian/control";
 
 parsecontrolfile($controlfile);
 
-my @status = parse_status("$admindir/status");
+my $facts = parse_status("$admindir/status");
 my (@unmet, @conflicts);
 
 my $dep_regex=qr/[ \t]*(([^\n]+|\n[ \t])*)\s/; # allow multi-line
 if (defined($fi{"C Build-Depends"})) {
 	push @unmet, build_depends('Build-Depends',
-				   parsedep($fi{"C Build-Depends"}, 1, 1),
-				   @status);
+				   Dpkg::Deps::parse($fi{"C Build-Depends"},
+					reduce_arch => 1), $facts);
 }
 if (defined($fi{"C Build-Conflicts"})) {
 	push @conflicts, build_conflicts('Build-Conflicts',
-					 parsedep($fi{"C Build-Conflicts"}, 1, 1),
-					 @status);
+					 Dpkg::Deps::parse($fi{"C Build-Conflicts"},
+					    reduce_arch => 1, union => 1), $facts);
 }
 if (! $binary_only && defined($fi{"C Build-Depends-Indep"})) {
 	push @unmet, build_depends('Build-Depends-Indep',
-				   parsedep($fi{"C Build-Depends-Indep"}, 1, 1),
-				   @status);
+				   Dpkg::Deps::parse($fi{"C Build-Depends-Indep"},
+					reduce_arch => 1), $facts);
 }
 if (! $binary_only && defined($fi{"C Build-Conflicts-Indep"})) {
 	push @conflicts, build_conflicts('Build-Conflicts-Indep',
-					 parsedep($fi{"C Build-Conflicts-Indep"}, 1, 1),
-					 @status);
+					 Dpkg::Deps::parse($fi{"C Build-Conflicts-Indep"},
+					    reduce_arch => 1, union => 1), $facts);
 }
 
 if (@unmet) {
 	printf STDERR _g("%s: Unmet build dependencies: "), $progname;
-	print STDERR join(" ", @unmet), "\n";
+	print STDERR join(" ", map { $_->dump() } @unmet), "\n";
 }
 if (@conflicts) {
 	printf STDERR _g("%s: Build conflicts: "), $progname;
-	print STDERR join(" ", @conflicts), "\n";
+	print STDERR join(" ", map { $_->dump() } @conflicts), "\n";
 }
 exit 1 if @unmet || @conflicts;
 
-# This part could be replaced. Silly little status file parser.
-# thanks to Matt Zimmerman. Returns two hash references that
-# are exactly what the other functions need...
+# Silly little status file parser that returns a Dpkg::Deps::KnownFacts
 sub parse_status {
 	my $status = shift;
 	
-	my %providers;
-	my %version;
+	my $facts = Dpkg::Deps::KnownFacts->new();
 	local $/ = '';
 	open(STATUS, "<$status") || die "$status: $!\n";
 	while (<STATUS>) {
 		next unless /^Status: .*ok installed$/m;
 	
 		my ($package) = /^Package: (.*)$/m;
-		push @{$providers{$package}}, $package;
-		($version{$package}) = /^Version: (.*)$/m;
+		my ($version) = /^Version: (.*)$/m;
+		$facts->add_installed_package($package, $version);
 	
 		if (/^Provides: (.*)$/m) {
-			foreach (split(/,\s*/, $1)) {
-				push @{$providers{$_}}, $package;
+			my $provides = Dpkg::Deps::parse($1,
+			    reduce_arch	=> 1, union => 1);
+			next if not defined $provides;
+			foreach (grep { $_->isa('Dpkg::Deps::Simple') }
+				 $provides->get_deps())
+			{
+				$facts->add_provided_package($_->{package},
+				    $_->{relation}, $_->{version},
+				    $package);
 			}
 		}
 	}
 	close STATUS;
 
-	return \%version, \%providers;
+	return $facts;
 }
 
 # This function checks the build dependencies passed in as the first
@@ -142,8 +147,7 @@ sub check_line {
 	my $build_depends=shift;
 	my $fieldname=shift;
 	my $dep_list=shift;
-	my %version=%{shift()};
-	my %providers=%{shift()};
+	my $facts=shift;
 	my $host_arch = shift || get_host_arch();
 	chomp $host_arch;
 
@@ -154,48 +158,20 @@ sub check_line {
 	                   $fieldname));
 	}
 
-	foreach my $dep_and (@$dep_list) {
-		my $ok=0;
-		my @possibles=();
-ALTERNATE:	foreach my $alternate (@$dep_and) {
-			my ($package, $relation, $version, $arch_list)= @{$alternate};
-
-			# This is a possibile way to meet the dependency.
-			# Remove the arch stuff from $alternate.
-			push @possibles, $package . ($relation && $version ? " ($relation $version)" : '');
-	
-			if ($relation && $version) {
-				if (! exists $version{$package}) {
-					# Not installed at all, so fail.
-					next;
-				}
-				else {
-					# Compare installed and needed
-					# version number.
-					system("dpkg", "--compare-versions",
-						$version{$package}, $relation,
-						 $version);
-					if (($? >> 8) != 0) {
-						next; # fail
-					}
-				}
-			}
-			elsif (! defined $providers{$package}) {
-				# It's not a versioned dependency, and
-				# nothing provides it, so fail.
-				next;
-			}
-			# If we get to here, the dependency was met.
-			$ok=1;
+	if ($build_depends) {
+		$dep_list->simplify_deps($facts);
+		if ($dep_list->is_empty()) {
+			return ();
+		} else {
+			return $dep_list->get_deps();
 		}
-	
-		if (@possibles && (($build_depends && ! $ok) ||
-		                   (! $build_depends && $ok))) {
-			# TODO: this could return a more complex
-			# data structure instead to save re-parsing.
-			push @unmet, join (" | ", @possibles);
+	} else { # Build-Conflicts
+		my @conflicts = ();
+		foreach my $dep ($dep_list->get_deps()) {
+			if ($dep->get_evaluation($facts)) {
+				push @conflicts, $dep;
+			}
 		}
+		return @conflicts;
 	}
-
-	return @unmet;
 }
-- 
1.5.3.4

>From 1f64be2a6bc8bd8c221260e72df1577d9a79924d Mon Sep 17 00:00:00 2001
From: Raphael Hertzog <hertzog@debian.org>
Date: Mon, 15 Oct 2007 15:03:26 +0200
Subject: [PATCH] controllib.pl: Remove obsolete and unused functions (parsedep and showdep).

---
 scripts/controllib.pl |   69 -------------------------------------------------
 1 files changed, 0 insertions(+), 69 deletions(-)

diff --git a/scripts/controllib.pl b/scripts/controllib.pl
index 5cd55eb..032b9aa 100755
--- a/scripts/controllib.pl
+++ b/scripts/controllib.pl
@@ -213,75 +213,6 @@ sub parsesubstvars {
     }
 }
 
-sub parsedep {
-    my ($dep_line, $use_arch, $reduce_arch) = @_;
-    my @dep_list;
-    my $host_arch = get_host_arch();
-
-    foreach my $dep_and (split(/,\s*/m, $dep_line)) {
-        my @or_list = ();
-ALTERNATE:
-        foreach my $dep_or (split(/\s*\|\s*/m, $dep_and)) {
-            my ($package, $relation, $version);
-            $package = $1 if ($dep_or =~ s/^([a-zA-Z0-9][a-zA-Z0-9+._-]*)\s*//m);
-            ($relation, $version) = ($1, $2)
-		if ($dep_or =~ s/^\(\s*(=|<=|>=|<<?|>>?)\s*([^)]+).*\)\s*//m);
-	    my @arches;
-	    @arches = split(/\s+/m, $1) if ($use_arch && $dep_or =~ s/^\[([^]]+)\]\s*//m);
-            if ($reduce_arch && @arches) {
-
-                my $seen_arch='';
-                foreach my $arch (@arches) {
-                    $arch=lc($arch);
-
-		    if ($arch =~ /^!/) {
-			my $not_arch;
-			($not_arch = $arch) =~ s/^!//;
-
-			if (debarch_is($host_arch, $not_arch)) {
-			    next ALTERNATE;
-			} else {
-			    # This is equivilant to
-			    # having seen the current arch,
-			    # unless the current arch
-			    # is also listed..
-			    $seen_arch=1;
-			}
-		    } elsif (debarch_is($host_arch, $arch)) {
-			$seen_arch=1;
-			next;
-		    }
-
-                }
-                if (! $seen_arch) {
-                    next;
-                }
-            }
-            if (length($dep_or)) {
-		warning(sprintf(_g("can't parse dependency %s"), $dep_and));
-		return undef;
-	    }
-	    push @or_list, [ $package, $relation, $version, \@arches ];
-        }
-        push @dep_list, \@or_list;
-    }
-    \@dep_list;
-}
-
-sub showdep {
-    my ($dep_list, $show_arch) = @_;
-    my @and_list;
-    foreach my $dep_and (@$dep_list) {
-        my @or_list = ();
-        foreach my $dep_or (@$dep_and) {
-            my ($package, $relation, $version, $arch_list) = @$dep_or; 
-            push @or_list, $package . ($relation && $version ? " ($relation $version)" : '') . ($show_arch && @$arch_list ? " [@$arch_list]" : '');
-        }
-        push @and_list, join(' | ', @or_list);
-    }
-    join(', ', @and_list);
-}
-
 sub parsechangelog {
     my ($changelogfile, $changelogformat, $since) = @_;
 
-- 
1.5.3.4

>From fbb5738a9a6cf9f30e2b84bf4479ee12e5768d39 Mon Sep 17 00:00:00 2001
From: Raphael Hertzog <hertzog@debian.org>
Date: Mon, 15 Oct 2007 15:38:19 +0200
Subject: [PATCH] Update changelog entries concerning the integration of Dpkg::Deps.

---
 ChangeLog        |    4 ++++
 debian/changelog |    6 ++++++
 2 files changed, 10 insertions(+), 0 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 469b248..ccaf0b9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,10 @@
 	exports ".." as $pkgdatadir during tests).
 	* scripts/dpkg-shlibdeps.pl: bugfix, avoid unwanted modification
 	of @pkg_shlibs by my_find_library.
+	* scripts/dpkg-{checkbuilddeps.pl,gencontrol.pl,source.pl}:
+	adapted to use the new Dpkg::Deps module. dpkg-gencontrol gains
+	new features such as automatic simplification of dependencies.
+	* scripts/controllib.pl: remove unused parsedep() and showdep().
 
 2007-10-14  Raphael Hertzog  <hertzog@debian.org>
 
diff --git a/debian/changelog b/debian/changelog
index d802814..b27abe9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -29,6 +29,12 @@ dpkg (1.14.8) UNRELEASED; urgency=low
     * If dpkg-shlibdeps doesn't find any dependency information for a
       shared library that is actively used, then it will fail. This can be
       disabled with the option --ignore-missing-info. Closes: #10807
+  * Switch perl programs to use the new Dpkg/Deps module. This changes the
+    behaviour of dpkg-gencontrol which will rewrite and simplify dependencies
+    as possible.
+    Multiple dependencies on the same package are replaced by their
+    intersection. Closes: #178203, #186809, #222652
+    This applies to dependencies as well as build dependencies.
 
   [ Frank Lichtenheld ]
   * Add $(MAKE) check to build target
-- 
1.5.3.4


Reply to: