File Coverage

blib/lib/ExtUtils/BundleMaker.pm
Criterion Covered Total %
statement 132 143 92.3
branch 28 36 77.7
condition 5 9 55.5
subroutine 21 22 95.4
pod 1 1 100.0
total 187 211 88.6


line stmt bran cond sub pod time code
1             package ExtUtils::BundleMaker;
2              
3 3     3   153197 use strict;
  3         7  
  3         118  
4 3     3   17 use warnings FATAL => 'all';
  3         8  
  3         137  
5 3     3   2450 use version;
  3         7091  
  3         19  
6              
7 3     3   3054 use Moo;
  3         52689  
  3         20  
8 3     3   8277 use MooX::Options with_config_from_file => 1;
  3         5096  
  3         18  
9 3     3   1243021 use Module::CoreList ();
  3         145703  
  3         1822  
10 3     3   57 use Module::Runtime qw/require_module use_module module_notional_filename/;
  3         9  
  3         41  
11 3     3   414 use File::Basename qw/dirname/;
  3         8  
  3         320  
12 3     3   20 use File::Path qw//;
  3         32  
  3         82  
13 3     3   3973 use File::Slurp qw/read_file write_file/;
  3         44252  
  3         261  
14 3     3   42 use File::Spec qw//;
  3         8  
  3         66  
15 3     3   21 use Params::Util qw/_HASH _ARRAY/;
  3         7  
  3         275  
16 3     3   17 use Sub::Quote qw/quote_sub/;
  3         16  
  3         5271  
