File Coverage

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


line stmt bran cond sub pod time code
1 8     8   5116006 use strict;
  8         13  
  8         262  
2 8     8   25 use warnings;
  8         9  
  8         345  
3             package Dist::Zilla::Plugin::OnlyCorePrereqs; # git description: v0.023-3-g573b029
4             # ABSTRACT: Check that no prerequisites are declared that are not part of core
5             # KEYWORDS: plugin distribution metadata prerequisites core
6             # vim: set ts=8 sts=4 sw=4 tw=78 et :
7              
8             our $VERSION = '0.024';
9              
10 8     8   27 use Moose;
  8         10  
  8         54  
11             with 'Dist::Zilla::Role::AfterBuild';
12 8     8   33426 use Moose::Util::TypeConstraints;
  8         9  
  8         53  
13 8     8   13718 use Module::CoreList 5.20150214;
  8         60368  
  8         47  
14 8     8   1036 use MooseX::Types::Perl 0.101340 'LaxVersionStr';
  8         158  
  8         57  
15 8     8   12357 use version;
  8         10  
  8         45  
16 8     8   386 use Encode;
  8         11  
  8         539  
17 8     8   4896 use HTTP::Tiny;
  8         206093  
  8         291  
18 8     8   62 use YAML::Tiny;
  8         8  
  8         384  
19 8     8   3479 use CPAN::DistnameInfo;
  8         4697  
  8         187  
20 8     8   33 use CPAN::Meta::Requirements 2.121;
  8         120  
  8         127  
