File Coverage

blib/lib/Dist/Zilla/Plugin/OnlyCorePrereqs.pm
Criterion Covered Total %
statement 100 101 99.0
branch 25 30 83.3
condition 21 23 91.3
subroutine 20 20 100.0
pod 0 3 0.0
total 166 177 93.7


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