17              
18             =head1 NAME
19              
20             ExtUtils::BundleMaker - Supports making bundles of modules recursively
21              
22             =cut
23              
24             our $VERSION = '0.006';
25              
26             =head1 SYNOPSIS
27              
28             use ExtUtils::BundleMaker;
29              
30             my $eu_bm = ExtUtils::BundleMaker->new(
31             modules => [ 'Important::One', 'Mandatory::Dependency' ],
32             # down to which perl version core modules shall be included?
33             recurse => 'v5.10',
34             target => 'inc/bundle.inc',
35             );
36             # create bundle
37             $eu_bm->make_bundle();
38              
39             =head1 DESCRIPTION
40              
41             ExtUtils::BundleMaker is designed to support authors automatically create
42             a bundle of important prerequisites which aren't needed outside of the
43             distribution but might interfere or overload target.
44              
45             Because of no dependencies are recorded within a distribution, entire
46             distributions of recorded dependencies are bundled.
47              
48             =head1 ATTRIBUTES
49              
50             Following attributes are supported by ExtUtils::BundleMaker
51              
52             =head2 modules
53              
54             Specifies name of module(s) to create bundle for
55              
56             =head2 target
57              
58             Specifies target for bundle
59              
60             =head2 recurse
61              
62             Specify the Perl core version to recurse until.
63              
64             =head2 name
65              
66             Allows to specify a package name for generated bundle. Has C<has_> predicate
67             for test whether it's set or not.
68              
69             =head1 METHODS
70              
71             =cut
72              
73             sub _coerce_modules
74             {
75 3     3   150 my $modules = shift;
76 3 50       24 _HASH($modules) and return $modules;
77 3         17 _ARRAY($modules) and return {
78             map {
79 3 50       24 my ( $m, $v ) = split( /=/, $_, 2 );
80 3 100       77 defined $v or $v = 0;
81 3         84 ( $m => $v )
82             } @$modules
83             };
84 0         0 die "Inappropriate format: $modules";
85             }
86              
87             option modules => (
88             is => "ro",
89             doc => "Specifies name of module(s) to create bundle for",
90             required => 1,
91             format => "s@",
92             autosplit => ",",
93             coerce => \&_coerce_modules,
94             );
95              
96             option recurse => (
97             is => "lazy",
98             doc => "Automatically bundles dependencies for specified Perl version",
99             required => 1,
100             format => "s",
101             isa => quote_sub(q{ exists $Module::CoreList::version{$_[0]} or die "Unsupported Perl version: $_[0]" }),
102             coerce => quote_sub(q{ my $nv = version->new($_[0])->numify; $nv =~ s/0+$//; $nv; }),
103             );
104              
105             option target => (
106             is => "ro",
107             doc => "Specifies target for bundle",
108             required => 1,
109             format => "s"
110             );
111              
112             option name => (
113             is => "ro",
114             doc => "Allows to specify a package name for generated bundle",
115             format => "s",
116             predicate => 1,
117             );
118              
119             has _remaining_deps => (
120             is => "lazy",
121             init_args => undef
122             );
123              
124             has _provided => (
125             is => "ro",
126             default => sub { {} },
127             init_args => undef
128             );
129              
130 3     3   1492 sub _build__remaining_deps { {} }
131              
132             sub _build_recurse
133             {
134 1     1   842 $];
135             }
136              
137             has chi_init => ( is => "lazy" );
138              
139             sub _build_chi_init
140             {
141 0     0   0 my %chi_args = (
142             driver => 'File',
143             root_dir => '/tmp/metacpan-cache',
144             );
145 0         0 return \%chi_args;
146             }
147              
148             has _meta_cpan => (
149             is => "lazy",
150             init_arg => undef,
151             );
152              
153             sub _build__meta_cpan
154             {
155 3     3   846 my $self = shift;
156 3         21 require_module("MetaCPAN::Client");
157 3         312876 my %ua;
158 3         8 eval {
159 3         17 use_module("CHI");
160 0         0 use_module("WWW::Mechanize::Cached");
161 0         0 use_module("HTTP::Tiny::Mech");
162 0         0 %ua = (
163             ua => HTTP::Tiny::Mech->new(
164             mechua => WWW::Mechanize::Cached->new(
165 0         0 cache => CHI->new( %{ $self->chi_init } ),
166             )
167             )
168             );
169             };
170 3         1572 my $mcpan = MetaCPAN::Client->new(%ua);
171 3         6099 return $mcpan;
172             }
173              
174             has requires => (
175             is => "lazy",
176             );
177              
178             sub _build_requires
179             {
180 3     3   840 my $self = shift;
181 3         36 my $core_v = $self->recurse;
182 3         936 my $mcpan = $self->_meta_cpan;
183 3         6 my %modules = %{ $self->modules };
  3         29  
184 3         13 my @required = sort keys %modules;
185 3         9 my %core_req;
186             my %satisfied;
187 0         0 my @loaded;
188              
189 3         26 while (@required)
190             {
191 10         37 my $modname = shift @required;
192 10 50       38 $modname eq "perl" and next; # XXX update $core_v if gt and rerun?
193 10         66 my $mod = $mcpan->module($modname);
194 10 50       593904 $mod->distribution eq "perl" and next;
195 10         1465 my $dist = $mcpan->release( $mod->distribution );
196 10 50       251667 unless ( $dist->provides )
197             {
198 0         0 warn $mod->distribution . " provides nothing - skip bundling";
199 0         0 $core_req{$modname} = $modules{$modname};
200 0         0 next;
201             }
202 10         1556 foreach my $dist_mod ( @{ $dist->provides } )
  10         404  
203             {
204 43 100       1652 $satisfied{$dist_mod} and next;
205 28         117 push @loaded, $dist_mod;
206 28         103 $satisfied{$dist_mod} = 1;
207 28         355 eval {
208 28         418 my $pmod = $mcpan->module($dist_mod);
209 28         864822 $satisfied{$_} = 1 for ( map { $_->{name} } @{ $pmod->module } );
  43         4715  
  28         1258  
210             };
211             }
212              
213 19 100       109 my %deps = map { $_->{module} => $_->{version} }
  48         8061  
214 10         33 grep { $_->{phase} eq "runtime" and $_->{relationship} eq "requires" } @{ $dist->dependency };
  10         284  
215 10         184 foreach my $dep ( keys %deps )
216             {
217 19 50       94 defined $satisfied{$dep} and next;
218             # nice use-case for part, but will result in chicken-egg situation
219 19 100 33     204 if (
    100 66        
220             Module::CoreList::is_core( $dep, $deps{$dep} ? $deps{$dep} : undef, $core_v )
221             and not( Module::CoreList::deprecated_in($dep)
222             or Module::CoreList::removed_from($dep) )
223             )
224             {
225 12 50 66     437094 defined( $core_req{$dep} )
226             and version->new( $core_req{$dep} ) > version->new( $deps{$dep} )
227             and next;
228 12         261 $core_req{$dep} = $deps{$dep};
229             }
230             else
231             {
232 7         161847 push @required, $dep;
233 7         317 $modules{$dep} = $deps{$dep};
234             }
235             }
236             }
237              
238 3         12 delete $modules{perl};
239              
240             # update modules for loader ...
241 3         12 %{ $self->modules } = %modules;
  3         42  
242 3         17 %{ $self->_remaining_deps } = %core_req;
  3         79  
243              
244 3         61 [ reverse @loaded ];
245             }
246              
247             has _bundle_body_stub => ( is => "lazy" );
248              
249             sub _build__bundle_body_stub
250             {
251 2     2   960 my $self = shift;
252 2         7 my $_body_stub = "";
253              
254 2 100       20 $self->has_name and $_body_stub .= "package " . $self->name . ";\n\n";
255              
256 2         5 $_body_stub .= <<'EOU';
257             use IPC::Cmd qw(run QUOTE);
258              
259             sub check_module
260             {
261             my ($mod, $ver) = @_;
262             my $test_code = QUOTE . "$mod->VERSION($ver)" . QUOTE;
263             ($ok, $err, $full_buf, $stdout_buff, $stderr_buff) = run( command => "$^X -M$mod -e $test_code");
264             return $ok;
265             }
266              
267             EOU
268              
269 2         5 my @requires = @{ $self->requires };
  2         49  
270 2 100       52 $self->has_name
271             and $_body_stub .= sprintf
272             <<'EOR', Data::Dumper->new( [ $self->_remaining_deps ] )->Terse(1)->Purity(1)->Useqq(1)->Sortkeys(1)->Dump, Data::Dumper->new( [ $self->_provided ] )->Terse(1)->Purity(1)->Useqq(1)->Sortkeys(1)->Dump, Data::Dumper->new( [ $self->requires ] )->Terse(1)->Purity(1)->Useqq(1)->Sortkeys(1)->Dump;
273             sub remaining_deps
274             {
275             return %s
276             }
277              
278             sub provided_bundle
279             {
280             return %s
281             }
282              
283             sub required_order
284             {
285             return %s
286             }
287              
288             EOR
289              
290 2         623 return $_body_stub;
291             }
292              
293             has _bundle_body => ( is => "lazy" );
294              
295             sub _build__bundle_body
296             {
297 3     3   1063 my $self = shift;
298              
299 3         10 my @requires = @{ $self->requires };
  3         36  
300             # keep order; requires builder might update modules
301 3         7 my %modules = %{ $self->modules };
  3         23  
302 3         9 my $body = "";
303              
304 3         9 foreach my $mod (@requires)
305             {
306 18         37 my $modv = $modules{$mod};
307 18 100       58 defined $modv or $modv = 0;
308 18 100       86 my $mnf = module_notional_filename( $modv ? use_module( $mod, $modv ) : use_module($mod) );
309 17         42926 $body .= sprintf <<'EOU', $mod, $modv;
310             check_module("%s", "%s") or do { eval <<'END_OF_EXTUTILS_BUNDLE_MAKER_MARKER';
311             EOU
312              
313 17         93 $body .= read_file( $INC{$mnf} );
314 17         3548 $body .= "\nEND_OF_EXTUTILS_BUNDLE_MAKER_MARKER\n\n";
315 17         38 $body .= " \$@ and die \$@;\n";
316 17         62 $body .= sprintf " defined \$INC{'%s'} or \$INC{'%s'} = 'Bundled';\n};\n", $mnf, $mnf;
317 17         43 $body .= "\n";
318              
319 17         240 $modv = $mod->VERSION;
320 17 100       75 defined $modv or $modv = 0;
321 17         65 $modules{$mod} = $modv;
322             }
323              
324 2         11 %{ $self->_provided } = %modules;
  2         38  
325              
326 2         498 return $body;
327             }
328              
329             =head2 make_bundle
330              
331             =cut
332              
333             sub make_bundle
334             {
335 3     3 1 571 my $self = shift;
336 3         19 my $target = $self->target;
337              
338 3         49 my $body = $self->_bundle_body . "\n1;\n";
339             # stub contains additional information when module is generated
340 2         15 $body = $self->_bundle_body_stub . $body;
341              
342 2         358 my $target_dir = dirname($target);
343 2 50       102 -d $target_dir or File::Path::make_path($target_dir);
344              
345 2         14 return write_file( $target, $body );
346             }
347              
348             =head1 AUTHOR
349              
350             Jens Rehsack, C<< <rehsack at cpan.org> >>
351              
352             =head1 BUGS
353              
354             Please report any bugs or feature requests to
355             C<bug-extutils-bundlemaker at rt.cpan.org>, or through the web interface at
356             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ExtUtils-BundleMaker>.
357             I will be notified, and then you'll automatically be notified of progress
358             on your bug as I make changes.
359              
360             =head1 SUPPORT
361              
362             You can find documentation for this module with the perldoc command.
363              
364             perldoc ExtUtils::BundleMaker
365              
366             You can also look for information at:
367              
368             =over 4
369              
370             =item * RT: CPAN's request tracker (report bugs here)
371              
372             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ExtUtils-BundleMaker>
373              
374             =item * AnnoCPAN: Annotated CPAN documentation
375              
376             L<http://annocpan.org/dist/ExtUtils-BundleMaker>
377              
378             =item * CPAN Ratings
379              
380             L<http://cpanratings.perl.org/d/ExtUtils-BundleMaker>
381              
382             =item * Search CPAN
383              
384             L<http://search.cpan.org/dist/ExtUtils-BundleMaker/>
385              
386             =back
387              
388              
389             =head1 ACKNOWLEDGEMENTS
390              
391              
392             =head1 LICENSE AND COPYRIGHT
393              
394             Copyright 2014 Jens Rehsack.
395              
396             This program is free software; you can redistribute it and/or modify it
397             under the terms of either: the GNU General Public License as published
398             by the Free Software Foundation; or the Artistic License.
399              
400             See L<http://dev.perl.org/licenses/> for more information.
401              
402              
403             =cut
404              
405             1; # End of ExtUtils::BundleMaker