Browse Source

scripts: Update restriction formula syntax

 - The restriction list parsing now reflects the changes at
   <https://wiki.debian.org/BuildProfileSpec> which were agreed upon
   in the bootstrap sprint 2014 in Paris.
 - Restriction lists are now restriction formulas.
 - Restriction formulas are given in disjunctive normal form expression:
       pkg <bar baz> <blub>
 - Removal of the implicit prefix/namespace mechanic.
 - Since there can be more than one <> block, the regex in
   parse_string() in Dpkg::Deps is now greedy.
 - Construct the profiles entry of the Packages-List field by converting
   the "<bar baz> <blub>" syntax into "bar,baz+blub".
 - Include a temporary compatibility mapping for the old way to write
   the Build-Profiles field in binary packages which can be removed once
   all affected source packages have moved to the new syntax.
 - Adjust testcases.

Closes: #760158

[guillem@debian.org:
 - Add a warning when using the old syntax in dpkg-source.
 - Move comments before the regexes, not besides them in dpkg-source.
 - Change from an xnor to == in evaluate_restriction_formula().
 - Move the Dpkg::Util import close to the other Dpkg imports.
 - Add test cases for the new parse_build_profiles() behavior. ]

Signed-off-by: Guillem Jover <guillem@debian.org>
Johannes Schauer 12 years ago
parent
commit
98956c26a2

+ 11 - 0
debian/changelog

@@ -72,6 +72,17 @@ dpkg (1.17.14) UNRELEASED; urgency=low
   * Map i786 to i386 also for the multiarch pathname in dpkg-architecture.
   * Map i786 to i386 also for the multiarch pathname in dpkg-architecture.
   * Handle omitted binary packages due to build profiles in dpkg-genchanges.
   * Handle omitted binary packages due to build profiles in dpkg-genchanges.
     Based on a patch by Johannes Schauer <j.schauer@email.de>. Closes: #758191
     Based on a patch by Johannes Schauer <j.schauer@email.de>. Closes: #758191
+  * Update restriction formula syntax for build profiles:
+    - Restriction lists are now restriction formulas.
+    - Restriction formulas are given in disjunctive normal form expression:
+      <foo> <bar baz> <blub>
+    - Removal of the implicit prefix/namespace mechanic.
+    - Construct the profiles entry of the Packages-List field by converting
+      the "<bar baz> <blub>" syntax into "bar,baz+blub".
+    - Include a temporary compatibility mapping with the old way to write
+      the Build-Profiles field in binary packages which can be removed once
+      all affected source packages have moved to the new syntax.
+    Thanks to Johannes Schauer <j.schauer@email.de>. Closes: #760158
 
 
   [ Raphaël Hertzog ]
   [ Raphaël Hertzog ]
   * Explain better in deb-triggers(5) why interest/activate-noawait should be
   * Explain better in deb-triggers(5) why interest/activate-noawait should be

+ 10 - 7
man/deb-src-control.5

@@ -187,8 +187,8 @@ of packages separated by vertical bar (or "pipe") symbols, "|". The
 groups are separated by commas. Commas are to be read as "AND", and pipes
 groups are separated by commas. Commas are to be read as "AND", and pipes
 as "OR", with pipes binding more tightly. Each package name is
 as "OR", with pipes binding more tightly. Each package name is
 optionally followed by a version number specification in parentheses, an
 optionally followed by a version number specification in parentheses, an
-architecture specification in square brackets, and a profile specification
-in angle brackets.
+architecture specification in square brackets, and a restriction formula
+consisting of one or more lists of profile names in angle brackets.
 
 
 The syntax of the
 The syntax of the
 .BR Build\-Conflicts ,
 .BR Build\-Conflicts ,
@@ -198,8 +198,9 @@ and
 fields is a list of comma-separated package names, where the comma is read
 fields is a list of comma-separated package names, where the comma is read
 as an "AND". Specifying alternative packages using a "pipe" is not supported.
 as an "AND". Specifying alternative packages using a "pipe" is not supported.
 Each package name is optionally followed by a version number specification in
 Each package name is optionally followed by a version number specification in
