1#!perl 2# Reports, in a perl source tree, which dual-lived core modules have not the 3# same version than the corresponding module on CPAN. 4# with -t option, can compare multiple source trees in tabular form. 5 6use 5.9.0; 7use strict; 8use Getopt::Std; 9use ExtUtils::MM_Unix; 10use lib 'Porting'; 11use Maintainers qw(get_module_files reload_manifest %Modules); 12use Cwd; 13 14use List::Util qw(max); 15 16our $packagefile = '02packages.details.txt'; 17 18sub usage () { 19 die <<USAGE; 20$0 21$0 -t home1[:label] home2[:label] ... 22 23Report which core modules are outdated. 24To be run at the root of a perl source tree. 25 26Options : 27-h : help 28-v : verbose (print all versions of all files, not only those which differ) 29-f : force download of $packagefile from CPAN 30 (it's expected to be found in the current directory) 31-t : display in tabular form CPAN vs one or more perl source trees 32USAGE 33} 34 35sub get_package_details () { 36 my $url = 'http://www.cpan.org/modules/02packages.details.txt.gz'; 37 unlink $packagefile; 38 system("wget $url && gunzip $packagefile.gz") == 0 39 or die "Failed to get package details\n"; 40} 41 42getopts('fhvt'); 43our $opt_h and usage; 44our $opt_t; 45 46my @sources = @ARGV ? @ARGV : '.'; 47die "Too many directories specified without -t option\n" 48 if @sources != 1 and ! $opt_t; 49 50@sources = map { 51 # handle /home/user/perl:bleed style labels 52 my ($dir,$label) = split /:/; 53 $label = $dir unless defined $label; 54 [ $dir, $label ]; 55 } @sources; 56 57our $opt_f || !-f $packagefile and get_package_details; 58 59# Load the package details. All of them. 60my %cpanversions; 61open my $fh, '<', $packagefile or die $!; 62while (<$fh>) { 63 my ($p, $v) = split ' '; 64 next if 1../^\s*$/; # skip header 65 $cpanversions{$p} = $v; 66} 67close $fh; 68 69my %results; 70 71# scan source tree(s) and CPAN module list, and put results in %results 72 73foreach my $source (@sources) { 74 my ($srcdir, $label) = @$source; 75 my $olddir = getcwd(); 76 chdir $srcdir or die "chdir $srcdir: $!\n"; 77 78 # load the MANIFEST file in the new directory 79 reload_manifest; 80 81 for my $dist (sort keys %Modules) { 82 next unless $Modules{$dist}{CPAN}; 83 for my $file (get_module_files($dist)) { 84 next if $file !~ /(\.pm|_pm.PL)\z/ 85 or $file =~ m{^t/} or $file =~ m{/t/}; 86 my $vcore = '!EXIST'; 87 $vcore = MM->parse_version($file) // 'undef' if -f $file; 88 89 # get module name from filename to lookup CPAN version 90 my $module = $file; 91 $module =~ s/\_pm.PL\z//; 92 $module =~ s/\.pm\z//; 93 # some heuristics to figure out the module name from the file name 94 $module =~ s{^(lib|ext|dist|cpan)/}{} 95 and $1 =~ /(?:ext|dist|cpan)/ 96 and ( 97 # ext/Foo-Bar/Bar.pm 98 $module =~ s{^(\w+)-(\w+)/\2$}{$1/lib/$1/$2}, 99 # ext/Encode/Foo/Foo.pm 100 $module =~ s{^(Encode)/(\w+)/\2$}{$1/lib/$1/$2}, 101 $module =~ s{^[^/]+/}{}, 102 $module =~ s{^lib/}{}, 103 ); 104 $module =~ s{/}{::}g; 105 my $vcpan = $cpanversions{$module} // 'undef'; 106 $results{$dist}{$file}{$label} = $vcore; 107 $results{$dist}{$file}{CPAN} = $vcpan; 108 } 109 } 110 111 chdir $olddir or die "chdir $olddir: $!\n"; 112} 113 114# output %results in the requested format 115 116my @labels = ((map $_->[1], @sources), 'CPAN' ); 117 118if ($opt_t) { 119 my %changed; 120 my @fields; 121 for my $dist (sort { lc $a cmp lc $b } keys %results) { 122 for my $file (sort keys %{$results{$dist}}) { 123 my @versions = @{$results{$dist}{$file}}{@labels}; 124 for (0..$#versions) { 125 $fields[$_] = max($fields[$_], 126 length $versions[$_], 127 length $labels[$_], 128 length '!EXIST' 129 ); 130 } 131 if (our $opt_v or grep $_ ne $versions[0], @versions) { 132 $changed{$dist} = 1; 133 } 134 } 135 } 136 printf "%*s ", $fields[$_], $labels[$_] for 0..$#labels; 137 print "\n"; 138 printf "%*s ", $fields[$_], '-' x length $labels[$_] for 0..$#labels; 139 print "\n"; 140 141 my $field_total; 142 $field_total += $_ + 1 for @fields; 143 144 for my $dist (sort { lc $a cmp lc $b } keys %results) { 145 next unless $changed{$dist}; 146 print " " x $field_total, " $dist\n"; 147 for my $file (sort keys %{$results{$dist}}) { 148 my @versions = @{$results{$dist}{$file}}{@labels}; 149 for (0..$#versions) { 150 printf "%*s ", $fields[$_], $versions[$_]//'!EXIST' 151 } 152 print " $file\n"; 153 } 154 } 155} 156else { 157 for my $dist (sort { lc $a cmp lc $b } keys %results) { 158 my $distname_printed = 0; 159 for my $file (sort keys %{$results{$dist}}) { 160 my ($vcore, $vcpan) = @{$results{$dist}{$file}}{@labels}; 161 if (our $opt_v or $vcore ne $vcpan) { 162 print "\n$dist ($Modules{$dist}{MAINTAINER}):\n" unless ($distname_printed++); 163 print "\t$file: core=$vcore, cpan=$vcpan\n"; 164 } 165 } 166 } 167} 168