File Coverage

blib/lib/Perl5/Dist/Backcompat.pm
Criterion Covered Total %
statement 35 366 9.5
branch 0 186 0.0
condition 0 32 0.0
subroutine 12 27 44.4
pod 12 13 92.3
total 59 624 9.4


line stmt bran cond sub pod time code
1             package Perl5::Dist::Backcompat;
2 1     1   106512 use 5.14.0;
  1         5  
3 1     1   7 use warnings;
  1         2  
  1         60  
4             our $VERSION = '0.04';
5 1     1   2921 use Archive::Tar;
  1         133332  
  1         108  
6 1     1   13 use Carp qw( carp croak );
  1         2  
  1         59  
7 1     1   6 use Cwd qw( cwd );
  1         2  
  1         46  
8 1     1   695 use File::Copy qw( copy move );
  1         6220  
  1         113  
9 1     1   10 use File::Find qw( find );
  1         2  
  1         77  
10 1     1   8 use File::Spec;
  1         2  
  1         24  
11 1     1   1824 use File::Temp qw( tempdir );
  1         13144  
  1         75  
12             # From CPAN
13 1     1   1343 use CPAN::DistnameInfo;
  1         1077  
  1         48  
14 1     1   550 use Data::Dump qw( dd pp );
  1         5915  
  1         125  
15 1     1   751 use File::Copy::Recursive::Reduced qw( dircopy );
  1         2110  
  1         5079  
