File Coverage

blib/lib/Module/ScanDeps.pm
Criterion Covered Total %
statement 443 541 81.8
branch 195 278 70.1
condition 44 82 53.6
subroutine 46 52 88.4
pod 6 14 42.8
total 734 967 75.9


line stmt bran cond sub pod time code
1             package Module::ScanDeps;
2 17     17   1149896 use 5.008001;
  17         202  
3 17     17   93 use strict;
  17         31  
  17         412  
4 17     17   82 use warnings;
  17         30  
  17         752  
5 17     17   108 use vars qw( $VERSION @EXPORT @EXPORT_OK @ISA $CurrentPackage @IncludeLibs $ScanFileRE );
  17         56  
  17         2990  
6              
7             $VERSION = '1.34';
8             @EXPORT = qw( scan_deps scan_deps_runtime );
9             @EXPORT_OK = qw( scan_line scan_chunk add_deps scan_deps_runtime path_to_inc_name );
10              
11 17     17   116 use Config;
  17         47  
  17         1963  
12             require Exporter;
13             our @ISA = qw(Exporter);
14 17   66     2878 use constant is_insensitive_fs => (
15             -s $0
16             and (-s lc($0) || -1) == (-s uc($0) || -1)
17             and (-s lc($0) || -1) == -s $0
18 17     17   148 );
  17         67  
19              
20 17     17   7942 use version;
  17         34121  
  17         93  
21 17     17   1430 use File::Path ();
  17         43  
  17         247  
22 17     17   12432 use File::Temp ();
  17         333712  
  17         508  
23 17     17   7882 use FileHandle;
  17         40347  
  17         94  
24 17     17   17184 use Module::Metadata;
  17         107335  
  17         758  
25              
26             # NOTE: Keep the following imports exactly as specified, even if the Module::ScanDeps source
27             # doesn't reference some of them. See '"use lib" idioms' for the reason.
28 17     17   122 use Cwd (qw(abs_path));
  17         38  
  17         880  
29 17     17   118 use File::Spec;
  17         51  
  17         317  
30 17     17   7827 use File::Spec::Functions;
  17         13229  
  17         1327  
31 17     17   144 use File::Basename;
  17         38  
  17         10234  
