summaryrefslogtreecommitdiffstats
path: root/scripts/Dpkg/Deps.pm
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/Dpkg/Deps.pm')
-rw-r--r--scripts/Dpkg/Deps.pm490
1 files changed, 490 insertions, 0 deletions
diff --git a/scripts/Dpkg/Deps.pm b/scripts/Dpkg/Deps.pm
new file mode 100644
index 0000000..5afa384
--- /dev/null
+++ b/scripts/Dpkg/Deps.pm
@@ -0,0 +1,490 @@
+# Copyright © 1998 Richard Braakman
+# Copyright © 1999 Darren Benham
+# Copyright © 2000 Sean 'Shaleh' Perry
+# Copyright © 2004 Frank Lichtenheld
+# Copyright © 2006 Russ Allbery
+# Copyright © 2007-2009 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2008-2009,2012-2014 Guillem Jover <guillem@debian.org>
+#
+# This program is free software; you may 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 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, see <https://www.gnu.org/licenses/>.
+
+package Dpkg::Deps;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::Deps - parse and manipulate dependencies of Debian packages
+
+=head1 DESCRIPTION
+
+The Dpkg::Deps module provides classes implementing various types of
+dependencies.
+
+The most important function is deps_parse(), it turns a dependency line in
+a set of Dpkg::Deps::{Simple,AND,OR,Union} objects depending on the case.
+
+=head1 FUNCTIONS
+
+All the deps_* functions are exported by default.
+
+=over 4
+
+=cut
+
+use strict;
+use warnings;
+use feature qw(current_sub);
+
+our $VERSION = '1.07';
+our @EXPORT = qw(
+ deps_concat
+ deps_parse
+ deps_eval_implication
+ deps_iterate
+ deps_compare
+);
+
+use Carp;
+use Exporter qw(import);
+
+use Dpkg::Version;
+use Dpkg::Arch qw(get_host_arch get_build_arch debarch_to_debtuple);
+use Dpkg::BuildProfiles qw(get_build_profiles);
+use Dpkg::ErrorHandling;
+use Dpkg::Gettext;
+use Dpkg::Deps::Simple;
+use Dpkg::Deps::Union;
+use Dpkg::Deps::AND;
+use Dpkg::Deps::OR;
+use Dpkg::Deps::KnownFacts;
+
+=item deps_eval_implication($rel_p, $v_p, $rel_q, $v_q)
+
+($rel_p, $v_p) and ($rel_q, $v_q) express two dependencies as (relation,
+version). The relation variable can have the following values that are
+exported by Dpkg::Version: REL_EQ, REL_LT, REL_LE, REL_GT, REL_GT.
+
+This functions returns 1 if the "p" dependency implies the "q"
+dependency. It returns 0 if the "p" dependency implies that "q" is
+not satisfied. It returns undef when there's no implication.
+
+The $v_p and $v_q parameter should be Dpkg::Version objects.
+
+=cut
+
+sub deps_eval_implication {
+ my ($rel_p, $v_p, $rel_q, $v_q) = @_;
+
+ # If versions are not valid, we can't decide of any implication
+ return unless defined($v_p) and $v_p->is_valid();
+ return unless defined($v_q) and $v_q->is_valid();
+
+ # 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 REL_EQ) {
+ if ($rel_p eq REL_LT) {
+ return ($v_p <= $v_q) ? 0 : undef;
+ } elsif ($rel_p eq REL_LE) {
+ return ($v_p < $v_q) ? 0 : undef;
+ } elsif ($rel_p eq REL_GT) {
+ return ($v_p >= $v_q) ? 0 : undef;
+ } elsif ($rel_p eq REL_GE) {
+ return ($v_p > $v_q) ? 0 : undef;
+ } elsif ($rel_p eq REL_EQ) {
+ return ($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 REL_LE) {
+ if ($rel_p eq REL_GT) {
+ return ($v_p >= $v_q) ? 0 : undef;
+ } elsif ($rel_p eq REL_GE) {
+ return ($v_p > $v_q) ? 0 : undef;
+ } elsif ($rel_p eq REL_EQ) {
+ return ($v_p <= $v_q) ? 1 : 0;
+ } else { # <<, <=
+ return ($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 REL_LT) {
+ if ($rel_p eq REL_GT or $rel_p eq REL_GE) {
+ return ($v_p >= $v_p) ? 0 : undef;
+ } elsif ($rel_p eq REL_LT) {
+ return ($v_p <= $v_q) ? 1 : undef;
+ } elsif ($rel_p eq REL_EQ) {
+ return ($v_p < $v_q) ? 1 : 0;
+ } else { # <<, <=
+ return ($v_p < $v_q) ? 1 : undef;
+ }
+ }
+
+ # Same logic as above, only inverted.
+ if ($rel_q eq REL_GE) {
+ if ($rel_p eq REL_LT) {
+ return ($v_p <= $v_q) ? 0 : undef;
+ } elsif ($rel_p eq REL_LE) {
+ return ($v_p < $v_q) ? 0 : undef;
+ } elsif ($rel_p eq REL_EQ) {
+ return ($v_p >= $v_q) ? 1 : 0;
+ } else { # >>, >=
+ return ($v_p >= $v_q) ? 1 : undef;
+ }
+ }
+ if ($rel_q eq REL_GT) {
+ if ($rel_p eq REL_LT or $rel_p eq REL_LE) {
+ return ($v_p <= $v_q) ? 0 : undef;
+ } elsif ($rel_p eq REL_GT) {
+ return ($v_p >= $v_q) ? 1 : undef;
+ } elsif ($rel_p eq REL_EQ) {
+ return ($v_p > $v_q) ? 1 : 0;
+ } else {
+ return ($v_p > $v_q) ? 1 : undef;
+ }
+ }
+
+ return;
+}
+
+=item $dep = deps_concat(@dep_list)
+
+This function concatenates multiple dependency lines into a single line,
+joining them with ", " if appropriate, and always returning a valid string.
+
+=cut
+
+sub deps_concat {
+ my (@dep_list) = @_;
+
+ return join ', ', grep { defined } @dep_list;
+}
+
+=item $dep = deps_parse($line, %options)
+
+This function parses the dependency line and returns an object, either a
+Dpkg::Deps::AND or a Dpkg::Deps::Union. Various options can alter the
+behaviour of that function.
+
+=over 4
+
+=item use_arch (defaults to 1)
+
+Take into account the architecture restriction part of the dependencies.
+Set to 0 to completely ignore that information.
+
+=item host_arch (defaults to the current architecture)
+
+Define the host architecture. By default it uses
+Dpkg::Arch::get_host_arch() to identify the proper architecture.
+
+=item build_arch (defaults to the current architecture)
+
+Define the build architecture. By default it uses
+Dpkg::Arch::get_build_arch() to identify the proper architecture.
+
+=item reduce_arch (defaults to 0)
+
+If set to 1, ignore dependencies that do not concern the current host
+architecture. This implicitly strips off the architecture restriction
+list so that the resulting dependencies are directly applicable to the
+current architecture.
+
+=item use_profiles (defaults to 1)
+
+Take into account the profile restriction part of the dependencies. Set
+to 0 to completely ignore that information.
+
+=item build_profiles (defaults to no profile)
+
+Define the active build profiles. By default no profile is defined.
+
+=item reduce_profiles (defaults to 0)
+
+If set to 1, ignore dependencies that do not concern the current build
+profile. This implicitly strips off the profile restriction formula so
+that the resulting dependencies are directly applicable to the current
+profiles.
+
+=item reduce_restrictions (defaults to 0)
+
+If set to 1, ignore dependencies that do not concern the current set of
+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
+them if set.
+
+=item union (defaults to 0)
+
+If set to 1, returns a Dpkg::Deps::Union instead of a Dpkg::Deps::AND. Use
+this when parsing non-dependency fields like Conflicts.
+
+=item virtual (defaults to 0)
+
+If set to 1, allow only virtual package version relations, that is none,
+or "=".
+This should be set whenever working with Provides fields.
+
+=item build_dep (defaults to 0)
+
+If set to 1, allow build-dep only arch qualifiers, that is ":native".
+This should be set whenever working with build-deps.
+
+=item tests_dep (defaults to 0)
+
+If set to 1, allow tests-specific package names in dependencies, that is
+"@" and "@builddeps@" (since dpkg 1.18.7). This should be set whenever
+working with dependency fields from F<debian/tests/control>.
+
+=back
+
+=cut
+
+sub deps_parse {
+ my ($dep_line, %options) = @_;
+
+ # Validate arguments.
+ croak "invalid host_arch $options{host_arch}"
+ if defined $options{host_arch} and not defined debarch_to_debtuple($options{host_arch});
+ croak "invalid build_arch $options{build_arch}"
+ if defined $options{build_arch} and not defined debarch_to_debtuple($options{build_arch});
+
+ $options{use_arch} //= 1;
+ $options{reduce_arch} //= 0;
+ $options{use_profiles} //= 1;
+ $options{reduce_profiles} //= 0;
+ $options{reduce_restrictions} //= 0;
+ $options{union} //= 0;
+ $options{virtual} //= 0;
+ $options{build_dep} //= 0;
+ $options{tests_dep} //= 0;
+
+ if ($options{reduce_restrictions}) {
+ $options{reduce_arch} = 1;
+ $options{reduce_profiles} = 1;
+ }
+ if ($options{reduce_arch}) {
+ $options{host_arch} //= get_host_arch();
+ $options{build_arch} //= get_build_arch();
+ }
+ if ($options{reduce_profiles}) {
+ $options{build_profiles} //= [ get_build_profiles() ];
+ }
+
+ # Options for Dpkg::Deps::Simple.
+ my %deps_options = (
+ host_arch => $options{host_arch},
+ build_arch => $options{build_arch},
+ build_dep => $options{build_dep},
+ tests_dep => $options{tests_dep},
+ );
+
+ # Merge in a single-line
+ $dep_line =~ s/\s*[\r\n]\s*/ /g;
+ # Strip trailing/leading spaces
+ $dep_line =~ s/^\s+//;
+ $dep_line =~ s/\s+$//;
+
+ 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, %deps_options);
+ if (not defined $dep_simple->{package}) {
+ warning(g_("can't parse dependency %s"), $dep_or);
+ return;
+ }
+ if ($options{virtual} && defined $dep_simple->{relation} &&
+ $dep_simple->{relation} ne '=') {
+ warning(g_('virtual dependency contains invalid relation: %s'),
+ $dep_simple->output);
+ return;
+ }
+ $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});
+ }
+ $dep_simple->{restrictions} = undef if not $options{use_profiles};
+ if ($options{reduce_profiles}) {
+ $dep_simple->reduce_profiles($options{build_profiles});
+ next if not $dep_simple->profile_is_concerned($options{build_profiles});
+ }
+ 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;
+ }
+ }
+ my $dep_and;
+ if ($options{union}) {
+ $dep_and = Dpkg::Deps::Union->new();
+ } else {
+ $dep_and = Dpkg::Deps::AND->new();
+ }
+ foreach my $dep (@dep_list) {
+ if ($options{union} and not $dep->isa('Dpkg::Deps::Simple')) {
+ warning(g_('an union dependency can only contain simple dependencies'));
+ return;
+ }
+ $dep_and->add($dep);
+ }
+ return $dep_and;
+}
+
+=item $bool = deps_iterate($deps, $callback_func)
+
+This function visits all elements of the dependency object, calling the
+callback function for each element.
+
+The callback function is expected to return true when everything is fine,
+or false if something went wrong, in which case the iteration will stop.
+
+Return the same value as the callback function.
+
+=cut
+
+sub deps_iterate {
+ my ($deps, $callback_func) = @_;
+
+ my $visitor_func = sub {
+ foreach my $dep (@_) {
+ return unless defined $dep;
+
+ if ($dep->isa('Dpkg::Deps::Simple')) {
+ return unless $callback_func->($dep);
+ } else {
+ return unless __SUB__->($dep->get_deps());
+ }
+ }
+ return 1;
+ };
+
+ return $visitor_func->($deps);
+}
+
+=item deps_compare($a, $b)
+
+Implements a comparison operator between two dependency objects.
+This function is mainly used to implement the sort() method.
+
+=back
+
+=cut
+
+my %relation_ordering = (
+ undef => 0,
+ REL_GE() => 1,
+ REL_GT() => 2,
+ REL_EQ() => 3,
+ REL_LT() => 4,
+ REL_LE() => 5,
+);
+
+sub deps_compare {
+ my ($aref, $bref) = @_;
+
+ my (@as, @bs);
+ deps_iterate($aref, sub { push @as, @_ });
+ deps_iterate($bref, sub { push @bs, @_ });
+
+ while (1) {
+ my ($a, $b) = (shift @as, shift @bs);
+ my $aundef = not defined $a or $a->is_empty();
+ my $bundef = not defined $b or $b->is_empty();
+
+ return 0 if $aundef and $bundef;
+ return -1 if $aundef;
+ return 1 if $bundef;
+
+ my $ar = $a->{relation} // 'undef';
+ my $br = $b->{relation} // 'undef';
+ my $av = $a->{version} // '';
+ my $bv = $b->{version} // '';
+
+ my $res = (($a->{package} cmp $b->{package}) ||
+ ($relation_ordering{$ar} <=> $relation_ordering{$br}) ||
+ ($av cmp $bv));
+ return $res if $res != 0;
+ }
+}
+
+=head1 CLASSES - Dpkg::Deps::*
+
+There are several kind of dependencies. A Dpkg::Deps::Simple dependency
+represents a single dependency statement (it relates to one package only).
+Dpkg::Deps::Multiple dependencies are built on top of this class
+and combine several dependencies in different manners. Dpkg::Deps::AND
+represents the logical "AND" between dependencies while Dpkg::Deps::OR
+represents the logical "OR". Dpkg::Deps::Multiple objects can contain
+Dpkg::Deps::Simple object as well as other Dpkg::Deps::Multiple objects.
+
+In practice, the code is only meant to handle the realistic cases which,
+given Debian's dependencies structure, imply those restrictions: AND can
+contain Simple or OR objects, OR can only contain Simple objects.
+
+Dpkg::Deps::KnownFacts is a special class that is used while evaluating
+dependencies and while trying to simplify them. It represents a set of
+installed packages along with the virtual packages that they might
+provide.
+
+=head1 CHANGES
+
+=head2 Version 1.07 (dpkg 1.20.0)
+
+New option: Add virtual option to Dpkg::Deps::deps_parse().
+
+=head2 Version 1.06 (dpkg 1.18.7; module version bumped on dpkg 1.18.24)
+
+New option: Add tests_dep option to Dpkg::Deps::deps_parse().
+
+=head2 Version 1.05 (dpkg 1.17.14)
+
+New function: Dpkg::Deps::deps_iterate().
+
+=head2 Version 1.04 (dpkg 1.17.10)
+
+New options: Add use_profiles, build_profiles, reduce_profiles and
+reduce_restrictions to Dpkg::Deps::deps_parse().
+
+=head2 Version 1.03 (dpkg 1.17.0)
+
+New option: Add build_arch option to Dpkg::Deps::deps_parse().
+
+=head2 Version 1.02 (dpkg 1.17.0)
+
+New function: Dpkg::Deps::deps_concat()
+
+=head2 Version 1.01 (dpkg 1.16.1)
+
+<Used to document changes to Dpkg::Deps::* modules before they were split.>
+
+=head2 Version 1.00 (dpkg 1.15.6)
+
+Mark the module as public.
+
+=cut
+
+1;