File Coverage

blib/lib/Dist/Zilla/Plugin/CheckPrereqsIndexed.pm
Criterion Covered Total %
statement 73 79 92.4
branch 21 26 80.7
condition 11 13 84.6
subroutine 8 8 100.0
pod 0 3 0.0
total 113 129 87.6


line stmt bran cond sub pod time code
1             package Dist::Zilla::Plugin::CheckPrereqsIndexed;
2             # ABSTRACT: prevent a release if you have prereqs not found on CPAN
3             $Dist::Zilla::Plugin::CheckPrereqsIndexed::VERSION = '0.020';
4 3     3   6186582 use 5.10.0; # //
  3         6  
5 3     3   10 use Moose;
  3         4  
  3         18  
6              
7             #pod =head1 OVERVIEW
8             #pod
9             #pod Sometimes, AutoPrereqs is a little overzealous and finds a prereq that you
10             #pod wrote inline or have in your F<./t> directory. Although AutoPrereqs should
11             #pod grow more accurate over time, and avoid these mistakes, it's not perfect right
12             #pod now. CheckPrereqsIndexed will check every required package against the CPAN
13             #pod index to ensure that they're all real, installable packages.
14             #pod
15             #pod If any are unknown, it will prompt the user to continue or abort.
16             #pod
17             #pod Previously, CheckPrereqsIndexed queried CPANIDX, but it now queries
18             #pod cpanmetadb. This behavior may change again in the future, or it may become
19             #pod pluggable. In the meantime, this makes releasing while offline impossible...
20             #pod but it was anyway, right?
21             #pod
22             #pod =cut
23              
24             with 'Dist::Zilla::Role::BeforeRelease';
25              
26 3     3   12901 use List::Util 1.33 qw(any);
  3         62  
  3         232  
27              
28 3     3   15 use namespace::autoclean;
  3         3  
  3         26  
