File Coverage

blib/lib/CPAN/FindDependencies.pm
Criterion Covered Total %
statement 139 148 93.9
branch 49 60 81.6
condition 32 39 82.0
subroutine 23 24 95.8
pod 1 1 100.0
total 244 272 89.7


line stmt bran cond sub pod time code
1             package CPAN::FindDependencies;
2              
3 8     8   628426 use strict;
  8         70  
  8         205  
4 8     8   35 use warnings;
  8         15  
  8         224  
5 8     8   34 use vars qw($p $VERSION @ISA @EXPORT_OK);
  8         15  
  8         561  
6              
7 8     8   3856 use YAML::Tiny ();
  8         36793  
  8         172  
8 8     8   3872 use LWP::UserAgent;
  8         289381  
  8         245  
9 8     8   21419 use Module::CoreList;
  8         769818  
  8         85  
10 8     8   4386 use Scalar::Util qw(blessed);
  8         16  
  8         677  
11 8     8   4021 use CPAN::FindDependencies::Dependency;
  8         22  
  8         228  
12 8     8   2610 use CPAN::FindDependencies::MakeMaker qw(getreqs_from_mm);
  8         24  
  8         413  
13 8     8   3211 use Parse::CPAN::Packages;
  8         6989216  
  8         514  
14              
15             require Exporter;
16             @ISA = qw(Exporter);
17             @EXPORT_OK = qw(finddeps);
18              
19             $VERSION = '2.49';
20              
21 8     8   65 use constant DEFAULT02PACKAGES => 'http://www.cpan.org/modules/02packages.details.txt.gz';
  8         12  
  8         536  
22 8     8   42 use constant MAXINT => ~0;
  8         14  
  8         11636  