32              
33              
34             $ScanFileRE = qr/(?:^|\\|\/)(?:[^.]*|.*\.(?i:p[ml]|t|al))$/;
35              
36              
37             =head1 NAME
38              
39             Module::ScanDeps - Recursively scan Perl code for dependencies
40              
41             =head1 SYNOPSIS
42              
43             Via the command-line program L:
44              
45             % scandeps.pl *.pm # Print PREREQ_PM section for *.pm
46             % scandeps.pl -e "use utf8" # Read script from command line
47             % scandeps.pl -B *.pm # Include core modules
48             % scandeps.pl -V *.pm # Show autoload/shared/data files
49              
50             Used in a program;
51              
52             use Module::ScanDeps;
53              
54             # standard usage
55             my $hash_ref = scan_deps(
56             files => [ 'a.pl', 'b.pl' ],
57             recurse => 1,
58             );
59              
60             # shorthand; assume recurse == 1
61             my $hash_ref = scan_deps( 'a.pl', 'b.pl' );
62              
63             # App::Packer::Frontend compatible interface
64             # see App::Packer::Frontend for the structure returned by get_files
65             my $scan = Module::ScanDeps->new;
66             $scan->set_file( 'a.pl' );
67             $scan->set_options( add_modules => [ 'Test::More' ] );
68             $scan->calculate_info;
69             my $files = $scan->get_files;
70              
71             =head1 DESCRIPTION
72              
73             This module scans potential modules used by perl programs, and returns a
74             hash reference; its keys are the module names as appears in C<%INC>
75             (e.g. C); the values are hash references with this structure:
76              
77             {
78             file => '/usr/local/lib/perl5/5.8.0/Test/More.pm',
79             key => 'Test/More.pm',
80             type => 'module', # or 'autoload', 'data', 'shared'
81             used_by => [ 'Test/Simple.pm', ... ],
82             uses => [ 'Test/Other.pm', ... ],
83             }
84              
85             One function, C, is exported by default. Other
86             functions such as (C, C, C, C)
87             are exported upon request.
88              
89             Users of B may also use this module as the dependency-checking
90             frontend, by tweaking their F like below:
91              
92             use Module::ScanDeps;
93             ...
94             my $packer = App::Packer->new( frontend => 'Module::ScanDeps' );
95             ...
96              
97             Please see L for detailed explanation on
98             the structure returned by C.
99              
100             =head2 B
101              
102             $rv_ref = scan_deps(
103             files => \@files, recurse => $recurse,
104             rv => \%rv, skip => \%skip,
105             compile => $compile, execute => $execute,
106             );
107             $rv_ref = scan_deps(@files); # shorthand, with recurse => 1
108              
109             This function scans each file in C<@files>, registering their
110             dependencies into C<%rv>, and returns a reference to the updated
111             C<%rv>. The meaning of keys and values are explained above.
112              
113             If C<$recurse> is true, C will call itself recursively,
114             to perform a breadth-first search on text files (as defined by the
115             -T operator) found in C<%rv>.
116              
117             If the C<\%skip> is specified, files that exists as its keys are
118             skipped. This is used internally to avoid infinite recursion.
119              
120             If C<$compile> or C<$execute> is true, runs C in either
121             compile-only or normal mode, then inspects their C<%INC> after
122             termination to determine additional runtime dependencies.
123              
124             If C<$execute> is an array reference, passes C<@$execute>
125             as arguments to each file in C<@files> when it is run.
126              
127             If performance of the scanning process is a concern, C can be
128             set to a filename. The scanning results will be cached and written to the
129             file. This will speed up the scanning process on subsequent runs.
130              
131             Additionally, an option C is recognized. If set to true,
132             C issues a warning to STDERR for every module file that the
133             scanned code depends but that wasn't found. Please note that this may
134             also report numerous false positives. That is why by default, the heuristic
135             silently drops all dependencies it cannot find.
136              
137             =head2 B
138              
139             Like B, but skips the static scanning part.
140              
141             =head2 B
142              
143             @modules = scan_line($line);
144              
145             Splits a line into chunks (currently with the semicolon characters), and
146             return the union of C calls of them.
147              
148             If the line is C<__END__> or C<__DATA__>, a single C<__END__> element is
149             returned to signify the end of the program.
150              
151             Similarly, it returns a single C<__POD__> if the line matches C;
152             the caller is responsible for skipping appropriate number of lines
153             until C<=cut>, before calling C again.
154              
155             =head2 B
156              
157             $module = scan_chunk($chunk);
158             @modules = scan_chunk($chunk);
159              
160             Apply various heuristics to C<$chunk> to find and return the module
161             name(s) it contains. In scalar context, returns only the first module
162             or C.
163              
164             =head2 B
165              
166             $rv_ref = add_deps( rv => \%rv, modules => \@modules );
167             $rv_ref = add_deps( @modules ); # shorthand, without rv
168              
169             Resolves a list of module names to its actual on-disk location, by
170             finding in C<@INC> and C<@Module::ScanDeps::IncludeLibs>;
171             modules that cannot be found are skipped.
172              
173             This function populates the C<%rv> hash with module/filename pairs, and
174             returns a reference to it.
175              
176             =head2 B
177              
178             $perl_name = path_to_inc_name($path, $warn)
179              
180             Assumes C<$path> refers to a perl file and does it's best to return the
181             name as it would appear in %INC. Returns undef if no match was found
182             and a prints a warning to STDERR if C<$warn> is true.
183              
184             E.g. if C<$path> = perl/site/lib/Module/ScanDeps.pm then C<$perl_name>
185             will be Module/ScanDeps.pm.
186              
187             =head1 NOTES
188              
189             =head2 B<@Module::ScanDeps::IncludeLibs>
190              
191             You can set this global variable to specify additional directories in
192             which to search modules without modifying C<@INC> itself.
193              
194             =head2 B<$Module::ScanDeps::ScanFileRE>
195              
196             You can set this global variable to specify a regular expression to
197             identify what files to scan. By default it includes all files of
198             the following types: .pm, .pl, .t and .al. Additionally, all files
199             without a suffix are considered.
200              
201             For instance, if you want to scan all files then use the following:
202              
203             C<$Module::ScanDeps::ScanFileRE = qr/./>
204              
205             =head1 CAVEATS
206              
207             This module intentionally ignores the B hack on FreeBSD -- the
208             additional directory is removed from C<@INC> altogether.
209              
210             The static-scanning heuristic is not likely to be 100% accurate, especially
211             on modules that dynamically load other modules.
212              
213             Chunks that span multiple lines are not handled correctly. For example,
214             this one works:
215              
216             use base 'Foo::Bar';
217              
218             But this one does not:
219              
220             use base
221             'Foo::Bar';
222              
223             =cut
224              
225             my $SeenTk;
226             my %SeenRuntimeLoader;
227              
228             # match "use LOADER LIST" chunks; sets $1 to LOADER and $2 to LIST
229             my $LoaderRE =
230             qr/^ use \s+
231             ( asa
232             | base
233             | parent
234             | prefork
235             | POE
236             | encoding
237             | maybe
238             | only::matching
239             | Mojo::Base
240             | Catalyst
241             )(?!\:) \b \s* (.*)
242             /sx;
243              
244             # Pre-loaded module dependencies {{{
245             my %Preload = (
246             'AnyDBM_File.pm' => [qw( SDBM_File.pm )],
247             'AnyEvent.pm' => 'sub',
248             'Authen/SASL.pm' => 'sub',
249              
250             'B/Hooks/EndOfScope.pm' =>
251             [qw( B/Hooks/EndOfScope/PP.pm B/Hooks/EndOfScope/XS.pm )],
252             'Bio/AlignIO.pm' => 'sub',
253             'Bio/Assembly/IO.pm' => 'sub',
254             'Bio/Biblio/IO.pm' => 'sub',
255             'Bio/ClusterIO.pm' => 'sub',
256             'Bio/CodonUsage/IO.pm' => 'sub',
257             'Bio/DB/Biblio.pm' => 'sub',
258             'Bio/DB/Flat.pm' => 'sub',
259             'Bio/DB/GFF.pm' => 'sub',
260             'Bio/DB/Taxonomy.pm' => 'sub',
261             'Bio/Graphics/Glyph.pm' => 'sub',
262             'Bio/MapIO.pm' => 'sub',
263             'Bio/Matrix/IO.pm' => 'sub',
264             'Bio/Matrix/PSM/IO.pm' => 'sub',
265             'Bio/OntologyIO.pm' => 'sub',
266             'Bio/PopGen/IO.pm' => 'sub',
267             'Bio/Restriction/IO.pm' => 'sub',
268             'Bio/Root/IO.pm' => 'sub',
269             'Bio/SearchIO.pm' => 'sub',
270             'Bio/SeqIO.pm' => 'sub',
271             'Bio/Structure/IO.pm' => 'sub',
272             'Bio/TreeIO.pm' => 'sub',
273             'Bio/LiveSeq/IO.pm' => 'sub',
274             'Bio/Variation/IO.pm' => 'sub',
275              
276             'Catalyst.pm' => sub {
277             return ('Catalyst/Runtime.pm',
278             'Catalyst/Dispatcher.pm',
279             _glob_in_inc('Catalyst/DispatchType', 1));
280             },
281             'Catalyst/Engine.pm' => 'sub',
282             'CGI/Application/Plugin/Authentication.pm' =>
283             [qw( CGI/Application/Plugin/Authentication/Store/Cookie.pm )],
284             'CGI/Application/Plugin/AutoRunmode.pm' => [qw( Attribute/Handlers.pm )],
285             'charnames.pm' => \&_unicore,
286             'Class/Load.pm' => [qw( Class/Load/PP.pm )],
287             'Class/MakeMethods.pm' => 'sub',
288             'Class/MethodMaker.pm' => 'sub',
289             'Class/Plain.pm' => [qw( XS/Parse/Keyword.pm )],
290             'Config/Any.pm' =>'sub',
291             'Crypt/Random.pm' => sub {
292             _glob_in_inc('Crypt/Random/Provider', 1);
293             },
294             'Crypt/Random/Generator.pm' => sub {
295             _glob_in_inc('Crypt/Random/Provider', 1);
296             },
297              
298             'Date/Manip.pm' =>
299             [qw( Date/Manip/DM5.pm Date/Manip/DM6.pm )],
300             'Date/Manip/Base.pm' => sub {
301             _glob_in_inc('Date/Manip/Lang', 1);
302             },
303             'Date/Manip/TZ.pm' => sub {
304             return (_glob_in_inc('Date/Manip/TZ', 1),
305             _glob_in_inc('Date/Manip/Offset', 1));
306             },
307             'DateTime/Format/Builder/Parser.pm' => 'sub',
308             'DateTime/Format/Natural.pm' => 'sub',
309             'DateTime/Locale.pm' => 'sub',
310             'DateTime/TimeZone.pm' => 'sub',
311             'DBI.pm' => sub {
312             grep !/\bProxy\b/, _glob_in_inc('DBD', 1);
313             },
314             'DBIx/Class.pm' => 'sub',
315             'DBIx/SearchBuilder.pm' => 'sub',
316             'DBIx/Perlish.pm' => [qw( attributes.pm )],
317             'DBIx/ReportBuilder.pm' => 'sub',
318             'Device/ParallelPort.pm' => 'sub',
319             'Device/SerialPort.pm' =>
320             [qw( termios.ph asm/termios.ph sys/termiox.ph sys/termios.ph sys/ttycom.ph )],
321             'diagnostics.pm' => sub {
322             # shamelessly taken and adapted from diagnostics.pm
323 17     17   137 use Config;
  17         58  
  17         67421  
324             my($privlib, $archlib) = @Config{qw(privlibexp archlibexp)};
325             if ($^O eq 'VMS') {
326             require VMS::Filespec;
327             $privlib = VMS::Filespec::unixify($privlib);
328             $archlib = VMS::Filespec::unixify($archlib);
329             }
330              
331             for (
332             "pod/perldiag.pod",
333             "Pod/perldiag.pod",
334             "pod/perldiag-$Config{version}.pod",
335             "Pod/perldiag-$Config{version}.pod",
336             "pods/perldiag.pod",
337             "pods/perldiag-$Config{version}.pod",
338             ) {
339             return $_ if _find_in_inc($_);
340             }
341              
342             for (
343             "$archlib/pods/perldiag.pod",
344             "$privlib/pods/perldiag-$Config{version}.pod",
345             "$privlib/pods/perldiag.pod",
346             ) {
347             return $_ if -f $_;
348             }
349              
350             return 'pod/perldiag.pod';
351             },
352              
353             'Email/Send.pm' => 'sub',
354             'Event.pm' => sub {
355             map "Event/$_.pm", qw( idle io signal timer var );
356             },
357             'ExtUtils/MakeMaker.pm' => sub {
358             grep /\bMM_/, _glob_in_inc('ExtUtils', 1);
359             },
360              
361             'FFI/Platypus.pm' => 'sub',
362             'File/Basename.pm' => [qw( re.pm )],
363             'File/BOM.pm' => [qw( Encode/Unicode.pm )],
364             'File/HomeDir.pm' => 'sub',
365             'File/Spec.pm' => sub {
366             require File::Spec;
367             map { my $name = $_; $name =~ s!::!/!g; "$name.pm" } @File::Spec::ISA;
368             },
369             'Future/AsyncAwait.pm' => [qw( XS/Parse/Keyword.pm )],
370             'Future/AsyncAwait/Hooks.pm' => [qw( XS/Parse/Keyword.pm )],
371              
372             'Gtk2.pm' => [qw( Cairo.pm )], # Gtk2.pm does: eval "use Cairo;"
373              
374             'HTTP/Entity/Parser.pm' => 'sub',
375             'HTTP/Message.pm' => [qw( URI/URL.pm URI.pm )],
376              
377             'Image/ExifTool.pm' => sub {
378             return(
379             (map $_->{name}, _glob_in_inc('Image/ExifTool', 0)), # also *.pl files
380             qw( File/RandomAccess.pm ),
381             );
382             },
383             'Image/Info.pm' => sub {
384             return(
385             _glob_in_inc('Image/Info', 1),
386             qw( Image/TIFF.pm ),
387             );
388             },
389             'IO.pm' => [qw(
390             IO/Handle.pm IO/Seekable.pm IO/File.pm
391             IO/Pipe.pm IO/Socket.pm IO/Dir.pm
392             )],
393             'IO/Socket.pm' => [qw( IO/Socket/UNIX.pm )],
394             'IUP.pm' => 'sub',
395              
396             'JSON.pm' => sub {
397             # add JSON/PP*.pm, JSON/PP/*.pm
398             # and ignore other JSON::* modules (e.g. JSON/Syck.pm, JSON/Any.pm);
399             # but accept JSON::XS, too (because JSON.pm might use it if present)
400             return( grep /^JSON\/(PP|XS)/, _glob_in_inc('JSON', 1) );
401             },
402             'JSON/MaybeXS.pm' => [qw(
403             Cpanel/JSON/XS.pm JSON/XS.pm JSON/PP.pm
404             )],
405              
406             'List/Keywords.pm' => [qw( XS/Parse/Keyword.pm )],
407             'List/MoreUtils.pm' => 'sub',
408             'List/SomeUtils.pm' => 'sub',
409             'Locale/Maketext/Lexicon.pm' => 'sub',
410             'Locale/Maketext/GutsLoader.pm' => [qw( Locale/Maketext/Guts.pm )],
411             'Log/Any.pm' => 'sub',
412             'Log/Dispatch.pm' => 'sub',
413             'Log/Log4perl.pm' => 'sub',
414             'Log/Report/Dispatcher.pm' => 'sub',
415             'LWP/MediaTypes.pm' => [qw( LWP/media.types )],
416             'LWP/Parallel.pm' => sub {
417             _glob_in_inc( 'LWP/Parallel', 1 ),
418             qw(
419             LWP/ParallelUA.pm LWP/UserAgent.pm
420             LWP/RobotPUA.pm LWP/RobotUA.pm
421             ),
422             },
423             'LWP/Parallel/UserAgent.pm' => [qw( LWP/Parallel.pm )],
424             'LWP/UserAgent.pm' => sub {
425             return(
426             qw( URI/URL.pm URI/http.pm LWP/Protocol/http.pm ),
427             _glob_in_inc("LWP/Authen", 1),
428             _glob_in_inc("LWP/Protocol", 1),
429             );
430             },
431              
432             'Mail/Audit.pm' => 'sub',
433             'Math/BigInt.pm' => 'sub',
434             'Math/BigFloat.pm' => 'sub',
435             'Math/Symbolic.pm' => 'sub',
436             'MIME/Decoder.pm' => 'sub',
437             'MIME/Types.pm' => [qw( MIME/types.db )],
438             'Module/Build.pm' => 'sub',
439             'Module/Pluggable.pm' => sub {
440             _glob_in_inc('$CurrentPackage/Plugin', 1);
441             },
442             'Moo.pm' => [qw( Class/XSAccessor.pm )],
443             'Moose.pm' => sub {
444             _glob_in_inc('Moose', 1),
445             _glob_in_inc('Class/MOP', 1),
446             },
447             'MooseX/AttributeHelpers.pm' => 'sub',
448             'MooseX/POE.pm' => sub {
449             _glob_in_inc('MooseX/POE', 1),
450             _glob_in_inc('MooseX/Async', 1),
451             },
452             'Mozilla/CA.pm' => [qw( Mozilla/CA/cacert.pem )],
453             'MozRepl.pm' => sub {
454             qw( MozRepl/Log.pm MozRepl/Client.pm Module/Pluggable/Fast.pm ),
455             _glob_in_inc('MozRepl/Plugin', 1),
456             },
457             'Module/Implementation.pm' => \&_warn_of_runtime_loader,
458             'Module/Runtime.pm' => \&_warn_of_runtime_loader,
459             'Mojo/Util.pm' => sub { # html_entities.txt
460             map { $_->{name} } _glob_in_inc('Mojo/resources', 0)
461             },
462             'Mojo/IOLoop/TLS.pm' => sub { # server.{crt,key}
463             map { $_->{name} } _glob_in_inc('Mojo/IOLoop/resources', 0)
464             },
465              
466             'Net/DNS/Resolver.pm' => 'sub',
467             'Net/DNS/RR.pm' => 'sub',
468             'Net/FTP.pm' => 'sub',
469             'Net/HTTPS.pm' => [qw( IO/Socket/SSL.pm Net/SSL.pm )],
470             'Net/Server.pm' => 'sub',
471             'Net/SSH/Perl.pm' => 'sub',
472              
473             'Object/Pad.pm' => [qw( XS/Parse/Keyword.pm )],
474             'Object/Pad/Keyword/Accessor.pm' => [qw( XS/Parse/Keyword.pm )],
475              
476             'Package/Stash.pm' => [qw( Package/Stash/PP.pm Package/Stash/XS.pm )],
477             'Pango.pm' => [qw( Cairo.pm )], # Pango.pm does: eval "use Cairo;"
478             'PAR/Repository.pm' => 'sub',
479             'PAR/Repository/Client.pm' => 'sub',
480             'Params/Validate.pm' => 'sub',
481             'Parse/AFP.pm' => 'sub',
482             'Parse/Binary.pm' => 'sub',
483             'PDF/API2/Resource/Font.pm' => 'sub',
484             'PDF/API2/Basic/TTF/Font.pm' => sub {
485             _glob_in_inc('PDF/API2/Basic/TTF', 1);
486             },
487             'PDF/Writer.pm' => 'sub',
488             'PDL/NiceSlice.pm' => 'sub',
489             'Perl/Critic.pm' => 'sub', #not only Perl/Critic/Policy
490             'PerlIO.pm' => [qw( PerlIO/scalar.pm )],
491             'Pod/Simple/Transcode.pm' => [qw( Pod/Simple/TranscodeDumb.pm Pod/Simple/TranscodeSmart.pm )],
492             'Pod/Usage.pm' => sub { # from Pod::Usage (as of 1.61)
493             $] >= 5.005_58 ? 'Pod/Text.pm' : 'Pod/PlainText.pm'
494             },
495             'POE.pm' => [qw( POE/Kernel.pm POE/Session.pm )],
496             'POE/Component/Client/HTTP.pm' => sub {
497             _glob_in_inc('POE/Component/Client/HTTP', 1),
498             qw( POE/Filter/HTTPChunk.pm POE/Filter/HTTPHead.pm ),
499             },
500             'POE/Kernel.pm' => sub {
501             _glob_in_inc('POE/XS/Resource', 1),
502             _glob_in_inc('POE/Resource', 1),
503             _glob_in_inc('POE/XS/Loop', 1),
504             _glob_in_inc('POE/Loop', 1),
505             },
506             'POSIX.pm' => sub {
507             map $_->{name},
508             _glob_in_inc('auto/POSIX/SigAction', 0), # *.al files
509             _glob_in_inc('auto/POSIX/SigRt', 0), # *.al files
510             },
511             'PPI.pm' => 'sub',
512              
513             'Regexp/Common.pm' => 'sub',
514             'RPC/XML/ParserFactory.pm' => sub {
515             _glob_in_inc('RPC/XML/Parser', 1);
516             },
517              
518             'SerialJunk.pm' => [qw(
519             termios.ph asm/termios.ph sys/termiox.ph sys/termios.ph sys/ttycom.ph
520             )],
521             'SOAP/Lite.pm' => sub {
522             _glob_in_inc('SOAP/Transport', 1),
523             _glob_in_inc('SOAP/Lite', 1),
524             },
525             'Socket/GetAddrInfo.pm' => 'sub',
526             'Specio/PartialDump.pm' => \&_unicore,
527             'SQL/Parser.pm' => sub {
528             _glob_in_inc('SQL/Dialects', 1);
529             },
530             'SQL/Translator/Schema.pm' => sub {
531             _glob_in_inc('SQL/Translator', 1);
532             },
533             'Sub/Exporter/Progressive.pm' => [qw( Sub/Exporter.pm )],
534             'SVK/Command.pm' => sub {
535             _glob_in_inc('SVK', 1);
536             },
537             'SVN/Core.pm' => sub {
538             _glob_in_inc('SVN', 1),
539             map { $_->{name} } _glob_in_inc('auto/SVN', 0), # *.so, *.bs files
540             },
541             'Syntax/Keyword/Combine/Keys.pm' => [qw( XS/Parse/Keyword.pm )],
542             'Syntax/Keyword/Defer.pm' => [qw( XS/Parse/Keyword.pm )],
543             'Syntax/Keyword/Dynamically.pm' => [qw( XS/Parse/Keyword.pm )],
544             'Syntax/Keyword/Inplace.pm' => [qw( XS/Parse/Keyword.pm )],
545             'Syntax/Keyword/Match.pm' => [qw( XS/Parse/Keyword.pm )],
546             'Syntax/Keyword/Try.pm' => [qw( XS/Parse/Keyword.pm )],
547              
548             'Template.pm' => 'sub',
549             'Term/ReadLine.pm' => 'sub',
550             'Test/Deep.pm' => 'sub',
551             'threads/shared.pm' => [qw( attributes.pm )],
552             # anybody using threads::shared is likely to declare variables
553             # with attribute :shared
554             'Tk.pm' => sub {
555             $SeenTk = 1;
556             qw( Tk/FileSelect.pm Encode/Unicode.pm );
557             },
558             'Tk/Balloon.pm' => [qw( Tk/balArrow.xbm )],
559             'Tk/BrowseEntry.pm' => [qw( Tk/cbxarrow.xbm Tk/arrowdownwin.xbm )],
560             'Tk/ColorEditor.pm' => [qw( Tk/ColorEdit.xpm )],
561             'Tk/DragDrop/Common.pm' => sub {
562             _glob_in_inc('Tk/DragDrop', 1),
563             },
564             'Tk/FBox.pm' => [qw( Tk/folder.xpm Tk/file.xpm )],
565             'Tk/Getopt.pm' => [qw( Tk/openfolder.xpm Tk/win.xbm )],
566             'Tk/Toplevel.pm' => [qw( Tk/Wm.pm )],
567              
568             'Unicode/Normalize.pm' => \&_unicore,
569             'Unicode/UCD.pm' => \&_unicore,
570             'URI.pm' => sub { grep !/urn/, _glob_in_inc('URI', 1) },
571             'utf8_heavy.pl' => \&_unicore,
572              
573             'Win32/EventLog.pm' => [qw( Win32/IPC.pm )],
574             'Win32/Exe.pm' => 'sub',
575             'Win32/TieRegistry.pm' => [qw( Win32API/Registry.pm )],
576             'Win32/SystemInfo.pm' => [qw( Win32/cpuspd.dll )],
577             'Wx.pm' => [qw( attributes.pm )],
578              
579             'XML/Parser.pm' => sub {
580             _glob_in_inc('XML/Parser/Style', 1),
581             _glob_in_inc('XML/Parser/Encodings', 1),
582             },
583             'XML/SAX.pm' => [qw( XML/SAX/ParserDetails.ini ) ],
584             'XML/Twig.pm' => [qw( URI.pm )], # or URI::File or LWP
585             'XML/Twig/XPath.pm' => [qw( XML/XPathEngine.pm XML/XPath.pm )],
586             'XMLRPC/Lite.pm' => sub {
587             _glob_in_inc('XMLRPC/Transport', 1);
588             },
589             'XS/Parse/Keyword/FromPerl.pm' => [qw( XS/Parse/Keyword.pm )],
590              
591             'YAML.pm' => [qw( YAML/Loader.pm YAML/Dumper.pm )],
592             'YAML/Any.pm' => sub {
593             # try to figure out what YAML::Any would have used
594             my $impl = eval "use YAML::Any; YAML::Any->implementation;";
595             return _mod2pm($impl) unless $@;
596              
597             _glob_in_inc('YAML', 1); # fallback
598             },
599             );
600              
601             # }}}
602              
603             sub path_to_inc_name($$) {
604 157     157 1 55383 my $path = shift;
605 157         344 my $warn = shift;
606 157         241 my $inc_name;
607              
608 157 100       614 if ($path =~ m/\.pm$/io) {
609 34 50       691 die "$path doesn't exist" unless (-f $path);
610 34         286 my $module_info = Module::Metadata->new_from_file($path);
611 34 50       14305 die "Module::Metadata error: $!" unless defined($module_info);
612 34         149 $inc_name = $module_info->name();
613 34 50       187 if (defined($inc_name)) {
614 34         107 $inc_name =~ s|\:\:|\/|og;
615 34         204 $inc_name .= '.pm';
616             } else {
617 0 0       0 warn "# Couldn't find include name for $path\n" if $warn;
618             }
619             } else {
620             # Bad solution!
621 123         1377 (my $vol, my $dir, $inc_name) = File::Spec->splitpath($path);
622             }
623              
624 157         935 return $inc_name;
625             }
626              
627             my $Keys = 'files|keys|recurse|rv|skip|first|execute|compile|warn_missing|cache_cb|cache_file';
628             sub scan_deps {
629 40 100 66 40 1 608079 my %args = (
630             rv => {},
631             (@_ and $_[0] =~ /^(?:$Keys)$/o) ? @_ : (files => [@_], recurse => 1)
632             );
633              
634 40 100       269 if (!defined($args{keys})) {
635 39         98 $args{keys} = [map {path_to_inc_name($_, $args{warn_missing})} @{$args{files}}];
  56         220  
  39         158  
636             }
637 40         127 my $cache_file = $args{cache_file};
638 40         93 my $using_cache;
639 40 100       206 if ($cache_file) {
640 8         60 require Module::ScanDeps::Cache;
641 8         31 $using_cache = Module::ScanDeps::Cache::init_from_file($cache_file);
642 8 50       33 if( $using_cache ){
643 8         35 $args{cache_cb} = Module::ScanDeps::Cache::get_cache_cb();
644             }else{
645 0         0 my @missing = Module::ScanDeps::Cache::prereq_missing();
646 0         0 warn join(' ',
647             "Can not use cache_file: Needs Modules [",
648             @missing,
649             "]\n",);
650             }
651             }
652 40         106 my ($type, $path);
653 40         93 foreach my $input_file (@{$args{files}}) {
  40         139  
654 57 100       693 if ($input_file !~ $ScanFileRE) {
655             warn "Skipping input file $input_file"
656             . " because it doesn't match \$Module::ScanDeps::ScanFileRE\n"
657 1 50       4 if $args{warn_missing};
658 1         3 next;
659             }
660              
661 56         167 $type = _gettype($input_file);
662 56         117 $path = $input_file;
663 56 100       165 if ($type eq 'module') {
664             # necessary because add_deps does the search for shared libraries and such
665             add_deps(
666             used_by => undef,
667             rv => $args{rv},
668             modules => [path_to_inc_name($path, $args{warn_missing})],
669             skip => undef,
670             warn_missing => $args{warn_missing},
671 11         65 );
672             }
673             else {
674             _add_info(
675             rv => $args{rv},
676 45         198 module => path_to_inc_name($path, $args{warn_missing}),
677             file => $path,
678             used_by => undef,
679             type => $type,
680             );
681             }
682             }
683              
684             {
685             ## "use lib" idioms
686             #
687             # We want to correctly interprete stuff like
688             #
689             # use FindBin;
690             # use lib "$FindBin/../lib";
691             #
692             # Find out what $FindBin::Bin etc would have been if "use FindBin" had been
693             # called in the first file to analyze.
694             #
695             # Notes:
696             # (1) We don't want to reimplement FindBin, hence fake $0 locally (as the path of the
697             # first file analyzed) and call FindBin::again().
698             # (2) If the caller of scan_deps() itself uses FindBin, we don't want to overwrite
699             # the value of "their" $FindBin::Bin.
700             #
701             # Other idioms seen sometimes:
702             #
703             # use lib "$ENV{FOO}/path";
704             # use lib File::Spec->catdir($FindBin::Bin, qw[.. qqlib] );
705             # use lib catdir(dirname($0), "perl");
706             # use lib dirname(abs_path($0));
707             #
708             # In order to correctly interprete these, the modules referenced have to be imported.
709              
710              
711 40         139 require FindBin;
  40         7458  
712              
713 40         14037 local $FindBin::Bin;
714 40         100 local $FindBin::RealBin;
715 40         132 local $FindBin::Script;
716 40         72 local $FindBin::RealScript;
717              
718 40         131 my $_0 = $args{files}[0];
719 40         139 local *0 = \$_0;
720 40         300 FindBin->again();
721              
722 40         8178 our $Bin = $FindBin::Bin;
723 40         137 our $RealBin = $FindBin::RealBin;
724 40         140 our $Script = $FindBin::Script;
725 40         125 our $RealScript = $FindBin::RealScript;
726              
727 40         185 scan_deps_static(\%args);
728             }
729              
730 40 50 33     300 if ($args{execute} or $args{compile}) {
731             scan_deps_runtime(
732             rv => $args{rv},
733             files => $args{files},
734             execute => $args{execute},
735             compile => $args{compile},
736             skip => $args{skip}
737 0         0 );
738             }
739              
740 40 100       148 if ( $using_cache ){
741 8         37 Module::ScanDeps::Cache::store_cache();
742             }
743              
744             # do not include the input files themselves as dependencies!
745 40         2541 delete $args{rv}{$_} foreach @{$args{files}};
  40         190  
746              
747 40         288 return ($args{rv});
748             }
749              
750             sub scan_deps_static {
751 121     121 0 308 my ($args) = @_;
752             my ($files, $keys, $recurse, $rv,
753             $skip, $first, $execute, $compile,
754             $cache_cb, $_skip)
755 121         610 = @$args{qw( files keys recurse rv
756             skip first execute compile
757             cache_cb _skip )};
758              
759 121   50     415 $rv ||= {};
760 121 100 100     388 $_skip ||= { %{$skip || {}} };
  40         364  
761              
762 121         271 foreach my $file (@{$files}) {
  121         427  
763 2257         2884 my $key = shift @{$keys};
  2257         3621  
764 2257 100       7635 next if $_skip->{$file}++;
765             next if is_insensitive_fs()
766 573         857 and $file ne lc($file) and $_skip->{lc($file)}++;
767 573 100       5706 next unless $file =~ $ScanFileRE;
768              
769 568         1167 my @pm;
770             my $found_in_cache;
771 568 100       1089 if ($cache_cb){
772 51         93 my $pm_aref;
773             # cache_cb populates \@pm on success
774 51         192 $found_in_cache = $cache_cb->(action => 'read',
775             key => $key,
776             file => $file,
777             modules => \@pm,
778             );
779 51 100       1542 unless( $found_in_cache ){
780 20         66 @pm = scan_file($file);
781 20         94 $cache_cb->(action => 'write',
782             key => $key,
783             file => $file,
784             modules => \@pm,
785             );
786             }
787             }else{ # no caching callback given
788 517         1183 @pm = scan_file($file);
789             }
790              
791 568         3120 foreach my $pm (@pm){
792             add_deps(
793             used_by => $key,
794             rv => $args->{rv},
795             modules => [$pm],
796             skip => $args->{skip},
797             warn_missing => $args->{warn_missing},
798 1627         8043 );
799              
800 1627 100       5231 my @preload = _get_preload($pm) or next;
801              
802             add_deps(
803             used_by => $key,
804             rv => $args->{rv},
805             modules => \@preload,
806             skip => $args->{skip},
807             warn_missing => $args->{warn_missing},
808 47         264 );
809             }
810             }
811              
812             # Top-level recursion handling {{{
813              
814             # prevent utf8.pm from being scanned
815 121 50       497 $_skip->{$rv->{"utf8.pm"}{file}}++ if $rv->{"utf8.pm"};
816              
817 121         356 while ($recurse) {
818 81         188 my $count = keys %$rv;
819 81 50       436 my @files = sort grep { defined $_->{file} && -T $_->{file} } values %$rv;
  2412         258284  
820             scan_deps_static({
821             files => [ map $_->{file}, @files ],
822 81         3641 keys => [ map $_->{key}, @files ],
823             rv => $rv,
824             skip => $skip,
825             recurse => 0,
826             cache_cb => $cache_cb,
827             _skip => $_skip,
828             });
829 81 100       1243 last if $count == keys %$rv;
830             }
831              
832             # }}}
833              
834 121         554 return $rv;
835             }
836              
837             sub scan_deps_runtime {
838 17 50 33 17 1 56864 my %args = (
839             rv => {},
840             (@_ and $_[0] =~ /^(?:$Keys)$/o) ? @_ : (files => [@_], recurse => 1)
841             );
842             my ($files, $rv, $execute, $compile) =
843 17         126 @args{qw( files rv execute compile )};
844              
845 17 50       81 $files = (ref($files)) ? $files : [$files];
846              
847 17 100       88 if ($compile) {
    50          
848 5         23 foreach my $file (@$files) {
849 5 50       104 next unless $file =~ $ScanFileRE;
850              
851 5         30 _merge_rv(_info2rv(_compile_or_execute($file)), $rv);
852             }
853             }
854             elsif ($execute) {
855 12         43 foreach my $file (@$files) {
856 12 100       89 $execute = [] unless ref $execute; # make sure it's an array ref
857              
858 12         72 _merge_rv(_info2rv(_compile_or_execute($file, $execute)), $rv);
859             }
860             }
861              
862 17         861 return ($rv);
863             }
864              
865             sub scan_file{
866 537     537 0 848 my $file = shift;
867 537         762 my %found;
868 537 50       23950 open my $fh, $file or die "Cannot open $file: $!";
869              
870 537         2307 $SeenTk = 0;
871             # Line-by-line scanning
872             LINE:
873 537         12774 while (my $line = <$fh>) {
874 110212         170587 chomp($line);
875 110212         168420 foreach my $pm (scan_line($line)) {
876 2531 100       5849 last LINE if $pm eq '__END__';
877              
878 2273 100       4079 if ($pm eq '__POD__') {
879 226         556 while ($line = <$fh>) {
880 11945 100       26362 next LINE if $line =~ /^=cut/;
881             }
882             }
883              
884             # Skip Tk hits from Term::ReadLine and Tcl::Tk
885 2049         6361 my $pathsep = qr/\/|\\|::/;
886 2049 50       4317 if ($pm =~ /^Tk\b/) {
887 0 0       0 next if $file =~ /(?:^|${pathsep})Term${pathsep}ReadLine\.pm$/;
888 0 0       0 next if $file =~ /(?:^|${pathsep})Tcl${pathsep}Tk\W/;
889             }
890 2049   33     8111 $SeenTk ||= $pm =~ /Tk\.pm$/;
891              
892 2049         9082 $found{$pm}++;
893             }
894             }
895 537 50       8918 close $fh or die "Cannot close $file: $!";
896 537         4580 return keys %found;
897             }
898              
899             sub scan_line {
900 110234     110234 1 180416 my $line = shift;
901 110234         134292 my %found;
902              
903 110234 100       204302 return '__END__' if $line =~ /^__(?:END|DATA)__$/;
904 109976 100       176963 return '__POD__' if $line =~ /^=\w/;
905              
906 109750         183944 $line =~ s/\s*#.*$//;
907              
908             CHUNK:
909 109750         230699 foreach (split(/;/, $line)) {
910 87112         255412 s/^\s*//;
911 87112         157557 s/^\w+:\s*//; # remove LABEL:
912 87112         136726 s/^(?:do\s*)?\{\s*//; # handle single line blocks like 'do { package foo; use xx; }'
913 87112         137643 s/\s*\}$//;
914              
915 87112 100       157199 if (/^package\s+(\w+)/) {
916 536         1625 $CurrentPackage = $1;
917 536         930 $CurrentPackage =~ s{::}{/}g;
918 536         1212 next CHUNK;
919             }
920             # use VERSION:
921 86576 100       162849 if (/^(?:use|require)\s+v?(\d[\d\._]*)/) {
922             # include feature.pm if we have 5.9.5 or better
923 91 100       2232 if (version->new($1) >= version->new("5.9.5")) {
924             # seems to catch 5.9, too (but not 5.9.4)
925 14         59 $found{"feature.pm"}++;
926             }
927 91         437 next CHUNK;
928             }
929              
930 86485 100       177603 if (my ($pragma, $args) = /^use \s+ (autouse|if) \s+ (.+)/x)
931             {
932             # NOTE: There are different ways the MODULE may
933             # be specified for the "autouse" and "if" pragmas, e.g.
934             # use autouse Module => qw(func1 func2);
935             # use autouse "Module", qw(func1);
936             # To avoid to parse them ourself, we simply try to eval the
937             # string after the pragma (in a list context). The MODULE
938             # should be the first ("autouse") or second ("if") element
939             # of the list.
940 11         18 my $module;
941             {
942 17     17   161 no strict; no warnings;
  17     17   40  
  17         639  
  17         111  
  17         52  
  17         3959  
  11         18  
943 11 100       28 if ($pragma eq "autouse") {
944 5         320 ($module) = eval $args;
945             }
946             else {
947             # The syntax of the "if" pragma is
948             # use if COND, MODULE => ARGUMENTS
949             # The COND may contain undefined functions (i.e. undefined
950             # in Module::ScanDeps' context) which would throw an
951             # exception. Sneak "1 || " in front of COND so that
952             # COND will not be evaluated. This will work in most
953             # cases, but there are operators with lower precedence
954             # than "||" which will cause this trick to fail.
955 6         400 (undef, $module) = eval "1 || $args";
956             }
957             # punt if there was a syntax error
958 11 50 33     78 return if $@ or !defined $module;
959             };
960 11         33 $found{_mod2pm($pragma)}++;
961 11         28 $found{_mod2pm($module)}++;
962 11         32 next CHUNK;
963             }
964              
965 86474 100       187241 if (my ($how, $libs) = /^(use \s+ lib \s+ | (?:unshift|push) \s+ \@INC \s+ ,) (.+)/x)
966             {
967 4 50       69 my $archname = defined($Config{archname}) ? $Config{archname} : '';
968 4 50       33 my $ver = defined($Config{version}) ? $Config{version} : '';
969 17     17   154 foreach my $dir (do { no strict; no warnings; eval $libs }) {
  17     17   42  
  17         596  
  17         99  
  17         32  
  17         104877  
  4         10  
  4         279  
970 4 50       18 next unless defined $dir;
971 4         15 my @dirs = $dir;
972 4 50       29 push @dirs, "$dir/$ver", "$dir/$archname", "$dir/$ver/$archname"
973             if $how =~ /lib/;
974 4         12 foreach (@dirs) {
975 16 100       303 unshift(@INC, $_) if -d $_;
976             }
977             }
978 4         17 next CHUNK;
979             }
980              
981 86470         138076 $found{$_}++ for scan_chunk($_);
982             }
983              
984 109750         382788 return sort keys %found;
985             }
986              
987              
988             # convert module name to file name
989             sub _mod2pm {
990 1970     1970   3397 my $mod = shift;
991 1970         4068 $mod =~ s!::!/!g;
992 1970         6242 return "$mod.pm";
993             }
994              
995             # parse a comma-separated list of string literals and qw() lists
996             sub _parse_list {
997 1291     1291   2063 my $list = shift;
998              
999             # split $list on anything that's not a word character or ":"
1000             # and ignore "q", "qq" and "qw"
1001 1291 100       4755 return grep { length and !/^:|^q[qw]?$/ } split(/[^\w:]+/, $list);
  2348         13364  
1002             }
1003              
1004             sub scan_chunk {
1005 86483     86483 1 134820 my $chunk = shift;
1006              
1007             # Module name extraction heuristics {{{
1008 86483         114794 my $module = eval {
1009 86483         128942 local $_ = $chunk;
1010 86483         222645 s/^\s*//;
1011              
1012             # "if", "while" etc: analyze the expression
1013 86483         161104 s/^(?:if|elsif|unless|while|until) \s* \( \s*//x;
1014              
1015             # "eval" with a block: analyze the block
1016 86483         115085 s/^eval \s* \{ \s*//x;
1017              
1018             # "eval" with an expression that's a string literal:
1019             # analyze the string
1020 86483         113264 s/^eval \s+ (?:['"]|qq?\s*\W) \s*//x;
1021              
1022             # "use LOADER LIST"
1023             # TODO: There's many more of these "loader" type modules on CPAN!
1024 86483 100       287027 if (my ($loader, $list) = $_ =~ $LoaderRE) {
1025 49         114 my @mods = _parse_list($list);
1026              
1027 49 100       175 if ($loader eq "Catalyst") {
1028             # "use Catalyst 'Foo'" looks for "Catalyst::Plugin::Foo",
1029             # but "use Catalyst +Foo" looks for "Foo"
1030             @mods = map {
1031 2 100       9 ($list =~ /([+-])\Q$_\E(?:$|[^\w:])/)
  5 100       303  
1032             ? ($1 eq "-"
1033             ? () # "-Foo": it's a flag, eg. "-Debug", skip it
1034             : $_) # "+Foo": look for "Foo"
1035             : "Catalyst::Plugin::$_"
1036             # "Foo": look for "Catalyst::Plugin::Foo"
1037             } @mods;
1038             }
1039 49         90 return [ map { _mod2pm($_) } $loader, @mods ];
  114         192  
1040             }
1041              
1042 86434 50 33     278315 if (/^use \s+ Class::Autouse \b \s* (.*)/sx
1043             or /^Class::Autouse \s* -> \s* autouse \s* (.*)/sx) {
1044 0         0 return [ map { _mod2pm($_) } "Class::Autouse", _parse_list($1) ];
  0         0  
1045             }
1046              
1047             # generic "use ..."
1048 86434 100       180666 if (s/^(?:use|no) \s+//x) {
1049 1240         3077 my ($mod) = _parse_list($_); # just the first word
1050 1240         2920 return _mod2pm($mod);
1051             }
1052              
1053 85194 100       165974 if (s/^(require|do) [\s(]+//x) {
1054 665 100 100     4326 return ($1 eq "require" && /^([\w:]+)/)
1055             ? _mod2pm($1) # bareword ("require" only)
1056             : $_; # maybe string literal?
1057             }
1058              
1059 84529 100       157609 if (/(<[^>]*[^\$\w>][^>]*>)/) {
1060 361         977 my $diamond = $1;
1061 361 100       992 return "File/Glob.pm" if $diamond =~ /[*?\[\]{}~\\]/;
1062             }
1063              
1064 84497 50       153179 return "DBD/$1.pm" if /\bdbi:(\w+):/i;
1065              
1066             # Moose/Moo/Mouse style inheritance or composition
1067 84497 100       154586 if (s/^(with|extends)\s+//) {
1068 2         5 return [ map { _mod2pm($_) } _parse_list($_) ];
  4         9  
1069             }
1070              
1071             # check for stuff like
1072             # decode("klingon", ...)
1073             # open FH, "<:encoding(klingon)", ...
1074 84495 100       233703 if (my ($args) = /\b(?:open|binmode)\b(.*)/) {
1075 172         311 my @mods;
1076 172 100       427 push @mods, qw( PerlIO.pm PerlIO/encoding.pm Encode.pm ), _find_encoding($1)
1077             if $args =~ /:encoding\((.*?)\)/;
1078 172         1296 while ($args =~ /:(\w+)(?:\((.*?)\))?/g) {
1079 6         34 push @mods, "PerlIO/$1.pm";
1080 6 100       33 push @mods, "Encode.pm", _find_encoding($2) if $1 eq "encoding";
1081             }
1082 172 100       457 push @mods, "PerlIO.pm" if @mods;
1083 172 100       378 return \@mods if @mods;
1084             }
1085 84491 100       149334 if (/\b(?:en|de)code\(\s*['"]?([-\w]+)/) {
1086 2         12 return [qw( Encode.pm ), _find_encoding($1)];
1087             }
1088              
1089 84489 50       132462 if ($SeenTk) {
1090 0         0 my @modules;
1091 0         0 while (/->\s*([A-Z]\w+)/g) {
1092 0         0 push @modules, "Tk/$1.pm";
1093             }
1094 0         0 while (/->\s*Scrolled\W+([A-Z]\w+)/g) {
1095 0         0 push @modules, "Tk/$1.pm";
1096 0         0 push @modules, "Tk/Scrollbar.pm";
1097             }
1098 0 0       0 if (/->\s*setPalette/g) {
1099             push @modules,
1100 0         0 map { "Tk/$_.pm" }
  0         0  
1101             qw( Button Canvas Checkbutton Entry
1102             Frame Label Labelframe Listbox
1103             Menubutton Menu Message Radiobutton
1104             Scale Scrollbar Spinbox Text );
1105             }
1106 0         0 return \@modules;
1107             }
1108              
1109             # Module::Runtime
1110 84489 50       148310 return $_ if s/^(?:require_module|use_module|use_package_optimistically) \s* \( \s*//x;
1111              
1112             # Test::More
1113 84489 100       146843 return $_ if s/^(?:require_ok|use_ok) \s* \( \s*//x;
1114              
1115 84487         160622 return;
1116             };
1117              
1118             # }}}
1119              
1120 86483 100       224828 return unless defined($module);
1121 1996 50       3968 return wantarray ? @$module : $module->[0] if ref($module);
    100          
1122              
1123             # extract contents from string literals
1124 1939 100       5896 if ($module =~ /^(['"]) (.*?) \1/x) {
    50          
1125 31         137 $module = $2;
1126             }
1127             elsif ($module =~ s/^qq? \s* (\W)//x) {
1128 0         0 (my $closing = $1) =~ tr:([{<:)]}>:;
1129 0         0 $module =~ s/\Q$closing\E.*//;
1130             }
1131              
1132 1939         3259 $module =~ s/::/\//g;
1133 1939 100       5245 return if $module =~ /^(?:[\d\._]+|'.*[^']|".*[^"])$/;
1134              
1135 1938 100       4736 $module .= ".pm" unless $module =~ /\./;
1136 1938         8067 return $module;
1137             }
1138              
1139             sub _find_encoding {
1140 4     4   16 my ($enc) = @_;
1141 4 50 33     34 return unless $] >= 5.008 and eval { require Encode; %Encode::ExtModule };
  4         1567  
  4         22558  
1142              
1143 4 50       11 my $mod = eval { $Encode::ExtModule{ Encode::find_encoding($enc)->name } } or return;
  4         14  
1144 0         0 return _mod2pm($mod);
1145             }
1146              
1147             sub _add_info {
1148 2131     2131   8598 my %args = @_;
1149 2131         6327 my ($rv, $module, $file, $used_by, $type) = @args{qw/rv module file used_by type/};
1150              
1151 2131 50 33     8030 return unless defined($module) and defined($file);
1152              
1153             # Ensure file is always absolute
1154 2131         25094 $file = File::Spec->rel2abs($file);
1155 2131         5310 $file =~ s|\\|\/|go;
1156              
1157             # Avoid duplicates that can arise due to case differences that don't actually
1158             # matter on a case tolerant system
1159 2131 50       6592 if (File::Spec->case_tolerant()) {
1160 0         0 foreach my $key (keys %$rv) {
1161 0 0       0 if (lc($key) eq lc($module)) {
1162 0         0 $module = $key;
1163 0         0 last;
1164             }
1165             }
1166 0 0       0 if (defined($used_by)) {
1167 0 0       0 if (lc($used_by) eq lc($module)) {
1168 0         0 $used_by = $module;
1169             } else {
1170 0         0 foreach my $key (keys %$rv) {
1171 0 0       0 if (lc($key) eq lc($used_by)) {
1172 0         0 $used_by = $key;
1173 0         0 last;
1174             }
1175             }
1176             }
1177             }
1178             }
1179              
1180 2131   100     8691 $rv->{$module} ||= {
1181             file => $file,
1182             key => $module,
1183             type => $type,
1184             };
1185              
1186 2131 100 100     7534 if (defined($used_by) and $used_by ne $module) {
1187 1985         5186 push @{ $rv->{$module}{used_by} }, $used_by
1188             if ( (!File::Spec->case_tolerant() && !grep { $_ eq $used_by } @{ $rv->{$module}{used_by} })
1189 2063 100 66     5586 or ( File::Spec->case_tolerant() && !grep { lc($_) eq lc($used_by) } @{ $rv->{$module}{used_by} }));
  0   33     0  
  0   66     0  
1190              
1191             # We assume here that another _add_info will be called to provide the other parts of $rv->{$used_by}
1192 1985         7490 push @{ $rv->{$used_by}{uses} }, $module
1193             if ( (!File::Spec->case_tolerant() && !grep { $_ eq $module } @{ $rv->{$used_by}{uses} })
1194 2063 100 66     5484 or ( File::Spec->case_tolerant() && !grep { lc($_) eq lc($module) } @{ $rv->{$used_by}{uses} }));
  0   33     0  
  0   100     0  
1195             }
1196             }
1197              
1198             # This subroutine relies on not being called for modules that have already been visited
1199             sub add_deps {
1200 1685 0 33 1685 1 14726 my %args =
    50          
1201             ((@_ and $_[0] =~ /^(?:modules|rv|used_by|warn_missing)$/)
1202             ? @_
1203             : (rv => (ref($_[0]) ? shift(@_) : undef), modules => [@_]));
1204              
1205 1685   50     5162 my $rv = $args{rv} || {};
1206 1685   100     4876 my $skip = $args{skip} || {};
1207 1685         2843 my $used_by = $args{used_by};
1208              
1209 1685         2448 foreach my $module (@{ $args{modules} }) {
  1685         3531  
1210             my $file = _find_in_inc($module)
1211 2039 100       4048 or _warn_of_missing_module($module, $args{warn_missing}), next;
1212 1957 100       6827 next if $skip->{$file};
1213              
1214 1956 100       4921 if (exists $rv->{$module}) {
1215 1537         4425 _add_info( rv => $rv, module => $module,
1216             file => $file, used_by => $used_by,
1217             type => undef );
1218 1537         3802 next;
1219             }
1220              
1221 419         1259 _add_info( rv => $rv, module => $module,
1222             file => $file, used_by => $used_by,
1223             type => _gettype($file) );
1224              
1225 419 100       2356 if ((my $path = $module) =~ s/\.p[mh]$//i) {
1226              
1227 401         1287 foreach (_glob_in_inc("auto/$path")) {
1228 186 100       2411 next if $_->{name} =~ m{^auto/$path/.*/}; # weed out subdirs
1229 139 100       3123 next if $_->{name} =~ m{/(?:\.exists|\.packlist)$|\Q$Config{lib_ext}\E$};
1230              
1231             _add_info( rv => $rv, module => $_->{name},
1232             file => $_->{file}, used_by => $module,
1233 130         580 type => _gettype($_->{name}) );
1234             }
1235              
1236             ### Now, handle module and distribution share dirs
1237             # convert 'Module/Name' to 'Module-Name'
1238 401         1038 my $modname = $path;
1239 401         1400 $modname =~ s|/|-|g;
1240             # TODO: get real distribution name related to module name
1241 401         719 my $distname = $modname;
1242 401         1123 foreach (_glob_in_inc("auto/share/module/$modname")) {
1243             _add_info( rv => $rv, module => $_->{name},
1244 0         0 file => $_->{file}, used_by => $module,
1245             type => 'data' );
1246             }
1247 401         1430 foreach (_glob_in_inc("auto/share/dist/$distname")) {
1248             _add_info( rv => $rv, module => $_->{name},
1249 0         0 file => $_->{file}, used_by => $module,
1250             type => 'data' );
1251             }
1252             }
1253             } # end for modules
1254 1685         4757 return $rv;
1255             }
1256              
1257             sub _find_in_inc {
1258 2039     2039   3212 my $file = shift;
1259 2039 50       4063 return unless defined $file;
1260              
1261 2039         12998 foreach my $dir (grep !/\bBSDPAN\b/, @INC, @IncludeLibs) {
1262 19923 100       247047 return "$dir/$file" if -f "$dir/$file";
1263             }
1264              
1265             # absolute file names
1266 82 50       684 return $file if -f $file;
1267              
1268 82         581 return;
1269             }
1270              
1271             sub _glob_in_inc {
1272 1225     1225   2474 my ($subdir, $pm_only) = @_;
1273              
1274 1225         6004 require File::Find;
1275              
1276 1225         2427 $subdir =~ s/\$CurrentPackage/$CurrentPackage/;
1277              
1278 1225         1730 my @files;
1279 1225         6472 foreach my $inc (grep !/\bBSDPAN\b/, @INC, @IncludeLibs) {
1280 14089         37339 my $dir = "$inc/$subdir";
1281 14089 100       143123 next unless -d $dir;
1282              
1283             # canonicalize $inc (ie. use "/" as filename separator exclusively)
1284             # as newer versions of File::Find return a canonicalized $File::Find::name
1285 86         401 (my $canon = $inc) =~ s|\\|/|g;
1286             File::Find::find(
1287             sub {
1288 707 100   707   59485 return unless -f $_;
1289 555 50 66     2989 return if $pm_only and !/\.p[mh]$/i;
1290 555         1405 (my $file = $File::Find::name) =~ s|\\|/|g;
1291 555         4192 (my $name = $file) =~ s|^\Q$canon\E/||;
1292 555 100       10337 push @files, $pm_only ? $name
1293             : { file => $file, name => $name };
1294             },
1295 86         8307 $dir
1296             );
1297             }
1298              
1299 1225         4748 return @files;
1300             }
1301              
1302             # like _glob_in_inc, but looks only at the first level
1303             # (i.e. the children of $subdir)
1304             # NOTE: File::Find has no public notion of the depth of the traversal
1305             # in its "wanted" callback, so it's not helpful
1306             sub _glob_in_inc_1 {
1307 0     0   0 my ($subdir, $pm_only) = @_;
1308              
1309 0         0 $subdir =~ s/\$CurrentPackage/$CurrentPackage/;
1310              
1311 0         0 my @files;
1312 0         0 foreach my $inc (grep !/\bBSDPAN\b/, @INC, @IncludeLibs) {
1313 0         0 my $dir = "$inc/$subdir";
1314 0 0       0 next unless -d $dir;
1315              
1316 0 0       0 opendir(my $dh, $dir) or next;
1317 0         0 my @names = map { "$subdir/$_" } grep { -f "$dir/$_" } readdir $dh;
  0         0  
  0         0  
1318 0         0 closedir $dh;
1319              
1320 0         0 push @files, $pm_only ? ( grep { /\.p[mh]$/i } @names )
1321 0 0       0 : ( map { { file => "$inc/$_", name => $_ } } @names );
  0         0  
1322             }
1323              
1324 0         0 return @files;
1325             }
1326              
1327             my $unicore_stuff;
1328             sub _unicore {
1329 0   0 0   0 $unicore_stuff ||= [ 'utf8_heavy.pl', map $_->{name}, _glob_in_inc('unicore', 0) ];
1330 0         0 return @$unicore_stuff;
1331             }
1332              
1333             # App::Packer compatibility functions
1334              
1335             sub new {
1336 1     1 0 504 my ($class, $self) = @_;
1337 1   50     12 return bless($self ||= {}, $class);
1338             }
1339              
1340             sub set_file {
1341 1     1 0 8 my $self = shift;
1342 1         2 my $script = shift;
1343              
1344 1         20 my ($vol, $dir, $file) = File::Spec->splitpath($script);
1345             $self->{main} = {
1346 1         7 key => $file,
1347             file => $script,
1348             };
1349             }
1350              
1351             sub set_options {
1352 0     0 0 0 my $self = shift;
1353 0         0 my %args = @_;
1354 0         0 foreach my $module (@{ $args{add_modules} }) {
  0         0  
1355 0 0       0 $module = _mod2pm($module) unless $module =~ /\.p[mh]$/i;
1356             my $file = _find_in_inc($module)
1357 0 0       0 or _warn_of_missing_module($module, $args{warn_missing}), next;
1358 0         0 $self->{files}{$module} = $file;
1359             }
1360             }
1361              
1362             sub calculate_info {
1363 1     1 0 5 my $self = shift;
1364             my $rv = scan_deps(
1365 1         4 'keys' => [ $self->{main}{key}, sort keys %{ $self->{files} }, ],
1366             files => [ $self->{main}{file},
1367 1         2 map { $self->{files}{$_} } sort keys %{ $self->{files} },
  0         0  
  1         4  
1368             ],
1369             recurse => 1,
1370             );
1371              
1372             my $info = {
1373             main => { file => $self->{main}{file},
1374             store_as => $self->{main}{key},
1375             },
1376 1         10 };
1377              
1378 1         5 my %cache = ($self->{main}{key} => $info->{main});
1379 1         3 foreach my $key (sort keys %{ $self->{files} }) {
  1         6  
1380 0         0 my $file = $self->{files}{$key};
1381              
1382             $cache{$key} = $info->{modules}{$key} = {
1383             file => $file,
1384             store_as => $key,
1385 0         0 used_by => [ $self->{main}{key} ],
1386             };
1387             }
1388              
1389 1         3 foreach my $key (sort keys %{$rv}) {
  1         65  
1390 127         176 my $val = $rv->{$key};
1391 127 100       207 if ($cache{ $val->{key} }) {
1392 1 50       4 defined($val->{used_by}) or next;
1393 0         0 push @{ $info->{ $val->{type} }->{ $val->{key} }->{used_by} },
1394 0         0 @{ $val->{used_by} };
  0         0  
1395             }
1396             else {
1397             $cache{ $val->{key} } = $info->{ $val->{type} }->{ $val->{key} } =
1398             { file => $val->{file},
1399             store_as => $val->{key},
1400             used_by => $val->{used_by},
1401 126         469 };
1402             }
1403             }
1404              
1405 1         12 $self->{info} = { main => $info->{main} };
1406              
1407 1         3 foreach my $type (sort keys %{$info}) {
  1         7  
1408 4 100       11 next if $type eq 'main';
1409              
1410 3         5 my @val;
1411 3 50       13 if (UNIVERSAL::isa($info->{$type}, 'HASH')) {
1412 3         7 foreach my $val (sort values %{ $info->{$type} }) {
  3         318  
1413 126         219 @{ $val->{used_by} } = map $cache{$_} || "!!$_!!",
1414 126   33     150 @{ $val->{used_by} };
  126         681  
1415 126         199 push @val, $val;
1416             }
1417             }
1418              
1419 3 100       10 $type = 'modules' if $type eq 'module';
1420 3         130 $self->{info}{$type} = \@val;
1421             }
1422             }
1423              
1424             sub get_files {
1425 1     1 0 20 my $self = shift;
1426 1         8 return $self->{info};
1427             }
1428              
1429             sub add_preload_rule {
1430 0     0 0 0 my ($pm, $rule) = @_;
1431 0 0       0 die qq[a preload rule for "$pm" already exists] if $Preload{$pm};
1432 0         0 $Preload{$pm} = $rule;
1433             }
1434              
1435             # scan_deps_runtime utility functions
1436              
1437             # compile $file if $execute is undef,
1438             # otherwise execute $file with arguments @$execute
1439             sub _compile_or_execute {
1440 17     17   82 my ($file, $execute) = @_;
1441              
1442 17         191 local $ENV{MSD_ORIGINAL_FILE} = $file;
1443              
1444 17         88 my ($ih, $instrumented_file) = File::Temp::tempfile(UNLINK => 1);
1445              
1446 17         8250 my (undef, $data_file) = File::Temp::tempfile(UNLINK => 1);
1447 17         8269 local $ENV{MSD_DATA_FILE} = $data_file;
1448              
1449             # spoof $0 (to $file) so that FindBin works as expected
1450             # NOTE: We don't directly assign to $0 as it has magic (i.e.
1451             # assigning has side effects and may actually fail, cf. perlvar(1)).
1452             # Instead we alias *0 to a package variable holding the correct value.
1453 17         151 print $ih <<'...';
1454             BEGIN { my $_0 = $ENV{MSD_ORIGINAL_FILE}; *0 = \$_0; }
1455             ...
1456              
1457             # NOTE: When compiling the block will run as the last CHECK block;
1458             # when executing the block will run as the first END block and
1459             # the programs continues.
1460 17 100       98 print $ih
1461             $execute ? "END\n" : "CHECK\n",
1462             <<'...';
1463             {
1464             require DynaLoader;
1465             my @_dl_shared_objects = @DynaLoader::dl_shared_objects;
1466             my @_dl_modules = @DynaLoader::dl_modules;
1467              
1468             # save %INC etc so that requires below don't pollute them
1469             my %_INC = %INC;
1470             my @_INC = @INC;
1471              
1472             require Cwd;
1473             require Data::Dumper;
1474             require Config;
1475             my $dlext = $Config::Config{dlext};
1476              
1477             foreach my $k (keys %_INC)
1478             {
1479             # NOTES:
1480             # (1) An unsuccessful "require" may store an undefined value into %INC.
1481             # (2) If a key in %INC was located via a CODE or ARRAY ref or
1482             # blessed object in @INC the corresponding value in %INC contains
1483             # the ref from @INC.
1484             # (3) Some modules (e.g. Moose) fake entries in %INC, e.g.
1485             # "Class/MOP/Class/Immutable/Moose/Meta/Class.pm" => "(set by Moose)"
1486             # On some architectures (e.g. Windows) Cwd::abs_path() will throw
1487             # an exception for such a pathname.
1488              
1489             my $v = $_INC{$k};
1490             if (defined $v && !ref $v && -e $v)
1491             {
1492             $_INC{$k} = Cwd::abs_path($v);
1493             }
1494             else
1495             {
1496             delete $_INC{$k};
1497             }
1498             }
1499              
1500             # drop refs from @_INC
1501             @_INC = grep { !ref $_ } @_INC;
1502              
1503             my @dlls = grep { defined $_ && -e $_ } Module::ScanDeps::DataFeed::_dl_shared_objects();
1504             my @shared_objects = @dlls;
1505             push @shared_objects, grep { -e $_ } map { (my $bs = $_) =~ s/\.\Q$dlext\E$/.bs/; $bs } @dlls;
1506              
1507             # write data file
1508             my $data_file = $ENV{MSD_DATA_FILE};
1509             open my $fh, ">", $data_file
1510             or die "Couldn't open $data_file: $!\n";
1511             print $fh Data::Dumper::Dumper(
1512             {
1513             '%INC' => \%_INC,
1514             '@INC' => \@_INC,
1515             dl_shared_objects => \@shared_objects,
1516             });
1517             close $fh;
1518              
1519             sub Module::ScanDeps::DataFeed::_dl_shared_objects {
1520             if (@_dl_shared_objects) {
1521             return @_dl_shared_objects;
1522             }
1523             elsif (@_dl_modules) {
1524             return map { Module::ScanDeps::DataFeed::_dl_mod2filename($_) } @_dl_modules;
1525             }
1526             return;
1527             }
1528              
1529             sub Module::ScanDeps::DataFeed::_dl_mod2filename {
1530             my $mod = shift;
1531              
1532             return if $mod eq 'B';
1533             return unless defined &{"$mod\::bootstrap"};
1534              
1535              
1536             # cf. DynaLoader.pm
1537             my @modparts = split(/::/, $mod);
1538             my $modfname = defined &DynaLoader::mod2fname ? DynaLoader::mod2fname(\@modparts) : $modparts[-1];
1539             my $modpname = join('/', @modparts);
1540              
1541             foreach my $dir (@_INC) {
1542             my $file = "$dir/auto/$modpname/$modfname.$dlext";
1543             return $file if -e $file;
1544             }
1545             return;
1546             }
1547             } # END or CHECK
1548             ...
1549              
1550             # append the file to compile or execute
1551             {
1552 17 50       39 open my $fh, "<", $file or die "Couldn't open $file: $!";
  17         830  
1553 17         762 print $ih qq[#line 1 "$file"\n], <$fh>;
1554 17         367 close $fh;
1555             }
1556 17         696 close $ih;
1557              
1558             # run the instrumented file
1559             my $rc = system(
1560             $^X,
1561             $execute ? () : ("-c"),
1562 17 100       678158 (map { "-I$_" } @IncludeLibs),
  0 100       0  
1563             $instrumented_file,
1564             $execute ? @$execute : ());
1565              
1566 17 0       529 die $execute
    50          
1567             ? "SYSTEM ERROR in executing $file @$execute: $rc"
1568             : "SYSTEM ERROR in compiling $file: $rc"
1569             unless $rc == 0;
1570              
1571 17 50 0     26236 my $info = do $data_file
1572             or die "error extracting info from -c/-x file: ", ($@ || "can't read $data_file: $!");
1573              
1574 17         1233 return $info;
1575             }
1576              
1577             # create a new hashref, applying fixups
1578             sub _info2rv {
1579 17     17   96 my ($info) = @_;
1580              
1581 17         101 my $rv = {};
1582              
1583 209         721 my $incs = join('|', sort { length($b) <=> length($a) }
1584 121         429 map { s|\\|/|g; s|/+$||; quotemeta($_) }
  121         495  
  121         790  
1585 17         56 @{ $info->{'@INC'} });
  17         122  
1586 17         118 my $i = is_insensitive_fs() ? "i" : "";
1587 17         1157 my $strip_inc_prefix = qr{^(?$i:$incs)/};
1588              
1589 17         274 require File::Spec;
1590              
1591 17         66 foreach my $key (keys %{ $info->{'%INC'} }) {
  17         217  
1592 174         614 (my $path = $info->{'%INC'}{$key}) =~ s|\\|/|g;
1593              
1594             # NOTE: %INC may contain (as keys) absolute pathnames,
1595             # e.g. for autosplit .ix and .al files. In the latter case,
1596             # the key may also start with "./" if found via a relative path in @INC.
1597 174         367 $key =~ s|\\|/|g;
1598 174         420 $key =~ s|^\./||;
1599 174         722 $key =~ s/$strip_inc_prefix//;
1600              
1601 174         737 $rv->{$key} = {
1602             'used_by' => [],
1603             'file' => $path,
1604             'type' => _gettype($path),
1605             'key' => $key
1606             };
1607             }
1608              
1609 17         67 foreach my $path (@{ $info->{dl_shared_objects} }) {
  17         83  
1610 16         44 $path =~ s|\\|/|g;
1611 16         405 (my $key = $path) =~ s/$strip_inc_prefix//;
1612              
1613 16         215 $rv->{$key} = {
1614             'used_by' => [],
1615             'file' => $path,
1616             'type' => 'shared',
1617             'key' => $key
1618             };
1619             }
1620              
1621 17         340 return $rv;
1622             }
1623              
1624             sub _gettype {
1625 779     779   1665 my $name = shift;
1626              
1627 779 100       3883 return 'autoload' if $name =~ /\.(?:ix|al|bs)$/i;
1628 696 100       4574 return 'module' if $name =~ /\.p[mh]$/i;
1629 114 100       1732 return 'shared' if $name =~ /\.\Q$Config{dlext}\E$/i;
1630 65         259 return 'data';
1631             }
1632              
1633             # merge all keys from $rv_sub into the $rv mega-ref
1634             sub _merge_rv {
1635 17     17   119 my ($rv_sub, $rv) = @_;
1636              
1637 17         48 my $key;
1638 17         216 foreach $key (keys(%$rv_sub)) {
1639 190         349 my %mark;
1640 190 50 33     860 if ($rv->{$key} and _not_dup($key, $rv, $rv_sub)) {
    50          
1641             warn "Different modules for file '$key' were found.\n"
1642             . " -> Using '" . abs_path($rv_sub->{$key}{file}) . "'.\n"
1643 0         0 . " -> Ignoring '" . abs_path($rv->{$key}{file}) . "'.\n";
1644             $rv->{$key}{used_by} = [
1645             grep (!$mark{$_}++,
1646 0         0 @{ $rv->{$key}{used_by} },
1647 0         0 @{ $rv_sub->{$key}{used_by} })
  0         0  
1648             ];
1649 0         0 @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} };
  0         0  
  0         0  
1650 0         0 $rv->{$key}{file} = $rv_sub->{$key}{file};
1651             }
1652             elsif ($rv->{$key}) {
1653             $rv->{$key}{used_by} = [
1654             grep (!$mark{$_}++,
1655 0         0 @{ $rv->{$key}{used_by} },
1656 0         0 @{ $rv_sub->{$key}{used_by} })
  0         0  
1657             ];
1658 0         0 @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} };
  0         0  
  0         0  
1659             }
1660             else {
1661             $rv->{$key} = {
1662 190         1213 used_by => [ @{ $rv_sub->{$key}{used_by} } ],
1663             file => $rv_sub->{$key}{file},
1664             key => $rv_sub->{$key}{key},
1665             type => $rv_sub->{$key}{type}
1666 190         419 };
1667              
1668 190         468 @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} };
  190         873  
  190         505  
1669             }
1670             }
1671             }
1672              
1673             sub _not_dup {
1674 0     0   0 my ($key, $rv1, $rv2) = @_;
1675 0 0       0 if (File::Spec->case_tolerant()) {
1676 0         0 return lc(abs_path($rv1->{$key}{file})) ne lc(abs_path($rv2->{$key}{file}));
1677             }
1678             else {
1679 0         0 return abs_path($rv1->{$key}{file}) ne abs_path($rv2->{$key}{file});
1680             }
1681             }
1682              
1683             sub _warn_of_runtime_loader {
1684 0     0   0 my $module = shift;
1685 0 0       0 return if $SeenRuntimeLoader{$module}++;
1686 0         0 $module =~ s/\.pm$//;
1687 0         0 $module =~ s|/|::|g;
1688 0         0 warn "# Use of runtime loader module $module detected. Results of static scanning may be incomplete.\n";
1689 0         0 return;
1690             }
1691              
1692             sub _warn_of_missing_module {
1693 82     82   174 my $module = shift;
1694 82         127 my $warn = shift;
1695 82 50       246 return if not $warn;
1696 0 0       0 return if not $module =~ /\.p[ml]$/;
1697 0 0       0 warn "# Could not find source file '$module' in \@INC or \@IncludeLibs. Skipping it.\n"
1698             if not -f $module;
1699             }
1700              
1701             sub _get_preload1 {
1702 2034     2034   3122 my $pm = shift;
1703 2034 100       6927 my $preload = $Preload{$pm} or return();
1704 56 100       380 if ($preload eq 'sub') {
    100          
1705 1         8 $pm =~ s/\.p[mh]$//i;
1706 1         4 return _glob_in_inc($pm, 1);
1707             }
1708             elsif (UNIVERSAL::isa($preload, 'CODE')) {
1709 29         99 return $preload->($pm);
1710             }
1711 26         90 return @$preload;
1712             }
1713              
1714             sub _get_preload {
1715 2034     2034   4158 my ($pm, $seen) = @_;
1716 2034   100     7384 $seen ||= {};
1717 2034         4356 $seen->{$pm}++;
1718 2034         2936 my @preload;
1719              
1720 2034         3594 foreach $pm (_get_preload1($pm))
1721             {
1722 406 50       777 next if $seen->{$pm};
1723 406         877 $seen->{$pm}++;
1724 406         711 push @preload, $pm, _get_preload($pm, $seen);
1725             }
1726 2034         6996 return @preload;
1727             }
1728              
1729             1;
1730             __END__