File Coverage

blib/lib/Dist/Zilla/Plugin/MetaData/BuiltWith.pm
Criterion Covered Total %
statement 166 198 83.8
branch 45 70 64.2
condition 5 7 71.4
subroutine 26 29 89.6
pod 2 4 50.0
total 244 308 79.2


line stmt bran cond sub pod time code
1 8     8   8915283 use 5.006;
  8         22  
2 8     8   34 use strict;
  8         10  
  8         177  
3 8     8   30 use warnings;
  8         10  
  8         488  
4              
5             package Dist::Zilla::Plugin::MetaData::BuiltWith;
6              
7             our $VERSION = '1.004004';
8              
9             # ABSTRACT: Report what versions of things your distribution was built against
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 8     8   30 use Carp qw( carp croak );
  8         10  
  8         460  
14 8     8   29 use Config qw();
  8         14  
  8         117  
15 8     8   454 use Moose 2.0;
  8         335432  
  8         47  
16 8     8   34691 use Moose qw( with has around );
  8         12  
  8         28  
17 8     8   6675 use MooseX::Types::Moose qw( ArrayRef Bool Str );
  8         87504  
  8         74  
18 8     8   26686 use Module::Runtime qw( is_module_name );
  8         11  
  8         52  
19 8     8   4160 use Devel::CheckBin qw( can_run );
  8         577389  
  8         499  
20 8     8   1387 use Path::Tiny qw( path );
  8         15341  
  8         349  
21 8     8   41 use namespace::autoclean;
  8         26  
  8         69  
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 946 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   3226 no Moose;
  8         16  
  8         67  
