File Coverage

blib/lib/Dist/Zilla/Plugin/MetaData/BuiltWith.pm
Criterion Covered Total %
statement 166 200 83.0
branch 45 70 64.2
condition 5 7 71.4
subroutine 26 30 86.6
pod 2 4 50.0
total 244 311 78.4


line stmt bran cond sub pod time code
1 8     8   3047714 use 5.006;
  8         23  
2 8     8   33 use strict;
  8         9  
  8         178  
3 8     8   28 use warnings;
  8         12  
  8         502  
4              
5             package Dist::Zilla::Plugin::MetaData::BuiltWith;
6              
7             our $VERSION = '1.004003';
8              
9             # ABSTRACT: Report what versions of things your distribution was built against
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 8     8   34 use Carp qw( carp croak );
  8         9  
  8         524  
14 8     8   34 use Config qw();
  8         11  
  8         143  
15 8     8   461 use Moose 2.0;
  8         347669  
  8         48  
16 8     8   36073 use Moose qw( with has around );
  8         12  
  8         28  
17 8     8   7570 use MooseX::Types::Moose qw( ArrayRef Bool Str );
  8         113012  
  8         114  
18 8     8   30097 use Module::Runtime qw( is_module_name );
  8         12  
  8         55  
19 8     8   4890 use Devel::CheckBin qw( can_run );
  8         615370  
  8         598  
20 8     8   1816 use Path::Tiny qw( path );
  8         20363  
  8         432  
21 8     8   49 use namespace::autoclean;
  8         12  
  8         137  
22             with 'Dist::Zilla::Role::FileGatherer';
23             with 'Dist::Zilla::Role::FileMunger';
24             with 'Dist::Zilla::Role::MetaProvider';
25              
26              
27              
28              
29              
30              
31              
32 6     6 1 965 sub mvp_multivalue_args { return qw( exclude include ) }
33              
34              
35              
36              
37              
38              
39              
40              
41              
42              
43             has _exclude => (
44             init_arg => 'exclude',
45             is => 'ro',
46             isa => ArrayRef,
47             default => sub { [] },
48             traits => [qw( Array )],
49             handles => { exclude => 'elements', },
50             );
51              
52              
53              
54              
55              
56              
57              
58              
59              
60              
61             has _include => (
62             init_arg => 'include',
63             is => 'ro',
64             isa => ArrayRef,
65             default => sub { [] },
66             traits => [qw( Array )],
67             handles => { include => 'elements', },
68              
69             );
70              
71              
72              
73              
74              
75              
76              
77              
78              
79             has show_config => ( is => 'ro', isa => 'Bool', default => 0 );
80              
81              
82              
83              
84              
85              
86              
87              
88              
89             has show_uname => ( is => 'ro', isa => Bool, default => 0 );
90              
91              
92              
93              
94              
95              
96              
97              
98              
99             has uname_call => ( is => 'ro', isa => Str, default => 'uname' );
100              
101              
102              
103              
104              
105              
106              
107              
108              
109             has uname_args => ( is => 'ro', isa => Str, default => '-a' );
110             has _uname_args => (
111             init_arg => undef,
112             is => 'ro',
113             isa => ArrayRef,
114             lazy_build => 1,
115             traits => [qw( Array )],
116             handles => { _all_uname_args => 'elements', },
117             );
118             has _stash_key => ( is => 'ro', isa => Str, default => 'x_BuiltWith' );
119              
120              
121              
122              
123              
124              
125              
126              
127              
128              
129              
130              
131              
132              
133              
134              
135              
136              
137              
138              
139              
140              
141              
142              
143              
144              
145              
146             has 'use_external_file' => (
147             is => 'ro',
148             lazy_build => 1,
149             );
150              
151              
152              
153              
154              
155              
156              
157              
158              
159              
160              
161              
162              
163              
164              
165              
166              
167             has 'external_file_name' => (
168             is => 'ro',
169             isa => Str,
170             lazy_build => 1,
171             );
172              
173             around dump_config => sub {
174             my ( $orig, $self, @args ) = @_;
175             my $config = $self->$orig(@args);
176             my $payload = $config->{ +__PACKAGE__ } = {};
177              
178             $payload->{show_uname} = $self->show_uname;
179             $payload->{_stash_key} = $self->_stash_key;
180             $payload->{show_config} = $self->show_config;
181             $payload->{use_external_file} = $self->use_external_file;
182             $payload->{external_file_name} = $self->external_file_name;
183              
184             if ( $self->show_uname ) {
185             $payload->{'uname'} = {
186             uname_call => $self->uname_call,
187             uname_args => $self->_uname_args,
188             };
189             }
190              
191             if ( $self->exclude ) {
192             $payload->{exclude} = [ $self->exclude ];
193             }
194             if ( $self->include ) {
195             $payload->{include} = [ $self->include ];
196             }
197              
198             ## no critic (RequireInterpolationOfMetachars)
199             # Self report when inherited.
200             $payload->{ q[$] . __PACKAGE__ . '::VERSION' } = $VERSION unless __PACKAGE__ eq ref $self;
201             $payload->{q[$Module::Metadata::VERSION]} = $Module::Metadata::VERSION if $INC{'Module/Metadata.pm'};
202             return $config;
203             };
204              
205             __PACKAGE__->meta->make_immutable;
206 8     8   3513 no Moose;
  8         13  
  8         70  
