File Coverage

blib/lib/Dist/Zilla/Plugin/StaticInstall.pm
Criterion Covered Total %
statement 133 134 99.2
branch 72 84 85.7
condition 33 40 82.5
subroutine 16 17 94.1
pod 0 3 0.0
total 254 278 91.3


line stmt bran cond sub pod time code
1 9     9   30208118 use strict;
  9         30  
  9         358  
2 9     9   61 use warnings;
  9         25  
  9         671  
3             package Dist::Zilla::Plugin::StaticInstall; # git description: v0.010-7-gd38f166
4             # vim: set ts=8 sts=4 sw=4 tw=115 et :
5             # ABSTRACT: (EXPERIMENTAL, DANGEROUS) Identify a distribution as eligible for static installation
6             # KEYWORDS: distribution metadata toolchain static dynamic installation
7              
8             our $VERSION = '0.011';
9              
10 9     9   66 use Moose;
  9         24  
  9         105  
11             with 'Dist::Zilla::Role::MetaProvider',
12             'Dist::Zilla::Role::InstallTool';
13              
14 9     9   74601 use Moose::Util::TypeConstraints;
  9         28  
  9         101  
15 9     9   23640 use MooseX::Types::Moose qw(Str Bool);
  9         29  
  9         129  
16 9     9   56060 use Scalar::Util 'blessed';
  9         29  
  9         754  
17 9     9   70 use List::Util 1.33 qw(first any);
  9         325  
  9         659  
18 9     9   4074 no autovivification;
  9         7548  
  9         72  
19 9     9   3168 use Term::ANSIColor 3.00 'colored';
  9         54343  
  9         5571  
20 9     9   103 use Path::Tiny;
  9         26  
  9         523  
21 9     9   73 use namespace::autoclean;
  9         47  
  9         106  
