File Coverage

blib/lib/CPAN/Unwind.pm
Criterion Covered Total %
statement 189 214 88.3
branch 33 58 56.9
condition 3 5 60.0
subroutine 34 41 82.9
pod 3 5 60.0
total 262 323 81.1


line stmt bran cond sub pod time code
1             ###########################################
2             # CPAN::Unwind -- 2005, Mike Schilli
3             ###########################################
4              
5             ###########################################
6             package CPAN::Unwind;
7             ###########################################
8              
9 1     1   39478 use strict;
  1         3  
  1         38  
10 1     1   4 use warnings;
  1         2  
  1         91  
11 1     1   16335 use CPAN qw();
  1         393973  
  1         125  
12 1     1   1879 use File::Temp qw(tempfile tempdir);
  1         14564  
  1         97  
13 1     1   7305 use Log::Log4perl qw(:easy);
  1         156049  
  1         7  
14 1     1   1186 use Log::Log4perl::Util;
  1         3  
  1         100  
15 1     1   3724 use Data::Dumper;
  1         11863  
  1         77  
16 1     1   1337 use LWP::UserAgent;
  1         67485  
  1         45  
17 1     1   1134 use Module::Depends::Intrusive;
  1         11825  
  1         16  
18 1     1   2406 use Archive::Tar;
  1         131125  
  1         94  
19 1     1   12 use Storable qw(freeze thaw);
  1         2  
  1         91  
20 1     1   1075 use Cache::FileCache;
  1         41918  
  1         69  
21 1     1   25 use Cache::Cache;
  1         2  
  1         46  
22 1     1   21 use Cwd;
  1         2  
  1         2190  
