File Coverage

blib/lib/Dist/Zilla/Plugin/CheckPrereqsIndexed.pm
Criterion Covered Total %
statement 81 87 93.1
branch 21 26 80.7
condition 11 13 84.6
subroutine 12 12 100.0
pod 0 3 0.0
total 125 141 88.6


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