23              
24             =head1 NAME
25              
26             CPAN::FindDependencies - find dependencies for modules on the CPAN
27              
28             =head1 SYNOPSIS
29              
30             use CPAN::FindDependencies;
31             my @dependencies = CPAN::FindDependencies::finddeps("CPAN");
32             foreach my $dep (@dependencies) {
33             print ' ' x $dep->depth();
34             print $dep->name().' ('.$dep->distribution().")\n";
35             }
36              
37             =head1 HOW IT WORKS
38              
39             The module uses the CPAN packages index to map modules to distributions and
40             vice versa, and then fetches distributions' META.yml or Makefile.PL files from
41             C<http://metacpan.org/> to determine pre-requisites. This means that a
42             working interwebnet connection is required.
43              
44             =head1 FUNCTIONS
45              
46             There is just one function, which is not exported by default
47             although you can make that happen in the usual fashion.
48              
49             =head2 finddeps
50              
51             Takes a single compulsory parameter, the name of a module
52             (ie Some::Module); and the following optional
53             named parameters:
54              
55             =over
56              
57             =item nowarnings
58              
59             Warnings about modules where we can't find their META.yml or Makefile.PL, and
60             so can't divine their pre-requisites, will be suppressed;
61              
62             =item fatalerrors
63              
64             Failure to get a module's dependencies will be a fatal error
65             instead of merely emitting a warning;
66              
67             =item perl
68              
69             Use this version of perl to figure out what's in core. If not
70             specified, it defaults to 5.005. Three part version numbers
71             (eg 5.8.8) are supported but discouraged.
72              
73             =item 02packages
74              
75             The location of CPAN.pm's C<02packages.details.txt.gz> file as a
76             local filename, with either a relative or an absolute path. If not
77             specified, it is fetched from a CPAN mirror instead. The file is
78             fetched just once.
79              
80             =item cachedir
81              
82             A directory to use for caching. It defaults to no caching. Even if
83             caching is turned on, this is only for META.yml or Makefile.PL files.
84             02packages is not cached - if you want to read that from a local disk, see the
85             C<02packages> option.
86              
87             =item maxdepth
88              
89             Cuts off the dependency tree at the specified depth. Your specified
90             module is at depth 0, your dependencies at depth 1, their dependencies
91             at depth 2, and so on.
92              
93             =item usemakefilepl
94              
95             If set to true, then for any module that doesn't have a META.yml,
96             try to use its Makefile.PL as well. Note that this involves
97             downloading code from the Internet and running it. This obviously
98             opens you up to all kinds of bad juju, hence why it is disabled
99             by default.
100              
101             =item recommended
102              
103             Adds recommended modules to the list of dependencies, if set to a true value.
104              
105             =item configreqs
106            
107             Adds modules required for configuration (the 'configure_requires' list in yaml) to the list of dependencies, if set to a true value.
108              
109              
110             =back
111              
112             It returns a list of CPAN::FindDependencies::Dependency objects, whose
113             useful methods are:
114              
115             =over
116              
117             =item name
118              
119             The module's name;
120              
121             =item distribution
122              
123             The distribution containing this module;
124              
125             =item version
126              
127             The minimum required version of his module (if specified in the requirer's
128             pre-requisites list);
129              
130             =item depth
131              
132             How deep in the dependency tree this module is;
133              
134             =item warning
135              
136             If any warning was generated (even if suppressed) for the module,
137             it will be recorded here.
138              
139             =back
140              
141             Any modules listed as dependencies but which are in the perl core
142             distribution for the version of perl you specified are suppressed.
143              
144             These objects are returned in a semi-defined order. You can be sure
145             that a module will be immediately followed by one of its dependencies,
146             then that dependency's dependencies, and so on, followed by the 'root'
147             module's next dependency, and so on. You can reconstruct the tree
148             by paying attention to the depth of each object.
149              
150             The ordering of any particular module's immediate 'children' can be
151             assumed to be random - it's actually hash key order.
152              
153             =head1 SECURITY
154              
155             If you set C<usemakefilepl> to a true value, this module may download code
156             from the internet and execute it. You should think carefully before enabling
157             that feature.
158              
159             =head1 BUGS/WARNINGS/LIMITATIONS
160              
161             You must have web access to L<http://metacpan.org/> and (unless
162             you tell it where else to look for the index)
163             L<http://www.cpan.org/>, or have all the data cached locally..
164             If any
165             META.yml or Makefile.PL files are missing, the distribution's dependencies will
166             not be found and a warning will be spat out.
167              
168             Startup can be slow, especially if it needs to fetch the index from
169             the interweb.
170              
171             =head1 FEEDBACK
172              
173             I welcome feedback about my code, including constructive criticism
174             and bug reports. The best bug reports include files that I can add
175             to the test suite, which fail with the current code in my git repo and
176             will pass once I've fixed the bug
177              
178             Feature requests are far more likely to get implemented if you submit
179             a patch yourself.
180              
181             =head1 SOURCE CODE REPOSITORY
182              
183             L<git://github.com/DrHyde/perl-modules-CPAN-FindDependencies.git>
184              
185             =head1 SEE ALSO
186              
187             L<CPAN>
188              
189             L<http://deps.cpantesters.org/>
190              
191             L<http://metacpan.org>
192              
193             =head1 AUTHOR, LICENCE and COPYRIGHT
194              
195             Copyright 2007 - 2019 David Cantrell E<lt>F<david@cantrell.org.uk>E<gt>
196              
197             This software is free-as-in-speech software, and may be used,
198             distributed, and modified under the terms of either the GNU
199             General Public Licence version 2 or the Artistic Licence. It's
200             up to you which one you use. The full text of the licences can
201             be found in the files GPL2.txt and ARTISTIC.txt, respectively.
202              
203             =head1 THANKS TO
204              
205             Stephan Loyd (for fixing problems with some META.yml files)
206              
207             Alexandr Ciornii (for a patch to stop it segfaulting on Windows)
208              
209             Brian Phillips (for the code to report on required versions of modules)
210              
211             Ian Tegebo (for the code to extract deps from Makefile.PL)
212              
213             Georg Oechsler (for the code to also list 'recommended' modules)
214              
215             Jonathan Stowe (for making it work through HTTP proxies)
216              
217             Kenneth Olwing (for support for 'configure_requires')
218              
219             =head1 CONSPIRACY
220              
221             This module is also free-as-in-mason software.
222              
223             =cut
224              
225             sub finddeps {
226 13     13 1 43885 my($module, %opts) = @_;
227              
228 13   100     109 $opts{perl} ||= 5.005;
229 13   100     79 $opts{maxdepth} ||= MAXINT;
230              
231             die(__PACKAGE__.": $opts{perl} is a broken version number\n")
232 13 50       123 if($opts{perl} =~ /[^0-9.]/);
233              
234 13 50       74 if($opts{perl} =~ /\..*\./) {
235 0         0 _emitwarning(
236             "Three-part version numbers are a bad idea",
237             %opts
238             );
239 0         0 my @parts = split(/\./, $opts{perl});
240 0         0 $opts{perl} = $parts[0] + $parts[1] / 1000 + $parts[2] / 1000000;
241             }
242              
243 13 100       49 if(!$p) {
244 8     0   60 local $SIG{__WARN__} = sub {};
245 8         34 $p = Parse::CPAN::Packages->new(_get02packages($opts{'02packages'}));
246             }
247              
248 13 50       81604188 return _finddeps(
249             opts => \%opts,
250             target => $module,
251             seen => {},
252             version => ($p->package($module) ? $p->package($module)->version() : 0)
253             );
254             }
255              
256             sub _emitwarning {
257 13     13   101 my($msg, %opts) = @_;
258 13         61 $msg = __PACKAGE__.": $msg\n";
259 13 100       65 if(!$opts{nowarnings}) {
260 2 100       6 if($opts{fatalerrors} ) {
261 1         14 die('FATAL: '.$msg);
262             } else {
263 1         13 warn('WARNING: '.$msg);
264             }
265             }
266             }
267              
268             sub _module2obj {
269 50     50   102 my $module = shift;
270 50         172 $module = $p->package($module);
271 50 100       1426 return undef if(!$module);
272 47         747 return $module->distribution();
273             }
274              
275             # FIXME make these memoise, maybe to disk
276 94     94   1147 sub _finddeps { return @{_finddeps_uncached(@_)}; }
  94         316  
