浏览代码

Merge branch 'master' of git://git.debian.org/git/dpkg/dpkg into sourcev3

Joey Hess 18 年之前
父节点
当前提交
3ac223ca9e

+ 16 - 0
ChangeLog

@@ -1,3 +1,19 @@
+2007-10-18  Raphael Hertzog  <hertzog@debian.org>
+
+	* 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.
+	* scripts/dpkg-checkbuilddeps.pl: adapted to use the new
+	Dpkg::Deps module.
+	* scripts/dpkg-source.pl: Likewise.
+	* scripts/dpkg-scanpackages.pl: Likewise.
+	* scripts/dpkg-gencontrol.pl: Likewise. Also gains new features
+	such as automatic simplification of dependencies.
+	* scripts/controllib.pl: remove unused parsedep(), showdep(),
+	@pkg_dep_fields and @src_dep_fields.
+	* man/dpkg-gencontrol.1: document the new behaviour with
+	dependency fields.
+
 2007-10-18  Guillem Jover  <guillem@debian.org>
 
 	* scripts/Dpkg/ErrorHandling.pm (report): New function.

+ 6 - 0
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

+ 7 - 6
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,5 +60,12 @@ 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/Source
+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

+ 21 - 1
man/dpkg-gencontrol.1

@@ -10,7 +10,25 @@ dpkg\-gencontrol \- generate Debian control files
 .B dpkg\-gencontrol
 reads information from an unpacked Debian source tree and generates a
 binary package control file (which defaults to debian/tmp/DEBIAN/control);
-it also adds an entry for the binary package to
+during this process it will simplify the relation fields and rewrite them
+in a sorted manner.
+.sp
+Thus
+.IR Pre-Depends ", " Depends ", " Recommends " and " Suggests
+are simplified in this
+order by removing dependencies which are known to be true according to the
+stronger dependencies already parsed. It will also remove any self-dependency
+(in fact it will remove any dependency which evaluates to true given the
+current version of the package as installed). Logically it keeps the
+intersection of multiple dependencies on the same package.
+.sp
+The other relation fields
+.RI ( Enhances ", " Conflicts ", " Breaks ", " Replaces " and " Provides )
+are also simplified individually by computing the union of the various
+dependencies when a package is listed multiple times in the field.
+.sp
+.B dpkg\-gencontrol
+also adds an entry for the binary package to
 .BR debian/files .
 .
 .SH OPTIONS
@@ -120,6 +138,8 @@ generates here.
 Copyright (C) 1995-1996 Ian Jackson
 .br
 Copyright (C) 2000 Wichert Akkerman
+.br
+Copyright (C) 2007 Rapha\[:e]l Hertzog
 .sp
 This is free software; see the GNU General Public Licence version 2 or later
 for copying conditions. There is NO WARRANTY.

文件差异内容过多而无法显示
+ 1113 - 0
scripts/Dpkg/Deps.pm


+ 5 - 4
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/Source/VCS/git.pm \
 	Dpkg.pm

+ 0 - 75
scripts/controllib.pl

@@ -26,12 +26,6 @@ our %substvar;      # - map with substitution variables
 
 my $parsechangelog = 'dpkg-parsechangelog';
 
-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);
-
-
 sub getfowner
 {
     my $getlogin = getlogin();
@@ -217,75 +211,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(_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) = @_;
 

+ 40 - 64
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;
 
@@ -153,48 +157,20 @@ sub check_line {
 	    error(_g("error occurred while parsing %s"), $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;
 }

+ 37 - 10
scripts/dpkg-gencontrol.pl

@@ -10,6 +10,7 @@ use Dpkg::Gettext;
 use Dpkg::ErrorHandling qw(warning error failure unknown internerr syserr
                            subprocerr usageerr);
 use Dpkg::Arch qw(get_host_arch debarch_eq debarch_is);
+use Dpkg::Deps qw(@pkg_dep_fields %dep_field_type);
 
 push(@INC,$dpkglibdir);
 require 'controllib.pl';
@@ -17,7 +18,6 @@ require 'controllib.pl';
 our %substvar;
 our (%f, %fi);
 our %p2i;
-our @pkg_dep_fields;
 our $sourcepackage;
 
 textdomain("dpkg-dev");
@@ -226,16 +226,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(_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(_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(_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 - 2
scripts/dpkg-scanpackages.pl

@@ -7,13 +7,12 @@ use IO::Handle;
 use IO::File;
 use Dpkg;
 use Dpkg::Gettext;
+use Dpkg::Deps qw(@pkg_dep_fields);
 
 push(@INC,$dpkglibdir);
 require 'controllib.pl';
 textdomain("dpkg-dev");
 
-our @pkg_dep_fields;
-
 my (@samemaint, @changedmaint);
 my %packages;
 my %overridden;

+ 16 - 4
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;
@@ -318,10 +319,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);
-		error(_g("error occurred while parsing %s"), $_)
-		    unless defined $dep;
-		$f{$_}= showdep($dep, 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(_g("error occurred while parsing %s"), $_) unless defined $dep;
+		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) { }

+ 56 - 0
scripts/t/400_Dpkg_Deps.t

@@ -0,0 +1,56 @@
+# -*- mode: cperl;-*-
+
+use Test::More tests => 12;
+
+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 $dep_or1 = Dpkg::Deps::parse("a|b (>=1.0)|c (>= 2.0)");
+my $dep_or2 = Dpkg::Deps::parse("x|y|a|b|c (<= 0.5)|c (>=1.5)|d|e");
+is($dep_or1->implies($dep_or2), 1, "Implication between OR 1/2");
+is($dep_or2->implies($dep_or1), undef, "Implication between OR 2/2");
+
+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");
+
+