File Coverage

blib/lib/Perl5/Dist/Backcompat.pm
Criterion Covered Total %
statement 35 364 9.6
branch 0 186 0.0
condition 0 38 0.0
subroutine 12 27 44.4
pod 12 13 92.3
total 59 628 9.3


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