File Coverage

blib/lib/Dist/Zilla/Plugin/OnlyCorePrereqs.pm
Criterion Covered Total %
statement 99 100 99.0
branch 26 32 81.2
condition 21 23 91.3
subroutine 20 20 100.0
pod 0 3 0.0
total 166 178 93.2


line stmt bran cond sub pod time code
1 8     8   18032575 use strict;
  8         23  
  8         252  
2 8     8   42 use warnings;
  8         16  
  8         433  
3             package Dist::Zilla::Plugin::OnlyCorePrereqs; # git description: v0.024-27-g2ca3b05
4             # vim: set ts=8 sts=2 sw=2 tw=115 et :
5             # ABSTRACT: Check that no prerequisites are declared that are not part of core
6             # KEYWORDS: plugin distribution metadata prerequisites core
7              
8             our $VERSION = '0.025';
9              
10 8     8   464 use Moose;
  8         263730  
  8         50  
11             with 'Dist::Zilla::Role::AfterBuild';
12 8     8   44630 use Moose::Util::TypeConstraints;
  8         16  
  8         61  
13 8     8   19268 use Module::CoreList 5.20150214;
  8         164185  
  8         59  
14 8     8   1781 use MooseX::Types::Perl 0.101340 'LaxVersionStr';
  8         85820  
  8         68  
15 8     8   16085 use version;
  8         16  
  8         47  
16 8     8   951 use Encode ();
  8         10139  
  8         121  
17 8     8   4751 use HTTP::Tiny;
  8         224192  
  8         273  
18 8     8   57 use YAML::Tiny;
  8         20  
  8         487  
19 8     8   2871 use CPAN::DistnameInfo;
  8         6017  
  8         244  
20 8     8   458 use CPAN::Meta::Requirements 2.121;
  8         3723  
  8         164  
21 8     8   40 use namespace::autoclean;
  8         67  
  8         70  
