File Coverage

blib/lib/Debian/Dependency.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Debian::Dependency;
2              
3 21     21   841933 use strict;
  21         57  
  21         681  
4 21     21   116 use warnings;
  21         40  
  21         1389  
5              
6             our $VERSION = '0.77';
7              
8 21     21   16177 use AptPkg::Config;
  0            
  0            
9             use Carp;
10             use Dpkg::Version ();
11             use List::MoreUtils qw(mesh);
12              
13             =head1 NAME
14              
15             Debian::Dependency - dependency relationship between Debian packages
16              
17             =head1 SYNOPSIS
18              
19             # simple dependency
20             my $d = Debian::Dependency->new( 'perl' );
21             # also parses a single argument
22             my $d = Debian::Dependency->new('perl (>= 5.10)');
23             # dependency with a version
24             my $d = Debian::Dependency->new( 'perl', '5.10' );
25             # dependency with version and relation
26             my $d = Debian::Dependency->new( 'perl', '>=', '5.10' );
27              
28             print $d->pkg; # 'perl'
29             print $d->ver; # '5.10'
30              
31             # for people who like to type much
32             my $d = Debian::Dependency->new( { pkg => 'perl', ver => '5.10' } );
33              
34             # stringification
35             print "$d" # 'perl (>= 5.10)'
36              
37             # 'adding'
38             $deps = $dep1 + $dep2;
39             $deps = $dep1 + 'foo (>= 1.23)'
40              
41             =cut
42              
43             use base qw(Class::Accessor);
44             __PACKAGE__->mk_accessors(qw( pkg ver rel alternatives ));
45              
46             use Carp;
47              
48             use overload '""' => \&_stringify,
49             '+' => \&_add,
50             '<=>' => \&_compare;
51              
52             =head2 CLASS_METHODS
53              
54             =over 4
55              
56             =item new()
57              
58             Construct a new instance.
59              
60             =item new( { pkg => 'package', rel => '>=', ver => '1.9' } )
61              
62             If a hash reference is passed as an argument, its contents are used to
63             initialize the object.
64              
65             =item new( [ { pkg => 'foo' }, 'bar (<= 3)' ] );
66              
67             In an array reference is passed as an argument, its elements are used for
68             constructing a dependency with alternatives.
69              
70             =item new('foo (= 42)')
71              
72             =item new('foo (= 42) | bar')
73              
74             If a single argument is given, the construction is passed to the C
75             constructor.
76              
77             =item new( 'foo', '1.4' )
78              
79             Two arguments are interpreted as package name and version. The relation is
80             assumed to be '>='.
81              
82             =item new( 'foo', '=', '42' )
83              
84             Three arguments are interpreted as package name, relation and version.
85              
86             =cut
87              
88             sub new {
89             my $class = shift;
90             $class = ref($class) if ref($class);
91              
92             my $self = $class->SUPER::new();
93             my( $pkg, $rel, $ver );
94              
95             if( ref($_[0]) and ref($_[0]) eq 'HASH' ) {
96             $pkg = delete $_[0]->{pkg};
97             $rel = delete $_[0]->{rel} // '>=';
98             $ver = delete $_[0]->{ver};
99             # pass-through the rest
100             while( my($k,$v) = each %{$_[0]} ) {
101             $self->$k($v);
102             }
103             }
104             elsif( ref($_[0]) and ref($_[0]) eq 'ARRAY' ) {
105             $self->alternatives(
106             [ map { $self->new($_) } @{ $_[0] } ],
107             );
108              
109             for( @{ $self->alternatives } ) {
110             croak "Alternatives can't be nested"
111             if $_->alternatives;
112             }
113              
114             return $self;
115             }
116             elsif( @_ == 1 ) {
117             return $class->parse($_[0]);
118             }
119             elsif( @_ == 2 ) {
120             $pkg = shift;
121             $rel = '>=';
122             $ver = shift;
123             }
124             elsif( @_ == 3 ) {
125             ( $pkg, $rel, $ver ) = @_;
126             }
127             else {
128             die "Unsupported number of arguments";
129             }
130              
131             $self->ver($ver);
132              
133             unless( defined( $self->ver ) ) {
134             undef($rel);
135             delete $self->{ver};
136             };
137              
138             if ($rel) {
139             $rel = '<=' if $rel eq '<';
140             $rel = '>=' if $rel eq '>';
141             $self->rel($rel);
142             }
143              
144             croak "pkg is mandatory" unless $pkg or $self->alternatives;
145              
146             $self->pkg($pkg);
147              
148             return $self;
149             }
150              
151             sub _stringify {
152             my $self = shift;
153              
154             if( $self->alternatives ) {
155             return join( ' | ', @{ $self->alternatives } );
156             }
157              
158             return (
159             $self->ver
160             ? $self->pkg . ' (' . $self->rel . ' ' . $self->ver . ')'
161             : $self->pkg
162             );
163             }
164              
165             sub _add {
166             my $left = shift;
167             my $right = shift;
168             my $mode = shift;
169              
170             confess "cannot += Dependency. put Dependencies instance on the left instead" unless defined($mode);
171              
172             return bless( [ $left ], 'Debian::Dependencies' ) + $right;
173             }
174              
175             our %rel_order = (
176             '<<' => -2,
177             '<=' => -1,
178             '=' => 0,
179             '>=' => +1,
180             '>>' => +2,
181             );
182              
183             sub _compare {
184             my( $left, $right ) = @_;
185              
186             if( $left->alternatives ) {
187             if( $right->alternatives ) {
188             my @pairs = mesh(
189             @{ $left->alternatives }, @{ $right->alternatives },
190             );
191              
192             while(@pairs) {
193             my( $l, $r ) = splice @pairs, 0, 2;
194              
195             return -1 unless $l;
196             return 1 unless $r;
197             my $res = _compare( $l, $r );
198             return $res if $res;
199             }
200              
201             return 0;
202             }
203             else {
204             my $res = _compare( $left->alternatives->[0], $right );
205             return $res if $res;
206             return 1;
207             }
208             }
209             else {
210             if( $right->alternatives ) {
211             my $res = _compare( $left, $right->alternatives->[0] );
212             return $res if $res;
213             return -1;
214             }
215             else {
216             # nothing, the code below compares two plain dependencies
217             }
218             }
219              
220             my $res = $left->pkg cmp $right->pkg;
221              
222             return $res if $res != 0;
223              
224             return -1 if not defined( $left->ver ) and defined( $right->ver );
225             return +1 if defined( $left->ver ) and not defined( $right->ver );
226              
227             return 0 unless $left->ver; # both have no version
228              
229             $res = $left->ver <=> $right->ver;
230              
231             return $res if $res != 0;
232              
233             # same versions, compare relations
234             return $rel_order{ $left->rel } <=> $rel_order{ $right->rel };
235             }
236              
237             =item set
238              
239             Overrides the set method from L. Used to convert zero versions
240             (for example I<0> or I<0.000>) to void versions.
241              
242             =cut
243              
244             sub set {
245             my( $self, $field, $value ) = @_;
246              
247             undef($value)
248             if $field eq 'ver'
249             and defined($value)
250             and $value =~ /^0[0.]*$/;
251              
252             $value = Dpkg::Version->new( $value, check => 1 )
253             if $field eq 'ver' and defined($value);
254              
255             $self->SUPER::set( $field, $value );
256             }
257              
258             =item parse()
259              
260             Takes a single string argument and parses it.
261              
262             Examples:
263              
264             =over
265              
266             =item perl
267              
268             =item perl (>= 5.8)
269              
270             =item libversion-perl (<< 3.4)
271              
272             =back
273              
274             =cut
275              
276             sub parse {
277             my ( $class, $str ) = @_;
278              
279             if( $str =~ /\|/ ) {
280             # alternative dependencies
281             return $class->new( {
282             alternatives => [
283             map { $class->new($_) } split( /\s*\|\s*/, $str )
284             ],
285             } );
286             }
287              
288             if ($str =~ m{
289             ^ # start from the beginning
290             \s* # stray space
291             ([^\(\s]+) # package name - no paren, no space
292             \s* # optional space
293             (?: # version is optional
294             \( # opening paren
295             ( # various relations
296             <<
297             | <=
298             | =
299             | >=
300             | >>
301             | <
302             | >
303             )
304             \s* # optional space
305             (.+) # version
306             \) # closing paren
307             )?
308             \s* # optional space
309             (?: # architecture is optional
310             \[
311             (?:
312             !? # negation is optional
313             [^\s\]]+ # architecture name
314             (?:\s+|(?=\])) # whitespace or end
315             )+
316             \]
317             )?
318             $}x # done
319             )
320             {
321             return $class->new(
322             { pkg => $1,
323             ( ( defined($2) and defined($3) )
324             ? ( rel => $2, ver => $3 )
325             : ()
326             )
327             }
328             );
329             }
330             else {
331             die "Unable to parse '$str'";
332             }
333             }
334              
335             1;
336              
337             =back
338              
339             =head2 FIELDS
340              
341             =over
342              
343             =item pkg
344              
345             Contains the name of the package that is depended upon
346              
347             =item rel
348              
349             Contains the relation of the dependency. May be any of '<<', '<=', '=', '>='
350             or '>>'. Default is '>='.
351              
352             =item ver
353              
354             Contains the version of the package the dependency is about. The value is an
355             instance of L class. If you set it to a scalar value, that is
356             given to L->new().
357              
358             =back
359              
360             C and C are either both present or both missing.
361              
362             Examples
363              
364             print $dep->pkg;
365             $dep->ver('3.4');
366              
367             =head1 METHODS
368              
369             =over
370              
371             =item satisfies($dep)
372              
373             Returns true if I<$dep> states a dependency that is already covered by this
374             instance. In other words, if this method returns true, any package satisfying
375             the dependency of this instance will also satisfy I<$dep> ($dep is redundant in
376             dependency lists where this instance is already present).
377              
378             I<$dep> can be either an instance of the L class, or a
379             plain string.
380              
381             my $dep = Debian::Dependency->new('foo (>= 2)');
382             print $dep->satisfies('foo') ? 'yes' : 'no'; # no
383             print $dep->satisfies('bar') ? 'yes' : 'no'; # no
384             print $dep->satisfies('foo (>= 2.1)') ? 'yes' : 'no'; # yes
385              
386             =cut
387              
388             sub satisfies {
389             my( $self, $dep ) = @_;
390              
391             $dep = Debian::Dependency->new($dep)
392             unless ref($dep);
393              
394             # we have alternatives? then we satisfy the dependency only if
395             # all of the alternatives satisfy it
396             if( $self->alternatives ) {
397             for( @{ $self->alternatives } ) {
398             return 0 unless $_->satisfies($dep);
399             }
400              
401             return 1;
402             }
403              
404             # $dep has alternatives? then we satisfy it if we satisfy any of them
405             if( $dep->alternatives ) {
406             for( @{ $dep->alternatives } ) {
407             return 1 if $self->satisfies($_);
408             }
409              
410             return 0;
411             }
412              
413             # different package?
414             return 0 unless $self->pkg eq $dep->pkg;
415              
416             # $dep has no relation?
417             return 1 unless $dep->rel;
418              
419             # $dep has relation, but we don't?
420             return 0 if not $self->rel;
421              
422             # from this point below both $dep and we have relation (and version)
423             my $cmpver = ( $self->ver <=> $dep->ver );
424              
425             if( $self->rel eq '>>' ) {
426             # >> 4 satisfies also >> 3
427             return 1 if $dep->rel eq '>>'
428             and $cmpver >= 0;
429              
430             # >> 4 satisfies >= 3 and >= 4
431             return 1 if $dep->rel eq '>='
432             and $cmpver >= 0;
433              
434             # >> 4 can't satisfy =, <= and << relations
435             return 0;
436             }
437             elsif( $self->rel eq '>=' ) {
438             # >= 4 satisfies >= 3
439             return 1 if $dep->rel eq '>='
440             and $cmpver >= 0;
441              
442             # >= 4 satisvies >> 3, but not >> 4
443             return 1 if $dep->rel eq '>>'
444             and $cmpver > 0;
445              
446             # >= 4 can't satosfy =, <= and << relations
447             }
448             elsif( $self->rel eq '=' ) {
449             return 1 if $dep->rel eq '='
450             and $cmpver == 0;
451              
452             # = 4 also satisfies >= 3 and >= 4
453             return 1 if $dep->rel eq '>='
454             and $cmpver >= 0;
455              
456             # = 4 satisfies >> 3, but not >> 4
457             return 1 if $dep->rel eq '>>'
458             and $cmpver > 0;
459              
460             # = 4 satisfies <= 4 and <= 5
461             return 1 if $dep->rel eq '<='
462             and $cmpver <= 0;
463              
464             # = 4 satisfies << 5, but not << 4
465             return 1 if $dep->rel eq '<<'
466             and $cmpver < 0;
467              
468             # other cases mean 'no'
469             return 0;
470             }
471             elsif( $self->rel eq '<=' ) {
472             # <= 4 satisfies <= 5
473             return 1 if $dep->rel eq '<='
474             and $cmpver <= 0;
475              
476             # <= 4 satisfies << 5, but not << 4
477             return 1 if $dep->rel eq '<<'
478             and $cmpver < 0;
479              
480             # <= 4 can't satisfy =, >= and >>
481             return 0;
482             }
483             elsif( $self->rel eq '<<' ) {
484             # << 4 satisfies << 5
485             return 1 if $dep->rel eq '<<'
486             and $cmpver <= 0;
487              
488             # << 4 satisfies <= 5 and <= 4
489             return 1 if $dep->rel eq '<='
490             and $cmpver <= 0;
491              
492             # << 4 can't satisfy =, >= and >>
493             return 0;
494             }
495             else {
496             croak "Should not happen: $self satisfies $dep?";
497             }
498             }
499              
500             =back
501              
502             =head1 SEE ALSO
503              
504             L
505              
506             =head1 AUTHOR
507              
508             =over 4
509              
510             =item Damyan Ivanov
511              
512             =back
513              
514             =head1 COPYRIGHT & LICENSE
515              
516             =over 4
517              
518             =item Copyright (C) 2008,2009,2010 Damyan Ivanov
519              
520             =back
521              
522             This program is free software; you can redistribute it and/or modify it under
523             the terms of the GNU General Public License version 2 as published by the Free
524             Software Foundation.
525              
526             This program is distributed in the hope that it will be useful, but WITHOUT ANY
527             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
528             PARTICULAR PURPOSE. See the GNU General Public License for more details.
529              
530             You should have received a copy of the GNU General Public License along with
531             this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
532             Street, Fifth Floor, Boston, MA 02110-1301 USA.
533              
534             =cut