16              
17             =head1 NAME
18              
19             Perl5::Dist::Backcompat - Analyze F distributions for CPAN release viability
20              
21             =head1 SYNOPSIS
22              
23             my $params = {
24             perl_workdir => '/path/to/git/checkout/of/perl',
25             verbose => 1,
26             };
27             my $self = Perl5::Dist::Backcompat->new( $params );
28              
29             =head1 DESCRIPTION
30              
31             This module serves as the backend for the program F which
32             is also part of the F distribution. This document's
33             focus is on documenting the methods used publicly in that program as well as
34             internal methods and subroutines called by those public methods. For
35             discussion on the problem which this distribution tries to solve, and how well
36             it currently does that or not, please (i) read the plain-text F in the
37             CPAN distribution or the F in the L
38             repository|https://github.com/jkeenan/p5-dist-backcompat>; and (ii) read the
39             front-end program's documentation via F.
40              
41             =head1 PREREQUISITES
42              
43             F 5.14.0 or newer, with the following modules installed from CPAN:
44              
45             =over 4
46              
47             =item * F
48              
49             =item * F
50              
51             =item * F
52              
53             =back
54              
55             =head1 PUBLIC METHODS
56              
57             =head2 C
58              
59             =over 4
60              
61             =item * Purpose
62              
63             Perl5::Dist::Backcompat constructor.
64              
65             =item * Arguments
66              
67             my $self = Perl5::Dist::Backcompat->new( $params );
68              
69             Single hash reference.
70              
71             =item * Return Value
72              
73             Perl5::Dist::Backcompat object.
74              
75             =back
76              
77             =cut
78              
79             sub new {
80 0     0 1   my ($class, $params) = @_;
81 0 0 0       if (defined $params and ref($params) ne 'HASH') {
82 0           croak "Argument supplied to constructor must be hashref";
83             }
84 0           my %valid_params = map {$_ => 1} qw(
  0            
85             verbose
86             host
87             path_to_perls
88             perl_workdir
89             tarball_dir
90             );
91 0           my @invalid_params = ();
92 0           for my $p (keys %$params) {
93 0 0         push @invalid_params, $p unless $valid_params{$p};
94             }
95 0 0         if (@invalid_params) {
96 0           my $msg = "Constructor parameter(s) @invalid_params not valid";
97 0           croak $msg;
98             }
99             croak "Must supply value for 'perl_workdir'"
100 0 0         unless $params->{perl_workdir};
101              
102 0           my $data = {};
103 0           for my $p (keys %valid_params) {
104 0 0         $data->{$p} = (defined $params->{$p}) ? $params->{$p} : '';
105             }
106 0   0       $data->{host} ||= 'dromedary.p5h.org';
107 0   0       $data->{path_to_perls} ||= '/media/Tux/perls-t/bin';
108 0   0       $data->{tarball_dir} ||= "$ENV{P5P_DIR}/dist-backcompat/tarballs";
109              
110             croak "Could not locate directory $data->{path_to_perls} for perl executables"
111 0 0         unless -d $data->{path_to_perls};
112             croak "Could not locate directory $data->{tarball_dir} for downloaded tarballs"
113 0 0         unless -d $data->{tarball_dir};
114              
115 0           return bless $data, $class;
116             }
117              
118             =head2 C
119              
120             =over 4
121              
122             =item * Purpose
123              
124             Guarantee that we can find the F executables we'll be using; the F
125             checkout of the core distribution; metadata files and loading of data
126             therefrom.
127              
128             =item * Arguments
129              
130             $self->init();
131              
132             None; all data needed is found within the object.
133              
134             =item * Return Value
135              
136             Returns the object itself.
137              
138             =back
139              
140             =cut
141              
142             sub init {
143             # From here on, we assume we're starting from the home directory of
144             # someone with an account on Dromedary.
145              
146 0     0 1   my $self = shift;
147              
148 0           my $currdir = cwd();
149             chdir $self->{perl_workdir}
150 0 0         or croak "Unable to change to $self->{perl_workdir}";
151              
152 0           my $describe = `git describe`;
153 0           chomp($describe);
154 0 0         croak "Unable to get value for 'git describe'"
155             unless $describe;
156 0           $self->{describe} = $describe;
157 0 0         chdir $currdir or croak "Unable to change back to starting directory";
158              
159 0           my $manifest = File::Spec->catfile($self->{perl_workdir}, 'MANIFEST');
160 0 0         croak "Could not locate $manifest" unless -f $manifest;
161 0           $self->{manifest} = $manifest;
162              
163 0           my $maint_file = File::Spec->catfile($self->{perl_workdir}, 'Porting', 'Maintainers.pl');
164 0           require $maint_file; # to get %Modules in package Maintainers
165 0           $self->{maint_file} = $maint_file;
166              
167 0           my $manilib_file = File::Spec->catfile($self->{perl_workdir}, 'Porting', 'manifest_lib.pl');
168 0           require $manilib_file; # to get function sort_manifest()
169 0           $self->{manilib_file} = $manilib_file;
170              
171 0           my %distmodules = ();
172 0           for my $m (keys %Maintainers::Modules) {
173 0 0         if ($Maintainers::Modules{$m}{FILES} =~ m{dist/}) {
174 0           $distmodules{$m} = $Maintainers::Modules{$m};
175             }
176             }
177              
178             # Sanity checks; all modules under dist/ should be blead-upstream and have P5P
179             # as maintainer.
180 0           _sanity_check(\%distmodules, $self->{describe}, $self->{verbose});
181 0           $self->{distmodules} = \%distmodules;
182              
183 0           my $metadata_file = File::Spec->catfile(
184             '.', 'etc', 'dist-backcompat-distro-metadata.txt');
185 0 0         croak "Could not locate $metadata_file" unless -f $metadata_file;
186 0           $self->{metadata_file} = $metadata_file;
187              
188 0           my %distro_metadata = ();
189              
190 0 0         open my $IN, '<', $metadata_file or croak "Unable to open $metadata_file for reading";
191 0           while (my $l = <$IN>) {
192 0           chomp $l;
193 0 0         next if $l =~ m{^(\#|\s*$)};
194 0           my @rowdata = split /\|/, $l;
195             # Refine this later
196 0   0       $distro_metadata{$rowdata[0]} = {
      0        
      0        
      0        
      0        
197             minimum_perl_version => $rowdata[1] // '',
198             needs_threaded_perl => $rowdata[2] // '',
199             needs_ppport_h => $rowdata[3] // '',
200             needs_threads_h => $rowdata[4] // '',
201             needs_shared_h => $rowdata[5] // '',
202              
203             };
204             }
205 0 0         close $IN or die "Unable to close $metadata_file after reading: $!";
206              
207 0           my $this = $self->identify_cpan_tarballs_with_makefile_pl();
208 0           for my $d (keys %{$this}) {
  0            
209 0           $distro_metadata{$d}{tarball} = $this->{$d}->{tarball};
210 0           $distro_metadata{$d}{distvname} = $this->{$d}->{distvname};
211             }
212              
213 0           $self->{distro_metadata} = \%distro_metadata;
214              
215 0           my $older_perls_file = File::Spec->catfile(
216             '.', 'etc', 'dist-backcompat-older-perls.txt');
217 0 0         croak "Could not locate $older_perls_file" unless -f $older_perls_file;
218 0           $self->{older_perls_file} = $older_perls_file;
219              
220 0           return $self;
221             }
222              
223             =head2 C
224              
225             =over 4
226              
227             =item * Purpose
228              
229             Categorize each F distro in one of 4 categories based on the status and
230             appropriateness of its F (if any).
231              
232             =item * Arguments
233              
234             $self->categorize_distros();
235              
236             None; all data needed is already within the object.
237              
238             =item * Return Value
239              
240             Returns the object.
241              
242             =item * Comment
243              
244             Since our objective is to determine the CPAN release viability of code found
245             within F distros in core, we need various ways to categorize those
246             distros. This method will make a categorization based on the status of the
247             distros's F. The categories will be mutually exclusive. By order
248             of processing the categories will be:
249              
250             =item *
251              
252             B As based on an examination of C<%Maintainers::Modules> in
253             F, at least one distro has no current CPAN release.
254             Such modules will be categorized as C.
255              
256             =item *
257              
258             B Certain F distros have a CPAN release which contains a F.
259             Such distros I also have a F in core; that F
260             may or may not be functionally identical to that on CPAN. In either case, we
261             shall make an assumption that the F found in the most recent CPAN
262             release is the version to be preferred for the purpose of this program. Such
263             distros will be categorized as C.
264              
265             B The following 3 categories should be considered I because,
266             as the code in this methods is currently structured, all current F
267             distros are categorized as either C or C. These categories
268             may be removed in a future release.
269              
270             =over 4
271              
272             =item *
273              
274             B Certain F distros have a F in core. Assuming that such a
275             distro has not already been categorized as C, we will use that version
276             in this program. Such distros will be categorized as C.
277              
278             =item *
279              
280             B If a F distro has no F either on CPAN or in core but, at
281             the end of F in the Perl 5 build process does have a F
282             generated by that process, we will categorize such a distro as C.
283              
284             =item *
285              
286             B The remaining F distros have a F neither on CPAN nor in
287             core. For purpose of compilation in core they I have a F
288             generated by core's F process, but this file, if created, does
289             not appear to be retained on disk at the end of F. Such a distro might
290             lack a F in its CPAN release because the CPAN releasor uses
291             technology such as F to produce such a release and such
292             technology does not require a F to be included in the CPAN
293             tarball. At the present time we will categorize such distros as C and
294             these will be skipped by subsequent methods.
295              
296             =back
297              
298             =back
299              
300             =cut
301              
302             sub categorize_distros {
303 0     0 1   my $self = shift;
304 0           my %makefile_pl_status = ();
305              
306             # First, identify those dist/ distros which, on the basis of data in
307             # Porting/Maintainers.PL, do not currently have CPAN releases.
308              
309 0           for my $m (keys %{$self->{distmodules}}) {
  0            
310 0 0         if (! exists $self->{distmodules}->{$m}{DISTRIBUTION}) {
311 0           my ($distname) = $self->{distmodules}->{$m}{FILES} =~ m{^dist/(.*)/?$};
312 0           $makefile_pl_status{$distname} = 'unreleased';
313             }
314             }
315              
316             # Second, identify those dist/ distros which have their own hard-coded
317             # Makefile.PLs in their CPAN releases. We'll call these 'cpan'. (We've
318             # already done some of the work for this in
319             # $self->identify_cpan_tarballs_with_makefile_pl() called from within
320             # init(). The location of a distro's tarball is given by:
321             # $self->{distro_metadata}->{$d}->{tarball}.)
322              
323 0           for my $d (keys %{$self->{distro_metadata}}) {
  0            
324 0 0         if (! $makefile_pl_status{$d}) {
325 0           my $tb = $self->{distro_metadata}->{$d}->{tarball};
326 0           my ($tar, $hasmpl);
327 0           $tar = Archive::Tar->new($tb);
328 0 0         croak "Unable to create Archive::Tar object for $d" unless defined $tar;
329 0           $self->{distro_metadata}->{$d}->{tar} = $tar;
330             $hasmpl = $self->{distro_metadata}->{$d}->{tar}->contains_file(
331 0           File::Spec->catfile($self->{distro_metadata}->{$d}->{distvname},'Makefile.PL')
332             );
333 0 0         if ($hasmpl) {
334 0           $makefile_pl_status{$d} = 'cpan';
335             }
336             else {
337 0 0         carp "$d Makefile.PL doubtful" unless $hasmpl;
338             }
339             }
340             }
341              
342             # Third, identify those dist/ distros which have their own hard-coded
343             # Makefile.PLs in the core distribution. We'll call these 'native'.
344              
345 0           my @sorted = read_manifest($self->{manifest});
346              
347 0           for my $f (@sorted) {
348 0 0         next unless $f =~ m{^dist/};
349 0           my $path = (split /\t+/, $f)[0];
350 0 0         if ($path =~ m{/(.*?)/Makefile\.PL$}) {
351 0           my $distro = $1;
352             $makefile_pl_status{$distro} = 'native'
353 0 0         unless $makefile_pl_status{$distro};
354             }
355             }
356              
357             # Fourth, identify those dist/ distros whose Makefile.PL is generated during
358             # Perl's own 'make' process.
359              
360             my $get_generated_makefiles = sub {
361 0     0     my $pattern = qr{dist/(.*?)/Makefile\.PL$};
362 0 0         if ( $File::Find::name =~ m{$pattern} ) {
363 0           my $distro = $1;
364 0 0         if (! $makefile_pl_status{$distro}) {
365 0           $makefile_pl_status{$distro} = 'generated';
366             }
367             }
368 0           };
369             find(
370 0           \&{$get_generated_makefiles},
371 0           File::Spec->catdir($self->{perl_workdir}, 'dist' )
372             );
373              
374             # Fifth, identify those dist/ distros whose Makefile.PLs are not yet
375             # accounted for.
376              
377 0           for my $d (sort keys %{$self->{distmodules}}) {
  0            
378 0 0         next unless exists $self->{distmodules}->{$d}{FILES};
379 0           my ($distname) = $self->{distmodules}->{$d}{FILES} =~ m{^dist/([^/]+)/?$};
380 0 0         if (! exists $makefile_pl_status{$distname}) {
381 0           $makefile_pl_status{$distname} = 'tbd';
382             }
383             }
384              
385 0           $self->{makefile_pl_status} = \%makefile_pl_status;
386 0           return $self;
387             }
388              
389             =head2 C
390              
391             =over 4
392              
393             =item * Purpose
394              
395             Display a chart listing F distros in one column and the status of their
396             respective Fs in the second column.
397              
398             =item * Arguments
399              
400             $self->show_makefile_pl_status();
401              
402             None; this method simply displays data already present in the object.
403              
404             =item * Return Value
405              
406             Returns a true value when complete.
407              
408             =item * Comment
409              
410             Does nothing unless a true value for C was passed to C.
411              
412             =back
413              
414             =cut
415              
416             sub show_makefile_pl_status {
417 0     0 1   my $self = shift;
418 0           my %counts;
419 0           for my $module (sort keys %{$self->{makefile_pl_status}}) {
  0            
420 0           $counts{$self->{makefile_pl_status}->{$module}}++;
421             }
422 0 0         if ($self->{verbose}) {
423 0           for my $k (sort keys %counts) {
424 0           printf " %-18s%4s\n" => ($k, $counts{$k});
425             }
426 0           say '';
427 0           printf "%-24s%-12s\n" => ('Distribution', 'Status');
428 0           printf "%-24s%-12s\n" => ('------------', '------');
429 0           for my $module (sort keys %{$self->{makefile_pl_status}}) {
  0            
430 0           printf "%-24s%-12s\n" => ($module, $self->{makefile_pl_status}->{$module});
431             }
432             }
433 0           return 1;
434             }
435              
436             =head2 C
437              
438             =over 4
439              
440             =item * Purpose
441              
442             Assemble the list of F distros which the program will actually test
443             against older Fs.
444              
445             =item * Arguments
446              
447             my @distros_for_testing = $self->get_distros_for_testing( [ @distros_requested ] );
448              
449             Single arrayref, optional (though recommended). If no arrayref is provided,
450             then the program will test I F distros I those whose
451             "Makefile.PL status" is C.
452              
453             =item * Return Value
454              
455             List holding distros to be tested. (This is provided for readability of the
456             code, but the list will be stored within the object and subsequently
457             referenced therefrom.
458              
459             =item * Comment
460              
461             In a production program, the list of distros selected for testing may be
462             provided on the command-line and processed by C
463             within that program. But it's only at this point that we need to add such a
464             list to the object.
465              
466             =back
467              
468             =cut
469              
470             sub get_distros_for_testing {
471 0     0 1   my ($self, $distros) = @_;
472 0 0         if (defined $distros) {
473 0 0         croak "Argument passed to get_distros_for_testing() must be arrayref"
474             unless ref($distros) eq 'ARRAY';
475             }
476             else {
477 0           $distros = [];
478             }
479 0           my @distros_for_testing = (scalar @{$distros})
480 0           ? @{$distros}
481 0           : sort grep { $self->{makefile_pl_status}->{$_} ne 'unreleased' }
482 0 0         keys %{$self->{makefile_pl_status}};
  0            
483 0 0         if ($self->{verbose}) {
484 0           say "\nWill test ", scalar @distros_for_testing,
485             " distros which have been presumably released to CPAN:";
486 0           say " $_" for @distros_for_testing;
487             }
488 0           $self->{distros_for_testing} = [ @distros_for_testing ];
489 0           return @distros_for_testing;
490             }
491              
492             =head2 C
493              
494             =over 4
495              
496             =item * Purpose
497              
498             Validate the paths and executability of the older perl versions against which
499             we're going to test F distros.
500              
501             =item * Arguments
502              
503             my @perls = $self->validate_older_perls();
504              
505             None; all necessary information is found within the object.
506              
507             =item * Return Value
508              
509             List holding older F executables against which distros will be tested.
510             (This is provided for readability of the code, but the list will be stored
511             within the object and subsequently referenced therefrom.
512              
513             =back
514              
515             =cut
516              
517             sub validate_older_perls {
518 0     0 1   my $self = shift;
519 0           my @perllist = ();
520             open my $IN1, '<', $self->{older_perls_file}
521 0 0         or croak "Unable to open $self->{older_perls_file} for reading";
522 0           while (my $l = <$IN1>) {
523 0           chomp $l;
524 0 0         next if $l =~ m{^(\#|\s*$)};
525 0           push @perllist, $l;
526             }
527 0 0         close $IN1
528             or croak "Unable to close $self->{older_perls_file} after reading";
529              
530 0           my @perls = ();
531              
532 0           for my $p (@perllist) {
533 0 0         say "Locating $p executable ..." if $self->{verbose};
534 0           my $rv;
535 0           my $path_to_perl = File::Spec->catfile($self->{path_to_perls}, $p);
536 0 0         warn "Could not locate $path_to_perl" unless -e $path_to_perl;
537 0           $rv = system(qq| $path_to_perl -v 1>/dev/null 2>&1 |);
538 0 0         warn "Could not execute perl -v with $path_to_perl" if $rv;
539              
540 0           my ($major, $minor, $patch) = $p =~ m{^perl(5)\.(\d+)\.(\d+)$};
541 0           my $canon = sprintf "%s.%03d%03d" => ($major, $minor, $patch);
542              
543 0           push @perls, {
544             version => $p,
545             path => $path_to_perl,
546             canon => $canon,
547             };
548             }
549 0           $self->{perls} = [ @perls ];
550 0           return @perls;
551             }
552              
553             =head2 C
554              
555             =over 4
556              
557             =item * Purpose
558              
559             Test a given F distro against each of the older Fs against which
560             it is eligible to be tested.
561              
562             =item * Arguments
563              
564             $self->test_distros_against_older_perls('/path/to/debugging/directory');
565              
566             String holding absolute path to an already created directory to which files
567             can be written for later study and debugging. That directory I be
568             created by C, but it should I be created with C<(
569             CLEANUP => 1)>; the user should manually remove this directory after analysis
570             is complete.
571              
572             =item * Return Value
573              
574             Returns the object itself.
575              
576             =item * Comment
577              
578             The method will loop over the selected distros, calling
579             C against each.
580              
581             =back
582              
583             =cut
584              
585             sub test_distros_against_older_perls {
586 0     0 1   my ($self, $results_dir) = @_;
587             # $results_dir will be explicitly user-created to hold the results of
588             # testing.
589              
590             # A program using Perl5::Dist::Backcompat won't need it until now. So even
591             # if we feed that directory to the program via GetOptions, it doesn't need
592             # to go into the constructor. It may be a tempdir but should almost
593             # certainly NOT be set to get automatically cleaned up at program
594             # conclusion (otherwise, where would you look for the results?).
595              
596 0 0         croak "Unable to locate $results_dir" unless -d $results_dir;
597 0           $self->{results_dir} = $results_dir;
598              
599             # Calculations WILL, however, be done in a true tempdir. We'll create
600             # subdirs and files underneath that tempdir. We'll cd to that tempdir but
601             # come back to where we started before this method exits.
602             # $self->{temp_top_dir} will be the conceptual equivalent of the top-level
603             # directory in the Perl 5 distribution. Hence, underneath it we'll create
604             # the equivalents of the F, F, etc., and
605             # F directories.
606 0           $self->{currdir} = cwd();
607 0           $self->{temp_top_dir} = tempdir( CLEANUP => 1 );
608 0           my %results = ();
609              
610 0 0         chdir $self->{temp_top_dir} or croak "Unable to change to tempdir $self->{temp_top_dir}";
611              
612             # Create a 't/' directory underneath the temp_top_dir
613 0           my $temp_t_dir = File::Spec->catdir($self->{temp_top_dir}, 't');
614 0 0         mkdir $temp_t_dir or croak "Unable to mkdir $temp_t_dir";
615 0           $self->{temp_t_dir} = $temp_t_dir;
616              
617             # Several of the F distros need F for their tests; copy
618             # it into position once only.
619 0           my $testpl = File::Spec->catfile($self->{perl_workdir}, 't', 'test.pl');
620 0 0         croak "Could not locate $testpl" unless -f $testpl;
621 0 0         copy $testpl => $self->{temp_t_dir} or croak "Unable to copy $testpl";
622              
623             # Create a 'dist/' directory underneath the temp_top_dir
624 0           my $temp_dist_dir = File::Spec->catdir($self->{temp_top_dir}, 'dist');
625 0 0         mkdir $temp_dist_dir or croak "Unable to mkdir $temp_dist_dir";
626 0           $self->{temp_dist_dir} = $temp_dist_dir;
627              
628 0           for my $d (@{$self->{distros_for_testing}}) {
  0            
629 0           my $this_result = $self->test_one_distro_against_older_perls($d);
630 0           $results{$d} = $this_result;
631             }
632              
633             chdir $self->{currdir}
634 0 0         or croak "Unable to change back to starting directory $self->{currdir}";
635              
636 0           $self->{results} = { %results };
637 0           return $self;
638              
639             # temp_top_dir should go out of scope here (though its path and those of
640             # temp_t_dir and temp_dist_dir will still be in the object)
641             }
642              
643             =head2 C
644              
645             =over 4
646              
647             =item * Purpose
648              
649             Print on F:
650              
651             =over 4
652              
653             =item 1
654              
655             A list of the F files created for each
656             tested distro (each file containing a summary of the results for that distro
657             against each designated F executable. Example:
658              
659             Summaries
660             ---------
661             Attribute-Handlers /tmp/29LsgNfjVb/Attribute-Handlers.summary.txt
662             Carp /tmp/29LsgNfjVb/Carp.summary.txt
663             Data-Dumper /tmp/29LsgNfjVb/Data-Dumper.summary.txt
664             ...
665             threads /tmp/29LsgNfjVb/threads.summary.txt
666             threads-shared /tmp/29LsgNfjVb/threads-shared.summary.txt
667              
668             =item 2
669              
670             A concatenation of all those files.
671              
672             =back
673              
674             =item * Arguments
675              
676             To simply list the summary files:
677              
678             $self->print_distro_summaries();
679              
680             To list the summary files and concatenate their content:
681              
682             $self->print_distro_summaries( {cat_summaries => 1} );
683              
684             =item * Return Value
685              
686             Returns true value upon success.
687              
688             =item * Comment
689              
690             You'll probably want to redirect or F F to a file for further
691             study.
692              
693             =back
694              
695             =cut
696              
697             sub print_distro_summaries {
698 0     0 1   my ($self, $args) = @_;
699 0 0         if (! defined $args) { $args = {}; }
  0            
700             else {
701 0 0         croak "Argument to print_distro_summaries must be hashref"
702             unless ref($args) eq 'HASH';
703             }
704              
705 0           say "\nSummaries";
706 0           say '-' x 9;
707 0           for my $d (sort keys %{$self->{results}}) {
  0            
708 0           $self->print_distro_summary($d);
709             }
710              
711 0 0         if ($args->{cat_summaries}) {
712 0           say "\nOverall (at $self->{describe}):";
713 0           for my $d (sort keys %{$self->{results}}) {
  0            
714 0           say "\n$d";
715 0           dd $self->{results}->{$d};
716             }
717             }
718 0           return 1;
719             }
720              
721             =head2 C
722              
723             =over 4
724              
725             =item * Purpose
726              
727             Provide an overall summary of PASSes and FAILs in the distro/perl-version matrix.
728              
729             =item * Arguments
730              
731             None, all data needed is stored within object.
732              
733             =item * Return Value
734              
735             Array ref with 4 elements: overall attempts, overall passes, overall failures,
736             overall skipped.
737              
738             =item * Comment
739              
740             An entry in the distro/perl-version matrix is skipped if there is a failure
741             running F, which causes the C, C and C
742             values to be all undefined.
743              
744             =back
745              
746             =cut
747              
748             sub tally_results {
749 0     0 1   my $self = shift;
750 0           my $overall_attempts = 0;
751 0           my $overall_successes = 0;
752 0           my $overall_skipped = 0;
753 0           for my $d (keys %{$self->{results}}) {
  0            
754 0           for my $p (keys %{$self->{results}->{$d}}) {
  0            
755 0           $overall_attempts++;
756 0           my %thisrun = %{$self->{results}->{$d}->{$p}};
  0            
757 0 0 0       if (
    0 0        
      0        
758             ! defined $thisrun{configure} and
759             ! defined $thisrun{make} and
760             ! defined $thisrun{test}
761             ) {
762 0           $overall_skipped++;
763             }
764             elsif (
765             $thisrun{configure} and
766             $thisrun{make} and
767             $thisrun{test}
768             ) {
769 0           $overall_successes++;
770             }
771             }
772             }
773 0           my $overall_failures = $overall_attempts - ($overall_successes + $overall_skipped);
774 0           return [$overall_attempts, $overall_successes, $overall_failures, $overall_skipped];
775             }
776              
777             =head1 INTERNAL METHODS
778              
779             The following methods use the Perl5::Dist::Backcompat object but are called
780             from within the public methods. Other than this library's author, you
781             shouldn't need to explicitly call these methods (or the internal subroutines
782             documented below) in a production program. The documentation here is mainly
783             for people working on this distribution itself.
784              
785             =cut
786              
787             =head2 C
788              
789             =over 4
790              
791             =item * Purpose
792              
793             Test one selected F distribution against the list of older Fs.
794              
795             =item * Arguments
796              
797             Single string holding the name of the distro in C format.
798              
799             =item * Return Value
800              
801             Hash reference with one element for each F executable selected:
802              
803             {
804             "5.006002" => { a => "perl5.6.2", configure => 1, make => 0, test => undef },
805             "5.008009" => { a => "perl5.8.9", configure => 1, make => 0, test => undef },
806             "5.010001" => { a => "perl5.10.1", configure => 1, make => 0, test => undef },
807             ...
808             "5.034000" => { a => "perl5.34.0", configure => 1, make => 1, test => 1 },
809             }
810              
811             The value of each element is a hashref with elements keyed as follows:
812              
813             =over 4
814              
815             =item * C
816              
817             Perl version in the spelling used in the default value for C.
818              
819             =item * C
820              
821             The result of calling F: C<1> for success; C<0> for failure;
822             C for not attempted.
823              
824             =item * C
825              
826             The result of calling F: same meaning as above.
827              
828             =item * C
829              
830             The result of calling F: same meaning as above.
831              
832             =back
833              
834             =back
835              
836             =cut
837              
838             sub test_one_distro_against_older_perls {
839 0     0 1   my ($self, $d) = @_;
840 0 0         say "Testing $d ..." if $self->{verbose};
841 0           my $this_result = {};
842              
843 0           my $source_dir = File::Spec->catdir($self->{perl_workdir}, 'dist', $d);
844 0           my $this_tempdir = File::Spec->catdir($self->{temp_dist_dir}, $d);
845 0 0         mkdir $this_tempdir or croak "Unable to mkdir $this_tempdir";
846 0 0         dircopy($source_dir, $this_tempdir)
847             or croak "Unable to copy $source_dir to $this_tempdir";
848              
849 0 0         chdir $this_tempdir or croak "Unable to chdir to tempdir for dist/$d";
850 0 0         say " Now in $this_tempdir ..." if $self->{verbose};
851              
852 0           THIS_PERL: for my $p (@{$self->{perls}}) {
  0            
853 0           $this_result->{$p->{canon}}{a} = $p->{version};
854             # Skip this perl version if (a) distro has a specified
855             # 'minimum_perl_version' and (b) that minimum version is greater than
856             # the current perl we're running.
857 0 0 0       if (
858             (
859             $self->{distro_metadata}->{$d}{minimum_perl_version}
860             and
861             $self->{distro_metadata}->{$d}{minimum_perl_version} >= $p->{canon}
862             )
863             # Since we're currently using threaded perls for this
864             # process, the following condition is not pertinent. But we'll
865             # retain it here commented out for possible future use.
866             #
867             # or
868             # (
869             # $self->{distro_metadata}->{$d}{needs_threaded_perl}
870             # )
871             ) {
872 0           $this_result->{$p->{canon}}{configure} = undef;
873 0           $this_result->{$p->{canon}}{make} = undef;
874 0           $this_result->{$p->{canon}}{test} = undef;
875 0           next THIS_PERL;
876             }
877 0           my $f = join '.' => ($d, $p->{version}, 'txt');
878 0           my $debugfile = File::Spec->catfile($self->{results_dir}, $f);
879 0 0         if ($self->{verbose}) {
880 0           say "Testing $d with $p->{canon} ($p->{version}); see $debugfile";
881             }
882              
883             # Here, assuming the distro ($d) is classified as 'cpan', we should
884             # extract the Makefile.PL from the tar and swap that into the
885             # following 'perl Makefile.PL' command.
886              
887 0           my ($rv, $cmd);
888 0           my $this_makefile_pl = 'Makefile.PL';
889 0 0         if ($self->{makefile_pl_status}->{$d} eq 'cpan') {
890             # We currently expect this branch to prevail 40 times
891 0 0         if (-f $this_makefile_pl) {
892 0           move $this_makefile_pl => "$this_makefile_pl.noncpan";
893             }
894 0           my $source = File::Spec->catfile($self->{distro_metadata}->{$d}->{distvname},'Makefile.PL');
895 0           my $destination = File::Spec->catfile('.', $this_makefile_pl);
896             my $extract = $self->{distro_metadata}->{$d}->{tar}->extract_file(
897 0           $source,
898             $destination,
899             );
900 0 0         croak "Unable to extract Makefile.PL from tarball" unless $extract;
901 0 0         croak "Unable to locate extracted Makefile.PL" unless -f $destination;
902             }
903 0 0         croak "Could not locate $this_makefile_pl for configuring" unless -f $this_makefile_pl;
904              
905 0 0         if ($self->{distro_metadata}->{$d}->{needs_ppport_h}) {
906 0           my $source = File::Spec->catfile($self->{distro_metadata}->{$d}->{distvname},'ppport.h');
907 0           my $destination = File::Spec->catfile('.', 'ppport.h');
908             my $extract = $self->{distro_metadata}->{$d}->{tar}->extract_file(
909 0           $source,
910             $destination,
911             );
912 0 0         croak "Unable to extract ppport.h from tarball" unless $extract;
913 0 0         croak "Unable to locate extracted ppport.h" unless -f $destination;
914             }
915              
916 0 0         if ($self->{distro_metadata}->{$d}->{needs_threads_h}) {
917 0           my $source = File::Spec->catfile($self->{distro_metadata}->{$d}->{distvname},'threads.h');
918 0           my $destination = File::Spec->catfile('.', 'threads.h');
919             my $extract = $self->{distro_metadata}->{$d}->{tar}->extract_file(
920 0           $source,
921             $destination,
922             );
923 0 0         croak "Unable to extract threads.h from tarball" unless $extract;
924 0 0         croak "Unable to locate extracted threads.h" unless -f $destination;
925             }
926              
927 0 0         if ($self->{distro_metadata}->{$d}->{needs_shared_h}) {
928 0           my $source = File::Spec->catfile($self->{distro_metadata}->{$d}->{distvname},'shared.h');
929 0           my $destination = File::Spec->catfile('.', 'shared.h');
930             my $extract = $self->{distro_metadata}->{$d}->{tar}->extract_file(
931 0           $source,
932             $destination,
933             );
934 0 0         croak "Unable to extract shared.h from tarball" unless $extract;
935 0 0         croak "Unable to locate extracted shared.h" unless -f $destination;
936             }
937              
938 0           $cmd = qq| $p->{path} $this_makefile_pl > $debugfile 2>&1 |;
939 0 0         $rv = system($cmd) and say STDERR " FAIL: $d: $p->{canon}: Makefile.PL";
940 0 0         $this_result->{$p->{canon}}{configure} = $rv ? 0 : 1; undef $rv;
  0            
941 0 0         unless ($this_result->{$p->{canon}}{configure}) {
942 0           undef $this_result->{$p->{canon}}{make};
943 0           undef $this_result->{$p->{canon}}{test};
944 0           next THIS_PERL;
945             }
946              
947 0 0         $rv = system(qq| make >> $debugfile 2>&1 |)
948             and say STDERR " FAIL: $d: $p->{canon}: make";
949 0 0         $this_result->{$p->{canon}}{make} = $rv ? 0 : 1; undef $rv;
  0            
950 0 0         unless ($this_result->{$p->{canon}}{make}) {
951 0           undef $this_result->{$p->{canon}}{test};
952 0           next THIS_PERL;
953             }
954              
955 0 0         $rv = system(qq| make test >> $debugfile 2>&1 |)
956             and say STDERR " FAIL: $d: $p->{canon}: make test";
957 0 0         $this_result->{$p->{canon}}{test} = $rv ? 0 : 1; undef $rv;
  0            
958              
959 0 0         system(qq| make clean 2>&1 1>/dev/null |)
960             and carp "Unable to 'make clean' for $d";
961             }
962             chdir $self->{temp_top_dir}
963 0 0         or croak "Unable to change to tempdir $self->{temp_top_dir}";
964 0           return $this_result;
965             }
966              
967             =head2 C
968              
969             =over 4
970              
971             =item * Purpose
972              
973             Create a file holding a summary of the results for running one distro against
974             each of the selected Fs.
975              
976             =item * Arguments
977              
978             $self->print_distro_summary('Some-Distro');
979              
980             String holding name of distro.
981              
982             =item * Return Value
983              
984             Returns true value on success.
985              
986             =item * Comment
987              
988             File created will be named like F.
989              
990             File's content will look like this:
991              
992             Attribute-Handlers v5.35.7-48-g34e3587
993             {
994             "5.006002" => { a => "perl5.6.2", configure => 1, make => 0, test => undef },
995             "5.008009" => { a => "perl5.8.9", configure => 1, make => 0, test => undef },
996             "5.010001" => { a => "perl5.10.1", configure => 1, make => 0, test => undef },
997             ...
998             "5.034000" => { a => "perl5.34.0", configure => 1, make => 1, test => 1 },
999             }
1000              
1001             =back
1002              
1003             =cut
1004              
1005             sub print_distro_summary {
1006 0     0 1   my ($self, $d) = @_;
1007 0           my $output = File::Spec->catfile($self->{results_dir}, "$d.summary.txt");
1008 0 0         open my $OUT, '>', $output or die "Unable to open $output for writing: $!";
1009 0           say $OUT sprintf "%-52s%20s" => ($d, $self->{describe});
1010 0           my $oldfh = select($OUT);
1011 0           dd $self->{results}->{$d};
1012 0 0         close $OUT or die "Unable to close $output after writing: $!";
1013 0           select $oldfh;
1014             say sprintf "%-24s%-48s" => ($d, $output)
1015 0 0         if $self->{verbose};
1016             }
1017              
1018             # Check tarballs we have on disk to see whether they contain a
1019             # Makefile.PL.
1020             # $ pwd
1021             # /home/jkeenan/learn/perl/p5p/dist-backcompat/tarballs/authors/id
1022             # $ ls . | head -n 5
1023             # Attribute-Handlers-0.99.tar.gz
1024             # autouse-1.11.tar.gz
1025             # base-2.23.tar.gz
1026             # Carp-1.50.tar.gz
1027             # constant-1.33.tar.gz
1028              
1029             sub identify_cpan_tarballs_with_makefile_pl {
1030 0     0 0   my $self = shift;
1031 0           my $id_dir = File::Spec->catdir($self->{tarball_dir}, 'authors', 'id');
1032 0 0         opendir my $DIR, $id_dir
1033             or croak "Unable to open directory $id_dir for reading";
1034 0           my @available = map { File::Spec->catfile('authors', 'id', $_) }
1035 0           grep { m/\.tar\.gz$/ } readdir $DIR;
  0            
1036 0 0         closedir $DIR or croak "Unable to close directory $id_dir after reading";
1037 0           my %this = ();
1038 0           for my $tb (@available) {
1039 0           my $d = CPAN::DistnameInfo->new($tb);
1040 0           my $dist = $d->dist;
1041 0           my $distvname = $d->distvname;
1042 0           $this{$dist}{tarball} = File::Spec->catfile($self->{tarball_dir}, $tb);
1043 0           $this{$dist}{distvname} = $distvname;
1044             }
1045 0           return \%this;
1046             }
1047              
1048             =head1 INTERNAL SUBROUTINES
1049              
1050             =head2 C
1051              
1052             =over 4
1053              
1054             =item * Purpose
1055              
1056             Assure us that our environment is adequate to the task.
1057              
1058             =item * Arguments
1059              
1060             sanity_check(\%distmodules, $verbose);
1061              
1062             List of two scalars: (i) reference to the hash which is storing list of
1063             F distros; (ii) verbosity selection.
1064              
1065             =item * Return Value
1066              
1067             Implicitly returns true on success, but does not otherwise return any
1068             meaningful value.
1069              
1070             =item * Comment
1071              
1072             If verbosity is selected, displays the current git commit and other useful
1073             information on F.
1074              
1075             =back
1076              
1077             =cut
1078              
1079             sub _sanity_check {
1080 0     0     my ($distmodules, $describe, $verbose) = @_;
1081 0           for my $m (keys %{$distmodules}) {
  0            
1082 0 0         if ($distmodules->{$m}{UPSTREAM} ne 'blead') {
1083 0           warn "Distro $m has UPSTREAM other than 'blead'";
1084             }
1085 0 0         if ($distmodules->{$m}{MAINTAINER} ne 'P5P') {
1086 0           warn "Distro $m has MAINTAINER other than 'P5P'";
1087             }
1088             }
1089              
1090 0 0         if ($verbose) {
1091 0           say "p5-dist-backcompat";
1092 0           my $ldescribe = length $describe;
1093             my $message = q|Found | .
1094 0           (scalar keys %{$distmodules}) .
  0            
1095             q| 'dist/' entries in %Maintainers::Modules|;
1096 0           my $lmessage = length $message;
1097 0           my $ldiff = $lmessage - $ldescribe;
1098 0           say sprintf "%-${ldiff}s%s" => ('Results at commit:', $describe);
1099 0           say "\n$message";
1100             }
1101 0           return 1;
1102             }
1103              
1104             =head2 C
1105              
1106             =over 4
1107              
1108             =item * Purpose
1109              
1110             Get a sorted list of all files in F (without their descriptions).
1111              
1112             =item * Arguments
1113              
1114             read_manifest('/path/to/MANIFEST');
1115              
1116             One scalar: the path to F in a git checkout of the Perl 5 core distribution.
1117              
1118             =item * Return Value
1119              
1120             List (sorted) of all files in F.
1121              
1122             =item * Comment
1123              
1124             Depends on C from F.
1125              
1126             (This is so elementary and useful that it should probably be in F!)
1127              
1128             =back
1129              
1130             =cut
1131              
1132             sub read_manifest {
1133 0     0 1   my $manifest = shift;
1134 0 0         open(my $IN, '<', $manifest) or die("Can't read '$manifest': $!");
1135 0           my @manifest = <$IN>;
1136 0 0         close($IN) or die($!);
1137 0           chomp(@manifest);
1138              
1139 0           my %seen= ( '' => 1 ); # filter out blank lines
1140 0           return grep { !$seen{$_}++ } sort_manifest(@manifest);
  0            
1141             }
1142              
1143             1;
1144