| 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__ |