207              
208             sub _config {
209 6     6   13 my $self = shift;
210 6 100       256 return () unless $self->show_config;
211 1         6 my @interesting = qw( git_describe git_commit_id git_commit_date myarchname gccversion osname osver );
212 1         2 my $interested = {};
213 1         3 for my $key (@interesting) {
214             ## no critic (ProhibitPackageVars)
215 7 100 100     141 if ( defined $Config::Config{$key} and $Config::Config{$key} ne q{} ) {
216 3         11 $interested->{$key} = $Config::Config{$key};
217             }
218             }
219 1         9 return ( 'perl-config', $interested );
220             }
221              
222             sub _uname {
223 6     6   11 my $self = shift;
224 6 100       211 return () unless $self->show_uname;
225             {
226 1         2 my $str;
  1         1  
227 1 50       34 if ( not can_run( $self->uname_call ) ) {
228 0         0 $self->log( q[can't invoke ] . $self->uname_call . q[ on this device] );
229 0         0 return ();
230             }
231 1 50       342 last unless open my $fh, q{-|}, $self->uname_call, $self->_all_uname_args;
232 1         392 while ( my $line = <$fh> ) {
233 1         4 chomp $line;
234 1         14 $str .= $line;
235             }
236 1 50       37 last unless close $fh;
237 1         39 return ( 'uname', $str );
238              
239             }
240             ## no critic ( ProhibitPunctuationVars )
241              
242 0         0 $self->_my_log_fatal( 'Error calling uname:', $@, $! );
243              
244 0         0 return ();
245              
246             }
247              
248             sub _my_log_fatal {
249 0     0   0 my ($self) = @_;
250             ## no critic ( RequireInterpolationOfMetachars )
251 0         0 return $self->log_fatal( [ "%s\n %s:%s\n %s:%s", shift, q{$@}, shift, q{$!}, shift ] );
252             }
253              
254             sub _build__uname_args {
255 1     1   1 my $self = shift;
256             ## no critic ( RequireDotMatchAnything RequireExtendedFormatting RequireLineBoundaryMatching )
257 1 50       34 return [ grep { defined $_ && $_ ne q{} } split /\s+/, $self->uname_args ];
  1         44  
258             }
259              
260             sub _build_use_external_file {
261 6     6   189 return;
262             }
263              
264             sub _build_external_file_name {
265 0     0   0 return 'misc/built_with.json';
266             }
267              
268              
269              
270              
271              
272             sub metadata {
273 6     6 0 50839 my ($self) = @_;
274 6 50 50     227 return {} unless 'only' eq ( $self->use_external_file || q[] );
275 0         0 return { $self->_stash_key, { external_file => $self->external_file_name }, };
276             }
277              
278             sub _get_prereq_modnames {
279 6     6   13 my ($self) = @_;
280              
281 6         11 my $modnames = {};
282              
283 6         203 my $prereqs = $self->zilla->prereqs->as_string_hash;
284             ## use critic
285 6 50       2816 if ( not %{$prereqs} ) {
  6         52  
286 0         0 $self->log(q{WARNING: No prereqs were found, probably a bug});
287 0         0 return [];
288             }
289 6         13 $self->log_debug( [ '%s phases defined: %s ', scalar keys %{$prereqs}, ( join q{,}, keys %{$prereqs} ) ] );
  6         14  
  6         33  
290              
291 6         1403 for my $phase_name ( keys %{$prereqs} ) {
  6         18  
292 6         11 my $phase_data = $prereqs->{$phase_name};
293 6 50       43 next unless defined $phase_data;
294 6         12 my $phase_deps = {};
295 6         18 for my $type ( keys %{$phase_data} ) {
  6         17  
296 6         11 my $type_data = $phase_data->{$type};
297 6 50       17 next unless defined $type_data;
298 6         8 for my $module ( keys %{$type_data} ) {
  6         16  
299 12         27 $phase_deps->{$module} = 1;
300             }
301             }
302 6         11 $self->log_debug( [ 'Prereqs for %s: %s', $phase_name, join q{,}, keys %{$phase_deps} ] );
  6         46  
303 6         1383 $modnames = { %{$modnames}, %{$phase_deps} };
  6         14  
  6         28  
304              
305             }
306 6         10 return [ sort keys %{$modnames} ];
  6         42  
307             }
308              
309             sub _detect_installed {
310 1538     1538   1897 my ( undef, $module ) = @_;
311              
312 1538 50       2157 croak('Cannot determine a version if module=undef') if not defined $module;
313              
314 1538 100       2119 return [ undef, undef ] if 'perl' eq $module;
315              
316 1537 100       2658 return [ undef, 'not a valid module name' ] if not is_module_name($module);
317              
318 1341         20066 my @pmname = split qr/::|'/, $module; ## no critic (RegularExpressions)
319 1341         2133 $pmname[-1] .= '.pm';
320              
321 1341         1074 my $path;
322 1341         1815 for my $incdir (@INC) {
323 13271 50       148602 next if ref $incdir;
324 13271         18268 my $fullpath = path( $incdir, @pmname );
325 13271 100       219474 next unless -e $fullpath;
326 496 50       11942 next if -d $fullpath;
327 496         7485 $path = $fullpath;
328 496         623 last;
329             }
330              
331 1341 100       8380 return [ undef, 'module was not found in INC' ] if not defined $path;
332              
333 496         6512 require Module::Metadata;
334 496         26734 my $mm = Module::Metadata->new_from_file( $path, collect_pod => 0 );
335 496 50       1707745 return [ undef, 'Module::MetaData could not parse ' . $path ] if not defined $mm;
336              
337 496         1426 my $v = $mm->version($module);
338 496 100       8783 return [ undef, 'Module::MetaData could not parse a version from ' . $path ] if not $v;
339              
340 414         4257 return [ $v, undef ];
341              
342             }
343              
344              
345              
346              
347              
348              
349              
350              
351              
352              
353             sub _metadata {
354 6     6   13 my ($self) = @_;
355 6         32 $self->log_debug(q{Metadata called});
356 6         2102 my $report = $self->_get_prereq_modnames();
357 6         12 $self->log_debug( 'Found mods: ' . scalar @{$report} );
  6         37  
358 6         1217 my %modtable;
359             my %failures;
360              
361 6         8 for my $module ( @{$report}, $self->include ) {
  6         281  
362 14         48 my $result = $self->_detect_installed($module);
363 14 100       55 $modtable{$module} = $result->[0] if defined $result->[0];
364 14 100       57 $failures{$module} = $result->[1] if defined $result->[1];
365             }
366              
367 6         303 for my $badmodule ( $self->exclude ) {
368 2 100       8 delete $modtable{$badmodule} if exists $modtable{$badmodule};
369 2 50       5 delete $failures{$badmodule} if exists $failures{$badmodule};
370             }
371             ## no critic ( Variables::ProhibitPunctuationVars )
372 6         12 my $perlver;
373              
374 6 50       31 if ( $] < 5.010000 ) {
375 0         0 $perlver = { %{ version->parse( version->parse($])->normal ) } };
  0         0  
376             }
377             else {
378 6         11 $perlver = { %{$^V} };
  6         34  
379             }
380              
381 6         39 my $result = {
382             modules => \%modtable,
383             perl => $perlver,
384             platform => $^O,
385             $self->_uname(),
386             $self->_config(),
387             };
388              
389 6 100       27 $result->{failures} = \%failures if keys %failures;
390              
391 6         205 return $result;
392             }
393              
394              
395              
396              
397              
398             sub gather_files {
399 6     6 0 420832 my ($self) = @_;
400              
401 6 50       258 return unless $self->use_external_file;
402              
403 0 0       0 my $type =
    0          
404             $self->external_file_name =~ /[.]json\z/msix ? 'JSON'
405             : $self->external_file_name =~ /[.]ya?ml\z/msix ? 'YAML'
406             : croak 'Cant guess file type for ' . $self->external_file_name;
407              
408 0         0 my $code;
409              
410 0 0       0 if ( 'JSON' eq $type ) {
411 0         0 require JSON::MaybeXS;
412 0         0 require Dist::Zilla::File::FromCode;
413 0         0 my $json = JSON::MaybeXS->new;
414 0         0 $json->pretty(1);
415 0         0 $json->canonical(1);
416 0         0 $json->convert_blessed(1);
417 0         0 $json->allow_blessed(1);
418             $code = sub {
419 0     0   0 local *UNIVERSAL::TO_JSON = sub { "$_[0]" };
  0         0  
420 0         0 return $json->encode( $self->_metadata );
421 0         0 };
422             }
423 0 0       0 if ( 'YAML' eq $type ) {
424 0         0 require YAML::Tiny;
425             $code = sub {
426 0     0   0 return YAML::Tiny::Dump( $self->_metadata );
427 0         0 };
428             }
429              
430             $self->add_file(
431 0         0 Dist::Zilla::File::FromCode->new(
432             name => $self->external_file_name,
433             code => $code,
434             code_return_type => 'text',
435             ),
436             );
437 0         0 return;
438             }
439              
440             sub munge_files {
441 6     6 1 361383 my ($self) = @_;
442              
443 6         14 my $munged = {};
444              
445 6 50 50     247 return if 'only' eq ( $self->use_external_file || q[] );
446              
447 6         14 for my $file ( @{ $self->zilla->files } ) {
  6         174  
448 7 100       235 if ( 'META.json' eq $file->name ) {
449 5         228 require JSON::MaybeXS;
450 5         132 require CPAN::Meta::Converter;
451 5         107 my $json = JSON::MaybeXS->new->pretty->canonical(1);
452 5         399 my $old = $file->code;
453             $file->code(
454             sub {
455 5     5   75860 my $content = $json->decode( $old->() );
456 5         18126 $content->{ $self->_stash_key } = $self->_metadata;
457 5         81 my $normal = CPAN::Meta::Converter->new($content)->convert( version => $content->{'meta-spec'}->{version} );
458 5         97631 return $json->encode($normal);
459             },
460 5         174 );
461 5         74 $munged->{'META.json'} = 1;
462 5         16 next;
463             }
464 2 100       143 if ( 'META.yml' eq $file->name ) {
465 1         40 require YAML::Tiny;
466 1         2 require CPAN::Meta::Converter;
467 1         23 my $old = $file->code;
468             $file->code(
469             sub {
470 1     1   13773 my $content = YAML::Tiny::Load( $old->() );
471 1         4446 $content->{ $self->_stash_key } = $self->_metadata;
472 1         7 my $normal = CPAN::Meta::Converter->new($content)->convert( version => $content->{'meta-spec'}->{version} );
473 1         3872 return YAML::Tiny::Dump($normal);
474             },
475 1         28 );
476 1         11 $munged->{'META.yml'} = 1;
477 1         2 next;
478             }
479             }
480 6 50       10 if ( not keys %{$munged} ) {
  6         28  
481 0         0 my $message = <<'EOF';
482             No META.* files to munge.
483             BuiltWith cannot operate without one in tree prior to it
484             EOF
485 0         0 $self->log_fatal($message);
486             }
487 6         22 return;
488             }
489              
490             1;
491              
492             __END__
493              
494             =pod
495              
496             =encoding UTF-8
497              
498             =head1 NAME
499              
500             Dist::Zilla::Plugin::MetaData::BuiltWith - Report what versions of things your distribution was built against
501              
502             =head1 VERSION
503              
504             version 1.004003
505              
506             =head1 SYNOPSIS
507              
508             [MetaData::BuiltWith]
509             include = Some::Module::Thats::Not::In::Preq
510             exclude = Some::Module::Youre::Ashamed::Of
511             show_uname = 1 ; default is 0
512             show_config = 1 ; default is 0
513             uname_call = uname ; the default
514             uname_args = -s -r -m -p ; the default is -a
515             use_external_file = only ; the default is undef
516              
517             =head1 DESCRIPTION
518              
519             This module provides extra metadata in your distribution, automatically documenting what versions of dependencies the author was
520             using at the time of release.
521              
522             This allows consumers of said distributions to be able to see a range of versions that are "known good" should they experience
523             problems.
524              
525             =head1 OPTIONS
526              
527             =head2 exclude
528              
529             Specify modules to exclude from version reporting
530              
531             exclude = Foo
532             exclude = Bar
533              
534             =head2 include
535              
536             Specify additional modules to include the version of
537              
538             include = Foo
539             include = Bar
540              
541             =head2 show_config
542              
543             Report "interesting" values from C<%Config::Config>
544              
545             show_config = 1 ; Boolean
546              
547             =head2 show_uname
548              
549             Report the output from C<uname>
550              
551             show_uname = 1 ; Boolean
552              
553             =head2 uname_call
554              
555             Specify what the system C<uname> function is called
556              
557             uname_call = uname ; String
558              
559             =head2 uname_args
560              
561             Specify arguments passed to the C<uname> call.
562              
563             uname_args = -a ; String
564              
565             =head2 use_external_file
566              
567             This option regulates the optional output to an isolated file.
568              
569             An external file will be created as long as this value is a true value.
570              
571             use_external_file = 1
572              
573             If this true value is the string C<only>, then it won't also be exported to META.yml/META.json
574              
575             use_external_file = only
576              
577             NOTE:
578              
579             This will still leave an x_BuiltWith section in your META.*, however, its much less fragile
580             and will simply be:
581              
582             x_BuiltWith: {
583             external_file: "your/path/here"
584             }
585              
586             This is mostly a compatibility pointer so any tools traversing a distributions history will know where and when to change
587             behavior.
588              
589             =head2 external_file_name
590              
591             This option controls what the external file will be called in conjunction with C<use_external_file>
592              
593             Default value is:
594              
595             misc/built_with.json
596              
597             Extensions:
598              
599             .json => JSON is used.
600             .yml => YAML is used (untested)
601             .yaml => YAML is used (untested)
602              
603             =head1 METHODS
604              
605             =head2 mvp_multivalue_args
606              
607             This module can take, as parameters, any volume of 'exclude' or 'include' arguments.
608              
609             =head2 munge_files
610              
611             This module scrapes together the name of all modules that exist in the "C<Prereqs>" section
612             that Dist::Zilla collects, and then works out what version of things you have,
613             applies the various include/exclude rules, and ships that data back to Dist::Zilla
614             via this method. See L<< C<Dist::Zilla>'s C<MetaProvider> role|Dist::Zilla::Role::MetaProvider >> for more details.
615              
616             =for Pod::Coverage metadata
617              
618             =for Pod::Coverage gather_files
619              
620             =head1 EXAMPLE OUTPUT ( C<META.json> )
621              
622             "x_BuiltWith" : {
623             "modules" : {
624             "Dist::Zilla::Role::MetaProvider" : "4.101612",
625             "File::Find" : "1.15",
626             "File::Temp" : "0.22",
627             "Module::Build" : "0.3607",
628             "Moose" : "1.07",
629             "Test::More" : "0.94"
630             },
631             "perl" : "5.012000",
632             "platform" : "MSWin32"
633             },
634              
635             =head1 AUTHOR
636              
637             Kent Fredric <kentnl@cpan.org>
638              
639             =head1 COPYRIGHT AND LICENSE
640              
641             This software is copyright (c) 2016 by Kent Fredric <kentnl@cpan.org>.
642              
643             This is free software; you can redistribute it and/or modify it under
644             the same terms as the Perl 5 programming language system itself.
645              
646             =cut