23              
24             our $VERSION = "0.06";
25             our $TGZ = "tar.tgz";
26              
27             # These troublemakers are ignored when listed as a dependency
28             our %BLACKLISTED = map { $_ => 1 } qw(perl);
29              
30             ###########################################
31             sub new {
32             ###########################################
33 2     2 1 2165 my($class, %options) = @_;
34              
35 2         20 my $self = {
36             add => [],
37             core_include => 0,
38             %options,
39             };
40              
41 2 50       11 if(exists $options{cache}) {
42 2 50       11 $options{cache} = CPAN::Unwind::Pseudocache->new()
43             unless $options{cache};
44             } else {
45 0         0 $self->{cache} = Cache::FileCache->new(
46             {namespace => "cpan_unwind",
47             });
48             }
49              
50 2         10 bless $self, $class;
51             }
52              
53             ###########################################
54             sub tarball_url {
55             ###########################################
56 3     3 0 4 my($self, $mname) = @_;
57              
58 3         5 my $cpan_url;
59              
60 3         5 eval {
61 3         1526 require CPAN::Config;
62 0         0 $cpan_url = $CPAN::Config->{urllist}->[0];
63             };
64              
65 3   50     39 $cpan_url ||= "http://search.cpan.org/CPAN";
66 3         5 $cpan_url .= "/modules/by-authors/id";
67              
68 3         17 my ($fh, $filename) = tempfile(CLEANUP => 1);
69              
70 3         2049 local(*STDOUT);
71 3         8 local(*STDERR);
72 3 50       218 open STDOUT, ">$filename" or die "Can't open $filename";
73 3 50       136 open STDERR, ">>$filename" or die "Can't open $filename";
74              
75 3         18 for my $type (qw(Module Distribution)) {
76              
77 3         26 DEBUG "Expanding $type/$mname";
78 3         97 my @expands = CPAN::Shell->expand($type, $mname);
79              
80 3         2659149 DEBUG Dumper(\@expands);
81 3 50       771 next unless @expands;
82              
83 3         10 for (@expands) {
84 3 50       52 my $f = ($type eq "Module") ? $_->cpan_file : $_->id;
85 3         353 unlink $filename;
86 3         76 close STDOUT;
87 3         32 close STDERR;
88 3         169 return "$cpan_url/$f";
89             }
90             }
91              
92 0         0 unlink $filename;
93 0         0 close STDOUT;
94 0         0 close STDERR;
95              
96 0         0 return undef;
97             }
98              
99             ###########################################
100             sub lookup {
101             ###########################################
102 2     2 1 126 my($self, @mnames) = @_;
103              
104 2         7 my %unresolved = map { ($_ => 1) } @mnames;
  2         11  
105 2         5 my %resolved = ();
106 2         5 my @in_core = ();
107              
108 2         15 my $result = CPAN::Unwind::Response->new(mname => [@mnames],
109             success => 1);
110 2         25 $result->{dependency_graph} = Algorithm::Dependency::Source::Mem->new();
111 2         9 $result->{dependents} = {};
112              
113 2         7 while(keys %unresolved) {
114              
115 3         9 my $mname = (keys %unresolved)[0];
116              
117 3         10 delete $unresolved{$mname};
118              
119 3         11 $resolved{$mname}++;
120              
121 3         14 my $resp = $self->lookup_single($mname);
122              
123 3 50       21 return $resp unless $resp->is_success();
124              
125 3 100 66     53 if(!$self->{core_include} and $resp->is_core()) {
126             # Mark item as taken care of, it's in the core
127 1         206 $result->{dependency_graph}->item_select($mname);
128             }
129              
130 3         17 my $deps = $resp->dependent_versions();
131              
132 3         35 $result->{dependency_graph}->item_add($mname, keys %$deps);
133 3         12 $result->{dependents}->{$mname} = [];
134              
135 3         243 for(keys %$deps) {
136 2         17 DEBUG "Adding dependency $_";
137 2         17 push @{$result->{dependents}->{$mname}}, $_;
  2         11  
138              
139 2 100       17 $unresolved{$_} = 1 unless exists $resolved{$_};
140              
141 2 50       13 if(exists $result->{dependent_versions}->{$_}) {
142             # Already got that one, only store it if the
143             # required version number is higher
144 0 0       0 if($result->{dependent_versions}->{$_} < $deps->{$_}) {
145 0         0 $result->{dependent_versions}->{$_} = $deps->{$_};
146             }
147             } else {
148 2         42 $result->{dependent_versions}->{$_} = $deps->{$_};
149             }
150             }
151             }
152              
153 2         31 return $result;
154             }
155              
156             ###########################################
157             sub lookup_single {
158             ###########################################
159 3     3 1 7 my($self, $mname) = @_;
160              
161 3 50       15 if($self->{cache}) {
162 3         38 my $cached = $self->{cache}->get($mname);
163              
164 3 50       636 if($cached) {
165 0         0 my $href = thaw($cached);
166 0         0 DEBUG "Found $mname deps in cache";
167 0         0 return CPAN::Unwind::Response->new(
168             mname => $mname,
169             success => 1,
170             dependent_versions => $href);
171             }
172             }
173              
174 3         17 my $url = $self->tarball_url($mname);
175              
176 3 50       119 LOGDIE "Couldn't get tarball for $mname from CPAN" unless defined $url;
177              
178             # Don't knock yourself out on modules that are part of the core
179 3 100       26 if($url =~ m#/perl-\d#) {
180 1         15 return CPAN::Unwind::Response->new(
181             mname => $mname,
182             success => 1,
183             is_core => 1,
184             dependent_versions => {} );
185             }
186              
187 2 50       6 return CPAN::Unwind::Response->new(
188             mname => $mname,
189             message => "No tarball found for $mname") unless $url;
190              
191 2         18 my $tempdir = tempdir(
192             CLEANUP => 1
193             );
194              
195 2         1030 DEBUG "Created tempdir $tempdir";
196              
197 2         41 my $ua = LWP::UserAgent->new();
198 2         3844 my $resp = $ua->get("$url");
199              
200 2 50       945180 if($resp->is_error()) {
201 0         0 return CPAN::Unwind::Response->new(
202             mname => $mname,
203             message => "Fetching tarball $url failed");
204             }
205              
206 2         45 my $tgzfile = "$tempdir/$TGZ";
207 2 50       426 open FILE, ">$tgzfile" or LOGDIE "Can't open $tgzfile ($!)";
208 2         32 print FILE $resp->content();
209 2         345 close FILE;
210              
211 2         29 my $cwd = getcwd();
212 2 50       61 chdir $tempdir or LOGDIE "Cannot chdir to $tempdir";
213              
214 2         7 my $deps = {};
215              
216 2         7 eval {
217 2         39 my $tar = Archive::Tar->new();
218 2         57 $tar->read($TGZ, 1);
219 2 50       86312 $tar->extract() or LOGDIE "Cannot extract";
220            
221 2         93764 $deps = Module::Depends::Intrusive->new()->
222             dist_dir(subdir_find("."))->find_modules()->requires();
223              
224 2         463 DEBUG "Found dependent_versions of $mname: ", Dumper($deps);
225             };
226              
227 2         1025 delete $deps->{$_} for keys %BLACKLISTED;
228              
229 2 50       60 chdir $cwd or LOGDIE "Cannot chdir to $cwd";
230              
231 2 50       13 return CPAN::Unwind::Response->new(
232             mname => $mname,
233             message => "Determining dependencies failed") if $@;
234            
235 2 50       20 if($self->{cache}) {
236 2         15 DEBUG "Setting cache for $mname";
237 2         44 $self->{cache}->set($mname, freeze($deps));
238             }
239              
240 2         5630 return CPAN::Unwind::Response->new(
241             mname => $mname,
242             success => 1,
243             dependent_versions => $deps);
244             }
245              
246             ###########################################
247             sub subdir_find {
248             ###########################################
249 2     2 0 434 my($dir) = @_;
250              
251 2 50       77 opendir DIR, $dir or LOGDIE "opendir $dir failed ($!)";
252 2         153 my @dirs = readdir(DIR);
253 2         27 closedir DIR;
254              
255 2         11 for(@dirs) {
256 3 50       25 next if /^\./;
257 3 100       47 next unless -d;
258 2         41 return $_;
259             }
260              
261 0         0 return undef;
262             }
263              
264             ###########################################
265             package CPAN::Unwind::Response;
266             ###########################################
267 1     1   1103 use Algorithm::Dependency::Ordered;
  1         8773  
  1         30  
