File Coverage

blib/lib/Debian/Control/FromCPAN.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Debian::Control::FromCPAN - fill F from unpacked CPAN distribution
4              
5             =head1 SYNOPSIS
6              
7             my $c = Debian::Control::FromCPAN->new();
8             $c->discover_dependencies( { ... } );
9             $c->prune_perl_deps;
10              
11             Debian::Control::FromCPAN inherits from L.
12             =cut
13              
14             package Debian::Control::FromCPAN;
15              
16 2     2   4084175 use strict;
  2         13  
  2         105  
17 2     2   11 use warnings;
  2         2  
  2         187  
18              
19             our $VERSION = '0.77';
20              
21 2     2   77 use Carp qw(croak);
  2         6  
  2         291  
22              
23 2     2   11 use base 'Debian::Control';
  2         4  
  2         1348  
24              
25             use CPAN ();
26             use DhMakePerl::Utils qw( is_core_module find_cpan_module nice_perl_ver split_version_relation apt_cache );
27             use File::Spec qw( catfile );
28             use Module::Depends ();
29              
30             use constant oldstable_perl_version => '5.10.1';
31              
32             =head1 METHODS
33              
34             =over
35              
36             =item discover_dependencies( [ { options hash } ] )
37              
38             Discovers module dependencies and fills the dependency fields in
39             F accordingly.
40              
41             Options:
42              
43             =over
44              
45             =item apt_contents
46              
47             An instance of L to be used when locating to which package
48             a required module belongs.
49              
50             =item dpkg_available
51             An instance of L to be used when checking whether
52             the locally available package is the required version. For example:
53              
54             my $available = DPKG::Parse::Available->new;
55             $available->parse;
56              
57             =item dir
58              
59             The directory where the cpan distribution was unpacked.
60              
61             =item intrusive
62              
63             A flag indicating permission to use L for
64             discovering dependencies in case L fails. Since this requires
65             loading all Perl modules in the distribution (and running their BEGIN blocks
66             (and the BEGIN blocks of their dependencies, recursively), it is recommended to
67             use this only when dealing with trusted sources.
68              
69             =item require_deps
70              
71             If true, causes the method to die if some a package for some dependency cannot
72             be found. Otherwise only a warning is issued.
73              
74             =item verbose
75              
76             =item wnpp_query
77              
78             An instance of L to be used when checking for WNPP bugs of
79             depended upon packages.
80              
81             =back
82              
83             Returns a list of module names for which no suitable Debian packages were
84             found.
85              
86             =cut
87              
88             sub discover_dependencies {
89             my ( $self, $opts ) = @_;
90              
91             $opts //= {};
92             ref($opts) and ref($opts) eq 'HASH'
93             or die 'Usage: $obj->{ [ { opts hash } ] )';
94             my $apt_contents = delete $opts->{apt_contents};
95             my $dpkg_available = delete $opts->{dpkg_available};
96             my $dir = delete $opts->{dir};
97             my $intrusive = delete $opts->{intrusive};
98             my $require_deps = delete $opts->{require_deps};
99             my $verbose = delete $opts->{verbose};
100             my $wnpp_query = delete $opts->{wnpp_query};
101              
102             die "Unsupported option(s) given: " . join( ', ', sort( keys(%$opts) ) )
103             if %$opts;
104              
105             my $src = $self->source;
106             my $bin = $self->binary_tie->Values(0);
107              
108             local @INC = ( $dir, @INC );
109              
110             # try Module::Depends, but if that fails then
111             # fall back to Module::Depends::Intrusive.
112              
113             my $finder = Module::Depends->new->dist_dir($dir);
114             my $deps;
115             do {
116             no warnings;
117             local *STDERR;
118             open( STDERR, ">/dev/null" );
119             $deps = $finder->find_modules;
120             };
121              
122             my $error = $finder->error();
123             if ($error) {
124             if ($verbose) {
125             warn '=' x 70, "\n";
126             warn "Failed to detect dependencies using Module::Depends.\n";
127             warn "The error given was:\n";
128             warn "$error";
129             }
130              
131             if ( $intrusive ) {
132             warn "Trying again with Module::Depends::Intrusive ... \n"
133             if $verbose;
134             require Module::Depends::Intrusive;
135             $finder = Module::Depends::Intrusive->new->dist_dir($dir);
136             do {
137             no warnings;
138             local *STDERR;
139             open( STDERR, ">/dev/null" );
140             $deps = $finder->find_modules;
141             };
142              
143             if ( $finder->error ) {
144             if ($verbose) {
145             warn '=' x 70, "\n";
146             warn
147             "Could not find the "
148             . "dependencies for the requested module.\n";
149             warn "Generated error: " . $finder->error;
150              
151             warn "Please bug the module author to provide a"
152             . " proper META.yml file.\n"
153             . "Automatic find of"
154             . " dependencies failed. You may want to \n"
155             . "retry using the '--[b]depends[i]' options\n"
156             . "or just fill the dependency fields in debian/rules"
157             . " by hand\n";
158              
159             return;
160             }
161             }
162             }
163             else {
164             if ($verbose) {
165             warn "If you understand the security implications, try --intrusive.\n";
166             warn '=' x 70, "\n";
167             }
168             return;
169             }
170             }
171              
172             # run-time
173             my ( $debs, $missing )
174             = $self->find_debs_for_modules( $deps->{requires}, $apt_contents,
175             $verbose, $dpkg_available );
176              
177             if (@$debs) {
178             if ($verbose) {
179             print "\n";
180             print "Needs the following debian packages: "
181             . join( ", ", @$debs ) . "\n";
182             }
183             $bin->Depends->add(@$debs);
184             if ( $bin->Architecture eq 'all' ) {
185             $src->Build_Depends_Indep->add(@$debs);
186             }
187             else {
188             $src->Build_Depends->add(@$debs);
189             }
190             }
191              
192             # build-time
193             my ( $b_debs, $b_missing ) = $self->find_debs_for_modules(
194             { %{ $deps->{build_requires} || {} },
195             %{ $deps->{test_requires} || {} },
196             %{ $deps->{configure_requires} || {} }
197             },
198             $apt_contents,
199             $verbose,
200             $dpkg_available,
201             );
202              
203             if (@$b_debs) {
204             if ($verbose) {
205             print "\n";
206             print "Needs the following debian packages during building: "
207             . join( ", ", @$b_debs ) . "\n";
208             }
209             if ( $self->is_arch_dep ) {
210             $src->Build_Depends->add(@$b_debs);
211             }
212             else {
213             $src->Build_Depends_Indep->add(@$b_debs);
214             }
215             }
216              
217             push @$missing, @$b_missing;
218              
219             if (@$missing) {
220             my ($missing_debs_str);
221             if ($apt_contents) {
222             $missing_debs_str
223             = "Needs the following modules for which there are no debian packages available:\n";
224             for (@$missing) {
225             my $bug
226             = $wnpp_query
227             ? ( $wnpp_query->bugs_for_package($_) )[0]
228             : undef;
229             $missing_debs_str .= " - $_";
230             $missing_debs_str .= " (" . $bug->type_and_number . ')'
231             if $bug;
232             $missing_debs_str .= "\n";
233             }
234             }
235             else {
236             $missing_debs_str = "The following Perl modules are required and not installed in your system:\n";
237             for (@$missing) {
238             my $bug
239             = $wnpp_query
240             ? ( $wnpp_query->bugs_for_package($_) )[0]
241             : undef;
242             $missing_debs_str .= " - $_";
243             $missing_debs_str .= " (" . $bug->type_and_number . ')'
244             if $bug;
245             $missing_debs_str .= "\n";
246             }
247             $missing_debs_str .= <
248             You do not have 'apt-file' currently installed, or have not ran
249             'apt-file update' - If you install it and run 'apt-file update' as
250             root, I will be able to tell you which Debian packages are those
251             modules in (if they are packaged).
252             EOF
253             }
254              
255             if ($require_deps) {
256             die $missing_debs_str;
257             }
258             else {
259             warn $missing_debs_str;
260             }
261              
262             }
263              
264             return @$missing;
265             }
266              
267             =item find_debs_for_modules I[, APT contents[, verbose[, DPKG available]]]
268              
269             Scans the given hash of dependencies ( module => version ) and returns
270             matching Debian package dependency specification (as an instance of
271             L class) and a list of missing modules.
272              
273             Perl core is searched first, then installed packages, then the APT contents.
274              
275             If a DPKG::Parse::Available object is passed, also check the available package version
276              
277             =cut
278              
279             sub find_debs_for_modules {
280              
281             my ( $self, $dep_hash, $apt_contents, $verbose, $dpkg_available ) = @_;
282              
283             my $debs = Debian::Dependencies->new();
284             my $aptpkg_cache = apt_cache();
285              
286             my @missing;
287              
288             while ( my ( $module, $version ) = each %$dep_hash ) {
289              
290             my $ver_rel;
291              
292             ( $ver_rel, $version ) = split_version_relation($version) if $version;
293              
294             $version =~ s/^v// if $version;
295              
296             my $dep;
297              
298             require Debian::DpkgLists;
299             if ( my $ver = is_core_module( $module, $version ) ) {
300             $dep = Debian::Dependency->new( 'perl', $ver );
301             }
302             elsif ( my @pkgs = Debian::DpkgLists->scan_perl_mod($module) ) {
303             $dep = Debian::Dependency->new(
304             ( @pkgs > 1 )
305             ? [ map { { pkg => $_, ver => $version } } @pkgs ]
306             : ( $pkgs[0], $version )
307             );
308              
309             # Check the actual version available, if we've been passed
310             # a DPKG::Parse::Available object
311             if ( $dpkg_available ) {
312             my @available;
313             my @satisfied = grep {
314             if ( my $pkg = $dpkg_available->get_package('name' => $_) ) {
315             my $have_pkg = Debian::Dependency->new( $_, '=', $pkg->version );
316             push @available, $have_pkg;
317             $have_pkg->satisfies($dep);
318             }
319             else {
320             warn qq(Unable to obtain version information for $module. You may need to )
321             .qq(install and run "dselect update");
322             }
323             } @pkgs;
324             unless ( @satisfied ) {
325             print "$module is available locally as @available, but does not satisify $version"
326             if $verbose;
327             push @missing, $module;
328             }
329             }
330             else {
331             warn "DPKG::Parse not available. Not checking version of $module.";
332             }
333             }
334              
335             if (!$dep && $apt_contents) {
336             $dep = $apt_contents->find_perl_module_package( $module, $version );
337              
338             # Check the actual version in APT, if we've got
339             # a AptPkg::Cache object to search
340             if ( $dep && $aptpkg_cache ) {
341             my $pkg = $aptpkg_cache->{$dep->pkg};
342             if ( my $available = $pkg->{VersionList} ) {
343             for my $v ( @$available ) {
344             my $d = Debian::Dependency->new( $dep->pkg, '=', $v->{VerStr} );
345             unless ( $d->satisfies($dep) )
346             {
347             push @missing, $module;
348             print "$module package in APT ($d) does not satisfy $dep"
349             if $verbose;
350             }
351             }
352             }
353             }
354             }
355              
356              
357             $dep->rel($ver_rel) if $dep and $ver_rel and $dep->ver;
358              
359             my $mod_ver = join( " ", $module, $ver_rel // (), $version || () );
360             if ($dep) {
361             if ($verbose) {
362             if ( $dep->pkg and $dep->pkg eq 'perl' ) {
363             print "= $mod_ver is in core";
364             print " since " . $dep->ver if $dep->ver;
365             print "\n";
366             }
367             else {
368             print "+ $mod_ver found in $dep\n";
369             }
370             }
371              
372             my $target_perl_version = $^V;
373             $target_perl_version =~ s/^v//;
374             $target_perl_version = Dpkg::Version->new($target_perl_version);
375              
376             if ( $dep->pkg
377             and $dep->pkg eq 'perl'
378             and $dep->ver
379             and $dep->ver > $target_perl_version )
380             {
381             print " ! $dep is too new. Adding alternative dependency\n"
382             if $verbose;
383              
384             my $alt_dep;
385              
386             if ( my @pkgs = Debian::DpkgLists->scan_perl_mod($module) ) {
387             @pkgs = grep {
388             ( $_ ne 'perl-modules' )
389             and ( $_ ne 'perl-base' )
390             and ( $_ ne 'perl' )
391             } @pkgs;
392              
393             $alt_dep = Debian::Dependency->new(
394             ( @pkgs > 1 )
395             ? [ map { { pkg => $_, ver => $version } } @pkgs ]
396             : ( $pkgs[0], $version )
397             ) if @pkgs;
398             }
399              
400             if ( not $alt_dep and $apt_contents) {
401             $alt_dep
402             = $apt_contents->find_perl_module_package( $module,
403             $version );
404             }
405              
406             $alt_dep
407             //= Debian::Dependency->new(
408             $self->module_name_to_pkg_name($module),
409             '>=', $version );
410              
411             $dep = Debian::Dependency->new("$alt_dep | $dep");
412             #print " $dep\n";
413             }
414             }
415             else {
416             print "- $mod_ver not found in any package\n";
417             push @missing, $module;
418              
419             my $mod = find_cpan_module($module);
420             if ( $mod and $mod->distribution ) {
421             ( my $dist = $mod->distribution->base_id ) =~ s/-v?\d[^-]*$//;
422             my $pkg = $self->module_name_to_pkg_name($dist);
423              
424             print " CPAN contains it in $dist\n";
425             print " substituting package name of $pkg\n";
426              
427             $dep = Debian::Dependency->new( $pkg, $ver_rel, $version );
428             }
429             else {
430             print " - it seems it is not available even via CPAN\n";
431             }
432             }
433              
434             $debs->add($dep) if $dep;
435             }
436              
437             return $debs, \@missing;
438             }
439              
440             =item prune_simple_perl_dep
441              
442             Input:
443              
444             =over
445              
446             =item dependency object
447              
448             shall be a simple dependency (no alternatives)
449              
450             =item (optional) build dependency flag
451              
452             true value indicates the dependency is a build-time one
453              
454             =back
455              
456              
457             The following checks are made
458              
459             =over
460              
461             =item dependencies on C
462              
463             These are replaced with C as per Perl policy.
464              
465             =item dependencies on C and build-dependencies on C or
466             C
467              
468             These are removed, unless they specify a version greater than the one available
469             in C or the dependency relation is not C<< >= >> or C<<< >> >>>.
470              
471             =back
472              
473             Return value:
474              
475             =over
476              
477             =item undef
478              
479             if the dependency is redundant.
480              
481             =item pruned dependency
482              
483             otherwise. C replaced with C.
484              
485             =back
486              
487             =cut
488              
489             sub prune_simple_perl_dep {
490             my( $self, $dep, $build ) = @_;
491              
492             croak "No alternative dependencies can be given"
493             if $dep->alternatives;
494              
495             return $dep unless $dep->pkg =~ /^(?:perl|perl-base|perl-modules)$/;
496              
497             # perl-modules is replaced with perl
498             $dep->pkg('perl') if $dep->pkg eq 'perl-modules';
499              
500             my $unversioned = (
501             not $dep->ver
502             or $dep->rel =~ />/
503             and $dep->ver <= $self->oldstable_perl_version
504             );
505              
506             # if the dependency is considered unversioned, make sure there is no
507             # version
508             if ($unversioned) {
509             $dep->ver(undef);
510             $dep->rel(undef);
511             }
512              
513             # perl-base is (build-)essential
514             return undef
515             if $dep->pkg eq 'perl-base' and $unversioned;
516              
517             # perl is needed in build-dependencies (see Policy 4.2)
518             return $dep if $dep->pkg eq 'perl' and $build;
519              
520             # unversioned perl non-build-dependency is redundant, because it will be
521             # covered by ${perl:Depends}
522             return undef
523             if not $build
524             and $dep->pkg eq 'perl'
525             and $unversioned;
526              
527             return $dep;
528             }
529              
530             =item prune_perl_dep
531              
532             Similar to L, but supports alternative dependencies.
533             If any of the alternatives is redundant, the whole dependency is considered
534             redundant.
535              
536             =cut
537              
538             sub prune_perl_dep {
539             my( $self, $dep, $build ) = @_;
540              
541             return $self->prune_simple_perl_dep( $dep, $build )
542             unless $dep->alternatives;
543              
544             for my $simple ( @{ $dep->alternatives } ) {
545             my $pruned = $self->prune_simple_perl_dep( $simple, $build );
546              
547             # redundant alternative?
548             return undef unless $pruned;
549              
550             $simple = $pruned;
551             }
552              
553             return $dep;
554             }
555              
556             =item prune_perl_deps
557              
558             Remove redundant (build-)dependencies on perl, perl-modules and perl-base.
559              
560             =cut
561              
562             sub prune_perl_deps {
563             my $self = shift;
564              
565             # remove build-depending on ancient perl versions
566             for my $perl ( qw( perl perl-base perl-modules ) ) {
567             for ( qw( Build_Depends Build_Depends_Indep ) ) {
568             my @ess = $self->source->$_->remove($perl);
569             # put back non-redundant ones (possibly modified)
570             for my $dep (@ess) {
571             my $pruned = $self->prune_perl_dep( $dep, 1 );
572              
573             $self->source->$_->add($pruned) if $pruned;
574             }
575             }
576             }
577              
578             # remove depending on ancient perl versions
579             for my $perl ( qw( perl perl-base perl-modules ) ) {
580             for my $pkg ( $self->binary_tie->Values ) {
581             for my $rel ( qw(Depends Recommends Suggests) ) {
582             my @ess = $pkg->$rel->remove($perl);
583             for my $dep (@ess) {
584             my $pruned = $self->prune_perl_dep( $dep, 0 );
585              
586             $pkg->$rel->add($pruned) if $pruned;
587             }
588             }
589             }
590             }
591             }
592              
593             =back
594              
595             =head1 CLASS METHODS
596              
597             =over
598              
599             =item module_name_to_pkg_name
600              
601             Receives a perl module name like C and returns a suitable Debian
602             package name for it, like C.
603              
604             =cut
605              
606             sub module_name_to_pkg_name {
607             my ( $self, $module ) = @_;
608              
609             my $pkg = lc $module;
610              
611             # ensure policy compliant names and versions (from Joeyh)...
612             $pkg =~ s/[^-.+a-zA-Z0-9]+/-/g;
613              
614             $pkg =~ s/--+/-/g;
615              
616             $pkg = 'lib' . $pkg unless $pkg =~ /^lib/;
617             $pkg .= '-perl';
618              
619             return $pkg;
620             }
621              
622             =back
623              
624             =head1 COPYRIGHT & LICENSE
625              
626             Copyright (C) 2009, 2010, 2012 Damyan Ivanov L
627              
628             This program is free software; you can redistribute it and/or modify it under
629             the terms of the GNU General Public License version 2 as published by the Free
630             Software Foundation.
631              
632             This program is distributed in the hope that it will be useful, but WITHOUT ANY
633             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
634             PARTICULAR PURPOSE.
635              
636             =cut
637              
638             1;
639              
640