22              
23             has phases => (
24             isa => 'ArrayRef[Str]',
25             lazy => 1,
26             default => sub { [ qw(configure build runtime test) ] },
27             traits => ['Array'],
28             handles => { phases => 'elements' },
29             );
30              
31             has starting_version => (
32             is => 'ro',
33             isa => do {
34             my $version = subtype as class_type('version');
35             coerce $version, from LaxVersionStr, via { version->parse($_) };
36             $version;
37             },
38             coerce => 1,
39             predicate => '_has_starting_version',
40             lazy => 1,
41             default => sub {
42             my $self = shift;
43              
44             my $prereqs = $self->zilla->distmeta->{prereqs};
45             my @perl_prereqs = grep defined, map $prereqs->{$_}{requires}{perl}, keys %$prereqs;
46              
47             return '5.005' if not @perl_prereqs;
48              
49             my $req = CPAN::Meta::Requirements->new;
50             $req->add_minimum(perl => $_) foreach @perl_prereqs;
51             $req->requirements_for_module('perl');
52             },
53             );
54              
55             has deprecated_ok => (
56             is => 'ro', isa => 'Bool',
57             default => 0,
58             );
59              
60             has check_dual_life_versions => (
61             is => 'ro', isa => 'Bool',
62             default => 1,
63             );
64              
65             has skips => (
66             isa => 'ArrayRef[Str]',
67             traits => ['Array'],
68             handles => {
69             skips => 'elements',
70             skip_module => 'grep',
71             },
72             lazy => 1,
73             default => sub { [] },
74             );
75              
76             has also_disallow => (
77             isa => 'ArrayRef[Str]',
78             traits => ['Array'],
79             handles => {
80             also_disallow => 'elements',
81             disallowed => 'grep',
82             },
83             lazy => 1,
84             default => sub { [] },
85             );
86              
87 20     20 0 2738764 sub mvp_multivalue_args { qw(phases skips also_disallow) }
88 20     20 0 2492 sub mvp_aliases { { phase => 'phases', skip => 'skips' } }
89              
90             around BUILDARGS => sub
91             {
92             my $orig = shift;
93             my $self = shift;
94              
95             my $args = $self->$orig(@_);
96              
97             if (($args->{starting_version} // '') eq 'current')
98             {
99             $args->{starting_version} = "$]"; # preserve trailing zeros
100             }
101             elsif (($args->{starting_version} // '') eq 'latest')
102             {
103             my $latest = (reverse sort keys %Module::CoreList::released)[0];
104             $args->{starting_version} = "$latest"; # preserve trailing zeros
105             }
106              
107             $args;
108             };
109              
110             around dump_config => sub
111             {
112             my ($orig, $self) = @_;
113             my $config = $self->$orig;
114              
115             $config->{+__PACKAGE__} = {
116             ( map +($_ => [ sort $self->$_ ]), qw(phases skips also_disallow)),
117             ( map +($_ => ($self->$_ ? 1 : 0)), qw(deprecated_ok check_dual_life_versions)),
118             ( starting_version => ($self->_has_starting_version
119             ? $self->starting_version->stringify
120             : 'to be determined from perl prereq')),
121             blessed($self) ne __PACKAGE__ ? ( version => $VERSION ) : (),
122             };
123              
124             return $config;
125             };
126              
127             sub after_build
128             {
129 20     20 0 141113 my $self = shift;
130              
131             $self->log([ 'WARNING: Module::CoreList does not have information about this perl version of %s', $] ])
132 20 50       107 if not exists $Module::CoreList::version{$]};
133              
134 20         560 my $prereqs = $self->zilla->distmeta->{prereqs};
135              
136             # we build up a lists of all errors found
137 20         592 my (@disallowed, @non_core, @not_yet, @insufficient_version, @deprecated);
138              
139 20         670 foreach my $phase ($self->phases)
140             {
141 74   100     117 foreach my $prereq (keys %{ $prereqs->{$phase}{requires} // {} })
  74         404  
142             {
143 26 100       104 next if $prereq eq 'perl';
144              
145 23 100   2   896 if ($self->skip_module(sub { $_ eq $prereq }))
  2         7  
146             {
147 1         5 $self->log_debug([ 'skipping %s', $prereq ]);
148 1         292 next;
149             }
150              
151 22         195 $self->log_debug([ 'checking %s', $prereq ]);
152              
153 22 100   1   6381 if ($self->disallowed(sub { $_ eq $prereq }))
  1         5  
154             {
155 1         4 push @disallowed, [$phase, $prereq];
156 1         3 next;
157             }
158              
159 21         188 my $added_in = Module::CoreList->first_release($prereq);
160              
161 21 100       14437 if (not defined $added_in)
162             {
163 1         4 push @non_core, [$phase, $prereq];
164 1         4 next;
165             }
166              
167 20 100 66     880 if (version->parse($added_in) > $self->starting_version
      66        
168             and ($self->check_dual_life_versions or not $self->_is_dual($prereq)))
169             {
170 2         8 push @not_yet, [$phase, $added_in, $prereq];
171 2         10 next;
172             }
173              
174 18 100 100     579 if ($self->check_dual_life_versions or not $self->_is_dual($prereq))
175             {
176             my $has = !exists($Module::CoreList::version{$self->starting_version->stringify}) ? 0
177 15 50       435 : $Module::CoreList::version{$self->starting_version->stringify}{$prereq};
178 15         91 $has = version->parse($has); # version.pm XS hates tie() - RT#87983
179 15         82 my $wanted = version->parse($prereqs->{$phase}{requires}{$prereq});
180              
181 15 100       84 if ($has < $wanted)
182             {
183 4         120 push @insufficient_version, [ map "$_", $phase, $prereq, $wanted, $self->starting_version->stringify, $has ];
184 4         21 next;
185             }
186             }
187              
188 14 100       426 if (not $self->deprecated_ok)
189             {
190 13         57 my $deprecated_in = Module::CoreList->deprecated_in($prereq);
191 13 100       3160 if ($deprecated_in)
192             {
193 1         4 push @deprecated, [$phase, $deprecated_in, $prereq];
194 1         4 next;
195             }
196             }
197             }
198             }
199              
200             $self->log(['detected a %s requires dependency that is explicitly disallowed: %s', @$_])
201 20         69 for @disallowed;
202              
203             $self->log(['detected a %s requires dependency that is not in core: %s', @$_])
204 20         310 for @non_core;
205              
206             $self->log(['detected a %s requires dependency that was not added to core until %s: %s', @$_])
207 20         382 for @not_yet;
208              
209             $self->log(['detected a %s requires dependency on %s %s: perl %s only has %s', @$_])
210 20         585 for @insufficient_version;
211              
212             $self->log(['detected a %s requires dependency that was deprecated from core in %s: %s', @$_])
213 20         1109 for @deprecated;
214              
215 20 100 100     510 $self->log_fatal('aborting build due to invalid dependencies')
      100        
      100        
      100        
216             if @disallowed || @non_core || @not_yet || @insufficient_version || @deprecated;
217             }
218              
219             # this will get easier if we can just ask MCL for this information, rather
220             # than guessing.
221             # returns undef if not indexed, otherwise true/false.
222             sub _is_dual
223             {
224             my ($self, $module) = @_;
225              
226             my $upstream = $Module::CoreList::upstream{$module};
227             $self->log_debug([ '%s is upstream=%s', $module, sub { $upstream // 'undef' } ]);
228             return 1 if defined $upstream and ($upstream eq 'cpan' or $upstream eq 'first-come');
229              
230             # if upstream=blead or =undef, we can't be sure if it's actually dual or
231             # not, so for now we'll have to ask the index and hope that the
232             # 'no_index' entries in the last perl release were complete.
233             # TODO: keep checking Module::CoreList for fixes.
234             my $dist_name = $self->_indexed_dist($module);
235             return 1 if grep $module eq $_, qw(Config DynaLoader); # exists, but not in the index
236             $self->log([ 'Warning: %s not indexed?!', $module ]), return undef if not defined $dist_name;
237              
238             $self->log_debug([ '%s is indexed in the %s dist', $module, $dist_name ]);
239             return $dist_name eq 'perl' ? 0 : 1;
240             }
241             {
242             my %is_dual;
243             around _is_dual => sub {
244             my $orig = shift;
245             my ($self, $module) = @_;
246              
247             return $is_dual{$module} if exists $is_dual{$module};
248             $is_dual{$module} = $self->$orig($module);
249             };
250             }
251              
252              
253             # if only the index were cached somewhere locally that I could query...
254             sub _indexed_dist
255             {
256 2     2   6 my ($self, $module) = @_;
257              
258 2         7 my $url = 'http://cpanmetadb.plackperl.org/v1.0/package/' . $module;
259 2         9 $self->log_debug([ 'fetching %s', $url ]);
260 2         313 my $res = HTTP::Tiny->new->get($url);
261 2 50       2014 $self->log_debug('could not query the index?'), return undef if not $res->{success};
262              
263 2         4 my $data = $res->{content};
264              
265 2         800 require HTTP::Headers;
266 2 50       5512 if (my $charset = HTTP::Headers->new(%{ $res->{headers} })->content_type_charset)
  2         18  
267             {
268 0         0 $data = Encode::decode($charset, $data, Encode::FB_CROAK);
269             }
270 2     1   1645 $self->log_debug([ 'got response: %s', sub { chomp $data; $data } ]);
  1         165  
  1         3  
271              
272 2         216 my $payload = YAML::Tiny->read_string($data);
273              
274 2 50       528 $self->log_debug('invalid payload returned?'), return undef unless $payload;
275 2 50       7 $self->log_debug([ '%s not indexed', $module ]), return undef if not defined $payload->[0]{distfile};
276 2         16 return CPAN::DistnameInfo->new($payload->[0]{distfile})->dist;
277             }
278              
279             __PACKAGE__->meta->make_immutable;
280              
281             __END__
282              
283             =pod
284              
285             =encoding UTF-8
286              
287             =head1 NAME
288              
289             Dist::Zilla::Plugin::OnlyCorePrereqs - Check that no prerequisites are declared that are not part of core
290              
291             =head1 VERSION
292              
293             version 0.025
294              
295             =head1 SYNOPSIS
296              
297             In your F<dist.ini>:
298              
299             [OnlyCorePrereqs]
300             starting_version = 5.010
301             skip = Test::Warnings
302             also_disallow = Scalar::Util
303              
304             =head1 DESCRIPTION
305              
306             C<[OnlyCorePrereqs]> is a L<Dist::Zilla> plugin that checks at build time if
307             you have any declared prerequisites that are not shipped with Perl.
308              
309             You can specify the first Perl version to check against, and which
310             prerequisite phase(s) are significant.
311              
312             If the check fails, the build is aborted.
313              
314             =for Pod::Coverage after_build mvp_aliases mvp_multivalue_args
315              
316             =head1 OPTIONS
317              
318             =head2 C<phase>
319              
320             Indicates a phase to check against. Can be provided more than once; defaults
321             to C<configure>, C<build>, C<runtime>, C<test>. (See L<Dist::Zilla::Plugin::Prereqs> for more
322             information about phases.)
323              
324             Remember that you can use different settings for different phases by employing
325             this plugin twice, with different names.
326              
327             =head2 C<starting_version>
328              
329             Indicates the first Perl version that should be checked against; any versions
330             earlier than this are not considered significant for the purposes of core
331             checks. Defaults to the minimum version of perl declared in the distribution's
332             prerequisites, or C<5.005>.
333              
334             There are two special values supported (available since version 0.003):
335              
336             =over 4
337              
338             =item *
339              
340             C<current> - indicates the version of Perl that you are currently running with
341              
342             =item *
343              
344             C<latest> - indicates the most recent (stable or development) release of Perl
345              
346             =back
347              
348             (Note: if you wish to check against B<all> changes in core up to the very
349             latest Perl release, you should upgrade your L<Module::CoreList> installation.
350             You can guarantee you are always running the latest version with
351             L<Dist::Zilla::Plugin::PromptIfStale>. L<Module::CoreList> is also the mechanism used for
352             determining the version of the latest Perl release.)
353              
354             =head2 C<deprecated_ok>
355              
356             A boolean flag indicating whether it is considered acceptable to depend on a
357             deprecated module (that is, has been removed from core). Defaults to 0.
358              
359             =head2 C<check_dual_life_versions>
360              
361             Available since version 0.007.
362              
363             =for stopwords lifed blead
364              
365             A boolean flag indicating whether the specific module version available in the
366             C<starting_version> of perl should be checked (even) if the module is dual-lifed.
367             Defaults to 1.
368              
369             This is useful to B<unset> if you don't want to fail if you require a core module
370             that the user can still upgrade via the CPAN, but do want to fail if the
371             module is B<only> available in core.
372              
373             Note that at the moment, the "is this module dual-lifed?" heuristic is not
374             100% reliable, as we may need to interrogate the PAUSE index to see if the
375             module is available outside of perl -- which can generate a false negative if
376             the module is upstream-blead and there was a recent release of a stable perl.
377             This is hopefully going to be rectified soon (when I add the necessary feature
378             to L<Module::CoreList>).
379              
380             (For example, a prerequisite of L<Test::More> 0.88 at C<starting_version>
381             5.010 would fail with C<check_dual_life_versions = 1>, as the version of
382             L<Test::More> that shipped with that version of perl was only 0.72,
383             but not fail if C<check_dual_life_versions = 0>.
384              
385             =head2 C<skip>
386              
387             Available since version 0.012.
388              
389             The name of a module to exempt from checking. Can be used more than once.
390              
391             =head2 C<also_disallow>
392              
393             Available since version 0.021.
394              
395             The name of a module to disallow from being used as a prereq, even if it would
396             pass all the other checks. This is primarily of use when building core modules
397             themselves, where certain other core modules cannot be used, to avoid circular
398             dependencies. Can be used more than once.
399              
400             =head1 SUPPORT
401              
402             Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=Dist-Zilla-Plugin-OnlyCorePrereqs>
403             (or L<bug-Dist-Zilla-Plugin-OnlyCorePrereqs@rt.cpan.org|mailto:bug-Dist-Zilla-Plugin-OnlyCorePrereqs@rt.cpan.org>).
404              
405             There is also a mailing list available for users of this distribution, at
406             L<http://dzil.org/#mailing-list>.
407              
408             There is also an irc channel available for users of this distribution, at
409             L<C<#distzilla> on C<irc.perl.org>|irc://irc.perl.org/#distzilla>.
410              
411             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
412              
413             =head1 AUTHOR
414              
415             Karen Etheridge <ether@cpan.org>
416              
417             =head1 CONTRIBUTOR
418              
419             =for stopwords David Golden
420              
421             David Golden <dagolden@cpan.org>
422              
423             =head1 COPYRIGHT AND LICENCE
424              
425             This software is copyright (c) 2013 by Karen Etheridge.
426              
427             This is free software; you can redistribute it and/or modify it under
428             the same terms as the Perl 5 programming language system itself.
429              
430             =cut