File Coverage

blib/lib/App/scan_prereqs_cpanfile.pm
Criterion Covered Total %
statement 44 156 28.2
branch 0 62 0.0
condition 0 33 0.0
subroutine 15 28 53.5
pod 0 11 0.0
total 59 290 20.3


line stmt bran cond sub pod time code
1             package App::scan_prereqs_cpanfile;
2 1     1   534 use strict;
  1         2  
  1         21  
3 1     1   3 use warnings;
  1         2  
  1         17  
4 1     1   13 use 5.008005;
  1         3  
5             our $VERSION = "1.10";
6              
7 1     1   4 use Exporter 5.57 'import';
  1         10  
  1         43  
8             our @EXPORT_OK = qw(
9             debugf find_perl_files scan_inner_packages scan scan_test_requires load_diff_src
10             );
11              
12 1     1   364 use version ();
  1         1490  
  1         20  
13 1     1   426 use CPAN::Meta ();
  1         22331  
  1         21  
14 1     1   8 use CPAN::Meta::Requirements ();
  1         2  
  1         26  
15 1     1   6 use File::Find qw(find);
  1         1  
  1         43  
16 1     1   2237 use Module::CoreList ();
  1         78898  
  1         474  
17 1     1   538 use Module::CPANfile 0.9020 ();
  1         4541  
  1         24  
18 1     1   5 use File::Spec ();
  1         2  
  1         12  
19 1     1   3 use File::Basename ();
  1         2  
  1         11  
20 1     1   432 use Module::Metadata ();
  1         4378  
  1         26  
21 1     1   415 use Perl::PrereqScanner::Lite 0.21;
  1         8321  
  1         41  
