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