277              
278             sub _get02packages {
279 8     8   20 my $file = shift;
280 8 100       23 if($file) {
281 7     7   455 eval 'use URI::file';
  7         2921  
  7         28948  
  7         124  
282 7 50       41 die($@) if($@);
283 7         30 $file = URI::file->new_abs($file);
284             }
285 8 50 100     98857 _get($file || DEFAULT02PACKAGES) ||
286             die(__PACKAGE__.": Couldn't fetch 02packages index file\n");
287             }
288              
289             sub _get {
290 16     16   249 my $url = shift;
291 16         387 my $ua = LWP::UserAgent->new();
292 16         30344 $ua->env_proxy();
293 16         39602 $ua->agent(__PACKAGE__."/$VERSION");
294 16         3576 my $response = $ua->get($url);
295 16 100       1152725 if($response->is_success()) {
296 8         154 return $response->content();
297             } else {
298 8         267 return undef;
299             }
300             }
301              
302             sub _incore {
303 86     86   265 my %args = @_;
304 86         511 my $core = $Module::CoreList::version{$args{perl}}{$args{module}};
305 86 100       232 $core =~ s/_/00/g if($core);
306 86         193 $args{version} =~ s/_/00/g;
307 86 100 100     596 return ($core && $core >= $args{version}) ? $core : undef;
308             }
309              
310             sub _finddeps_uncached {
311 94     94   331 my %args = @_;
312 94         263 my( $target, $opts, $depth, $version, $seen) = @args{qw(
313             target opts depth version seen
314             )};
315 94   100     888 $depth ||= 0;
316              
317             return [] if(
318             $target eq 'perl' ||
319             _incore(
320             module => $target,
321             perl => $opts->{perl},
322 94 100 100     396 version => $version)
323             );
324              
325 50         121 my $dist = _module2obj($target);
326              
327 50 100       399 return [] unless(blessed($dist));
328              
329 47         929 my $author = $dist->cpanid();
330 47         851 my $distname = $dist->distvname();
331              
332 47 100       319 return [] if($seen->{$distname});
333 40         101 $seen->{$distname} = 1;
334              
335 40         61 my %reqs = @{_getreqs(
  40         138  
336             author => $author,
337             distname => $distname,
338             opts => $opts,
339             )};
340 37         129 my $warning = '';
341 37 100       119 if($reqs{'-warning'}) {
342 7         18 $warning = $reqs{'-warning'};
343 7         21 %reqs = ();
344             }
345              
346             return [
347             CPAN::FindDependencies::Dependency->_new(
348             depth => $depth,
349             cpanmodule => $target,
350             p => $p,
351             version => $version || 0,
352             ($warning ? (warning => $warning) : ())
353             ),
354             ($depth != $opts->{maxdepth}) ? (map {
355             # print "Looking at $_\n";
356 37 100 100     556 _finddeps(
    100          
357             target => $_,
358             opts => $opts,
359             depth => $depth + 1,
360             seen => $seen,
361 81         401 version => $reqs{$_}
362             );
363             } sort keys %reqs) : ()
364             ];
365             }
366              
367             sub _get_file_cached {
368 45     45   141 my %args = @_;
369 45         118 my($src, $destfile, $opts) = @args{qw(src destfile opts)};
370 45         65 my $contents;
371 45 100 66     1521 if($opts->{cachedir} && -d $opts->{cachedir} && -r $opts->{cachedir}."/$destfile") {
      100        
372             open(my $cachefh, $opts->{cachedir}."/$destfile") ||
373 37 50       1447 _emitwarning('Error reading '.$opts->{cachedir}."/$destfile: $!");
374 37         211 local $/ = undef;
375 37         896 $contents = <$cachefh>;
376 37         522 close($cachefh);
377             } else {
378 8         40 $contents = _get($src);
379 8 0 33     38 if($contents && $opts->{cachedir} && -d $opts->{cachedir}) {
      0        
380             open(my $cachefh, '>', $opts->{cachedir}."/$destfile") ||
381 0 0       0 _emitwarning('Error writing '.$opts->{cachedir}."/$destfile: $!");
382 0         0 print $cachefh $contents;
383 0         0 close($cachefh);
384             }
385             }
386 45         178 return $contents;
387             }
388              
389             sub _getreqs {
390 40     40   132 my %args = @_;
391 40         9121 my($author, $distname, $opts) = @args{qw(author distname opts)};
392              
393             # Prefer a META.yml, but if that's not found
394             # add the warning to the 'warning stack', if there is one
395             # Try scanning the Makefile.PL if this is enabled
396             # if found, remove the META.yml warning and return deps
397             # If neither is found, add warning to stack and return
398              
399 40         1314 my $yaml = _get_file_cached(
400             src => "http://fastapi.metacpan.org/source/$author/$distname/META.yml",
401             destfile => "$distname.yml",
402             opts => $opts
403             );
404 40 100       112 if ($yaml) {
405 32         56 my $yaml = eval { YAML::Tiny::Load($yaml); };
  32         128  
406 32 100 66     54554 if ($@ || !defined($yaml)) {
407 4         112 _emitwarning("$author/$distname: failed to parse META.yml", %{$opts})
  4         42  
408             } else {
409 28   100     319 $yaml->{requires} ||= {};
410 28   100     158 $yaml->{build_requires} ||= {};
411 28   100     93 $yaml->{recommends} ||= {};
412 28   100     213 $yaml->{configure_requires} ||= {};
413             return [
414 28         140 %{$yaml->{requires}}, %{$yaml->{build_requires}},
  28         385  
415 2         31 ($opts->{recommended} ? %{$yaml->{recommends}} : ()),
416 28 100       41 ($opts->{configreqs} ? %{$yaml->{configure_requires}} : ()),
  3 100       64  
417             ];
418             }
419             } else {
420 8         33 _emitwarning("$author/$distname: no META.yml", %{$opts});
  8         85  
421             }
422            
423             # We could have failed to parse the META.yml, but we still want to try the Makefile.PL
424 11 100       53 if(!$opts->{usemakefilepl}) {
425 6         45 return ['-warning', 'no META.yml'];
426             } else {
427 5         36 my $makefilepl = _get_file_cached(
428             src => "http://fastapi.metacpan.org/source/$author/$distname/Makefile.PL",
429             destfile => "$distname.MakefilePL",
430             opts => $opts
431             );
432 5 50       18 if($makefilepl) {
433 5         57 my $result = getreqs_from_mm($makefilepl);
434 3 100       36 if ('HASH' eq ref $result) {
435 2         6 return [ %{ $result } ];
  2         92  
436             } else {
437 1         12 _emitwarning("$author/$distname: $result", %{$opts});
  1         44  
438 1         35 return ['-warning', $result];
439             }
440             } else {
441 0         0 _emitwarning("$author/$distname: no META.yml nor Makefile.PL", %{$opts});
  0         0  
442 0         0 return ['-warning', 'no META.yml nor Makefile.PL'];
443             }
444             }
445             }
446              
447             1;