21 8     8   32 use namespace::autoclean;
  8         8  
  8         63  
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 853357 sub mvp_multivalue_args { qw(phases skips also_disallow) }
88 20     20 0 2200 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 { $_ => [ $self->$_ ] } qw(phases skips also_disallow)),
117             ( map { $_ => $self->$_ } 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             };
122              
123             return $config;
124             };
125              
126             sub after_build
127             {
128 20     20 0 358770 my $self = shift;
129              
130 20 50       101 $self->log([ 'WARNING: Module::CoreList does not have information about this perl version of %s', $] ])
131             if not exists $Module::CoreList::version{$]};
132              
133 20         560 my $prereqs = $self->zilla->distmeta->{prereqs};
134              
135             # we build up a lists of all errors found
136 20         658 my (@disallowed, @non_core, @not_yet, @insufficient_version, @deprecated);
137              
138 20         702 foreach my $phase ($self->phases)
139             {
140 74   100     151 foreach my $prereq (keys %{ $prereqs->{$phase}{requires} // {} })
  74         542  
141             {
142 26 100       126 next if $prereq eq 'perl';
143              
144 23 100   2   1103 if ($self->skip_module(sub { $_ eq $prereq }))
  2         8  
145             {
146 1         5 $self->log_debug([ 'skipping %s', $prereq ]);
147 1         241 next;
148             }
149              
150 22         169 $self->log_debug([ 'checking %s', $prereq ]);
151              
152 22 100   1   6546 if ($self->disallowed(sub { $_ eq $prereq }))
  1         4  
153             {
154 1         2 push @disallowed, [$phase, $prereq];
155 1         3 next;
156             }
157              
158 21         191 my $added_in = Module::CoreList->first_release($prereq);
159              
160 21 100       97605 if (not defined $added_in)
161             {
162 1         4 push @non_core, [$phase, $prereq];
163 1         6 next;
164             }
165              
166 20 100 66     1025 if (version->parse($added_in) > $self->starting_version
      66        
167             and ($self->check_dual_life_versions or not $self->_is_dual($prereq)))
168             {
169 2         8 push @not_yet, [$phase, $added_in, $prereq];
170 2         12 next;
171             }
172              
173 18 100 100     637 if ($self->check_dual_life_versions or not $self->_is_dual($prereq))
174             {
175 15 50       446 my $has = !exists($Module::CoreList::version{$self->starting_version->stringify}) ? 0
176             : $Module::CoreList::version{$self->starting_version->stringify}{$prereq};
177 15         522 $has = version->parse($has); # version.pm XS hates tie() - RT#87983
178 15         96 my $wanted = version->parse($prereqs->{$phase}{requires}{$prereq});
179              
180 15 100       126 if ($has < $wanted)
181             {
182 4         123 push @insufficient_version, [ map { "$_" } $phase, $prereq, $wanted, $self->starting_version->stringify, $has ];
  20         34  
183 4         22 next;
184             }
185             }
186              
187 14 100       450 if (not $self->deprecated_ok)
188             {
189 13         66 my $deprecated_in = Module::CoreList->deprecated_in($prereq);
190 13 100       193316 if ($deprecated_in)
191             {
192 1         4 push @deprecated, [$phase, $deprecated_in, $prereq];
193 1         6 next;
194             }
195             }
196             }
197             }
198              
199             $self->log(['detected a %s requires dependency that is explicitly disallowed: %s', @$_])
200 20         64 for @disallowed;
201              
202             $self->log(['detected a %s requires dependency that is not in core: %s', @$_])
203 20         241 for @non_core;
204              
205             $self->log(['detected a %s requires dependency that was not added to core until %s: %s', @$_])
206 20         392 for @not_yet;
207              
208             $self->log(['detected a %s requires dependency on %s %s: perl %s only has %s', @$_])
209 20         513 for @insufficient_version;
210              
211             $self->log(['detected a %s requires dependency that was deprecated from core in %s: %s', @$_])
212 20         1086 for @deprecated;
213              
214 20 100 100     635 $self->log_fatal('aborting build due to invalid dependencies')
      100        
      100        
      100        
215             if @disallowed || @non_core || @not_yet || @insufficient_version || @deprecated;
216             }
217              
218             # this will get easier if we can just ask MCL for this information, rather
219             # than guessing.
220             # returns undef if not indexed, otherwise true/false.
221             sub _is_dual
222             {
223             my ($self, $module) = @_;
224              
225             my $upstream = $Module::CoreList::upstream{$module};
226             $self->log_debug([ '%s is upstream=%s', $module, sub { $upstream // 'undef' } ]);
227             return 1 if defined $upstream and ($upstream eq 'cpan' or $upstream eq 'first-come');
228              
229             # if upstream=blead or =undef, we can't be sure if it's actually dual or
230             # not, so for now we'll have to ask the index and hope that the
231             # 'no_index' entries in the last perl release were complete.
232             # TODO: keep checking Module::CoreList for fixes.
233             my $dist_name = $self->_indexed_dist($module);
234             $self->log([ 'Warning: %s not indexed?!', $module ]), return undef if not defined $dist_name;
235              
236             $self->log_debug([ '%s is indexed in the %s dist', $module, $dist_name ]);
237             return $dist_name eq 'perl' ? 0 : 1;
238             }
239             {
240             my %is_dual;
241             around _is_dual => sub {
242             my $orig = shift;
243             my ($self, $module) = @_;
244              
245             return $is_dual{$module} if exists $is_dual{$module};
246             $is_dual{$module} = $self->$orig($module);
247             };
248             }
249              
250              
251             # if only the index were cached somewhere locally that I could query...
252             sub _indexed_dist
253             {
254 2     2   5 my ($self, $module) = @_;
255              
256 2         8 my $url = 'http://cpanmetadb.plackperl.org/v1.0/package/' . $module;
257 2         9 $self->log_debug([ 'fetching %s', $url ]);
258 2         378 my $res = HTTP::Tiny->new->get($url);
259 2 50       513 $self->log_debug('could not query the index?'), return undef if not $res->{success};
260              
261 2         5 my $data = $res->{content};
262              
263 2         2234 require HTTP::Headers;
264 2 50       5953 if (my $charset = HTTP::Headers->new(%{ $res->{headers} })->content_type_charset)
  2         19  
265             {
266 0         0 $data = Encode::decode($charset, $data, Encode::FB_CROAK);
267             }
268 2     1   1200 $self->log_debug([ 'got response: %s', sub { chomp $data; $data } ]);
  1         392  
  1         5  
269              
270 2         324 my $payload = YAML::Tiny->read_string($data);
271              
272 2 50       437 $self->log_debug('invalid payload returned?'), return undef unless $payload;
273 2 50       9 $self->log_debug([ '%s not indexed', $module ]), return undef if not defined $payload->[0]{distfile};
274 2         18 return CPAN::DistnameInfo->new($payload->[0]{distfile})->dist;
275             }
276              
277             __PACKAGE__->meta->make_immutable;
278              
279             __END__
280              
281             =pod
282              
283             =encoding UTF-8
284              
285             =head1 NAME
286              
287             Dist::Zilla::Plugin::OnlyCorePrereqs - Check that no prerequisites are declared that are not part of core
288              
289             =head1 VERSION
290              
291             version 0.024
292              
293             =head1 SYNOPSIS
294              
295             In your F<dist.ini>:
296              
297             [OnlyCorePrereqs]
298             starting_version = 5.010
299             skip = Test::Warnings
300             also_disallow = Scalar::Util
301              
302             =head1 DESCRIPTION
303              
304             C<[OnlyCorePrereqs]> is a L<Dist::Zilla> plugin that checks at build time if
305             you have any declared prerequisites that are not shipped with Perl.
306              
307             You can specify the first Perl version to check against, and which
308             prerequisite phase(s) are significant.
309              
310             If the check fails, the build is aborted.
311              
312             =for Pod::Coverage after_build mvp_aliases mvp_multivalue_args
313              
314             =head1 OPTIONS
315              
316             =head2 C<phase>
317              
318             Indicates a phase to check against. Can be provided more than once; defaults
319             to C<configure>, C<build>, C<runtime>, C<test>. (See L<Dist::Zilla::Plugin::Prereqs> for more
320             information about phases.)
321              
322             Remember that you can use different settings for different phases by employing
323             this plugin twice, with different names.
324              
325             =head2 C<starting_version>
326              
327             Indicates the first Perl version that should be checked against; any versions
328             earlier than this are not considered significant for the purposes of core
329             checks. Defaults to the minimum version of perl declared in the distribution's
330             prerequisites, or C<5.005>.
331              
332             There are two special values supported (available since version 0.003):
333              
334             =over 4
335              
336             =item * C<current> - indicates the version of Perl that you are currently running with
337             =item * C<latest> - indicates the most recent (stable or development) release of Perl
338              
339             =back
340              
341             (Note: if you wish to check against B<all> changes in core up to the very
342             latest Perl release, you should upgrade your L<Module::CoreList> installation.
343             You can guarantee you are always running the latest version with
344             L<Dist::Zilla::Plugin::PromptIfStale>. L<Module::CoreList> is also the mechanism used for
345             determining the version of the latest Perl release.)
346              
347             =head2 C<deprecated_ok>
348              
349             A boolean flag indicating whether it is considered acceptable to depend on a
350             deprecated module. Defaults to 0.
351              
352             =head2 C<check_dual_life_versions>
353              
354             Available since version 0.007.
355              
356             =for stopwords lifed blead
357              
358             A boolean flag indicating whether the specific module version available in the
359             C<starting_version> of perl be checked (even) if the module is dual-lifed.
360             Defaults to 1.
361              
362             This is useful to B<unset> if you don't want to fail if you require a core module
363             that the user can still upgrade via the CPAN, but do want to fail if the
364             module is B<only> available in core.
365              
366             Note that at the moment, the "is this module dual-lifed?" heuristic is not
367             100% reliable, as we may need to interrogate the PAUSE index to see if the
368             module is available outside of perl -- which can generate a false negative if
369             the module is upstream-blead and there was a recent release of a stable perl.
370             This is hopefully going to be rectified soon (when I add the necessary feature
371             to L<Module::CoreList>).
372              
373             (For example, a prerequisite of L<Test::More> 0.88 at C<starting_version>
374             5.010 would fail with C<check_dual_life_versions = 1>, as the version of
375             L<Test::More> that shipped with that version of perl was only 0.72,
376             but not fail if C<check_dual_life_versions = 0>.
377              
378             =head2 C<skip>
379              
380             Available since version 0.012.
381              
382             The name of a module to exempt from checking. Can be used more than once.
383              
384             =head2 C<also_disallow>
385              
386             Available since version 0.021.
387              
388             The name of a module to disallow from being used as a prereq, even if it would
389             pass all the other checks. This is primarily of use when building core modules
390             themselves, where certain other core modules cannot be used, to avoid circular
391             dependencies. Can be used more than once.
392              
393             =head1 SUPPORT
394              
395             =for stopwords irc
396              
397             Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=Dist-Zilla-Plugin-OnlyCorePrereqs>
398             (or L<bug-Dist-Zilla-Plugin-OnlyCorePrereqs@rt.cpan.org|mailto:bug-Dist-Zilla-Plugin-OnlyCorePrereqs@rt.cpan.org>).
399             I am also usually active on irc, as 'ether' at C<irc.perl.org>.
400              
401             =head1 AUTHOR
402              
403             Karen Etheridge <ether@cpan.org>
404              
405             =head1 COPYRIGHT AND LICENSE
406              
407             This software is copyright (c) 2013 by Karen Etheridge.
408              
409             This is free software; you can redistribute it and/or modify it under
410             the same terms as the Perl 5 programming language system itself.
411              
412             =head1 CONTRIBUTOR
413              
414             =for stopwords David Golden
415              
416             David Golden <dagolden@cpan.org>
417              
418             =cut