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.
11 use Maintainers qw(get_module_files reload_manifest %Modules);
14 use List::Util qw(max);
16 our $packagefile = '02packages.details.txt';
21 $0 -t home1[:label] home2[:label] ...
23 Report which core modules are outdated.
24 To be run at the root of a perl source tree.
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
35 sub get_package_details () {
36 my $url = 'http://www.cpan.org/modules/02packages.details.txt.gz';
38 system("wget $url && gunzip $packagefile.gz") == 0
39 or die "Failed to get package details\n";
46 my @sources = @ARGV ? @ARGV : '.';
47 die "Too many directories speficied without -t option\n"
48 if @sources != 1 and ! $opt_t;
51 # handle /home/user/perl:bleed style labels
52 my ($dir,$label) = split /:/;
53 $label = $dir unless defined $label;
57 our $opt_f || !-f $packagefile and get_package_details;
59 # Load the package details. All of them.
61 open my $fh, $packagefile or die $!;
63 my ($p, $v) = split ' ';
64 next if 1../^\s*$/; # skip header
65 $cpanversions{$p} = $v;
71 # scan source tree(s) and CPAN module list, and put results in %results
73 foreach my $source (@sources) {
74 my ($srcdir, $label) = @$source;
75 my $olddir = getcwd();
76 chdir $srcdir or die "chdir $srcdir: $!\n";
78 # load the MANIFEST file in the new directory
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/};
87 $vcore = MM->parse_version($file) // 'undef' if -f $file;
89 # get module name from filename to lookup CPAN version
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)/
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/}{},
104 $module =~ s{/}{::}g;
105 my $vcpan = $cpanversions{$module} // 'undef';
106 $results{$dist}{$file}{$label} = $vcore;
107 $results{$dist}{$file}{CPAN} = $vcpan;
111 chdir $olddir or die "chdir $olddir: $!\n";
114 # output %results in the requested format
116 my @labels = ((map $_->[1], @sources), 'CPAN' );
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[$_],
131 if (our $opt_v or grep $_ ne $versions[0], @versions) {
136 printf "%*s ", $fields[$_], $labels[$_] for 0..$#labels;
138 printf "%*s ", $fields[$_], '-' x length $labels[$_] for 0..$#labels;
142 $field_total += $_ + 1 for @fields;
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'
157 for my $dist (sort { lc $a cmp lc $b } keys %results) {
158 print "Module $dist...\n";
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 " $file: core=$vcore, cpan=$vcpan\n";