-parentheses, an architecture specification in square brackets, and a profile
-specification in angle brackets.
+parentheses, an architecture specification in square brackets, and a
+restriction formula consisting of one or more lists of profile names in
+angle brackets.
 
 
 A version number may start with a ">>", in which case any later version
 A version number may start with a ">>", in which case any later version
 will match, and may specify or omit the Debian packaging revision (separated
 will match, and may specify or omit the Debian packaging revision (separated
@@ -211,9 +212,11 @@ An architecture specification consists of one or more architecture names,
 separated by whitespace. Exclamation marks may be prepended to each of the
 separated by whitespace. Exclamation marks may be prepended to each of the
 names, meaning "NOT".
 names, meaning "NOT".
 
 
-A profile specification consists of one or more profile names, prefixed
-with the "\fBprofile.\fP" namespace, separated by whitespace. Exclamation
-marks may be prepended to each of the names, meaning "NOT".
+A restriction formula consists of one or more restriction lists, separated
+by whitespace. Each restriction list is enclosed in angle brackets. Items
+in the restriction list are build profile names, separated by whitespace
+and can be prefixed with an exclamation mark, meaning "NOT". A restriction
+formula represents a disjunctive normal form expression.
 
 
 Note that dependencies on packages in the
 Note that dependencies on packages in the
 .B build\-essential
 .B build\-essential

+ 7 - 7
man/dpkg-buildpackage.1

@@ -150,11 +150,11 @@ of \-a or as a complement to override the default GNU system type
 of the target Debian architecture.
 of the target Debian architecture.
 .TP
 .TP
 .BR \-P \fIprofile\fP[ , ...]
 .BR \-P \fIprofile\fP[ , ...]
-Specify the profile(s) we build, as a comma-separated list, without the
-"\fBprofile.\fP" namespace prefix. The default behavior is to build for
-no specific profile. Also adds them (as a space separated list) to the
-\fBDEB_BUILD_PROFILES\fP environment variable which allows, for example,
-\fBdebian/rules\fP files to use this information for conditional builds.
+Specify the profile(s) we build, as a comma-separated list. The default
+behavior is to build for no specific profile. Also sets them (as a space
+separated list) as the \fBDEB_BUILD_PROFILES\fP environment variable which
+allows, for example, \fBdebian/rules\fP files to use this information for
+conditional builds.
 .TP
 .TP
 .BR \-j [\fIjobs\fP|\fBauto\fP]
 .BR \-j [\fIjobs\fP|\fBauto\fP]
 Number of jobs allowed to be run simultaneously, number of jobs matching
 Number of jobs allowed to be run simultaneously, number of jobs matching
@@ -341,8 +341,8 @@ will be ignored.
 .TP
 .TP
 .B DEB_BUILD_PROFILES
 .B DEB_BUILD_PROFILES
 If set, it will be used as the active build profile(s) for the package
 If set, it will be used as the active build profile(s) for the package
-being built. It is a space separated list of profile names, without the
-"\fBprofile.\fP" namespace prefix. Overridden by the \fB\-P\fP option.
+being built. It is a space separated list of profile names. Overridden
+by the \fB\-P\fP option.
 
 
 .SS Reliance on exported environment flags
 .SS Reliance on exported environment flags
 Even if \fBdpkg\-buildpackage\fP exports some variables, \fBdebian/rules\fP
 Even if \fBdpkg\-buildpackage\fP exports some variables, \fBdebian/rules\fP

+ 3 - 4
man/dpkg-checkbuilddeps.1

@@ -64,8 +64,7 @@ the architecture of the current system.
 .BR "\-P " \fIprofile\fP[ , ...]
 .BR "\-P " \fIprofile\fP[ , ...]
 Check build dependencies/conflicts assuming that the package described
 Check build dependencies/conflicts assuming that the package described
 in the control file is to be built for the given build profile(s). The
 in the control file is to be built for the given build profile(s). The
-argument is a comma-separated list of profile names, without the
-"\fBprofile.\fP" namespace prefix.
+argument is a comma-separated list of profile names.
 .TP
 .TP
 .BR \-? ", " \-\-help
 .BR \-? ", " \-\-help
 Show the usage message and exit.
 Show the usage message and exit.
@@ -77,5 +76,5 @@ Show the version and exit.
 .TP
 .TP
 .B DEB_BUILD_PROFILES
 .B DEB_BUILD_PROFILES
 If set, it will be used as the active build profile(s) for the package
 If set, it will be used as the active build profile(s) for the package
-being built. It is a space separated list of profile names, without the
-"\fBprofile.\fP" namespace prefix. Overridden by the \fB\-P\fP option.
+being built. It is a space separated list of profile names. Overridden
+by the \fB\-P\fP option.

+ 25 - 24
scripts/Dpkg/BuildProfiles.pm

@@ -24,6 +24,7 @@ our @EXPORT_OK = qw(get_build_profiles set_build_profiles parse_build_profiles
 
 
 use Exporter qw(import);
 use Exporter qw(import);
 
 
+use Dpkg::Util qw(:list);
 use Dpkg::BuildEnv;
 use Dpkg::BuildEnv;
 
 
 my $cache_profiles;
 my $cache_profiles;
@@ -78,53 +79,53 @@ sub set_build_profiles {
 
 
 =item my @profiles = parse_build_profiles($string)
 =item my @profiles = parse_build_profiles($string)
 
 
-Parses a build profiles specification, into an array.
+Parses a build profiles specification, into an array of array references.
 
 
 =cut
 =cut
 
 
 sub parse_build_profiles {
 sub parse_build_profiles {
     my $string = shift;
     my $string = shift;
 
 
-    return map { lc } split /\s+/, $string;
+    $string =~ s/^\s*<(.*)>\s*$/$1/;
+
+    return map { [ split /\s+/ ] } split />\s+</, $string;
 }
 }
 
 
 =item evaluate_restriction_formula(\@formula, \@profiles)
 =item evaluate_restriction_formula(\@formula, \@profiles)
 
 
-Evaluate whether a restriction list, is true or false, given the array of
-enabled build profiles.
+Evaluate whether a restriction formula of the form "<foo bar> <baz>", given as
+a nested array, is true or false, given the array of enabled build profiles.
 
 
 =cut
 =cut
 
 
 sub evaluate_restriction_formula {
 sub evaluate_restriction_formula {
-    my ($restrictions, $build_profiles) = @_;
+    my ($formula, $profiles) = @_;
 
 
-    my $seen_profile = 0;
-    foreach my $restriction (@{$restrictions}) {
-        # Determine if this restriction is negated, and within the "profile"
-        # namespace, otherwise it does not concern this check.
-        next if $restriction !~ m/^(!)?profile\.(.*)/;
+    # Restriction formulas are in disjunctive normal form:
+    # (foo AND bar) OR (blub AND bla)
+    foreach my $restrlist (@{$formula}) {
+        my $seen_profile = 1;
 
 
-        my $negated = defined $1 && $1 eq '!';
-        my $profile = $2;
+        foreach my $restriction (@$restrlist) {
+            next if $restriction !~ m/^(!)?(.+)/;
 
 
-        # Determine if the restriction matches any of the specified profiles.
-        my $found = any { $_ eq $profile } @{$build_profiles};
+            my $negated = defined $1 && $1 eq '!';
+            my $profile = $2;
+            my $found = any { $_ eq $profile } @{$profiles};
 
 
-        if ($negated) {
-            if ($found) {
+            # If a negative set profile is encountered, stop processing.
+            # If a positive unset profile is encountered, stop processing.
+            if ($found == $negated) {
                 $seen_profile = 0;
                 $seen_profile = 0;
                 last;
                 last;
-            } else {
-                # "!profile.this" includes by default all other profiles
-                # unless they also appear in a "!profile.other".
-                $seen_profile = 1;
             }
             }
-        } elsif ($found) {
-            $seen_profile = 1;
-            last;
         }
         }
+
+        # This conjunction evaluated to true so we don't have to evaluate
+        # the others.
+        return 1 if $seen_profile;
     }
     }
-    return $seen_profile;
+    return 0;
 }
 }
 
 
 =back
 =back

+ 8 - 5
scripts/Dpkg/Deps.pm

@@ -209,15 +209,16 @@ Define the active build profiles. By default no profile is defined.
 =item reduce_profiles (defaults to 0)
 =item reduce_profiles (defaults to 0)
 
 
 If set to 1, ignore dependencies that do not concern the current build
 If set to 1, ignore dependencies that do not concern the current build
-profile. This implicitly strips off the profile restriction list so
+profile. This implicitly strips off the profile restriction formula so
 that the resulting dependencies are directly applicable to the current
 that the resulting dependencies are directly applicable to the current
 profiles.
 profiles.
 
 
 =item reduce_restrictions (defaults to 0)
 =item reduce_restrictions (defaults to 0)
 
 
 If set to 1, ignore dependencies that do not concern the current set of
 If set to 1, ignore dependencies that do not concern the current set of
-restrictions. This implicitly strips off any restriction list so that the
-resulting dependencies are directly applicable to the current restriction.
+restrictions. This implicitly strips off any architecture restriction list
+or restriction formula so that the resulting dependencies are directly
+applicable to the current restriction.
 This currently implies C<reduce_arch> and C<reduce_profiles>, and overrides
 This currently implies C<reduce_arch> and C<reduce_profiles>, and overrides
 them if set.
 them if set.
 
 
@@ -601,7 +602,7 @@ sub parse_string {
               )?                            # end of optional architecture
               )?                            # end of optional architecture
               (?:                           # start of optional restriction
               (?:                           # start of optional restriction
                 \s* <                       # open bracket for restriction
                 \s* <                       # open bracket for restriction
-                \s* (.*?)                   # don't parse restrictions now
+                \s* (.*)                    # do not parse restrictions now
                 \s* >                       # closing bracket
                 \s* >                       # closing bracket
               )?                            # end of optional restriction
               )?                            # end of optional restriction
               \s*$                          # trailing spaces at end
               \s*$                          # trailing spaces at end
@@ -636,7 +637,9 @@ sub output {
 	$res .= ' [' . join(' ', @{$self->{arches}}) . ']';
 	$res .= ' [' . join(' ', @{$self->{arches}}) . ']';
     }
     }
     if (defined($self->{restrictions})) {
     if (defined($self->{restrictions})) {
-	$res .= ' <' . join(' ', @{$self->{restrictions}}) . '>';
+        for my $restrlist (@{$self->{restrictions}}) {
+            $res .= ' <' . join(' ', @{$restrlist}) . '>';
+        }
     }
     }
     if (defined($fh)) {
     if (defined($fh)) {
 	print { $fh } $res;
 	print { $fh } $res;

+ 25 - 2
scripts/dpkg-source.pl

@@ -273,8 +273,31 @@ if ($options{opmode} =~ /^(-b|--print-format|--(before|after)-build|--commit)$/)
         my $pkg_summary = sprintf('%s %s %s %s', $p, $type, $sect, $prio);
         my $pkg_summary = sprintf('%s %s %s %s', $p, $type, $sect, $prio);
 
 
         $pkg_summary .= ' arch=' . join ',', split /\s+/, $arch;
         $pkg_summary .= ' arch=' . join ',', split /\s+/, $arch;
-        $pkg_summary .= ' profile=' . join ',', split /\s+/, $profile
-            if defined $profile;
+
+        if (defined $profile) {
+            # If the string does not contain brackets then it is using the
+            # old syntax (glibc, file, dbus and doxygen are affected).
+            # Thus we convert the old syntax to the new one. This conversion
+            # can be dropped once the old syntax is not in the archive anymore.
+            # <http://codesearch.debian.net/search?q=Build-Profiles%3A\s%2B[^<]+path%3Adebian%2Fcontrol>
+            if ($profile !~ m/^\s*<.*>\s*$/) {
+                # Issue an ephemereal non-translatable warning.
+                warning('binary package stanza %s is using an obsolete ' .
+                        'Build-Profiles syntax', $p);
+                $profile =~ s/([!a-z0-9]+)/<$1>/g;
+            }
+
+            # Instead of splitting twice and then joining twice, we just do
+            # simple string replacements:
+
+            # Remove the enclosing <>
+            $profile =~ s/^\s*<(.*)>\s*$/$1/;
+            # Join lists with a plus (OR)
+            $profile =~ s/>\s+</+/g;
+            # Join their elements with a comma (AND)
+            $profile =~ s/\s+/,/g;
+            $pkg_summary .= " profile=$profile";
+        }
 
 
         push @pkglist, $pkg_summary;
         push @pkglist, $pkg_summary;
 	push @binarypackages, $p;
 	push @binarypackages, $p;

+ 21 - 4
scripts/t/Dpkg_BuildProfiles.t

@@ -16,7 +16,7 @@
 use strict;
 use strict;
 use warnings;
 use warnings;
 
 
-use Test::More tests => 2;
+use Test::More tests => 6;
 
 
 BEGIN {
 BEGIN {
     use_ok('Dpkg::BuildProfiles', qw(parse_build_profiles));
     use_ok('Dpkg::BuildProfiles', qw(parse_build_profiles));
@@ -24,9 +24,26 @@ BEGIN {
 
 
 # TODO: Add actual test cases.
 # TODO: Add actual test cases.
 
 
-my @build_profiles = qw(nocheck nodoc stage1);
+my $formula;
 
 
-is(parse_build_profiles('nocheck nodoc stage1'), @build_profiles,
-   'parse build profiles');
+$formula = [ ];
+is_deeply([ parse_build_profiles('') ], $formula,
+    'parse build profiles formula empty');
+
+$formula = [ [ qw(nocheck) ] ];
+is_deeply([ parse_build_profiles('<nocheck>') ], $formula,
+    'parse build profiles formula single');
+
+$formula = [ [ qw(nocheck nodoc stage1) ] ];
+is_deeply([ parse_build_profiles('<nocheck nodoc stage1>') ], $formula,
+    'parse build profiles formula AND');
+
+$formula = [ [ qw(nocheck) ], [ qw(nodoc) ] ];
+is_deeply([ parse_build_profiles('<nocheck> <nodoc>') ], $formula,
+    'parse build profiles formula OR');
+
+$formula = [ [ qw(nocheck nodoc) ], [ qw(stage1) ] ];
+is_deeply([ parse_build_profiles('<nocheck nodoc> <stage1>') ], $formula,
+    'parse build profiles formula AND, OR');
 
 
 1;
 1;

+ 29 - 21
scripts/t/Dpkg_Deps.t

@@ -70,20 +70,26 @@ is($dep_i386->output(), 'libc6 (>= 2.5)', 'Arch reduce 1/3');
 is($dep_alpha->output(), 'libc6.1', 'Arch reduce 2/3');
 is($dep_alpha->output(), 'libc6.1', 'Arch reduce 2/3');
 is($dep_hurd->output(), 'libc0.1', 'Arch reduce 3/3');
 is($dep_hurd->output(), 'libc0.1', 'Arch reduce 3/3');
 
 
-my $field_profile = 'dep1 <!profile.stage1 !profile.notest>, ' .
-'dep2 <profile.stage1 !profile.notest>, ' .
-'dep3 <profile.notest !profile.stage1>, ' .
-'dep4 <profile.stage1 profile.notest>, ' .
-'dep5 <profile.stage1>, dep6 <!profile.stage1>, ' .
-'dep7 <profile.stage1> | dep8 <profile.notest>';
+my $field_profile = 'dep1 <!stage1 !notest>, ' .
+'dep2 <stage1 !notest>, ' .
+'dep3 <notest !stage1>, ' .
+'dep4 <stage1 notest>, ' .
+'dep5 <stage1>, dep6 <!stage1>, ' .
+'dep7 <stage1> | dep8 <notest>, ' .
+'dep9 <!stage1> <!notest>, ' .
+'dep10 <stage1> <!notest>, ' .
+'dep11 <stage1> <notest>, '.
+'dep12 <!notest> <!stage1>, ' .
+'dep13 <notest> <!stage1>, ' .
+'dep14 <notest> <stage1>';
 my $dep_noprof = deps_parse($field_profile, reduce_profiles => 1, build_profiles => []);
 my $dep_noprof = deps_parse($field_profile, reduce_profiles => 1, build_profiles => []);
 my $dep_stage1 = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['stage1']);
 my $dep_stage1 = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['stage1']);
 my $dep_notest = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['notest']);
 my $dep_notest = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['notest']);
 my $dep_stage1notest = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['stage1', 'notest']);
 my $dep_stage1notest = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['stage1', 'notest']);
-is($dep_noprof->output(), 'dep1, dep2, dep3, dep6', 'Profile reduce 1/4');
-is($dep_stage1->output(), 'dep2, dep4, dep5, dep7', 'Profile reduce 2/4');
-is($dep_notest->output(), 'dep3, dep4, dep6, dep8', 'Profile reduce 3/4');
-is($dep_stage1notest->output(), 'dep2, dep3, dep4, dep5, dep7 | dep8', 'Profile reduce 4/4');
+is($dep_noprof->output(), 'dep1, dep6, dep9, dep10, dep12, dep13', 'Profile reduce 1/4');
+is($dep_stage1->output(), 'dep2, dep5, dep7, dep9, dep10, dep11, dep12, dep14', 'Profile reduce 2/4');
+is($dep_notest->output(), 'dep3, dep6, dep8, dep9, dep11, dep12, dep13, dep14', 'Profile reduce 3/4');
+is($dep_stage1notest->output(), 'dep4, dep5, dep7 | dep8, dep10, dep11, dep13, dep14', 'Profile reduce 4/4');
 
 
 $dep_noprof = deps_parse($field_profile);
 $dep_noprof = deps_parse($field_profile);
 $dep_noprof->reduce_profiles([]);
 $dep_noprof->reduce_profiles([]);
@@ -93,21 +99,23 @@ $dep_notest = deps_parse($field_profile);
 $dep_notest->reduce_profiles(['notest']);
 $dep_notest->reduce_profiles(['notest']);
 $dep_stage1notest = deps_parse($field_profile);
 $dep_stage1notest = deps_parse($field_profile);
 $dep_stage1notest->reduce_profiles(['stage1', 'notest']);
 $dep_stage1notest->reduce_profiles(['stage1', 'notest']);
-is($dep_noprof->output(), 'dep1, dep2, dep3, dep6', 'Profile post-reduce 1/4');
-is($dep_stage1->output(), 'dep2, dep4, dep5, dep7', 'Profile post-reduce 2/4');
-is($dep_notest->output(), 'dep3, dep4, dep6, dep8', 'Profile post-reduce 3/4');
-is($dep_stage1notest->output(), 'dep2, dep3, dep4, dep5, dep7 | dep8', 'Profile post-reduce 4/4');
-
-my $field_restrict = 'dep1 <!profile.bootstrap !other.restrict>, ' .
-'dep2 <profile.bootstrap other.restrict>, ' .
-'dep3 <!other.restrict>, ' .
-'dep4 <other.restrict>';
+is($dep_noprof->output(), 'dep1, dep6, dep9, dep10, dep12, dep13', 'Profile post-reduce 1/4');
+is($dep_stage1->output(), 'dep2, dep5, dep7, dep9, dep10, dep11, dep12, dep14', 'Profile post-reduce 2/4');
+is($dep_notest->output(), 'dep3, dep6, dep8, dep9, dep11, dep12, dep13, dep14', 'Profile post-reduce 3/4');
+is($dep_stage1notest->output(), 'dep4, dep5, dep7 | dep8, dep10, dep11, dep13, dep14', 'Profile post-reduce 4/4');
+
+my $field_restrict = 'dep1 <!bootstrap !restrict>, ' .
+'dep2 <bootstrap restrict>, ' .
+'dep3 <!restrict>, ' .
+'dep4 <restrict>, ' .
+'dep5 <!bootstrap> <!restrict>, ' .
+'dep6 <bootstrap> <restrict>';
 my $dep_restrict = deps_parse($field_restrict, reduce_restrictions => 1, build_profiles => []);
 my $dep_restrict = deps_parse($field_restrict, reduce_restrictions => 1, build_profiles => []);
-is($dep_restrict->output(), 'dep1', 'Unknown restrictions reduce');
+is($dep_restrict->output(), 'dep1, dep3, dep5', 'Unknown restrictions reduce');
 
 
 $dep_restrict = deps_parse($field_restrict);
 $dep_restrict = deps_parse($field_restrict);
 $dep_restrict->reduce_profiles([]);
 $dep_restrict->reduce_profiles([]);
-is($dep_restrict->output(), 'dep1', 'Unknown restrictions post-reduce');
+is($dep_restrict->output(), 'dep1, dep3, dep5', 'Unknown restrictions post-reduce');
 
 
 my $facts = Dpkg::Deps::KnownFacts->new();
 my $facts = Dpkg::Deps::KnownFacts->new();
 $facts->add_installed_package('mypackage', '1.3.4-1', get_host_arch(), 'no');
 $facts->add_installed_package('mypackage', '1.3.4-1', get_host_arch(), 'no');