line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
2
|
|
|
2
|
|
761
|
use strict; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
59
|
|
3
|
2
|
|
|
2
|
|
6
|
use warnings; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
40
|
|
4
|
2
|
|
|
2
|
|
890
|
use ExtUtils::Installed; |
|
2
|
|
|
|
|
154053
|
|
|
2
|
|
|
|
|
60
|
|
5
|
2
|
|
|
2
|
|
1250
|
use Getopt::Long; |
|
2
|
|
|
|
|
13758
|
|
|
2
|
|
|
|
|
5
|
|
6
|
2
|
|
|
2
|
|
203
|
use Config; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
45
|
|
7
|
2
|
|
|
2
|
|
6
|
use version; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
9
|
|
8
|
2
|
|
|
2
|
|
1038
|
use IO::Zlib; |
|
2
|
|
|
|
|
91947
|
|
|
2
|
|
|
|
|
10
|
|
9
|
2
|
|
|
2
|
|
854
|
use CPAN::DistnameInfo; |
|
2
|
|
|
|
|
1242
|
|
|
2
|
|
|
|
|
48
|
|
10
|
2
|
|
|
2
|
|
996
|
use Module::Metadata; |
|
2
|
|
|
|
|
7478
|
|
|
2
|
|
|
|
|
57
|
|
11
|
2
|
|
|
2
|
|
911
|
use URI; |
|
2
|
|
|
|
|
6109
|
|
|
2
|
|
|
|
|
421
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = "0.30"; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
my $mirror = 'http://www.cpan.org/'; |
16
|
|
|
|
|
|
|
my $local_lib; |
17
|
|
|
|
|
|
|
my $self_contained = 0; |
18
|
|
|
|
|
|
|
my $index_file; |
19
|
|
|
|
|
|
|
my $help; |
20
|
|
|
|
|
|
|
Getopt::Long::Configure("bundling"); |
21
|
|
|
|
|
|
|
Getopt::Long::GetOptions( |
22
|
|
|
|
|
|
|
'h|help' => \$help, |
23
|
|
|
|
|
|
|
'verbose' => \my $verbose, |
24
|
|
|
|
|
|
|
'm|mirror=s' => \$mirror, |
25
|
|
|
|
|
|
|
'index=s' => \$index_file, |
26
|
|
|
|
|
|
|
'p|print-package' => \my $print_package, |
27
|
|
|
|
|
|
|
'I=s' => sub { die "this option was deprecated" }, |
28
|
|
|
|
|
|
|
'l|local-lib=s' => \$local_lib, |
29
|
|
|
|
|
|
|
'L|local-lib-contained=s' => |
30
|
|
|
|
|
|
|
sub { $local_lib = $_[1]; $self_contained = 1; }, |
31
|
|
|
|
|
|
|
'compare-changes' => sub { |
32
|
|
|
|
|
|
|
die "--compare-changes option was deprecated.\n" |
33
|
|
|
|
|
|
|
. "You can use 'cpan-listchanges `cpan-outdated -p`' instead.\n" |
34
|
|
|
|
|
|
|
. "cpanm cpan-listchanges # install from CPAN\n" |
35
|
|
|
|
|
|
|
}, |
36
|
|
|
|
|
|
|
'exclude-core' => \my $exclude_core, |
37
|
|
|
|
|
|
|
) or $help++; |
38
|
|
|
|
|
|
|
if ($help) { |
39
|
|
|
|
|
|
|
require Pod::Usage; |
40
|
|
|
|
|
|
|
Pod::Usage::pod2usage(); |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
$mirror =~ s:/$::; |
44
|
|
|
|
|
|
|
my $index_url = "${mirror}/modules/02packages.details.txt.gz"; |
45
|
|
|
|
|
|
|
$index_url = URI->new($index_url); |
46
|
|
|
|
|
|
|
if ($index_url->isa('URI::file')) { |
47
|
|
|
|
|
|
|
die '--index is incompatible with a file:// mirror' if defined $index_file; |
48
|
|
|
|
|
|
|
$index_file = $index_url->file |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
my $core_modules; |
52
|
|
|
|
|
|
|
if ($exclude_core) { |
53
|
|
|
|
|
|
|
require Module::CoreList; |
54
|
2
|
|
|
2
|
|
11
|
no warnings 'once'; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
1777
|
|
55
|
|
|
|
|
|
|
$core_modules = $Module::CoreList::version{$]}; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
unless ($ENV{HARNESS_ACTIVE}) { |
59
|
|
|
|
|
|
|
&main; |
60
|
|
|
|
|
|
|
exit; |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
sub modules_to_check { |
64
|
0
|
|
|
0
|
|
0
|
my @inc = @_; |
65
|
|
|
|
|
|
|
# TODO: if you want to filter the target modules, you can change them here. |
66
|
0
|
|
|
|
|
0
|
ExtUtils::Installed->new(skip_cwd => 1, inc_override => \@inc)->modules; |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub installed_version_for { |
70
|
0
|
|
|
0
|
|
0
|
my($pkg, $inc) = @_; |
71
|
|
|
|
|
|
|
|
72
|
0
|
|
|
0
|
|
0
|
local $SIG{__WARN__} = sub {}; |
73
|
0
|
|
|
|
|
0
|
my $meta = Module::Metadata->new_from_module($pkg, inc => $inc); |
74
|
0
|
0
|
|
|
|
0
|
$meta ? $meta->version($pkg) : undef; |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
sub main { |
78
|
0
|
|
|
0
|
|
0
|
my @inc = make_inc($local_lib, $self_contained); |
79
|
|
|
|
|
|
|
|
80
|
0
|
0
|
0
|
|
|
0
|
if ( !defined($index_file) |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
81
|
|
|
|
|
|
|
|| ! -e $index_file || -z $index_file |
82
|
|
|
|
|
|
|
|| !$index_url->isa('URI::file')) { |
83
|
|
|
|
|
|
|
|
84
|
0
|
|
|
|
|
0
|
$index_file = get_index($index_url, $index_file) |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
0
|
|
|
|
|
0
|
my %installed = map { $_ => 1 } modules_to_check(@inc); |
|
0
|
|
|
|
|
0
|
|
88
|
|
|
|
|
|
|
|
89
|
0
|
0
|
|
|
|
0
|
my $fh = zopen($index_file) or die "cannot open $index_file"; |
90
|
|
|
|
|
|
|
# skip header part |
91
|
0
|
|
|
|
|
0
|
while (my $line = <$fh>) { |
92
|
0
|
0
|
|
|
|
0
|
last if $line eq "\n"; |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
# body part |
95
|
0
|
|
|
|
|
0
|
my %seen; |
96
|
|
|
|
|
|
|
my %dist_latest_version; |
97
|
0
|
|
|
|
|
0
|
LINES: while (my $line = <$fh>) { |
98
|
0
|
|
|
|
|
0
|
my ($pkg, $version, $dist) = split /\s+/, $line; |
99
|
0
|
0
|
|
|
|
0
|
next unless $installed{$pkg}; |
100
|
0
|
0
|
|
|
|
0
|
next if $version eq 'undef'; |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# The note below about the latest version heuristics applies here too |
103
|
0
|
0
|
|
|
|
0
|
next if $seen{$dist}; |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# $Mail::SpamAssassin::Conf::VERSION is 'bogus' |
106
|
|
|
|
|
|
|
# https://rt.cpan.org/Public/Bug/Display.html?id=73465 |
107
|
0
|
0
|
|
|
|
0
|
next unless $version =~ /[0-9]/; |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# if excluding core modules |
110
|
0
|
0
|
0
|
|
|
0
|
next if $exclude_core && exists $core_modules->{$pkg}; |
111
|
|
|
|
|
|
|
|
112
|
0
|
0
|
|
|
|
0
|
next if $dist =~ m{/perl-[0-9._]+\.tar\.(gz|bz2)$}; |
113
|
|
|
|
|
|
|
|
114
|
0
|
0
|
|
|
|
0
|
my $inst_version = installed_version_for($pkg, \@inc) |
115
|
|
|
|
|
|
|
or next; |
116
|
|
|
|
|
|
|
|
117
|
0
|
0
|
|
|
|
0
|
if (compare_version($inst_version, $version)) { |
118
|
0
|
|
|
|
|
0
|
$seen{$dist}++; |
119
|
0
|
0
|
|
|
|
0
|
if ($verbose) { |
|
|
0
|
|
|
|
|
|
120
|
0
|
|
|
|
|
0
|
printf "%-30s %-7s %-7s %s\n", $pkg, $inst_version, $version, $dist; |
121
|
|
|
|
|
|
|
} elsif ($print_package) { |
122
|
0
|
|
|
|
|
0
|
print "$pkg\n"; |
123
|
|
|
|
|
|
|
} else { |
124
|
0
|
|
|
|
|
0
|
print "$dist\n"; |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
# return true if $inst_version is less than $version |
132
|
|
|
|
|
|
|
sub compare_version { |
133
|
7
|
|
|
7
|
|
14
|
my ($inst_version, $version) = @_; |
134
|
7
|
100
|
|
|
|
24
|
return 0 if $inst_version eq $version; |
135
|
|
|
|
|
|
|
|
136
|
5
|
|
66
|
|
|
5
|
my $inst_version_obj = eval { version->new($inst_version) } || version->new(permissive_filter($inst_version)); |
137
|
5
|
|
66
|
|
|
11
|
my $version_obj = eval { version->new($version) } || version->new(permissive_filter($version)); |
138
|
|
|
|
|
|
|
|
139
|
5
|
100
|
|
|
|
33
|
return $inst_version_obj < $version_obj ? 1 : 0; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
# for broken packages. |
143
|
|
|
|
|
|
|
sub permissive_filter { |
144
|
12
|
|
|
12
|
|
34
|
local $_ = $_[0]; |
145
|
12
|
|
|
|
|
23
|
s/^[Vv](\d)/$1/; # Bioinf V2.0 |
146
|
12
|
|
|
|
|
18
|
s/^(\d+)_(\d+)$/$1.$2/; # VMS-IndexedFile 0_02 |
147
|
12
|
|
|
|
|
12
|
s/-[a-zA-Z]+$//; # Math-Polygon-Tree 0.035-withoutworldwriteables |
148
|
12
|
|
|
|
|
27
|
s/([a-j])/ord($1)-ord('a')/gie; # DBD-Solid 0.20a |
|
7
|
|
|
|
|
16
|
|
149
|
12
|
|
|
|
|
19
|
s/[_h-z-]/./gi; # makepp 1.50.2vs.070506 |
150
|
12
|
|
|
|
|
11
|
s/\.{2,}/./g; |
151
|
12
|
|
|
|
|
73
|
$_; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# Return the $fname (a generated File::Temp object if not provided) |
156
|
|
|
|
|
|
|
sub get_index { |
157
|
0
|
|
|
0
|
|
|
my ($url, $fname) = @_; |
158
|
0
|
|
|
|
|
|
require HTTP::Tiny; |
159
|
0
|
|
|
|
|
|
my $ua = HTTP::Tiny->new; |
160
|
0
|
|
|
|
|
|
my $response; |
161
|
0
|
0
|
|
|
|
|
if (defined $fname) { |
162
|
|
|
|
|
|
|
# If the file is not empty, use it as a local cached copy |
163
|
0
|
0
|
|
|
|
|
if (-s $fname) { |
164
|
0
|
|
|
|
|
|
$response = $ua->mirror($url, $fname); |
165
|
|
|
|
|
|
|
} else { |
166
|
|
|
|
|
|
|
# If the file is empty we do not trust its timestamp |
167
|
|
|
|
|
|
|
# so set a custom If-Modified-Since (Perl 5.0 release) |
168
|
0
|
|
|
|
|
|
$response = $ua->mirror($url, $fname, |
169
|
|
|
|
|
|
|
{ |
170
|
|
|
|
|
|
|
headers => { |
171
|
|
|
|
|
|
|
'if-modified-since' => 'Wed, 19 Oct 1994 17:18:57 GMT', |
172
|
|
|
|
|
|
|
}, |
173
|
|
|
|
|
|
|
}); |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
} else { |
176
|
0
|
|
|
|
|
|
require File::Temp; |
177
|
0
|
|
|
|
|
|
$fname = File::Temp->new(UNLINK => 1, SUFFIX => '.gz'); |
178
|
0
|
|
|
|
|
|
binmode $fname; |
179
|
|
|
|
|
|
|
$response = $ua->request( |
180
|
|
|
|
|
|
|
'GET' => $url, |
181
|
|
|
|
|
|
|
{ |
182
|
0
|
|
|
0
|
|
|
data_callback => sub { print {$fname} $_[0] }, |
|
0
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
} |
184
|
0
|
|
|
|
|
|
); |
185
|
0
|
|
|
|
|
|
close $fname; |
186
|
|
|
|
|
|
|
} |
187
|
0
|
0
|
|
|
|
|
if ($response->{status} == 599) { |
|
|
0
|
|
|
|
|
|
188
|
0
|
|
|
|
|
|
die "Cannot get_index $url to $fname: $response->{content}"; |
189
|
|
|
|
|
|
|
# 304 = "Not Modified" is still a success since we are mirroring |
190
|
|
|
|
|
|
|
} elsif (! $response->{success}) { |
191
|
0
|
|
|
|
|
|
die "Cannot get_index $url to $fname: $response->{status} $response->{reason}"; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
#print "$fname $response->{status} $response->{reason}\n"; |
194
|
|
|
|
|
|
|
# Return the filename (which might be a File::Temp object) |
195
|
|
|
|
|
|
|
$fname |
196
|
0
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
sub zopen { |
199
|
|
|
|
|
|
|
# Explicitely stringify the filename as it may be a File::Temp object |
200
|
0
|
|
|
0
|
|
|
IO::Zlib->new("$_[0]", "rb"); |
201
|
|
|
|
|
|
|
} |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
sub make_inc { |
204
|
0
|
|
|
0
|
|
|
my ($base, $self_contained) = @_; |
205
|
|
|
|
|
|
|
|
206
|
0
|
0
|
|
|
|
|
if ($base) { |
207
|
0
|
|
|
|
|
|
require local::lib; |
208
|
0
|
|
|
|
|
|
my @modified_inc = ( |
209
|
|
|
|
|
|
|
local::lib->install_base_perl_path($base), |
210
|
|
|
|
|
|
|
local::lib->install_base_arch_path($base), |
211
|
|
|
|
|
|
|
); |
212
|
0
|
0
|
|
|
|
|
if ($self_contained) { |
213
|
0
|
|
|
|
|
|
push @modified_inc, @Config{qw(privlibexp archlibexp)}; |
214
|
|
|
|
|
|
|
} else { |
215
|
0
|
|
|
|
|
|
push @modified_inc, @INC; |
216
|
|
|
|
|
|
|
} |
217
|
0
|
|
|
|
|
|
return @modified_inc; |
218
|
|
|
|
|
|
|
} else { |
219
|
0
|
|
|
|
|
|
return @INC; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
__END__ |