22              
23             sub debugf {
24 0 0   0 0   if ($ENV{SCAN_PREREQS_CPANFILE_DEBUG}) {
25 0           require Data::Dumper;
26 0           my $format = shift;
27 1     1   6 no warnings 'once';
  1         2  
  1         1283  
28 0           local $Data::Dumper::Terse = 1;
29 0           local $Data::Dumper::Indent = 0;
30 0 0         my $txt = sprintf($format, map { defined($_) ? Data::Dumper::Dumper($_) : '-' } @_);
  0            
31 0           print $txt, "\n";
32             }
33             }
34              
35             sub scan {
36 0     0 0   my ($files, $inner_packages, $meta_prereqs, $prereq_types, $type, $optional_prereqs) = @_;
37              
38 0           my $prereqs = scan_files(@$files);
39              
40             # Remove internal packages.
41 0           remove_prereqs($prereqs, +{ map { $_ => 1 } @$inner_packages });
  0            
42              
43             # Remove from meta
44 0           for my $type (@$prereq_types) {
45 0           remove_prereqs($prereqs, $meta_prereqs->{$type}->{requires});
46 0           remove_prereqs($prereqs, $meta_prereqs->{$type}->{recommends});
47             }
48              
49             # Runtime prereqs.
50 0 0         if ($optional_prereqs) {
51 0           remove_prereqs($prereqs, $optional_prereqs);
52             }
53              
54             # Remove core modules.
55 0   0       my $perl_version = $meta_prereqs->{perl} || '5.008001';
56 0           remove_prereqs($prereqs, blead_corelist($perl_version));
57              
58 0           return $prereqs;
59             }
60              
61             sub scan_inner_packages {
62 0     0 0   my @files = @_;
63 0           my %uniq;
64             my @list;
65 0           for my $file (@files) {
66 0           push @list, grep { !$uniq{$_}++ } Module::Metadata->new_from_file($file)->packages_inside();
  0            
67             }
68 0           return @list;
69             }
70              
71             sub scan_files {
72 0     0 0   my @files = @_;
73              
74 0           my $combined = CPAN::Meta::Requirements->new;
75 0           for my $file (@files) {
76 0           debugf("Reading %s", $file);
77              
78 0           my $scanner = Perl::PrereqScanner::Lite->new;
79 0           $scanner->add_extra_scanner('Moose');
80 0           my $prereqs = $scanner->scan_file($file);
81 0           $combined->add_requirements($prereqs);
82             }
83 0           my $prereqs = $combined->as_string_hash;
84             }
85              
86             sub blead_corelist {
87 0     0 0   my $perl_version = shift;
88 0           my %corelist = %{$Module::CoreList::version{$perl_version}};
  0            
89 0           for my $module (keys %corelist) {
90 0           my $upstream = $Module::CoreList::upstream{$module};
91 0 0 0       if ($upstream && $upstream eq 'cpan') {
92 0           delete $corelist{$module};
93             }
94             }
95 0           return \%corelist;
96             }
97              
98             sub remove_prereqs {
99 0     0 0   my ($prereqs, $allowed) = @_;
100 0 0         return unless $allowed;
101              
102 0           for my $module (keys %$allowed) {
103 0 0         if (exists $allowed->{$module}) {
104 0 0         if (parse_version($allowed->{$module}) >= parse_version($prereqs->{$module})) {
105 0           debugf("Core: %s %s >= %s", $module, $allowed->{$module}, $prereqs->{$module});
106 0           delete $prereqs->{$module}
107             }
108             }
109             }
110             }
111              
112             sub parse_version {
113 0     0 0   my $v = shift;
114 0 0         return version->parse(0) unless defined $v;
115 0           return version->parse(''.$v);
116             }
117              
118             sub load_diff_src {
119 0     0 0   my $src = shift;
120 0 0         if (File::Basename::basename($src) eq 'cpanfile') {
    0          
121 0           return Module::CPANfile->load($src)->prereq_specs;
122             } elsif ($src =~ /\.(yml|json)$/) {
123 0           my $meta = CPAN::Meta->load_file($src);
124 0           my $meta_prereqs = CPAN::Meta::Prereqs->new($meta->prereqs)->as_string_hash;
125 0           return $meta_prereqs;
126             } else {
127 0           die "No META.json and cpanfile\n";
128             }
129             }
130              
131             sub read_from_file {
132 0     0 0   my ($fname, $length) = @_;
133 0 0         return q{} if !-f $fname;
134 0 0         open my $fh, '<', $fname
135             or Carp::croak("Can't open '$fname' for reading: '$!'");
136 0           my $buf;
137 0           read $fh, $buf, $length;
138 0           return $buf;
139             }
140              
141             sub find_perl_files {
142 0     0 0   my ($dir, %opts) = @_;
143 0   0       my $ignore = $opts{ignore} || [];
144 0           my $ignore_regexp = $opts{ignore_regexp};
145              
146 0           my (@runtime_files, @test_files, @configure_files, @develop_files);
147             find(
148             {
149             no_chdir => 1,
150             wanted => sub {
151 0 0   0     return if $_ eq '.';
152 0 0         return if -S $_; # Ignore UNIX socket
153              
154             # Ignore files.
155 0           my (undef, $topdir, ) = File::Spec->splitdir($_);
156 0           my $basename = File::Basename::basename($_);
157 0 0         return if $basename eq 'Build';
158 0 0 0       return if defined($ignore_regexp) && $_ =~ m/$ignore_regexp/;
159              
160             # Ignore build dir like Dist-Name-0.01/.
161 0 0         return if -f "$topdir/META.json";
162              
163 0           for my $ignored (@$ignore) {
164 0 0         return if $topdir eq $ignored;
165             }
166              
167 0 0 0       if ($basename eq 'Build.PL' || $basename eq 'Makefile.PL') {
    0 0        
    0 0        
168 0           push @configure_files, $_
169             } elsif ($topdir eq 't') {
170 0 0         if (/\.(pl|pm|psgi|t)$/) {
171 0 0         if ($basename =~ /^(?:author|release)-/) {
172             # dzil creates author test files to t/author-XXX.t
173 0           push @develop_files, $_
174             } else {
175 0           push @test_files, $_
176             }
177             }
178             } elsif ($topdir eq 'xt' || $topdir eq 'author' || $topdir eq 'benchmark') {
179 0 0         if (/\.(pl|pm|psgi|t)$/) {
180 0           push @develop_files, $_
181             }
182             } else {
183 0 0         if (/\.(pl|pm|psgi)$/) {
184 0           push @runtime_files, $_
185             } else {
186 0           my $header = read_from_file($_, 1024);
187 0 0 0       if ($header && $header =~ /^#!.*perl/) {
188             # Skip fatpacked file.
189 0 0         if ($header =~ /This chunk of stuff was generated by App::FatPacker./) {
190 0           debugf("fatpacked %s", $_);
191 0           return;
192             }
193              
194 0           push @runtime_files, $_
195             }
196             }
197             }
198             }
199             },
200 0           $dir
201             );
202 0           return (\@runtime_files, \@test_files, \@configure_files, \@develop_files);
203             }
204              
205             sub scan_test_requires {
206 0     0 0   my ($dir, $develop_prereqs) = @_;
207              
208 0           require Test::Requires::Scanner;
209              
210 0           my @test_files;
211             find(
212             {
213             no_chdir => 1,
214             wanted => sub {
215 0 0   0     return if $_ eq '.';
216 0 0         return if -S $_; # Ignore UNIX socket
217              
218 0           my (undef, $topdir, ) = File::Spec->splitdir($_);
219 0 0 0       if (($topdir eq 'xt' || $topdir eq 't') && /\.(?:t|pm)$/ ) {
      0        
220 0           push @test_files, $_
221             }
222             },
223             },
224 0           $dir
225             );
226 0           my $test_requires_prereqs = Test::Requires::Scanner->scan_files(@test_files);
227              
228 0           for my $module (keys %$test_requires_prereqs) {
229 0           my $version = $test_requires_prereqs->{$module};
230              
231 0 0 0       if (! exists $develop_prereqs->{$module} ||
232             parse_version($version) > parse_version($develop_prereqs->{$module})
233             ) {
234 0   0       $develop_prereqs->{$module} = $version || 0;
235             }
236             }
237              
238 0           return $develop_prereqs;
239             }
240              
241              
242             1;
243             __END__