207              
208             sub _config {
209 6     6   12 my $self = shift;
210 6 100       251 return () unless $self->show_config;
211 1         5 my @interesting = qw( git_describe git_commit_id git_commit_date myarchname gccversion osname osver );
212 1         2 my $interested = {};
213 1         2 for my $key (@interesting) {
214             ## no critic (ProhibitPackageVars)
215 7 100 100     135 if ( defined $Config::Config{$key} and $Config::Config{$key} ne q{} ) {
216 3         15 $interested->{$key} = $Config::Config{$key};
217             }
218             }
219 1         7 return ( 'perl-config', $interested );
220             }
221              
222             sub _uname {
223 6     6   10 my $self = shift;
224 6 100       211 return () unless $self->show_uname;
225             {
226 1         2 my $str;
  1         1  
227 1 50       33 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       308 last unless open my $fh, q{-|}, $self->uname_call, $self->_all_uname_args;
232 1         611 while ( my $line = <$fh> ) {
233 1         4 chomp $line;
234 1         13 $str .= $line;
235             }
236 1 50       31 last unless close $fh;
237 1         31 return ( 'uname', $str );
238              
239             }
240             ## no critic ( ProhibitPunctuationVars )
241              
242 0         0 $self->log(q{WARNING: `uname` invocation failed, omit from metadata});
243              
244 0         0 return ();
245              
246             }
247              
248             sub _build__uname_args {
249 1     1   2 my $self = shift;
250             ## no critic ( RequireDotMatchAnything RequireExtendedFormatting RequireLineBoundaryMatching )
251 1 50       37 return [ grep { defined $_ && $_ ne q{} } split /\s+/, $self->uname_args ];
  1         45  
252             }
253              
254             sub _build_use_external_file {
255 6     6   194 return;
256             }
257              
258             sub _build_external_file_name {
259 0     0   0 return 'misc/built_with.json';
260             }
261              
262              
263              
264              
265              
266             sub metadata {
267 6     6 0 28083 my ($self) = @_;
268 6 50 50     231 return {} unless 'only' eq ( $self->use_external_file || q[] );
269 0         0 return { $self->_stash_key, { external_file => $self->external_file_name }, };
270             }
271              
272             sub _get_prereq_modnames {
273 6     6   12 my ($self) = @_;
274              
275 6         12 my $modnames = {};
276              
277 6         202 my $prereqs = $self->zilla->prereqs->as_string_hash;
278             ## use critic
279 6 50       2729 if ( not %{$prereqs} ) {
  6         57  
280 0         0 $self->log(q{WARNING: No prereqs were found, probably a bug});
281 0         0 return [];
282             }
283 6         13 $self->log_debug( [ '%s phases defined: %s ', scalar keys %{$prereqs}, ( join q{,}, keys %{$prereqs} ) ] );
  6         12  
  6         32  
284              
285 6         1477 for my $phase_name ( keys %{$prereqs} ) {
  6         19  
286 6         10 my $phase_data = $prereqs->{$phase_name};
287 6 50       43 next unless defined $phase_data;
288 6         10 my $phase_deps = {};
289 6         9 for my $type ( keys %{$phase_data} ) {
  6         15  
290 6         13 my $type_data = $phase_data->{$type};
291 6 50       14 next unless defined $type_data;
292 6         8 for my $module ( keys %{$type_data} ) {
  6         16  
293 12         26 $phase_deps->{$module} = 1;
294             }
295             }
296 6         12 $self->log_debug( [ 'Prereqs for %s: %s', $phase_name, join q{,}, keys %{$phase_deps} ] );
  6         40  
297 6         1360 $modnames = { %{$modnames}, %{$phase_deps} };
  6         15  
  6         26  
298              
299             }
300 6         11 return [ sort keys %{$modnames} ];
  6         44  
301             }
302              
303             sub _detect_installed {
304 1261     1261   1585 my ( undef, $module ) = @_;
305              
306 1261 50       1854 croak('Cannot determine a version if module=undef') if not defined $module;
307              
308 1261 100       1725 return [ undef, undef ] if 'perl' eq $module;
309              
310 1260 100       2488 return [ undef, 'not a valid module name' ] if not is_module_name($module);
311              
312 1259         18444 my @pmname = split qr/::|'/, $module; ## no critic (RegularExpressions)
313 1259         1930 $pmname[-1] .= '.pm';
314              
315 1259         940 my $path;
316 1259         1601 for my $incdir (@INC) {
317 12443 50       135159 next if ref $incdir;
318 12443         17631 my $fullpath = path( $incdir, @pmname );
319 12443 100       203659 next unless -e $fullpath;
320 468 50       10941 next if -d $fullpath;
321 468         6397 $path = $fullpath;
322 468         611 last;
323             }
324              
325 1259 100       7782 return [ undef, 'module was not found in INC' ] if not defined $path;
326              
327 468         6259 require Module::Metadata;
328 468         25521 my $mm = Module::Metadata->new_from_file( $path, collect_pod => 0 );
329 468 50       2697893 return [ undef, 'Module::MetaData could not parse ' . $path ] if not defined $mm;
330              
331 468         1315 my $v = $mm->version($module);
332 468 100       7824 return [ undef, 'Module::MetaData could not parse a version from ' . $path ] if not $v;
333              
334 388         3304 return [ $v, undef ];
335              
336             }
337              
338              
339              
340              
341              
342              
343              
344              
345              
346              
347             sub _metadata {
348 6     6   12 my ($self) = @_;
349 6         33 $self->log_debug(q{Metadata called});
350 6         2266 my $report = $self->_get_prereq_modnames();
351 6         13 $self->log_debug( 'Found mods: ' . scalar @{$report} );
  6         41  
352 6         1316 my %modtable;
353             my %failures;
354              
355 6         12 for my $module ( @{$report}, $self->include ) {
  6         293  
356 14         52 my $result = $self->_detect_installed($module);
357 14 100       59 $modtable{$module} = $result->[0] if defined $result->[0];
358 14 100       52 $failures{$module} = $result->[1] if defined $result->[1];
359             }
360              
361 6         309 for my $badmodule ( $self->exclude ) {
362 2 100       7 delete $modtable{$badmodule} if exists $modtable{$badmodule};
363 2 50       4 delete $failures{$badmodule} if exists $failures{$badmodule};
364             }
365             ## no critic ( Variables::ProhibitPunctuationVars )
366 6         12 my $perlver;
367              
368 6 50       26 if ( $] < 5.010000 ) {
369 0         0 $perlver = { %{ version->parse( version->parse($])->normal ) } };
  0         0  