268 1     1   8 use Log::Log4perl qw(:easy);
  1         2  
  1         11  
269 1     1   613 use Data::Dumper;
  1         2  
  1         458  
270              
271             ###########################################
272             sub new {
273             ###########################################
274 5     5   44 my($class, %options) = @_;
275              
276 5         272 my $self = {
277             is_success => 0,
278             is_core => 0,
279             mname => [],
280             dependent_versions => {},
281             message => "",
282             %options,
283             };
284              
285 5         839 bless $self, $class;
286             }
287              
288             ###########################################
289 5     5   79 sub is_success { $_[0]->{success} }
290             ###########################################
291              
292             ###########################################
293 3     3   23 sub is_core { $_[0]->{is_core} }
294             ###########################################
295              
296             ###########################################
297 0     0   0 sub message { $_[0]->{message} }
298             ###########################################
299              
300             ###########################################
301 5     5   22 sub dependent_versions { return $_[0]->{dependent_versions} }
302             ###########################################
303              
304             ###########################################
305 0     0   0 sub dependents { return $_[0]->{dependents} }
306             ###########################################
307              
308             ###########################################
309             sub missing {
310             ###########################################
311 0     0   0 my($self) = @_;
312              
313 0         0 my %missing = map { $_ => $self->{dependent_versions}->{$_} }
  0         0  
314 0         0 grep { ! Log::Log4perl::Util::module_available($_) }
315 0         0 keys %{$self->{dependent_versions}};
316 0         0 return \%missing;
317             }
318              
319             ###########################################
320             sub schedule {
321             ###########################################
322 2     2   1405 my($self) = @_;
323              
324 2         22 DEBUG "Dependency graph: ", Dumper($self->{dependency_graph});
325              
326 2 50       880 my $dep = Algorithm::Dependency::Ordered->new(
327             source => $self->{dependency_graph},
328             selected => $self->{dependency_graph}->{selected},
329             ) or die "Failed to set up dependency algorithm";
330              
331 2         150 my $schedule = $dep->schedule(@{$self->{mname}});
  2         23  
332              
333 2 100       751 LOGDIE "Cannot determine schedule for @{$self->{mname}}" unless $schedule;
  1         17  
334 1         5 return @$schedule;
335             }
336              
337 0     0   0 sub CORE::GLOBAL::exit { }
338              
339             ################################################
340             package Algorithm::Dependency::Source::Mem;
341             ################################################
342 1     1   6 use base qw(Algorithm::Dependency::Source);
  1         2  
  1         94  
343 1     1   6 use Algorithm::Dependency::Item;
  1         1  
  1         25  
344 1     1   6 use Log::Log4perl qw(:easy);
  1         2  
  1         5  
345              
346             ################################################
347             sub new {
348             ################################################
349 2     2   5 my($class) = @_;
350              
351             # Get the basic source object
352 2 50       22 my $self = $class->SUPER::new() or return undef;
353              
354             # Add our arguments
355 2         50 $self->{deps} = [];
356 2         13 $self;
357             }
358              
359             #######################################
360             sub item_add {
361             #######################################
362 3     3   10 my($self, $item, @deps) = @_;
363              
364 3         465 DEBUG "Adding $item - (", join(', ', @deps), ")";
365              
366 3         36 push @{$self->{deps}}, [$item, @deps];
  3         27  
367             }
368              
369             #######################################
370             sub item_select {
371             #######################################
372 1     1   8 my($self, $item) = @_;
373              
374 1         154 DEBUG "Selecting $item";
375              
376 1         12 push @{$self->{selected}}, $item;
  1         9  
377             }
378              
379             #######################################
380             sub _load_item_list {
381             #######################################
382 2     2   109 my($self) = @_;
383              
384 2         6 my @items;
385              
386 2         7 for(@{$self->{deps}}) {
  2         10  
387 3         34 my $item = Algorithm::Dependency::Item->new(@$_);
388 3         53 push @items, $item;
389             }
390              
391 2         12 return \@items;
392             }
393              
394             ###########################################
395             package CPAN::Unwind::Pseudocache;
396             ###########################################
397 0     0     sub new { bless {}, shift }
398 0     0     sub get { return undef; }
399 0     0     sub set { }
400              
401             1;
402              
403             __END__