File Coverage

blib/lib/Config/Model/Dpkg/Dependency.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Config::Model::Dpkg::Dependency ;
2              
3 1     1   307312 use 5.10.1;
  1         6  
  1         57  
4              
5 1     1   1213 use Mouse;
  1         35115  
  1         5  
6 1     1   105613 use namespace::autoclean;
  1         26557  
  1         11  
7              
8             # Debian only module
9 1     1   77 use lib '/usr/share/lintian/lib' ;
  1         2  
  1         8  
10 1     1   705 use Lintian::Relation ;
  0            
  0            
11              
12             use DB_File ;
13             use Log::Log4perl qw(get_logger :levels);
14             use Module::CoreList;
15             use version ;
16              
17             use Parse::RecDescent ;
18              
19             use AnyEvent::HTTP ;
20              
21             # available only in debian. Black magic snatched from
22             # /usr/share/doc/libapt-pkg-perl/examples/apt-version
23             use AptPkg::Config '$_config';
24             use AptPkg::System '$_system';
25             use AptPkg::Version;
26             use AptPkg::Cache ;
27              
28             use vars qw/$test_filter/ ;
29             $test_filter = ''; # reserved for tests
30              
31             my $logger = get_logger("Tree::Element::Value::Dependency") ;
32             my $async_log = get_logger("Async::Value::Dependency") ;
33              
34             # initialise the global config object with the default values
35             $_config->init;
36              
37             # determine the appropriate system type
38             $_system = $_config->system;
39              
40             # fetch a versioning system
41             my $vs = $_system->versioning;
42              
43             my $apt_cache = AptPkg::Cache->new ;
44              
45             # end black magic
46              
47             extends qw/Config::Model::Value/ ;
48              
49             # when apply_fix is used ($arg[1]), this grammer will modify inline
50             # the dependency value through the value ref ($arg[2])
51             my $grammar = << 'EOG' ;
52              
53             {
54             my @dep_errors ;
55             my $add_error = sub {
56             my ($err, $txt) = @_ ;
57             push @dep_errors, "$err: '$txt'" ;
58             return ; # to ensure production error
59             } ;
60             }
61              
62             # comment this out when modifying the grammar
63            
64              
65             dependency: { @dep_errors = (); }
66              
67             dependency: depend(s /\|/) eofile {
68             $return = [ 1 , @{$item[1]} ] ;
69             }
70             | {
71             push( @dep_errors, "Cannot parse: '$text'" ) unless @dep_errors ;
72             $return = [ 0, @dep_errors ];
73             }
74              
75             depend: pkg_dep | variable
76              
77             # For the allowed stuff after ${foo}, see #702792
78             variable: /\${[\w:\-]+}[\w\.\-~+]*/
79              
80             pkg_dep: pkg_name dep_version(?) arch_restriction(?) {
81             my $dv = $item[2] ;
82             my $ar = $item[3] ;
83             my @ret = ( $item{pkg_name} ) ;
84             if (@$dv and @$ar) { push @ret, @{$dv->[0]}, @{$ar->[0]} ;}
85             elsif (@$dv) { push @ret, @{$dv->[0]} ;}
86             elsif (@$ar) { push @ret, undef, undef, @{$ar->[0]} ;}
87             $return = \@ret ; ;
88             }
89              
90             arch_restriction: '[' osarch(s) ']'
91             {
92             my $mismatch = 0;
93             my $ref = $item[2] ;
94             for (my $i = 0; $i < $#$ref -1 ; $i++ ) {
95             $mismatch ||= ($ref->[$i][0] xor $ref->[$i+1][0]) ;
96             }
97             my @a = map { ($_->[0] || '') . ($_->[1] || '') . $_->[2] } @$ref ;
98             if ($mismatch) {
99             $add_error->("some names are prepended with '!' while others aren't.", "@a") ;
100             }
101             else {
102             $return = \@a ;
103             }
104             }
105              
106             dep_version: '(' oper version ')' { $return = [ $item{oper}, $item{version} ] ;}
107              
108             pkg_name: /[a-z0-9][a-z0-9\+\-\.]+(?=\s|\Z|\(|\[)/
109             | /\S+/ { $add_error->("bad package name", $item[1]) ;}
110              
111             oper: '<<' | '<=' | '=' | '>=' | '>>'
112             | /\S+/ { $add_error->("bad dependency version operator", $item[1]) ;}
113              
114             version: variable | /[\w\.\-~:+]+(?=\s|\)|\Z)/
115             | /\S+/ { $add_error->("bad dependency version", $item[1]) ;}
116              
117             # valid arch are listed by dpkg-architecture -L
118             osarch: not(?) os(?) arch
119             {
120             $return = [ $item[1][0], $item[2][0], $item[3] ];
121             }
122              
123             not: '!'
124              
125             os: /(any|uclibc-linux|linux|kfreebsd|knetbsd|kopensolaris|hurd|darwin|freebsd|netbsd|openbsd|solaris|uclinux)
126             -/x
127             | /\w+/ '-' { $add_error->("bad os in architecture specification", $item[1]) ;}
128              
129             arch: / (any |alpha|amd64 |arm\b |arm64 |armeb |armel |armhf |avr32
130             |hppa |i386 |ia64 |lpia |m32r |m68k |mips\b |mipsel |powerpc
131             |powerpcspe |ppc64 |s390 |s390x |sh3\b |sh3eb |sh4\b |sh4eb |sparc\b |sparc64 |x32 )
132             (?=(\]| ))
133             /x
134             | /\w+/ { $add_error->("bad arch in architecture specification", $item[1]) ;}
135              
136              
137             eofile: /^\Z/
138              
139             EOG
140              
141             my $parser ;
142              
143             sub dep_parser {
144             $parser ||= Parse::RecDescent->new($grammar) ;
145             return $parser ;
146             }
147              
148             # this method may recurse bad:
149             # check_dep -> meta filter -> control maintainer -> create control class
150             # autoread started -> read all fileds -> read dependency -> check_dep ...
151              
152             sub check_value {
153             my $self = shift ;
154             my %args = @_ > 1 ? @_ : (value => $_[0]) ;
155             my $cb = delete $args{callback} || sub {} ;
156             my $my_cb = sub {
157             $self->check_dependency(@_, callback => $cb) ;
158             } ;
159            
160             $args{fix} //= 0;
161             $self->SUPER::check_value(%args, callback => $my_cb) ;
162              
163             }
164              
165             sub check_dependency {
166             my $self = shift;
167             my %args = @_ ;
168              
169             my ($value, $check, $silent, $notify_change, $ok, $callback,$apply_fix)
170             = @args{qw/value check silent notify_change ok callback fix/} ;
171              
172             # value is one dependency, something like "perl ( >= 1.508 )"
173             # or exim | mail-transport-agent or gnumach-dev [hurd-i386]
174              
175             # see http://www.debian.org/doc/debian-policy/ch-relationships.html
176            
177             # to get package list
178             # wget -q -O - 'http://qa.debian.org/cgi-bin/madison.cgi?package=perl-doc&text=on'
179              
180             my @dep_chain ;
181             if (defined $value) {
182             $logger->debug("calling check_depend with Parse::RecDescent with '$value'");
183             my $ret = dep_parser->dependency ( $value ) ;
184             my $ok = shift @$ret ;
185             if ($ok) {
186             @dep_chain = @$ret ;
187             }
188             else {
189             $self->add_error(@$ret) ;
190             }
191             }
192              
193             # check_dependency is always called with a callback. This callback must
194             # must called *after* all asynchronous calls are done (which depends on the
195             # packages listed in the dependency). So use begin and end on this condvar and
196             # nothing else, not send/recv
197             my $pending_check = AnyEvent->condvar ;
198              
199             my $old = $value ;
200              
201             my $check_depend_chain_cb = sub {
202             # blocking with inner async calls
203             $self->check_depend_chain($apply_fix, \@dep_chain, $old ) ;
204             $self->on_check_all_done($apply_fix,\@dep_chain,$old, sub { $callback->(%args) if $callback; });
205             } ;
206            
207             $async_log->debug("begin for ",$self->composite_name, " fix is $apply_fix") if $async_log->is_debug;
208             $pending_check->begin($check_depend_chain_cb) ;
209            
210             foreach my $dep (@dep_chain) {
211             next unless ref($dep) ; # no need to check variables
212             $pending_check->begin ;
213             my $cb = sub {
214             $self->check_or_fix_essential_package($apply_fix, $dep, $old) ; # sync
215             $self->check_or_fix_dep($apply_fix, $dep, $old, sub { $pending_check -> end}) ; # async
216             };
217             $self->check_or_fix_pkg_name($apply_fix, $dep, $old, $cb) ; # async
218             }
219              
220            
221             $async_log->debug("end for ",$self->composite_name) if $async_log->is_debug;
222             $pending_check->end;
223             }
224              
225             # this callback will be launched when all checks are done. this can be at
226             # the 'end' call at this end of this sub if all calls of check_depend are
227             # synchronous (which may be the case if all dependency informations are in cache)
228             # or it can be in one of the call backs
229             sub on_check_all_done {
230             my ($self, $apply_fix, $dep_chain, $old, $next) = @_ ;
231              
232             # "ideal" dependency is always computed, but it does not always change
233             my $new = $self->struct_to_dep(@$dep_chain);
234              
235             if ( $logger->is_debug ) {
236             my $new //= '';
237             $async_log->debug( "in on_check_all_done callback for ",
238             $self->composite_name, " ($new) fix is $apply_fix" )
239             if $async_log->is_debug;
240             no warnings 'uninitialized';
241             $logger->debug( "'$old' done" . ( $apply_fix ? " changed to '$new'" : '' ) );
242             }
243              
244             {
245             no warnings 'uninitialized';
246             $self->_store_fix( $old, $new ) if $apply_fix and $new ne $old;
247             }
248             $next->();
249             }
250              
251             sub check_debhelper_version {
252             my ($self, $apply_fix, $depend) = @_ ;
253             my ( $dep_name, $oper, $dep_v, @archs ) = @$depend ;
254              
255             my $dep_string = $self->struct_to_dep($depend) ;
256             my $lintian_dep = Lintian::Relation->new( $dep_string ) ;
257             $logger->debug("checking '$dep_string' with lintian");
258              
259             # using mode loose because debian-control model can be used alone
260             # and compat is outside of debian-control
261             my $compat = $self->grab_value(mode => 'loose', step => "!Dpkg compat") ;
262             return unless defined $compat ;
263              
264             my $min_dep = Lintian::Relation->new("debhelper ( >= $compat)") ;
265             $logger->debug("checking if ".$lintian_dep->unparse." implies ". $min_dep->unparse);
266            
267             return if $lintian_dep->implies ($min_dep) ;
268            
269             $logger->debug("'$dep_string' does not imply debhelper >= $compat");
270            
271             # $show_rel avoids undef warnings
272             my $show_rel = join(' ', map { $_ || ''} ($oper, $dep_v));
273             if ($apply_fix) {
274             @$depend = ( 'debhelper', '>=', $compat ) ; # notify_change called in check_value
275             $logger->info("fixed debhelper dependency from "
276             ."$dep_name $show_rel -> ".$min_dep->unparse." (for compat $compat)");
277             }
278             else {
279             $self->{nb_of_fixes}++ ;
280             my $msg = "should be (>= $compat) not ($show_rel) because compat is $compat" ;
281             $self->add_warning( $msg );
282             $logger->info("will warn: $msg (fix++)");
283             }
284             }
285              
286             my @deb_releases = qw/etch lenny squeeze wheezy/;
287              
288             my %deb_release_h ;
289             while (@deb_releases) {
290             my $k = pop @deb_releases ;
291             my $regexp = join('|',@deb_releases,$k);
292             $deb_release_h{$k} = qr/$regexp/;
293             }
294              
295             # called in check_versioned_dep and in Parse::RecDescent grammar
296             sub xxget_pkg_versions {
297             my ($self,$cb,$pkg) = @_ ;
298             $logger->debug("get_pkg_versions: called with $pkg");
299              
300             # check if Debian has version older than required version
301             my ($has_info, @dist_version) = $self->get_available_version($pkg) ;
302             # print "\t'$pkg' => '@dist_version',\n";
303              
304             return () unless $has_info ;
305              
306             return @dist_version ;
307             }
308              
309             #
310             # New subroutine "struct_to_dep" extracted - Mon Aug 27 13:45:02 2012.
311             #
312             sub struct_to_dep {
313             my $self = shift ;
314             my @input = @_ ;
315              
316             my $skip = 0 ;
317             my @alternatives ;
318             foreach my $d (@input) {
319             my $line = '';
320             # empty str or ref to empty array are skipped
321             if( ref ($d) and @$d) {
322             $line .= "$d->[0]";
323              
324             # skip test for relations like << or <
325             $skip ++ if defined $d->[1] and $d->[1] =~ /
326             $line .= " ($d->[1] $d->[2])" if defined $d->[2];
327              
328             if (@$d > 3) {
329             $line .= ' ['. join(' ',@$d[3..$#$d]) .']' ;
330             }
331              
332             }
333             elsif (not ref($d) and $d) {
334             $line .= $d ;
335             } ;
336              
337             push @alternatives, $line if $line ;
338             }
339            
340             my $actual_dep = @alternatives ? join (' | ',@alternatives) : undef ;
341              
342             return wantarray ? ($actual_dep, $skip) : $actual_dep ;
343             }
344              
345             # @input contains the alternates dependencies (without '|') of one dependency values
346             # a bit like @input = split /|/, $dependency
347              
348             # will modify @input (array of ref) when applying fix
349             sub check_depend_chain {
350             my ($self, $apply_fix, $input, $old) = @_ ;
351            
352             my ($actual_dep, $skip) = $self->struct_to_dep (@$input);
353             my $ret = 1 ;
354              
355             return 1 unless defined $actual_dep; # may have been cleaned during fix
356             $logger->debug("called with $actual_dep with apply_fix $apply_fix");
357              
358             if ($skip) {
359             $logger->debug("skipping '$actual_dep': has a < relation ship") ;
360             return $ret ;
361             }
362            
363             $async_log->debug("begin check alternate deps for $actual_dep") ;
364             foreach my $depend (@$input) {
365             if (ref ($depend)) {
366             # is a dependency (not a variable a la ${perl-Depends})
367             my ($dep_name, $oper, $dep_v) = @$depend ;
368             $logger->debug("scanning dependency $dep_name"
369             .(defined $dep_v ? " $dep_v" : ''));
370             if ($dep_name =~ /lib([\w+\-]+)-perl/) {
371             my $pname = $1 ;
372             # AnyEvent condvar is involved in this method, blocks while inner async call are in progress
373             $ret &&= $self->check_perl_lib_dep ($apply_fix, $pname, $actual_dep, $depend,$input);
374             last;
375             }
376             }
377             }
378             $async_log->debug("end check alternate deps for $actual_dep") ;
379            
380             if ($logger->is_debug and $apply_fix) {
381             my $str = $self->struct_to_dep(@$input) ;
382             $str //= '' ;
383             $logger->debug("new dependency is $str");
384             }
385            
386             return $ret ;
387             }
388              
389             # called through check_depend_chain
390             # does modify $input when applying fix
391             sub check_perl_lib_dep {
392             my ($self, $apply_fix, $pname, $actual_dep, $depend, $input) = @_;
393             $logger->debug("called with $actual_dep with apply_fix $apply_fix");
394              
395             my ( $dep_name, $oper, $dep_v ) = @$depend;
396             my $ret = 1;
397              
398             $pname =~ s/-/::/g;
399              
400             # The dependency should be in the form perl (>= 5.10.1) | libtest-simple-perl (>= 0.88)".
401             # cf http://pkg-perl.alioth.debian.org/policy.html#debian_control_handling
402             # If the Perl version is not available in sid, the order of the dependency should be reversed
403             # libcpan-meta-perl | perl (>= 5.13.10)
404             # because buildd will use the first available alternative
405              
406             # check for dual life module, module name follows debian convention...
407             my @dep_name_as_perl = Module::CoreList->find_modules(qr/^$pname$/i) ;
408             return $ret unless @dep_name_as_perl;
409              
410             return $ret if defined $dep_v && $dep_v =~ m/^\$/ ;
411              
412             # here we have async consecutive calls to get_available_version, check_versioned_dep
413             # and get_available_version. Must block and return once they are done
414             # hence the condvar
415             my $perl_dep_cv = AnyEvent->condvar ;
416            
417             my @ideal_perl_dep = qw/perl/ ;
418             my @ideal_lib_dep ;
419             my @ideal_dep_chain = (\@ideal_perl_dep);
420              
421             my ($on_get_lib_version, $on_perl_check_done, $check_perl_lib, $get_perl_versions, $on_get_perl_versions) ;
422              
423             my ($v_normal) ;
424              
425             # check version for the first available version in Debian: debian
426             # dep may have no version specified but older versions can be found
427             # in CPAN that were never packaged in Debian
428             $on_get_lib_version = sub {
429             $async_log->debug("on_get_lib_version called with @_") ;
430             # get_available_version returns oldest first, like (etch,1.2,...)
431             my $oldest_lib_version_in_debian = $_[1] ;
432             # lob off debian release number
433             $oldest_lib_version_in_debian =~ s/-.*//;
434             my $check_v = $dep_v || $oldest_lib_version_in_debian ;
435             $logger->debug("dual life $dep_name has oldest debian $oldest_lib_version_in_debian, using $check_v");
436             my ($cpan_dep_v, $epoch_dep_v) ;
437              
438             ($cpan_dep_v, $epoch_dep_v) = reverse split /:/ ,$check_v if defined $check_v ;
439             my $v_decimal = Module::CoreList->first_release(
440             $dep_name_as_perl[0],
441             version->parse( $cpan_dep_v )
442             );
443              
444             if (defined $v_decimal) {
445             $v_normal = version->new($v_decimal)->normal;
446             $v_normal =~ s/^v//; # loose the v prefix
447             if ( $logger->is_debug ) {
448             my $dep_str = $dep_name . ( defined $check_v ? ' ' . $check_v : '' );
449             $logger->debug("dual life $dep_str aka $dep_name_as_perl[0] found in Perl core $v_normal");
450             }
451             $self->check_versioned_dep( $on_perl_check_done , ['perl', '>=', $v_normal] );
452             }
453             else {
454             # no need to check further. Call send to unblock wait done with recv
455             AnyEvent::postpone { $perl_dep_cv->send };
456             }
457             };
458              
459            
460             $on_perl_check_done = sub {
461             my $has_older_perl = shift ;
462             $async_log->debug("on_perl_check_done called") ;
463             push @ideal_perl_dep, '>=', $v_normal if $has_older_perl;
464             $check_perl_lib->($has_older_perl) ;
465             } ;
466              
467             $check_perl_lib = sub {
468             my $has_older_perl = shift;
469             $async_log->debug( "check_perl_lib called with dep_v " . ( $dep_v // 'undef' ) );
470              
471             my $on_perl_lib_check_done = sub {
472             my $has_older_lib = shift;
473             $async_log->debug("on_perl_lib_check_done called");
474             if ($has_older_perl) {
475             push @ideal_lib_dep, $dep_name;
476             push @ideal_lib_dep, '>=', $dep_v if $has_older_lib;
477             }
478             $get_perl_versions->();
479             };
480              
481             if ( defined $dep_v ) {
482             $self->check_versioned_dep( $on_perl_lib_check_done, $depend );
483             }
484             else {
485             $on_perl_lib_check_done->(0);
486             }
487             };
488              
489             $get_perl_versions = sub {
490             $self->get_available_version($on_get_perl_versions, 'perl');
491             } ;
492            
493             $on_get_perl_versions = sub {
494             my %perl_version = @_ ;
495             $async_log->debug("running on_get_perl_versions for $actual_dep") ;
496             my $has_older_perl_in_sid = ( $vs->compare( $v_normal, $perl_version{sid} ) < 0 ) ? 1 : 0;
497             $logger->debug(
498             "perl $v_normal is",
499             $has_older_perl_in_sid ? ' ' : ' not ',
500             "older than perl in sid ($perl_version{sid})"
501             );
502              
503             my @ordered_ideal_dep = $has_older_perl_in_sid ?
504             ( \@ideal_perl_dep, \@ideal_lib_dep ) :
505             ( \@ideal_lib_dep, \@ideal_perl_dep ) ;
506             my $ideal_dep = $self->struct_to_dep( @ordered_ideal_dep );
507              
508             if ( $actual_dep ne $ideal_dep ) {
509             if ($apply_fix) {
510             @$input = @ordered_ideal_dep ; # notify_change called in check_value
511             $logger->info("fixed dependency with: $ideal_dep, was @$depend");
512             }
513             else {
514             $self->{nb_of_fixes}++;
515             my $msg = "Dependency of dual life package should be '$ideal_dep' not '$actual_dep'";
516             $self->add_warning ($msg);
517             $logger->info("will warn: $msg (fix++)");
518             }
519             $ret = 0;
520             }
521             $perl_dep_cv->send ;
522             } ;
523              
524             # start the whole async stuff
525             $self->get_available_version($on_get_lib_version, $dep_name);
526              
527              
528             $async_log->debug("waiting for $actual_dep") ;
529             $perl_dep_cv->recv ;
530             $async_log->debug("waiting done for $actual_dep") ;
531             return $ret ;
532             }
533              
534             sub check_versioned_dep {
535             my ($self, $callback ,$dep_info) = @_ ;
536             my ( $pkg, $oper, $vers ) = @$dep_info;
537             $logger->debug("called with '" . $self->struct_to_dep($dep_info) ."'") if $logger->is_debug;
538              
539             # special case to keep lintian happy
540             $callback->(1) if $pkg eq 'debhelper' ;
541              
542             my $cb = sub {
543             my @dist_version = @_ ;
544             $async_log->debug("in check_versioned_dep callback with ". $self->struct_to_dep($dep_info)
545             ." -> @dist_version") if $async_log->is_debug;
546              
547             if ( @dist_version # no older for unknow packages
548             and defined $oper
549             and $oper =~ />/
550             and $vers !~ /^\$/ # a dpkg variable
551             ) {
552             my $src_pkg_name = $self->grab_value("!Dpkg::Control source Source") ;
553            
554             my $filter = $test_filter || $self->grab_value(
555             step => qq{!Dpkg my_config package-dependency-filter:"$src_pkg_name"},
556             mode => 'loose',
557             ) || '';
558             $callback->($self->has_older_version_than ($pkg, $vers, $filter, \@dist_version ));
559             }
560             else {
561             $callback->(1) ;
562             }
563             };
564              
565             # check if Debian has version older than required version
566             $self->get_available_version($cb, $pkg) ;
567              
568             }
569              
570             sub has_older_version_than {
571             my ($self, $pkg, $vers, $filter, $dist_version ) = @_;
572              
573             $logger->debug("using filter $filter") if $filter;
574             my $regexp = $deb_release_h{$filter} ;
575              
576             $logger->debug("using regexp $regexp") if defined $regexp;
577            
578             my @list ;
579             my $has_older = 0;
580             while (@$dist_version) {
581             my ($d,$v) = splice @$dist_version,0,2 ;
582            
583             next if defined $regexp and $d =~ $regexp ;
584              
585             push @list, "$d -> $v;" ;
586            
587             if ($vs->compare($vers,$v) > 0 ) {
588             $has_older = 1 ;
589             }
590             }
591              
592             $logger->debug("$pkg $vers has_older is $has_older (@list)");
593              
594             return 1 if $has_older ;
595             return wantarray ? (0,@list) : 0 ;
596             }
597              
598             #
599             # New subroutine "check_essential_package" extracted - Thu Aug 30 14:14:32 2012.
600             #
601             sub check_or_fix_essential_package {
602             my ( $self, $apply_fix, $dep_info ) = @_;
603             my ( $pkg, $oper, $vers ) = @$dep_info;
604             $logger->debug("called with '", scalar $self->struct_to_dep($dep_info), "' and fix $apply_fix") if $logger->is_debug;
605              
606             # Remove unversioned dependency on essential package (Debian bug 684208)
607             # see /usr/share/doc/libapt-pkg-perl/examples/apt-cache
608              
609             my $cache_item = $apt_cache->get($pkg);
610             my $is_essential = 0;
611             $is_essential++ if (defined $cache_item and $cache_item->get('Flags') =~ /essential/i);
612            
613             if ($is_essential and not defined $oper) {
614             $logger->debug( "found unversioned dependency on essential package: $pkg");
615             if ($apply_fix) {
616             @$dep_info = ();
617             $logger->info("fix: removed unversioned essential dependency on $pkg");
618             }
619             else {
620             my $msg = "unnecessary unversioned dependency on essential package: $pkg";
621             $self->add_warning($msg);
622             $self->{nb_of_fixes}++;
623             $logger->info("will warn: $msg (fix++)");
624             }
625             }
626             }
627              
628              
629             my %pkg_replace = (
630             'perl-module' => 'perl' ,
631             ) ;
632              
633             sub check_or_fix_pkg_name {
634             my ( $self, $apply_fix, $dep_info, $old, $next ) = @_;
635             my ( $pkg, $oper, $vers ) = @$dep_info;
636              
637             $logger->debug("called with '", scalar $self->struct_to_dep($dep_info), "' and fix $apply_fix")
638             if $logger->is_debug;
639              
640             my $new = $pkg_replace{$pkg} ;
641             if ( $new ) {
642             if ($apply_fix) {
643             $logger->info("fix: changed package name from $pkg to $new");
644             $dep_info->[0] = $pkg = $new;
645             }
646             else {
647             my $msg = "dubious package name: $pkg. Preferred package is $new";
648             $self-> add_warning ($msg);
649             $self->{nb_of_fixes}++;
650             $logger->info("will warn: $msg (fix++)");
651             }
652             }
653            
654             # check if this package is defined in current control file
655             if ($self->grab(step => "- - binary:$pkg", qw/mode loose autoadd 0/)) {
656             $logger->debug("dependency $pkg provided in control file") ;
657             $next->() ;
658             }
659             else {
660             my $cb = sub {
661             if ( @_ == 0 ) {
662             # no version found for $pkg
663             # don't know how to distinguish virtual package from source package
664             $logger->debug("unknown package $pkg");
665             $self->add_warning(
666             "package $pkg is unknown. Check for typos if not a virtual package.");
667             }
668             $async_log->debug("callback for check_or_fix_pkg_name -> end for $pkg");
669             $next->( );
670             };
671              
672             # is asynchronous
673             $async_log->debug("begin on $pkg");
674             $self->get_available_version( $cb, $pkg );
675              
676             # if no pkg was found
677             }
678             }
679              
680             # all subs but one there are synchronous
681             sub check_or_fix_dep {
682             my ( $self, $apply_fix, $dep_info, $old, $next ) = @_;
683             my ( $pkg, $oper, $vers, @archs ) = @$dep_info;
684              
685             $logger->debug("called with '", scalar $self->struct_to_dep($dep_info), "' and fix $apply_fix")
686             if $logger->is_debug;
687              
688             if(not defined $pkg) {
689             # pkg may be cleaned up during fix
690             $next->() ;
691             }
692             elsif ( $pkg eq 'debhelper' ) {
693             $self->check_debhelper_version( $apply_fix, $dep_info );
694             $next->() ;
695             }
696             else {
697             my $cb = sub {
698             my ( $vers_dep_ok, @list ) = @_ ;
699             $async_log->debug("callback for check_or_fix_dep with @_") ;
700             $self->warn_or_remove_vers_dep ($apply_fix, $dep_info, \@list) unless $vers_dep_ok ;
701              
702             $async_log->debug("callback for check_or_fix_dep -> end") ;
703             $next->() ;
704             } ;
705              
706             $async_log->debug("begin") ;
707             $self->check_versioned_dep($cb, $dep_info );
708              
709             }
710             }
711              
712             sub warn_or_remove_vers_dep {
713             my ( $self, $apply_fix, $dep_info, $list ) = @_;
714             my ( $pkg, $oper, $vers ) = @$dep_info;
715              
716             if ($apply_fix) {
717             splice @$dep_info, 1, 2; # remove versioned dep, notify_change called in check_value
718             $logger->info("fix: removed versioned dependency from @$dep_info -> $pkg");
719             }
720             else {
721             $self->{nb_of_fixes}++;
722             my $msg = "unnecessary versioned dependency: @$dep_info. Debian has @$list";
723             $self->add_warning( $msg);
724             $logger->info("will warn: $msg (fix++)");
725             }
726             }
727              
728             use vars qw/%cache/ ;
729              
730             # Set up persistence
731             my $cache_file_name = $ENV{HOME}.'/.config_model_depend_cache' ;
732              
733             # this condition is used during tests
734             if (not %cache) {
735             tie %cache => 'DB_File', $cache_file_name,
736             }
737              
738             # required to write data back to DB_File
739             END {
740             untie %cache ;
741             }
742              
743             my %requested ;
744              
745             sub push_cb {
746             my $pkg = shift;
747             my $ref = $requested{$pkg} ||= [] ;
748             push @$ref, @_ ;
749             }
750              
751             sub call_cbs {
752             my $pkg = shift;
753             return unless $requested{$pkg} ;
754             my $ref = delete $requested{$pkg} ;
755             map { $_->(@_) } @$ref ;
756             }
757              
758              
759             # asynchronous method
760             sub get_available_version {
761             my ($self, $callback,$pkg_name) = @_ ;
762              
763             $async_log->debug("called on $pkg_name");
764              
765             my ($time,@res) = split / /, ($cache{$pkg_name} || '');
766             if (defined $time and $time =~ /^\d+$/ and $time + 24 * 60 * 60 * 7 > time) {
767             $async_log->debug("using cached info for $pkg_name");
768             $callback->(@res) ;
769             return;
770             }
771              
772             # package info was requested but info is still not there
773             # this may be called twice for the same package: one for source, one
774             # for binary package
775             if ($requested{$pkg_name}){
776             push_cb($pkg_name,$callback) ;
777             return ;
778             } ;
779              
780             my $url = "http://qa.debian.org/cgi-bin/madison.cgi?package=$pkg_name&text=on" ;
781              
782             push_cb($pkg_name,$callback);
783              
784             say "Connecting to qa.debian.org to check $pkg_name versions. Please wait..." ;
785              
786             my $request;
787             $request = http_request(
788             GET => $url,
789             timeout => 20, # seconds
790             sub {
791             my ($body, $hdr) = @_;
792             $async_log->debug("callback of get_available_version called on $pkg_name");
793             if ($hdr->{Status} =~ /^2/) {
794             my @res ;
795             foreach my $line (split /\n/, $body) {
796             my ($name,$available_v,$dist,$type) = split /\s*\|\s*/, $line ;
797             $type =~ s/\s//g ;
798             push @res , $dist, $available_v unless $type eq 'source';
799             }
800             say "got info for $pkg_name" ;
801             $cache{$pkg_name} = time ." @res" ;
802             call_cbs($pkg_name,@res) ;
803             }
804             else {
805             say "Error for $url: ($hdr->{Status}) $hdr->{Reason}";
806             delete $requested{$pkg_name} ; # trash the callbacks
807             }
808             undef $request;
809             }
810             );
811             }
812              
813             __PACKAGE__->meta->make_immutable;
814              
815             1;
816              
817             =head1 NAME
818              
819             Config::Model::Dpkg::Dependency - Checks Debian dependency declarations
820              
821             =head1 SYNOPSIS
822              
823             use Config::Model ;
824             use Log::Log4perl qw(:easy) ;
825             use Data::Dumper ;
826              
827             Log::Log4perl->easy_init($WARN);
828              
829             # define configuration tree object
830             my $model = Config::Model->new ;
831             $model ->create_config_class (
832             name => "MyClass",
833             element => [
834             Depends => {
835             'type' => 'leaf',
836             'value_type' => 'uniline',
837             class => 'Config::Model::Dpkg::Dependency',
838             },
839             ],
840             ) ;
841              
842             my $inst = $model->instance(root_class_name => 'MyClass' );
843              
844             my $root = $inst->config_root ;
845              
846             $root->load( 'Depends="libc6 ( >= 1.0 )"') ;
847             # Connecting to qa.debian.org to check libc6 versions. Please wait ...
848             # Warning in 'Depends' value 'libc6 ( >= 1.0 )': unnecessary
849             # versioned dependency: >= 1.0. Debian has lenny-security ->
850             # 2.7-18lenny6; lenny -> 2.7-18lenny7; squeeze-security ->
851             # 2.11.2-6+squeeze1; squeeze -> 2.11.2-10; wheezy -> 2.11.2-10; sid
852             # -> 2.11.2-10; sid -> 2.11.2-11;
853              
854             =head1 DESCRIPTION
855              
856             This class is derived from L. Its purpose is to
857             check the value of a Debian package dependency for the following:
858              
859             =over
860              
861             =item *
862              
863             syntax as described in http://www.debian.org/doc/debian-policy/ch-relationships.html
864              
865             =item *
866              
867             Whether the version specified with C<< > >> or C<< >= >> is necessary.
868             This module will check with Debian server whether older versions can be
869             found in Debian old-stable or not. If no older version can be found, a
870             warning will be issued. Note a warning will also be sent if the package
871             is not found on madison and if the package is not virtual.
872              
873             =item *
874              
875             Whether a Perl library is dual life. In this case the dependency is checked according to
876             L.
877             Because Debian auto-build systems (buildd) will use the first available alternative,
878             the dependency should be in the form :
879              
880             =over
881              
882             =item *
883              
884             C<< perl (>= 5.10.1) | libtest-simple-perl (>= 0.88) >> when
885             the required perl version is available in sid. ".
886              
887             =item *
888              
889             C<< libcpan-meta-perl | perl (>= 5.13.10) >> when the Perl version is not available in sid
890              
891             =back
892              
893             =back
894              
895             =head1 Cache
896              
897             Queries to Debian server are cached in C<~/.config_model_depend_cache>
898             for about one month.
899              
900             =head1 BUGS
901              
902             =over
903              
904             =item *
905              
906             Virtual package names are found scanning local apt cache. Hence an unknown package
907             on your system may a virtual package on another system.
908              
909             =item *
910              
911             More advanced checks can probably be implemented. The author is open to
912             new ideas. He's even more open to patches (with tests).
913              
914             =back
915              
916             =head1 AUTHOR
917              
918             Dominique Dumont, ddumont [AT] cpan [DOT] org
919              
920             =head1 SEE ALSO
921              
922             L,
923             L,
924             L,
925             L