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.022;
2             # ABSTRACT: prevent a release if you have prereqs not found on CPAN
3              
4 3     3   8070466 use Moose;
  3         9  
  3         26  
5             with 'Dist::Zilla::Role::BeforeRelease';
6              
7             # BEGIN BOILERPLATE
8 3     3   18752 use v5.20.0;
  3         11  
9 3     3   16 use warnings;
  3         13  
  3         112  
10 3     3   21 use utf8;
  3         8  
  3         22  
11 3     3   89 no feature 'switch';
  3         7  
  3         327  
12 3     3   28 use experimental qw(postderef postderef_qq); # This experiment gets mainlined.
  3         14  
  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   336 use List::Util 1.33 qw(any);
  3         48  
  3         302  
33              
34 3     3   22 use namespace::autoclean;
  3         5  
  3         40  
35              
36 13     13 0 448918 sub mvp_multivalue_args { qw(skips) }
37 13     13 0 1902 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 2032539 my ($self) = @_;
60              
61 13         210 $self->log("checking prereqs against CPAN index");
62              
63 13         8829 require version;
64              
65 13         955 my @skips = map {; qr/$_/ } $self->skips;
  1         44  
66              
67 13         336 my $requirements = CPAN::Meta::Requirements->new;
68              
69             # find the package => version for all modules in this distribution
70 13   100     1199 my $provides = $self->zilla->distmeta->{provides} // {};
71 13         966 my %self_modules = map { $_ => $provides->{$_}{version} } keys %$provides;
  8         51  
72              
73 13 100       131 if (not keys %self_modules) {
74 9         286 (my $pkg = $self->zilla->name) =~ s/-/::/g;
75 9         667 $self->log_debug([ 'no "provides" metadata; guessing distribution contains module %s', $pkg ]);
76 9         2440 %self_modules = ( $pkg => $self->zilla->version );
77             }
78              
79 13   100     868 for my $prereqs_hash (
80             $self->zilla->prereqs->as_string_hash,
81 1         632 (map { $_->{prereqs} } values(($self->zilla->distmeta->{optional_features} // {})->%*)),
82             ) {
83 14         9911 for my $phase (keys %$prereqs_hash) {
84 24         1945 for my $type (keys $prereqs_hash->{$phase}->%*) {
85 24         96 REQ_PKG: for my $pkg (keys $prereqs_hash->{$phase}{$type}->%*) {
86 34 50       1344 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   348 if (any { $pkg =~ $_ } @skips) {
  4         26  
92 1         22 $self->log_debug([ 'explicitly skipping module %s', $pkg ]);
93 1         86 next;
94             }
95              
96 33         222 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     361 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         66 $self->log_debug([ 'skipping develop prereq on ourself (%s => %s)', $pkg, $ver ]);
106 4         1308 next;
107             }
108              
109 29         150 $requirements->add_string_requirement($pkg => $ver);
110             }
111             }
112             }
113             }
114              
115 13         1108 my @modules = $requirements->required_modules;
116 13 50       126 return unless @modules; # no prereqs!?
117              
118 13         2760 require HTTP::Tiny;
119 13         86860 require YAML::Tiny;
120              
121 13         222 my $ua = HTTP::Tiny->new;
122              
123 13         1765 my %missing;
124             my %unmet;
125              
126 13         82 PKG: for my $pkg (sort @modules) {
127 29         2199 my $res = $ua->get("http://cpanmetadb.plackperl.org/v1.0/package/$pkg");
128 29 100       558777 unless ($res->{success}) {
129 6 50       31 if ($res->{status} == 404) { # Not found
130 6         27 $missing{ $pkg } = 1;
131 6         45 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         468 my $payload = YAML::Tiny->read_string( $res->{content} );
138              
139 23 50       154208 unless (@$payload) {
140 0         0 $missing{ $pkg } = 1;
141 0         0 next PKG;
142             }
143              
144 23         312 my $indexed_version = version->parse($payload->[0]{version});
145 23 100       332 next PKG if $requirements->accepts_module($pkg, $indexed_version->stringify);
146              
147 1         120 $unmet{ $pkg } = {
148             required => $requirements->requirements_for_module($pkg),
149             indexed => $indexed_version,
150             };
151             }
152              
153 13 100 100     1767 unless (keys %missing or keys %unmet) {
154 6         67 $self->log("all prereqs appear to be indexed");
155 6         2988 return;
156             }
157              
158 7 100       29 if (keys %missing) {
159 6         28 my @missing = sort keys %missing;
160 6         58 $self->log("the following prereqs could not be found on CPAN: @missing");
161             }
162              
163 7 100       2376 if (keys %unmet) {
164 1         6 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       797 return if $self->zilla->chrome->prompt_yn(
175             "release despite missing prereqs?",
176             { default => 0 }
177             );
178              
179 7         10178 $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.022
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
214              
215             This module should work on any version of perl still receiving updates from
216             the Perl 5 Porters. This means it should work on any version of perl released
217             in the last two to three years. (That is, if the most recently released
218             version is v5.40, then this module should work on both v5.40 and v5.38.)
219              
220             Although it may work on older versions of perl, no guarantee is made that the
221             minimum required version will not be increased. The version may be increased
222             for any reason, and there is no promise that patches will be accepted to lower
223             the minimum required perl.
224              
225             =head1 ATTRIBUTES
226              
227             =head2 skips
228              
229             This is an array of regular expressions. Any module names matching any of
230             these regex will not be checked. This should only be necessary if you have a
231             prerequisite that is not available on CPAN (because it's distributed in some
232             other way).
233              
234             =head1 AUTHOR
235              
236             Ricardo Signes <cpan@semiotic.systems>
237              
238             =head1 CONTRIBUTORS
239              
240             =for stopwords Christopher J. Madsen Dave Rolsky David Golden Karen Etheridge Olivier Mengué Piers Cawley Ricardo Signes Sébastien Deseille Van de Bugger
241              
242             =over 4
243              
244             =item *
245              
246             Christopher J. Madsen <perl@cjmweb.net>
247              
248             =item *
249              
250             Dave Rolsky <autarch@urth.org>
251              
252             =item *
253              
254             David Golden <dagolden@cpan.org>
255              
256             =item *
257              
258             Karen Etheridge <ether@cpan.org>
259              
260             =item *
261              
262             Olivier Mengué <dolmen@cpan.org>
263              
264             =item *
265              
266             Piers Cawley <pdcawley@bofh.org.uk>
267              
268             =item *
269              
270             Ricardo Signes <rjbs@semiotic.systems>
271              
272             =item *
273              
274             Sébastien Deseille <sebastien.deseille@gmail.com>
275              
276             =item *
277              
278             Van de Bugger <van.de.bugger@gmail.com>
279              
280             =back
281              
282             =head1 COPYRIGHT AND LICENSE
283              
284             This software is copyright (c) 2011 by Ricardo Signes.
285              
286             This is free software; you can redistribute it and/or modify it under
287             the same terms as the Perl 5 programming language system itself.
288              
289             =cut