22              
23             my $mode_type = enum([qw(off on auto)]);
24             coerce $mode_type, from Str, via { $_ eq '0' ? 'off' : $_ eq '1' ? 'on' : $_ };
25             has mode => (
26             is => 'ro', isa => $mode_type,
27             default => 'on',
28             coerce => 1,
29             );
30              
31             has dry_run => (
32             is => 'ro', isa => Bool,
33             default => 0,
34             );
35              
36             around dump_config => sub
37             {
38             my ($orig, $self) = @_;
39             my $config = $self->$orig;
40              
41             $config->{+__PACKAGE__} = {
42             mode => $self->mode,
43             dry_run => $self->dry_run ? 1 : 0,
44             blessed($self) ne __PACKAGE__ ? ( version => $VERSION ) : (),
45             };
46              
47             return $config;
48             };
49              
50             sub BUILD
51             {
52 33     33 0 89 my $self = shift;
53 33 100 100     1411 $self->log_fatal('dry_run cannot be true if mode is "off" or "on"')
54             if $self->dry_run and $self->mode ne 'auto';
55             }
56              
57             sub metadata
58             {
59 31     31 0 118171 my $self = shift;
60 31         1816 my $mode = $self->mode;
61              
62 31 100       225 my $value = $mode eq 'on' ? 1 : $mode eq 'off' ? 0 : undef;
    100          
63 31 100       133 if (defined $value)
64             {
65 10         86 $self->log([ 'setting x_static_install to %s', $value ]);
66 10         5683 return +{ x_static_install => $value };
67             }
68              
69             # if mode = auto and dry_run = 0, we'll add it later
70 21         107 return +{};
71             }
72              
73             sub setup_installer
74             {
75 29     29 0 563337 my $self = shift;
76              
77             # even if mode = off or on, we still run all the heuristics, as an extra check.
78 29         187 my ($value, $message) = $self->_heuristics;
79              
80 29         1090 my $distmeta = $self->zilla->distmeta;
81              
82 29 100 100     2476 if ($self->mode ne 'off' and exists $distmeta->{x_static_install} and ($distmeta->{x_static_install} xor $value))
      100        
      100        
83             {
84 3 100       21 $self->log_fatal('something set x_static_install = 0 but we want to set it to 1') if $value;
85              
86 2 100       77 my $str1 = $self->mode eq 'on' ? 'mode = on' : 'x_static_install was set';
87 2 50       12 $message = [ $message ] if not ref $message;
88              
89 2         23 $self->log_fatal([
90             $str1 . ' but this distribution is ineligible: ' . $message->[0],
91             splice(@$message, 1)
92             ]);
93             }
94              
95 26 50       131 $self->${ \ ($self->dry_run ? 'log' : 'log_debug') }($message) if $message;
  17 100       785  
96              
97             # say what we would do, if dry run or heuristic different than requested
98 26 100 100     11969 $self->log([ colored('would set x_static_install to %s', 'yellow'), $value ])
      100        
99             if $self->dry_run or ($value and $self->mode eq 'off');
100              
101 26 100 66     2871 if (not exists $distmeta->{x_static_install} and $self->mode eq 'auto' and not $self->dry_run)
      100        
102             {
103 16         186 $self->log([ 'setting x_static_install to %s', $value ]);
104 16         8802 $distmeta->{x_static_install} = $value;
105             }
106             }
107              
108             # returns value, log message
109             sub _heuristics
110             {
111 29     29   89 my $self = shift;
112              
113 29         1118 my $distmeta = $self->zilla->distmeta;
114 29 100       2633 my $log = $self->dry_run ? 'log' : 'log_debug';
115              
116 29         308 $self->$log('checking dynamic_config');
117 29 100       14979 return (0, 'dynamic_config is true') if $distmeta->{dynamic_config};
118              
119 28         181 $self->$log('checking configure prereqs');
120 28 100       10699 my %extra_configure_requires = %{ $distmeta->{prereqs}{configure}{requires} || {} };
  28         471  
121 28         217 delete @extra_configure_requires{qw(ExtUtils::MakeMaker Module::Build::Tiny File::ShareDir::Install perl)};
122 28 50       191 return (0, [ 'found configure prereq%s %s',
    100          
123             keys(%extra_configure_requires) > 1 ? 's' : '',
124             join(', ', sort keys %extra_configure_requires) ]) if keys %extra_configure_requires;
125              
126 27         175 $self->$log('checking build prereqs');
127 27         10227 my @build_requires = grep { $_ ne 'perl' } keys %{ $distmeta->{prereqs}{build}{requires} };
  1         9  
  27         325  
128 27 50       169 return (0, [ 'found build prereq%s %s',
    100          
129             @build_requires > 1 ? 's' : '',
130             join(', ', sort @build_requires) ]) if @build_requires;
131              
132 26         176 $self->$log('checking execdirs');
133 26 100       9387 if (my @execfiles_plugins = @{ $self->zilla->plugins_with(-ExecFiles) })
  26         931  
134             {
135             my @bad_unempty_execdirs =
136 1         88 map { m{^([^/]+)/}g }
137 1         101 grep { path($_) !~ m{^script/} }
138 1         592 map { $_->name }
139 2         4231 map {; @{ $_->find_files } }
  2         6  
  2         18  
140             @execfiles_plugins;
141              
142 2 50       451 return (0, [ 'found ineligible executable dir%s \'%s\'',
    100          
143             (@bad_unempty_execdirs == 1 ? '' : 's'), join(', ', @bad_unempty_execdirs) ])
144             if @bad_unempty_execdirs;
145              
146 1 50       6 if (my @bad_execdirs =
147 1         20 grep { $_ ne 'script' }
148 1         43 map { $_->dir }
149 1         12 grep { $_->isa('Dist::Zilla::Plugin::ExecDir') }
150             @execfiles_plugins)
151             {
152 1 50       14 $self->log([ colored('found ineligible executable dir%s \'%s\' configured: better to avoid', 'yellow'),
153             (@bad_execdirs == 1 ? '' : 's'), join(', ', @bad_execdirs) ]);
154             }
155             }
156              
157 25         31267 $self->$log('checking sharedirs');
158 25         10114 my @module_sharedirs = keys %{ $self->zilla->_share_dir_map->{module} };
  25         978  
159 25 50       5471 return (0, [ 'found module sharedir%s for %s',
    100          
160             @module_sharedirs > 1 ? 's' : '',
161             join(', ', sort @module_sharedirs) ]) if @module_sharedirs;
162              
163 24         143 $self->$log('checking installer plugins');
164 24         8896 my @installers = @{ $self->zilla->plugins_with(-InstallTool) };
  24         800  
165              
166             # we need to be last, to see the final copy of the installer files
167 24 100       27565 return (0, [ 'this plugin must be after %s', blessed($installers[-1]) ]) if $installers[-1] != $self;
168              
169 23 100       124 return (0, [ 'a recognized installer plugin must be used' ]) if @installers < 2;
170              
171             # only these installer plugins can be trusted to not add disqualifying content
172 22         80 my @other_installers = grep { blessed($_) !~ /^Dist::Zilla::Plugin::((MakeMaker|ModuleBuildTiny)(::Fallback)?|StaticInstall)$/ } @installers;
  44         400  
173             return (0, [ 'found install tool%s %s that will add extra content to Makefile.PL, Build.PL',
174             @other_installers > 1 ? 's' : '',
175 22 50       133 join(', ', sort map { blessed($_) } @other_installers) ]) if @other_installers;
  1 100       22  
176              
177             # check that no other plugins put their grubby hands on our installer file(s)
178 21 100       58 foreach my $installer_file (grep { $_->name eq 'Makefile.PL' or $_->name eq 'Build.PL' } @{ $self->zilla->files })
  88         7820  
  21         703  
179             {
180 21         1862 $self->$log([ 'checking for munging of %s', $installer_file->name ]);
181              
182 21         10772 foreach my $added_by (split(/; /, $installer_file->added_by))
183             {
184             return (0, [ '%s %s', $installer_file->name, $added_by ])
185             if $added_by =~ /from coderef added by/
186             or $added_by =~ /filename set by/
187             or ($added_by =~ /content set by .* \((.*) line \d+\)/
188             and not ($1 eq 'Dist::Zilla::Plugin::MakeMaker::Awesome'
189 42 100 33 0   2542 and any { blessed($_) eq 'Dist::Zilla::Plugin::MakeMaker::Fallback' } @installers)
  0   33        
      66        
      100        
      66        
190             and $1 !~ /Dist::Zilla::Plugin::(MakeMaker|ModuleBuildTiny)(::Fallback)?$/);
191             }
192             }
193              
194 20         157 $self->$log('checking META.json');
195 20     148   7059 my $metajson = first { blessed($_) eq 'Dist::Zilla::Plugin::MetaJSON' } @{ $self->zilla->plugins };
  148         1173  
  20         698  
196 20 100       173 return (0, 'META.json is not being added to the distribution') if not $metajson;
197 14 50       519 return (0, [ 'META.json is using meta-spec version %s', $metajson->version ]) if $metajson->version < '2';
198              
199 14         183 my @filenames = map { $_->name } @{ $self->zilla->files };
  67         3465  
  14         401  
200              
201 14         776 $self->$log('checking for .xs files');
202 14         4483 my @xs_files = grep { /\.xs$/ } @filenames;
  67         213  
203 14 50       90 return (0, [ 'found .xs file%s %s', @xs_files > 1 ? 's' : '', join(', ', sort @xs_files) ]) if @xs_files;
    100          
204              
205 13         507 my $BASEEXT = (split(/-/, $self->zilla->name))[-1];
206              
207 13         708 $self->$log('checking .pm, .pod, .pl files');
208 13 100       3762 my @root_files = grep { m{^[^/]*\.(pm|pl|pod)$} and !m{^lib/} } @filenames;
  62         335  
209 13 100       121 return (0, [ 'found %s in the root', join(', ', sort @root_files) ]) if @root_files;
210              
211 12 50       77 my @baseext_files = $BASEEXT eq 'lib' ? () : grep { m{^$BASEEXT/[^/]*\.(pm|pl|pod)$} } @filenames;
  54         597  
212 12 100       48 return (0, [ 'found %s in %s/', join(', ', sort map { s{^$BASEEXT/}{}; $_ } @baseext_files), $BASEEXT ]) if @baseext_files;
  3         19  
  3         20  
213              
214 11         70 $self->$log('checking for .PL, .pmc files');
215 11 100       3108 my @PL_files = grep { !/^(Makefile|Build)\.PL$/ and /\.(PL|pmc)$/ } @filenames;
  46         323  
216 11 100       63 return (0, [ 'found %s', join(', ', sort @PL_files) ]) if @PL_files;
217              
218 10         94 return 1;
219             }
220              
221             __PACKAGE__->meta->make_immutable;
222              
223             __END__
224              
225             =pod
226              
227             =encoding UTF-8
228              
229             =head1 NAME
230              
231             Dist::Zilla::Plugin::StaticInstall - (EXPERIMENTAL, DANGEROUS) Identify a distribution as eligible for static installation
232              
233             =head1 VERSION
234              
235             version 0.011
236              
237             =head1 SYNOPSIS
238              
239             In your F<dist.ini>:
240              
241             ; when you are confident this is correct
242             [StaticInstall]
243             mode = on
244              
245             ; trust us to set the right value (DANGER!)
246             [StaticInstall]
247             mode = auto
248              
249             ; be conservative; just tell us what the value should be
250             [StaticInstall]
251             mode = auto
252             dry_run = 1
253              
254             =head1 DESCRIPTION
255              
256             This is a L<Dist::Zilla> plugin that, when C<mode> is C<on>, provides the following distribution metadata:
257              
258             x_static_install : "1"
259              
260             The plugin performs a number of checks against the distribution to determine
261             the proper value of the C<x_static_install> metadata field. When set to a true
262             value, this indicates that the can skip a number of installation steps
263             (such as running F<Makefile.PL> or F<Build.PL> and acting on its side effects).
264              
265             The definition of a "static installation" is being prototyped by the Perl
266             Toolchain Gang and is still being refined. B<DO NOT USE THIS PLUGIN> if you
267             are not involved in this testing. The proper installation of the built
268             distribution cannot be guaranteed if installed with a static install-enabled
269             client.
270              
271             The tentative specification is spelled out in more detail in
272             L<https://github.com/Leont/cpan-static/blob/master/lib/CPAN/Static/Spec.pm>.
273              
274             This plugin currently checks these conditions (if all are true, C<x_static_install> can be true):
275              
276             =for stopwords sharedir
277              
278             =over 4
279              
280             =item *
281              
282             C<dynamic_config> must be false in metadata
283              
284             =item *
285              
286             no prerequisites in configure-requires other than L<ExtUtils::MakeMaker>, L<Module::Build::Tiny>, or L<File::ShareDir::Install>
287              
288             =item *
289              
290             no prerequisites in build-requires
291              
292             =item *
293              
294             no L<files to be installed as executables|Dist::Zilla::Plugin::ExecDir> outside of the F<script> directory
295              
296             =item *
297              
298             no L<module sharedir|Dist::Zilla::Plugin::ModuleShareDirs> (a L<distribution sharedir|Dist::Zilla::Plugin::ShareDir> is okay)
299              
300             =item *
301              
302             no installer plugins permitted other than:
303              
304             =over 4
305              
306             =item *
307              
308             L<Dist::Zilla::Plugin::MakeMaker>
309              
310             =item *
311              
312             L<Dist::Zilla::Plugin::MakeMaker::Fallback>
313              
314             =item *
315              
316             L<Dist::Zilla::Plugin::ModuleBuildTiny>
317              
318             =item *
319              
320             L<Dist::Zilla::Plugin::ModuleBuildTiny::Fallback>
321              
322             =back
323              
324             =item *
325              
326             an installer plugin from the above list B<must> be used (a manually-generated F<Makefile.PL> or F<Build.PL> is not permitted)
327              
328             =item *
329              
330             no other plugins may modify F<Makefile.PL> nor F<Build.PL>
331              
332             =item *
333              
334             the L<C<[MetaJSON]>|Dist::Zilla::Plugin::MetaJSON> plugin must be used, at (the default) meta-spec version 2
335              
336             =item *
337              
338             no F<.xs> files may be present
339              
340             =item *
341              
342             F<.pm>, F<.pod>, F<.pl> files may not be present in the root of the distribution or in C<BASEEXT> (where C<BASEEXT> is the last component of the distribution name)
343              
344             =item *
345              
346             F<.pmc> and F<.PL> files (excluding F<Makefile.PL>, F<Build.PL>) may not be present
347              
348             =back
349              
350             =head1 CONFIGURATION OPTIONS
351              
352             =head2 C<mode>
353              
354             =for stopwords usecase
355              
356             When set to C<on>, the value of C<x_static_install> is set to 1 (the normal usecase).
357              
358             When set to C<off>, the value of C<x_static_install> is set to 0, which is
359             equivalent to not providing this field at all.
360              
361             When set to C<auto>, we attempt to calculate the proper value. When used with
362             C<dry_run = 1>, the value isn't actually stored, but just provided in a
363             diagnostic message. This is the recommended usage in a plugin bundle, for
364             testing against a number of distributions at once.
365              
366             The calculations are always performed, no matter the value of C<mode> -- if it
367             comes up with a different result than what you are setting, this is logged. If
368             C<mode = on> and the calculations discover the distribution is ineligible for
369             this flag, the build fails, to prevent you from releasing bad metadata.
370              
371             =head2 C<dry_run>
372              
373             When true, no value is set in metadata, but verbose logging is enabled so you
374             can see what the value would have been.
375              
376             =for Pod::Coverage BUILD metadata setup_installer
377              
378             =head1 SEE ALSO
379              
380             =over 4
381              
382             =item *
383              
384             L<CPAN::Meta::Spec>
385              
386             =item *
387              
388             L<CPAN::Static::Spec|https://github.com/Leont/cpan-static>.
389              
390             =back
391              
392             =head1 SUPPORT
393              
394             Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=Dist-Zilla-Plugin-StaticInstall>
395             (or L<bug-Dist-Zilla-Plugin-StaticInstall@rt.cpan.org|mailto:bug-Dist-Zilla-Plugin-StaticInstall@rt.cpan.org>).
396              
397             There is also a mailing list available for users of this distribution, at
398             L<http://dzil.org/#mailing-list>.
399              
400             There is also an irc channel available for users of this distribution, at
401             L<C<#distzilla> on C<irc.perl.org>|irc://irc.perl.org/#distzilla>.
402              
403             I am also usually active on irc, as 'ether' at C<irc.perl.org>.
404              
405             =head1 AUTHOR
406              
407             Karen Etheridge <ether@cpan.org>
408              
409             =head1 COPYRIGHT AND LICENCE
410              
411             This software is copyright (c) 2015 by Karen Etheridge.
412              
413             This is free software; you can redistribute it and/or modify it under
414             the same terms as the Perl 5 programming language system itself.
415              
416             =cut