370             }
371             else {
372 6         10 $perlver = { %{$^V} };
  6         37  
373             }
374              
375 6         32 my $result = {
376             modules => \%modtable,
377             perl => $perlver,
378             platform => $^O,
379             $self->_uname(),
380             $self->_config(),
381             };
382              
383 6 100       31 $result->{failures} = \%failures if keys %failures;
384              
385 6         220 return $result;
386             }
387              
388              
389              
390              
391              
392             sub gather_files {
393 6     6 0 415318 my ($self) = @_;
394              
395 6 50       223 return unless $self->use_external_file;
396              
397 0 0       0 my $type =
    0          
398             $self->external_file_name =~ /[.]json\z/msix ? 'JSON'
399             : $self->external_file_name =~ /[.]ya?ml\z/msix ? 'YAML'
400             : croak 'Cant guess file type for ' . $self->external_file_name;
401              
402 0         0 my $code;
403              
404 0 0       0 if ( 'JSON' eq $type ) {
405 0         0 require JSON::MaybeXS;
406 0         0 require Dist::Zilla::File::FromCode;
407 0         0 my $json = JSON::MaybeXS->new;
408 0         0 $json->pretty(1);
409 0         0 $json->canonical(1);
410 0         0 $json->convert_blessed(1);
411 0         0 $json->allow_blessed(1);
412             $code = sub {
413 0     0   0 local *UNIVERSAL::TO_JSON = sub { "$_[0]" };
  0         0  
414 0         0 return $json->encode( $self->_metadata );
415 0         0 };
416             }
417 0 0       0 if ( 'YAML' eq $type ) {
418 0         0 require YAML::Tiny;
419             $code = sub {
420 0     0   0 return YAML::Tiny::Dump( $self->_metadata );
421 0         0 };
422             }
423              
424             $self->add_file(
425 0         0 Dist::Zilla::File::FromCode->new(
426             name => $self->external_file_name,
427             code => $code,
428             code_return_type => 'text',
429             ),
430             );
431 0         0 return;
432             }
433              
434             sub munge_files {
435 6     6 1 353783 my ($self) = @_;
436              
437 6         12 my $munged = {};
438              
439 6 50 50     221 return if 'only' eq ( $self->use_external_file || q[] );
440              
441 6         13 for my $file ( @{ $self->zilla->files } ) {
  6         159  
442 7 100       231 if ( 'META.json' eq $file->name ) {
443 5         279 require JSON::MaybeXS;
444 5         55 require CPAN::Meta::Converter;
445 5         98 my $json = JSON::MaybeXS->new->pretty->canonical(1);
446 5         246 my $old = $file->code;
447             $file->code(
448             sub {
449 5     5   46748 my $content = $json->decode( $old->() );
450 5         17859 $content->{ $self->_stash_key } = $self->_metadata;
451 5         77 my $normal = CPAN::Meta::Converter->new($content)->convert( version => $content->{'meta-spec'}->{version} );
452 5         97055 return $json->encode($normal);
453             },
454 5         140 );
455 5         66 $munged->{'META.json'} = 1;
456 5         15 next;
457             }
458 2 100       82 if ( 'META.yml' eq $file->name ) {
459 1         35 require YAML::Tiny;
460 1         4 require CPAN::Meta::Converter;
461 1         23 my $old = $file->code;
462             $file->code(
463             sub {
464 1     1   8999 my $content = YAML::Tiny::Load( $old->() );
465 1         4895 $content->{ $self->_stash_key } = $self->_metadata;
466 1         11 my $normal = CPAN::Meta::Converter->new($content)->convert( version => $content->{'meta-spec'}->{version} );
467 1         4306 return YAML::Tiny::Dump($normal);
468             },
469 1         29 );
470 1         12 $munged->{'META.yml'} = 1;
471 1         3 next;
472             }
473             }
474 6 50       9 if ( not keys %{$munged} ) {
  6         26  
475 0         0 my $message = <<'EOF';
476             No META.* files to munge.
477             BuiltWith cannot operate without one in tree prior to it
478             EOF
479 0         0 $self->log_fatal($message);
480             }
481 6         19 return;
482             }
483              
484             1;
485              
486             __END__
487              
488             =pod
489              
490             =encoding UTF-8
491              
492             =head1 NAME
493              
494             Dist::Zilla::Plugin::MetaData::BuiltWith - Report what versions of things your distribution was built against
495              
496             =head1 VERSION
497              
498             version 1.004004
499              
500             =head1 SYNOPSIS
501              
502             [MetaData::BuiltWith]
503             include = Some::Module::Thats::Not::In::Preq
504             exclude = Some::Module::Youre::Ashamed::Of
505             show_uname = 1 ; default is 0
506             show_config = 1 ; default is 0
507             uname_call = uname ; the default
508             uname_args = -s -r -m -p ; the default is -a
509             use_external_file = only ; the default is undef
510              
511             =head1 DESCRIPTION
512              
513             This module provides extra metadata in your distribution, automatically documenting what versions of dependencies the author was
514             using at the time of release.
515              
516             This allows consumers of said distributions to be able to see a range of versions that are "known good" should they experience
517             problems.
518              
519             =head1 OPTIONS
520              
521             =head2 exclude
522              
523             Specify modules to exclude from version reporting
524              
525             exclude = Foo
526             exclude = Bar
527              
528             =head2 include
529              
530             Specify additional modules to include the version of
531              
532             include = Foo
533             include = Bar
534              
535             =head2 show_config
536              
537             Report "interesting" values from C<%Config::Config>
538              
539             show_config = 1 ; Boolean
540              
541             =head2 show_uname
542              
543             Report the output from C<uname>
544              
545             show_uname = 1 ; Boolean
546              
547             =head2 uname_call
548              
549             Specify what the system C<uname> function is called
550              
551             uname_call = uname ; String
552              
553             =head2 uname_args
554              
555             Specify arguments passed to the C<uname> call.
556              
557             uname_args = -a ; String
558              
559             =head2 use_external_file
560              
561             This option regulates the optional output to an isolated file.
562              
563             An external file will be created as long as this value is a true value.
564              
565             use_external_file = 1
566              
567             If this true value is the string C<only>, then it won't also be exported to META.yml/META.json
568              
569             use_external_file = only
570              
571             NOTE:
572              
573             This will still leave an x_BuiltWith section in your META.*, however, its much less fragile
574             and will simply be:
575              
576             x_BuiltWith: {
577             external_file: "your/path/here"
578             }
579              
580             This is mostly a compatibility pointer so any tools traversing a distributions history will know where and when to change
581             behavior.
582              
583             =head2 external_file_name
584              
585             This option controls what the external file will be called in conjunction with C<use_external_file>
586              
587             Default value is:
588              
589             misc/built_with.json
590              
591             Extensions:
592              
593             .json => JSON is used.
594             .yml => YAML is used (untested)
595             .yaml => YAML is used (untested)
596              
597             =head1 METHODS
598              
599             =head2 mvp_multivalue_args
600              
601             This module can take, as parameters, any volume of 'exclude' or 'include' arguments.
602              
603             =head2 munge_files
604              
605             This module scrapes together the name of all modules that exist in the "C<Prereqs>" section
606             that Dist::Zilla collects, and then works out what version of things you have,
607             applies the various include/exclude rules, and ships that data back to Dist::Zilla
608             via this method. See L<< C<Dist::Zilla>'s C<MetaProvider> role|Dist::Zilla::Role::MetaProvider >> for more details.
609              
610             =for Pod::Coverage metadata
611              
612             =for Pod::Coverage gather_files
613              
614             =head1 EXAMPLE OUTPUT ( C<META.json> )
615              
616             "x_BuiltWith" : {
617             "modules" : {
618             "Dist::Zilla::Role::MetaProvider" : "4.101612",
619             "File::Find" : "1.15",
620             "File::Temp" : "0.22",
621             "Module::Build" : "0.3607",
622             "Moose" : "1.07",
623             "Test::More" : "0.94"
624             },
625             "perl" : "5.012000",
626             "platform" : "MSWin32"
627             },
628              
629             =head1 AUTHOR
630              
631             Kent Fredric <kentnl@cpan.org>
632              
633             =head1 COPYRIGHT AND LICENSE
634              
635             This software is copyright (c) 2016 by Kent Fredric <kentnl@cpan.org>.
636              
637             This is free software; you can redistribute it and/or modify it under
638             the same terms as the Perl 5 programming language system itself.
639              
640             =cut