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 16     16   1001051 use strict;
  16         50  
  16         480  
4 16     16   125 use warnings;
  16         33  
  16         708  
5              
6             our $VERSION = '0.96';
7              
8 16     16   4832 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             (?: # "restriction formulas" (build profile) is optional
319             \s* # optional space
320             <
321             (?:
322             !? # negation is optional
323             [^\s>]+ # build profile name
324             (?:\s+|(?=>) ) # whitespace or end
325             )+
326             >
327             )* # can appear several times
328             $}x # done
329             )
330             {
331             return $class->new(
332             { pkg => $1,
333             ( ( defined($2) and defined($3) )
334             ? ( rel => $2, ver => $3 )
335             : ()
336             )
337             }
338             );
339             }
340             else {
341             die "Unable to parse '$str'";
342             }
343             }
344              
345             1;
346              
347             =back
348              
349             =head2 FIELDS
350              
351             =over
352              
353             =item pkg
354              
355             Contains the name of the package that is depended upon
356              
357             =item rel
358              
359             Contains the relation of the dependency. May be any of '<<', '<=', '=', '>='
360             or '>>'. Default is '>='.
361              
362             =item ver
363              
364             Contains the version of the package the dependency is about. The value is an
365             instance of L class. If you set it to a scalar value, that is
366             given to L->new().
367              
368             =back
369              
370             C and C are either both present or both missing.
371              
372             Examples
373              
374             print $dep->pkg;
375             $dep->ver('3.4');
376              
377             =head1 METHODS
378              
379             =over
380              
381             =item satisfies($dep)
382              
383             Returns true if I<$dep> states a dependency that is already covered by this
384             instance. In other words, if this method returns true, any package satisfying
385             the dependency of this instance will also satisfy I<$dep> ($dep is redundant in
386             dependency lists where this instance is already present).
387              
388             I<$dep> can be either an instance of the L class, or a
389             plain string.
390              
391             my $dep = Debian::Dependency->new('foo (>= 2)');
392             print $dep->satisfies('foo') ? 'yes' : 'no'; # no
393             print $dep->satisfies('bar') ? 'yes' : 'no'; # no
394             print $dep->satisfies('foo (>= 2.1)') ? 'yes' : 'no'; # yes
395              
396             =cut
397              
398             sub satisfies {
399             my( $self, $dep ) = @_;
400              
401             $dep = Debian::Dependency->new($dep)
402             unless ref($dep);
403              
404             # we have alternatives? then we satisfy the dependency only if
405             # all of the alternatives satisfy it
406             if( $self->alternatives ) {
407             for( @{ $self->alternatives } ) {
408             return 0 unless $_->satisfies($dep);
409             }
410              
411             return 1;
412             }
413              
414             # $dep has alternatives? then we satisfy it if we satisfy any of them
415             if( $dep->alternatives ) {
416             for( @{ $dep->alternatives } ) {
417             return 1 if $self->satisfies($_);
418             }
419              
420             return 0;
421             }
422              
423             # different package?
424             return 0 unless $self->pkg eq $dep->pkg;
425              
426             # $dep has no relation?
427             return 1 unless $dep->rel;
428              
429             # $dep has relation, but we don't?
430             return 0 if not $self->rel;
431              
432             # from this point below both $dep and we have relation (and version)
433             my $cmpver = ( $self->ver <=> $dep->ver );
434              
435             if( $self->rel eq '>>' ) {
436             # >> 4 satisfies also >> 3
437             return 1 if $dep->rel eq '>>'
438             and $cmpver >= 0;
439              
440             # >> 4 satisfies >= 3 and >= 4
441             return 1 if $dep->rel eq '>='
442             and $cmpver >= 0;
443              
444             # >> 4 can't satisfy =, <= and << relations
445             return 0;
446             }
447             elsif( $self->rel eq '>=' ) {
448             # >= 4 satisfies >= 3
449             return 1 if $dep->rel eq '>='
450             and $cmpver >= 0;
451              
452             # >= 4 satisvies >> 3, but not >> 4
453             return 1 if $dep->rel eq '>>'
454             and $cmpver > 0;
455              
456             # >= 4 can't satosfy =, <= and << relations
457             }
458             elsif( $self->rel eq '=' ) {
459             return 1 if $dep->rel eq '='
460             and $cmpver == 0;
461              
462             # = 4 also satisfies >= 3 and >= 4
463             return 1 if $dep->rel eq '>='
464             and $cmpver >= 0;
465              
466             # = 4 satisfies >> 3, but not >> 4
467             return 1 if $dep->rel eq '>>'
468             and $cmpver > 0;
469              
470             # = 4 satisfies <= 4 and <= 5
471             return 1 if $dep->rel eq '<='
472             and $cmpver <= 0;
473              
474             # = 4 satisfies << 5, but not << 4
475             return 1 if $dep->rel eq '<<'
476             and $cmpver < 0;
477              
478             # other cases mean 'no'
479             return 0;
480             }
481             elsif( $self->rel eq '<=' ) {
482             # <= 4 satisfies <= 5
483             return 1 if $dep->rel eq '<='
484             and $cmpver <= 0;
485              
486             # <= 4 satisfies << 5, but not << 4
487             return 1 if $dep->rel eq '<<'
488             and $cmpver < 0;
489              
490             # <= 4 can't satisfy =, >= and >>
491             return 0;
492             }
493             elsif( $self->rel eq '<<' ) {
494             # << 4 satisfies << 5
495             return 1 if $dep->rel eq '<<'
496             and $cmpver <= 0;
497              
498             # << 4 satisfies <= 5 and <= 4
499             return 1 if $dep->rel eq '<='
500             and $cmpver <= 0;
501              
502             # << 4 can't satisfy =, >= and >>
503             return 0;
504             }
505             else {
506             croak "Should not happen: $self satisfies $dep?";
507             }
508             }
509              
510             =back
511              
512             =head1 SEE ALSO
513              
514             L
515              
516             =head1 AUTHOR
517              
518             =over 4
519              
520             =item Damyan Ivanov
521              
522             =back
523              
524             =head1 COPYRIGHT & LICENSE
525              
526             =over 4
527              
528             =item Copyright (C) 2008,2009,2010 Damyan Ivanov
529              
530             =back
531              
532             This program is free software; you can redistribute it and/or modify it under
533             the terms of the GNU General Public License version 2 as published by the Free
534             Software Foundation.
535              
536             This program is distributed in the hope that it will be useful, but WITHOUT ANY
537             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
538             PARTICULAR PURPOSE. See the GNU General Public License for more details.
539              
540             You should have received a copy of the GNU General Public License along with
541             this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
542             Street, Fifth Floor, Boston, MA 02110-1301 USA.
543              
544             =cut