29              
30 13     13 0 309103 sub mvp_multivalue_args { qw(skips) }
31 13     13 0 1525 sub mvp_aliases { return { skip => 'skips' } }
32              
33             #pod =attr skips
34             #pod
35             #pod This is an arrayref of regular expressions. Any module names matching
36             #pod any of these regex will not be checked. This should only be necessary
37             #pod if you have a prerequisite that is not available on CPAN (because it's
38             #pod distributed in some other way).
39             #pod
40             #pod =cut
41              
42             has skips => (
43             is => 'ro',
44             isa => 'ArrayRef[Str]',
45             default => sub { [] },
46             );
47              
48             my %NOT_INDEXED = map {; $_ => 1 }
49             qw(Config DB Errno integer NEXT perl Pod::Functions);
50              
51             sub before_release {
52 13     13 0 1556058 my ($self) = @_;
53              
54 13         149 $self->log("checking prereqs against CPAN index");
55              
56 13         5790 require version;
57              
58 13         27 my @skips = map {; qr/$_/ } @{ $self->skips };
  1         36  
  13         545  
59              
60 13         202 my $requirements = CPAN::Meta::Requirements->new;
61              
62             # find the package => version for all modules in this distribution
63 13   100     602 my $provides = $self->zilla->distmeta->{provides} // {};
64 13         641 my %self_modules = map { $_ => $provides->{$_}{version} } keys %$provides;
  8         29  
65              
66 13 100       82 if (not keys %self_modules) {
67 9         277 (my $pkg = $self->zilla->name) =~ s/-/::/g;
68 9         406 $self->log_debug([ 'no "provides" metadata; guessing distribution contains module %s', $pkg ]);
69 9         1748 %self_modules = ( $pkg => $self->zilla->version );
70             }
71              
72 13         594 for my $prereqs_hash (
73             $self->zilla->prereqs->as_string_hash,
74 1   100     35 (map { $_->{prereqs} } values %{ $self->zilla->distmeta->{optional_features} // {} }),
  13         8137  
75             ) {
76 14         569 for my $phase (keys %$prereqs_hash) {
77 24         980 for my $type (keys %{$prereqs_hash->{$phase}}) {
  24         80  
78 24         24 REQ_PKG: for my $pkg (keys %{$prereqs_hash->{$phase}{$type}}) {
  24         70  
79 34 50       968 if ($NOT_INDEXED{ $pkg }) {
80 0         0 $self->log_debug([ 'skipping unindexed module %s', $pkg ]);
81 0         0 next;
82             }
83              
84 34 100   4   245 if (any { $pkg =~ $_ } @skips) {
  4         20  
85 1         8 $self->log_debug([ 'explicitly skipping module %s', $pkg ]);
86 1         50 next;
87             }
88              
89 33         100 my $ver = $prereqs_hash->{$phase}{$type}{$pkg};
90              
91             # skip packages contained in the distribution we are releasing, from develop prereqs only
92 33 100 66     151 if ($phase eq 'develop' and exists $self_modules{$pkg} and $self_modules{$pkg} >= $ver) {
      66        
93 4         26 $self->log_debug([ 'skipping develop prereq on ourself (%s => %s)', $pkg, $ver ]);
94 4         1014 next;
95             }
96              
97 29         92 $requirements->add_string_requirement($pkg => $ver);
98             }
99             }
100             }
101             }
102              
103 13         917 my @modules = $requirements->required_modules;
104 13 50       98 return unless @modules; # no prereqs!?
105              
106 13         2478 require HTTP::Tiny;
107 13         61340 require YAML::Tiny;
108              
109 13         137 my $ua = HTTP::Tiny->new;
110              
111 13         1103 my %missing;
112             my %unmet;
113              
114 13         79 PKG: for my $pkg (sort @modules) {
115 29         2153 my $res = $ua->get("http://cpanmetadb.plackperl.org/v1.0/package/$pkg");
116 29 100       1139769 unless ($res->{success}) {
117 6 50       61 if ($res->{status} == 404) { # Not found
118 6         20 $missing{ $pkg } = 1;
119 6         319 next PKG;
120             }
121 0         0 chomp($res->{content});
122 0         0 $self->log_fatal(['%s %s: %s', $res->{status}, $res->{reason}, $res->{content}]);
123             }
124              
125 23         365 my $payload = YAML::Tiny->read_string( $res->{content} );
126              
127 23 50       103373 unless (@$payload) {
128 0         0 $missing{ $pkg } = 1;
129 0         0 next PKG;
130             }
131              
132 23         323 my $indexed_version = version->parse($payload->[0]{version});
133 23 100       292 next PKG if $requirements->accepts_module($pkg, $indexed_version->stringify);
134              
135 1         100 $unmet{ $pkg } = {
136             required => $requirements->requirements_for_module($pkg),
137             indexed => $indexed_version,
138             };
139             }
140              
141 13 100 100     1695 unless (keys %missing or keys %unmet) {
142 6         46 $self->log("all prereqs appear to be indexed");
143 6         3022 return;
144             }
145              
146 7 100       21 if (keys %missing) {
147 6         37 my @missing = sort keys %missing;
148 6         61 $self->log("the following prereqs could not be found on CPAN: @missing");
149             }
150              
151 7 100       2315 if (keys %unmet) {
152 1         6 for my $pkg (sort keys %unmet) {
153 1         17 $self->log([
154             "you required %s version %s but CPAN only has version %s",
155             $pkg,
156             "$unmet{$pkg}{required}",
157             "$unmet{$pkg}{indexed}",
158             ]);
159             }
160             }
161              
162 7 50       692 return if $self->zilla->chrome->prompt_yn(
163             "release despite missing prereqs?",
164             { default => 0 }
165             );
166              
167 7         6629 $self->log_fatal("aborting release due to apparently unindexed prereqs");
168             }
169              
170             1;
171              
172             __END__
173              
174             =pod
175              
176             =encoding UTF-8
177              
178             =head1 NAME
179              
180             Dist::Zilla::Plugin::CheckPrereqsIndexed - prevent a release if you have prereqs not found on CPAN
181              
182             =head1 VERSION
183              
184             version 0.020
185              
186             =head1 OVERVIEW
187              
188             Sometimes, AutoPrereqs is a little overzealous and finds a prereq that you
189             wrote inline or have in your F<./t> directory. Although AutoPrereqs should
190             grow more accurate over time, and avoid these mistakes, it's not perfect right
191             now. CheckPrereqsIndexed will check every required package against the CPAN
192             index to ensure that they're all real, installable packages.
193              
194             If any are unknown, it will prompt the user to continue or abort.
195              
196             Previously, CheckPrereqsIndexed queried CPANIDX, but it now queries
197             cpanmetadb. This behavior may change again in the future, or it may become
198             pluggable. In the meantime, this makes releasing while offline impossible...
199             but it was anyway, right?
200              
201             =head1 ATTRIBUTES
202              
203             =head2 skips
204              
205             This is an arrayref of regular expressions. Any module names matching
206             any of these regex will not be checked. This should only be necessary
207             if you have a prerequisite that is not available on CPAN (because it's
208             distributed in some other way).
209              
210             =head1 AUTHOR
211              
212             Ricardo Signes <rjbs@cpan.org>
213              
214             =head1 CONTRIBUTORS
215              
216             =for stopwords Christopher J. Madsen Dave Rolsky David Golden Karen Etheridge Olivier Mengué Piers Cawley Sébastien Deseille Van de Bugger
217              
218             =over 4
219              
220             =item *
221              
222             Christopher J. Madsen <perl@cjmweb.net>
223              
224             =item *
225              
226             Dave Rolsky <autarch@urth.org>
227              
228             =item *
229              
230             David Golden <dagolden@cpan.org>
231              
232             =item *
233              
234             Karen Etheridge <ether@cpan.org>
235              
236             =item *
237              
238             Olivier Mengué <dolmen@cpan.org>
239              
240             =item *
241              
242             Piers Cawley <pdcawley@bofh.org.uk>
243              
244             =item *
245              
246             Sébastien Deseille <sebastien.deseille@gmail.com>
247              
248             =item *
249              
250             Van de Bugger <van.de.bugger@gmail.com>
251              
252             =back
253              
254             =head1 COPYRIGHT AND LICENSE
255              
256             This software is copyright (c) 2011 by Ricardo Signes.
257              
258             This is free software; you can redistribute it and/or modify it under
259             the same terms as the Perl 5 programming language system itself.
260              
261             =cut