1 package Module::ScanDeps;
5 use vars qw( $VERSION @EXPORT @EXPORT_OK @ISA $CurrentPackage @IncludeLibs $ScanFileRE );
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 );
13 our @ISA = qw(Exporter);
14 use constant dl_ext => ".$Config{dlext}";
15 use constant lib_ext => $Config{lib_ext};
16 use constant is_insensitive_fs => (
18 and (-s lc($0) || -1) == (-s uc($0) || -1)
19 and (-s lc($0) || -1) == -s $0
27 use File::Basename ();
29 use Module::Build::ModuleInfo;
31 $ScanFileRE = qr/(?:^|\\|\/)(?:[^.]*|.*\.(?i:p[ml]|t|al))$/;
35 Module::ScanDeps - Recursively scan Perl code for dependencies
39 Via the command-line program L<scandeps.pl>:
41 % scandeps.pl *.pm # Print PREREQ_PM section for *.pm
42 % scandeps.pl -e "use utf8" # Read script from command line
43 % scandeps.pl -B *.pm # Include core modules
44 % scandeps.pl -V *.pm # Show autoload/shared/data files
51 my $hash_ref = scan_deps(
52 files => [ 'a.pl', 'b.pl' ],
56 # shorthand; assume recurse == 1
57 my $hash_ref = scan_deps( 'a.pl', 'b.pl' );
59 # App::Packer::Frontend compatible interface
60 # see App::Packer::Frontend for the structure returned by get_files
61 my $scan = Module::ScanDeps->new;
62 $scan->set_file( 'a.pl' );
63 $scan->set_options( add_modules => [ 'Test::More' ] );
64 $scan->calculate_info;
65 my $files = $scan->get_files;
69 This module scans potential modules used by perl programs, and returns a
70 hash reference; its keys are the module names as appears in C<%INC>
71 (e.g. C<Test/More.pm>); the values are hash references with this structure:
74 file => '/usr/local/lib/perl5/5.8.0/Test/More.pm',
75 key => 'Test/More.pm',
76 type => 'module', # or 'autoload', 'data', 'shared'
77 used_by => [ 'Test/Simple.pm', ... ],
78 uses => [ 'Test/Other.pm', ... ],
81 One function, C<scan_deps>, is exported by default. Other
82 functions such as (C<scan_line>, C<scan_chunk>, C<add_deps>, C<path_to_inc_name>)
83 are exported upon request.
85 Users of B<App::Packer> may also use this module as the dependency-checking
86 frontend, by tweaking their F<p2e.pl> like below:
90 my $packer = App::Packer->new( frontend => 'Module::ScanDeps' );
93 Please see L<App::Packer::Frontend> for detailed explanation on
94 the structure returned by C<get_files>.
99 files => \@files, recurse => $recurse,
100 rv => \%rv, skip => \%skip,
101 compile => $compile, execute => $execute,
103 $rv_ref = scan_deps(@files); # shorthand, with recurse => 1
105 This function scans each file in C<@files>, registering their
106 dependencies into C<%rv>, and returns a reference to the updated
107 C<%rv>. The meaning of keys and values are explained above.
109 If C<$recurse> is true, C<scan_deps> will call itself recursively,
110 to perform a breadth-first search on text files (as defined by the
111 -T operator) found in C<%rv>.
113 If the C<\%skip> is specified, files that exists as its keys are
114 skipped. This is used internally to avoid infinite recursion.
116 If C<$compile> or C<$execute> is true, runs C<files> in either
117 compile-only or normal mode, then inspects their C<%INC> after
118 termination to determine additional runtime dependencies.
120 If C<$execute> is an array reference, runs the files contained
121 in it instead of C<@files>.
123 If performance of the scanning process is a concern, C<cache_file> can be
124 set to a filename. The scanning results will be cached and written to the
125 file. This will speed up the scanning process on subsequent runs.
127 Additionally, an option C<warn_missing> is recognized. If set to true,
128 C<scan_deps> issues a warning to STDERR for every module file that the
129 scanned code depends but that wasn't found. Please note that this may
130 also report numerous false positives. That is why by default, the heuristic
131 silently drops all dependencies it cannot find.
133 =head2 B<scan_deps_runtime>
135 Like B<scan_deps>, but skips the static scanning part.
139 @modules = scan_line($line);
141 Splits a line into chunks (currently with the semicolon characters), and
142 return the union of C<scan_chunk> calls of them.
144 If the line is C<__END__> or C<__DATA__>, a single C<__END__> element is
145 returned to signify the end of the program.
147 Similarly, it returns a single C<__POD__> if the line matches C</^=\w/>;
148 the caller is responsible for skipping appropriate number of lines
149 until C<=cut>, before calling C<scan_line> again.
153 $module = scan_chunk($chunk);
154 @modules = scan_chunk($chunk);
156 Apply various heuristics to C<$chunk> to find and return the module
157 name(s) it contains. In scalar context, returns only the first module
162 $rv_ref = add_deps( rv => \%rv, modules => \@modules );
163 $rv_ref = add_deps( @modules ); # shorthand, without rv
165 Resolves a list of module names to its actual on-disk location, by
166 finding in C<@INC> and C<@Module::ScanDeps::IncludeLibs>;
167 modules that cannot be found are skipped.
169 This function populates the C<%rv> hash with module/filename pairs, and
170 returns a reference to it.
172 =head2 B<path_to_inc_name>
174 $perl_name = path_to_inc_name($path, $warn)
176 Assumes C<$path> refers to a perl file and does it's best to return the
177 name as it would appear in %INC. Returns undef if no match was found
178 and a prints a warning to STDERR if C<$warn> is true.
180 E.g. if C<$path> = perl/site/lib/Module/ScanDeps.pm then C<$perl_name>
181 will be Module/ScanDeps.pm.
185 =head2 B<@Module::ScanDeps::IncludeLibs>
187 You can set this global variable to specify additional directories in
188 which to search modules without modifying C<@INC> itself.
190 =head2 B<$Module::ScanDeps::ScanFileRE>
192 You can set this global variable to specify a regular expression to
193 identify what files to scan. By default it includes all files of
194 the following types: .pm, .pl, .t and .al. Additionally, all files
195 without a suffix are considered.
197 For instance, if you want to scan all files then use the following:
199 C<$Module::ScanDeps::ScanFileRE = qr/./>
203 This module intentially ignores the B<BSDPAN> hack on FreeBSD -- the
204 additional directory is removed from C<@INC> altogether.
206 The static-scanning heuristic is not likely to be 100% accurate, especially
207 on modules that dynamically load other modules.
209 Chunks that span multiple lines are not handled correctly. For example,
214 But this one does not:
223 # Pre-loaded module dependencies {{{
226 'AnyDBM_File.pm' => [qw( SDBM_File.pm )],
227 'Authen/SASL.pm' => 'sub',
228 'Bio/AlignIO.pm' => 'sub',
229 'Bio/Assembly/IO.pm' => 'sub',
230 'Bio/Biblio/IO.pm' => 'sub',
231 'Bio/ClusterIO.pm' => 'sub',
232 'Bio/CodonUsage/IO.pm' => 'sub',
233 'Bio/DB/Biblio.pm' => 'sub',
234 'Bio/DB/Flat.pm' => 'sub',
235 'Bio/DB/GFF.pm' => 'sub',
236 'Bio/DB/Taxonomy.pm' => 'sub',
237 'Bio/Graphics/Glyph.pm' => 'sub',
238 'Bio/MapIO.pm' => 'sub',
239 'Bio/Matrix/IO.pm' => 'sub',
240 'Bio/Matrix/PSM/IO.pm' => 'sub',
241 'Bio/OntologyIO.pm' => 'sub',
242 'Bio/PopGen/IO.pm' => 'sub',
243 'Bio/Restriction/IO.pm' => 'sub',
244 'Bio/Root/IO.pm' => 'sub',
245 'Bio/SearchIO.pm' => 'sub',
246 'Bio/SeqIO.pm' => 'sub',
247 'Bio/Structure/IO.pm' => 'sub',
248 'Bio/TreeIO.pm' => 'sub',
249 'Bio/LiveSeq/IO.pm' => 'sub',
250 'Bio/Variation/IO.pm' => 'sub',
251 'Catalyst.pm' => sub {
252 return ('Catalyst/Runtime.pm',
253 'Catalyst/Dispatcher.pm',
254 _glob_in_inc('Catalyst/DispatchType', 1));
256 'Catalyst/Engine.pm' => 'sub',
257 'Class/MakeMethods.pm' => 'sub',
258 'Class/MethodMaker.pm' => 'sub',
259 'Config/Any.pm' =>'sub',
260 'Crypt/Random.pm' => sub {
261 _glob_in_inc('Crypt/Random/Provider', 1);
263 'Crypt/Random/Generator.pm' => sub {
264 _glob_in_inc('Crypt/Random/Provider', 1);
266 'DateTime/Locale.pm' => 'sub',
268 grep !/\bProxy\b/, _glob_in_inc('DBD', 1);
270 'DBIx/Class.pm' => 'sub',
271 'DBIx/SearchBuilder.pm' => 'sub',
272 'DBIx/ReportBuilder.pm' => 'sub',
273 'Device/ParallelPort.pm' => 'sub',
274 'Device/SerialPort.pm' => [ qw(
275 termios.ph asm/termios.ph sys/termiox.ph sys/termios.ph sys/ttycom.ph
277 'Email/Send.pm' => 'sub',
278 'Event.pm' => [ map {"Event/$_.pm" } qw(idle io signal timer var)],
279 'ExtUtils/MakeMaker.pm' => sub {
280 grep /\bMM_/, _glob_in_inc('ExtUtils', 1);
282 'File/Basename.pm' => [qw( re.pm )],
283 'File/HomeDir.pm' => 'sub',
284 'File/Spec.pm' => sub {
286 map { my $name = $_; $name =~ s!::!/!g; "$name.pm" } @File::Spec::ISA;
288 'HTTP/Message.pm' => [ qw(
291 'Image/ExifTool.pm' => sub {
292 return( _glob_in_inc("Image/ExifTool", 1), qw(
296 'Image/Info.pm' => sub {
297 return( _glob_in_inc("Image/Info", 1), qw(
302 IO/Handle.pm IO/Seekable.pm IO/File.pm
303 IO/Pipe.pm IO/Socket.pm IO/Dir.pm
305 'IO/Socket.pm' => [qw( IO/Socket/UNIX.pm )],
306 'Log/Log4perl.pm' => 'sub',
307 'Log/Any.pm' => 'sub',
308 'LWP/UserAgent.pm' => sub {
311 URI/URL.pm URI/http.pm LWP/Protocol/http.pm
313 _glob_in_inc("LWP/Authen", 1),
314 _glob_in_inc("LWP/Protocol", 1),
317 'LWP/Parallel.pm' => sub {
318 _glob_in_inc( 'LWP/Parallel', 1 ),
320 LWP/ParallelUA.pm LWP/UserAgent.pm
321 LWP/RobotPUA.pm LWP/RobotUA.pm
324 'LWP/Parallel/UserAgent.pm' => sub {
325 qw( LWP/Parallel.pm ),
326 @{ _get_preload('LWP/Parallel.pm') }
328 'Locale/Maketext/Lexicon.pm' => 'sub',
329 'Locale/Maketext/GutsLoader.pm' => [qw( Locale/Maketext/Guts.pm )],
330 'Mail/Audit.pm' => 'sub',
331 'Math/BigInt.pm' => 'sub',
332 'Math/BigFloat.pm' => 'sub',
333 'Math/Symbolic.pm' => 'sub',
334 'Module/Build.pm' => 'sub',
335 'Module/Pluggable.pm' => sub {
336 _glob_in_inc('$CurrentPackage/Plugin', 1);
338 'MIME/Decoder.pm' => 'sub',
339 'Net/DNS/RR.pm' => 'sub',
340 'Net/FTP.pm' => 'sub',
341 'Net/Server.pm' => 'sub',
342 'Net/SSH/Perl.pm' => 'sub',
343 'PAR/Repository.pm' => 'sub',
344 'PAR/Repository/Client.pm' => 'sub',
345 'Perl/Critic.pm' => 'sub', #not only Perl/Critic/Policy
346 'PDF/API2/Resource/Font.pm' => 'sub',
347 'PDF/API2/Basic/TTF/Font.pm' => sub {
348 _glob_in_inc('PDF/API2/Basic/TTF', 1);
350 'PDF/Writer.pm' => 'sub',
352 POE/Kernel.pm POE/Session.pm
354 'POE/Kernel.pm' => sub {
355 _glob_in_inc('POE/XS/Resource', 1),
356 _glob_in_inc('POE/Resource', 1),
357 _glob_in_inc('POE/XS/Loop', 1),
358 _glob_in_inc('POE/Loop', 1),
361 'Parse/AFP.pm' => 'sub',
362 'Parse/Binary.pm' => 'sub',
363 'PerlIO.pm' => [ 'PerlIO/scalar.pm' ],
364 'Regexp/Common.pm' => 'sub',
365 'SerialJunk.pm' => [ qw(
366 termios.ph asm/termios.ph sys/termiox.ph sys/termios.ph sys/ttycom.ph
368 'SOAP/Lite.pm' => sub {
369 (($] >= 5.008 ? ('utf8.pm') : ()), _glob_in_inc('SOAP/Transport', 1));
371 'SQL/Parser.pm' => sub {
372 _glob_in_inc('SQL/Dialects', 1);
374 'SQL/Translator/Schema.pm' => sub {
375 _glob_in_inc('SQL/Translator', 1);
377 'SVK/Command.pm' => sub {
378 _glob_in_inc('SVK', 1);
380 'SVN/Core.pm' => sub {
381 _glob_in_inc('SVN', 1),
382 map "auto/SVN/$_->{name}", _glob_in_inc('auto/SVN'),
384 'Template.pm' => 'sub',
385 'Term/ReadLine.pm' => 'sub',
386 'Test/Deep.pm' => 'sub',
389 qw( Tk/FileSelect.pm Encode/Unicode.pm );
391 'Tk/Balloon.pm' => [qw( Tk/balArrow.xbm )],
392 'Tk/BrowseEntry.pm' => [qw( Tk/cbxarrow.xbm Tk/arrowdownwin.xbm )],
393 'Tk/ColorEditor.pm' => [qw( Tk/ColorEdit.xpm )],
394 'Tk/DragDrop/Common.pm' => sub {
395 _glob_in_inc('Tk/DragDrop', 1),
397 'Tk/FBox.pm' => [qw( Tk/folder.xpm Tk/file.xpm )],
398 'Tk/Getopt.pm' => [qw( Tk/openfolder.xpm Tk/win.xbm )],
399 'Tk/Toplevel.pm' => [qw( Tk/Wm.pm )],
401 grep !/.\b[_A-Z]/, _glob_in_inc('URI', 1);
403 'Win32/EventLog.pm' => [qw( Win32/IPC.pm )],
404 'Win32/Exe.pm' => 'sub',
405 'Win32/TieRegistry.pm' => [qw( Win32API/Registry.pm )],
406 'Win32/SystemInfo.pm' => [qw( Win32/cpuspd.dll )],
407 'Wx.pm' => [qw( attributes.pm Alien/wxWidgets/msw_2_8_10_uni_gcc_3_4/lib/wxbase28u_gcc_custom.dll)], #still cannot find this .dll
408 'XML/Parser.pm' => sub {
409 _glob_in_inc('XML/Parser/Style', 1),
410 _glob_in_inc('XML/Parser/Encodings', 1),
412 'XML/Parser/Expat.pm' => sub {
413 ($] >= 5.008) ? ('utf8.pm') : ();
415 'XML/SAX.pm' => [qw( XML/SAX/ParserDetails.ini ) ],
416 'XMLRPC/Lite.pm' => sub {
417 _glob_in_inc('XMLRPC/Transport', 1),;
419 'YAML.pm' => [qw( YAML/Loader.pm YAML/Dumper.pm )],
420 'diagnostics.pm' => sub {
421 # shamelessly taken and adapted from diagnostics.pm
423 my($privlib, $archlib) = @Config{qw(privlibexp archlibexp)};
425 require VMS::Filespec;
426 $privlib = VMS::Filespec::unixify($privlib);
427 $archlib = VMS::Filespec::unixify($archlib);
433 "pod/perldiag-$Config{version}.pod",
434 "Pod/perldiag-$Config{version}.pod",
436 "pods/perldiag-$Config{version}.pod",
438 return $_ if _find_in_inc($_);
442 "$archlib/pods/perldiag.pod",
443 "$privlib/pods/perldiag-$Config{version}.pod",
444 "$privlib/pods/perldiag.pod",
449 return 'pod/perldiag.pod';
451 'threads/shared.pm' => [qw( attributes.pm )],
452 # anybody using threads::shared is likely to declare variables
453 # with attribute :shared
455 'utf8_heavy.pl', do {
457 my @subdirs = qw( To );
458 my @files = map "$dir/lib/$_->{name}", _glob_in_inc("$dir/lib");
462 push @files, (map "$dir/$_.pl", qw( Exact Canonical ));
467 @files = map "$dir/Is/$_->{name}", _glob_in_inc("$dir/Is")
472 foreach my $subdir (@subdirs) {
473 foreach (_glob_in_inc("$dir/$subdir")) {
474 push @files, "$dir/$subdir/$_->{name}";
481 _find_in_inc('unicore/Name.pl') ? 'unicore/Name.pl' : 'unicode/Name.pl'
487 sub path_to_inc_name($$) {
492 if ($path =~ m/\.pm$/io) {
493 die "$path doesn't exist" unless (-f $path);
494 my $module_info = Module::Build::ModuleInfo->new_from_file($path);
495 die "Module::Build::ModuleInfo error: $!" unless defined($module_info);
496 $inc_name = $module_info->name();
497 if (defined($inc_name)) {
498 $inc_name =~ s|\:\:|\/|og;
501 warn "# Couldn't find include name for $path\n" if $warn;
505 (my $vol, my $dir, $inc_name) = File::Spec->splitpath($path);
511 my $Keys = 'files|keys|recurse|rv|skip|first|execute|compile|warn_missing|cache_cb|cache_file';
515 (@_ and $_[0] =~ /^(?:$Keys)$/o) ? @_ : (files => [@_], recurse => 1)
518 if (!defined($args{keys})) {
519 $args{keys} = [map {path_to_inc_name($_, $args{warn_missing})} @{$args{files}}];
521 my $cache_file = $args{cache_file};
524 require Module::ScanDeps::Cache;
525 $using_cache = Module::ScanDeps::Cache::init_from_file($cache_file);
527 $args{cache_cb} = Module::ScanDeps::Cache::get_cache_cb();
529 my @missing = Module::ScanDeps::Cache::prereq_missing();
531 "Can not use cache_file: Needs Modules [",
537 foreach my $input_file (@{$args{files}}) {
538 if ($input_file !~ $ScanFileRE) {
539 warn "Skipping input file $input_file because it matches \$Module::ScanDeps::ScanFileRE\n" if $args{warn_missing};
543 $type = _gettype($input_file);
545 if ($type eq 'module') {
546 # necessary because add_deps does the search for shared libraries and such
550 modules => [path_to_inc_name($path, $args{warn_missing})],
552 warn_missing => $args{warn_missing},
558 module => path_to_inc_name($path, $args{warn_missing}),
566 scan_deps_static(\%args);
568 if ($args{execute} or $args{compile}) {
571 files => $args{files},
572 execute => $args{execute},
573 compile => $args{compile},
579 Module::ScanDeps::Cache::store_cache();
582 # do not include the input files themselves as dependencies!
583 delete $args{rv}{$_} foreach @{$args{files}};
588 sub scan_deps_static {
590 my ($files, $keys, $recurse, $rv,
591 $skip, $first, $execute, $compile,
593 = @$args{qw( files keys recurse rv
594 skip first execute compile
598 $_skip ||= { %{$skip || {}} };
600 foreach my $file (@{$files}) {
601 my $key = shift @{$keys};
602 next if $_skip->{$file}++;
603 next if is_insensitive_fs()
604 and $file ne lc($file) and $_skip->{lc($file)}++;
605 next unless $file =~ $ScanFileRE;
611 # cache_cb populates \@pm on success
612 $found_in_cache = $cache_cb->(action => 'read',
617 unless( $found_in_cache ){
618 @pm = scan_file($file);
619 $cache_cb->(action => 'write',
625 }else{ # no caching callback given
626 @pm = scan_file($file);
629 foreach my $pm (@pm){
634 skip => $args->{skip},
635 warn_missing => $args->{warn_missing},
638 my $preload = _get_preload($pm) or next;
644 skip => $args->{skip},
645 warn_missing => $args->{warn_missing},
650 # Top-level recursion handling {{{
653 my $count = keys %$rv;
654 my @files = sort grep { defined $_->{file} && -T $_->{file} } values %$rv;
656 files => [ map $_->{file}, @files ],
657 keys => [ map $_->{key}, @files ],
661 cache_cb => $cache_cb,
663 }) or ($args->{_deep} and return);
664 last if $count == keys %$rv;
672 sub scan_deps_runtime {
676 (@_ and $_[0] =~ /^(?:$Keys)$/o) ? @_ : (files => [@_], recurse => 1)
678 my ($files, $rv, $execute, $compile, $skip, $perl) =
679 @args{qw( files rv execute compile skip perl )};
681 $files = (ref($files)) ? $files : [$files];
683 my ($inchash, $incarray, $dl_shared_objects) = ({}, [], []);
687 foreach $file (@$files) {
688 next unless $file =~ $ScanFileRE;
690 ($inchash, $dl_shared_objects, $incarray) = ({}, [], []);
691 _compile($perl, $file, $inchash, $dl_shared_objects, $incarray);
693 my $rv_sub = _make_rv($inchash, $dl_shared_objects, $incarray);
694 _merge_rv($rv_sub, $rv);
698 my $excarray = (ref($execute)) ? $execute : [@$files];
701 foreach $exc (@$excarray) {
702 ($inchash, $dl_shared_objects, $incarray) = ({}, [], []);
704 $perl, $exc, $inchash, $dl_shared_objects, $incarray,
710 # XXX only retains data from last execute ... Why? I suspect
711 # the above loop was added later. Needs test cases --Eric
712 my $rv_sub = _make_rv($inchash, $dl_shared_objects, $incarray);
713 _merge_rv($rv_sub, $rv);
723 open $FH, $file or die "Cannot open $file: $!";
726 # Line-by-line scanning
729 chomp(my $line = $_);
730 foreach my $pm (scan_line($line)) {
731 last LINE if $pm eq '__END__';
733 # Skip Tk hits from Term::ReadLine and Tcl::Tk
734 my $pathsep = qr/\/|\\|::/;
735 if ($pm =~ /^Tk\b/) {
736 next if $file =~ /(?:^|${pathsep})Term${pathsep}ReadLine\.pm$/;
737 next if $file =~ /(?:^|${pathsep})Tcl${pathsep}Tk\W/;
739 if ($pm eq '__POD__') {
745 $SeenTk || do{$SeenTk = 1 if $pm =~ /Tk\.pm$/;};
746 # the following line does not make much sense here ???
747 # $file is an absolute path and will never match
748 #$pm = 'CGI/Apache.pm' if $file =~ /^Apache(?:\.pm)$/;
752 close $FH or die "Cannot close $file: $!";
760 return '__END__' if $line =~ /^__(?:END|DATA)__$/;
761 return '__POD__' if $line =~ /^=\w/;
763 $line =~ s/\s*#.*$//;
764 $line =~ s/[\\\/]+/\//g;
766 foreach (split(/;/, $line)) {
767 if (/^\s*package\s+(\w+)/) {
768 $CurrentPackage = $1;
769 $CurrentPackage =~ s{::}{/}g;
773 if (/^\s*(?:use|require)\s+v?(\d[\d\._]*)/) {
774 # include feature.pm if we have 5.9.5 or better
775 if (version->new($1) >= version->new("5.9.5")) {
776 # seems to catch 5.9, too (but not 5.9.4)
781 if (my ($autouse) = /^\s*use\s+autouse\s+(["'].*?["']|\w+)/)
783 $autouse =~ s/["']//g;
784 $autouse =~ s{::}{/}g;
785 return ("autouse.pm", "$autouse.pm");
788 if (my ($libs) = /\b(?:use\s+lib\s+|(?:unshift|push)\W+\@INC\W+)(.+)/)
790 my $archname = defined($Config{archname}) ? $Config{archname} : '';
791 my $ver = defined($Config{version}) ? $Config{version} : '';
792 foreach (grep(/\w/, split(/["';() ]/, $libs))) {
793 unshift(@INC, "$_/$ver") if -d "$_/$ver";
794 unshift(@INC, "$_/$archname") if -d "$_/$archname";
795 unshift(@INC, "$_/$ver/$archname") if -d "$_/$ver/$archname";
800 $found{$_}++ for scan_chunk($_);
803 return sort keys %found;
806 # short helper for scan_chunk
807 sub _typical_module_loader_chunk {
814 my $loader_file = $loader;
815 $loader_file =~ s/::/\//;
816 $loader_file .= ".pm";
817 $loader = quotemeta($loader);
819 if (/^\s* use \s+ $loader(?!\:) \b \s* (.*)/sx) {
822 map { my $mod="$prefix$_";$mod=~s{::}{/}g; "$mod.pm" }
823 grep { length and !/^q[qw]?$/ and !/-/ } split(/[^\w:-]+/, $1)
824 #should skip any module name that contains '-', not split it in two
833 # Module name extraction heuristics {{{
837 # TODO: There's many more of these "loader" type modules on CPAN!
838 # scan for the typical module-loader modules
839 foreach my $loader (qw(asa base parent prefork POE encoding maybe only::matching)) {
840 my $retval = _typical_module_loader_chunk($_, $loader);
841 return $retval if $retval;
844 foreach my $loader (qw(Catalyst)) {
845 my $retval = _typical_module_loader_chunk($_, $loader,'Catalyst::Plugin');
846 return $retval if $retval;
849 return [ 'Class/Autouse.pm',
850 map { s{::}{/}g; "$_.pm" }
851 grep { length and !/^:|^q[qw]?$/ } split(/[^\w:]+/, $1) ]
852 if /^\s* use \s+ Class::Autouse \b \s* (.*)/sx
853 or /^\s* Class::Autouse \s* -> \s* autouse \s* (.*)/sx;
855 return $1 if /(?:^|\s)(?:use|no|require)\s+([\w:\.\-\\\/\"\']+)/;
857 if /(?:^|\s)(?:use|no|require)\s+\(\s*([\w:\.\-\\\/\"\']+)\s*\)/;
859 if ( s/(?:^|\s)eval\s+\"([^\"]+)\"/$1/
860 or s/(?:^|\s)eval\s*\(\s*\"([^\"]+)\"\s*\)/$1/)
862 return $1 if /(?:^|\s)(?:use|no|require)\s+([\w:\.\-\\\/\"\']*)/;
865 if (/(<[^>]*[^\$\w>][^>]*>)/) {
867 return "File/Glob.pm" if $diamond =~ /[*?\[\]{}~\\]/;
869 return "DBD/$1.pm" if /\b[Dd][Bb][Ii]:(\w+):/;
870 if (/(?:(:encoding)|\b(?:en|de)code)\(\s*['"]?([-\w]+)/) {
871 my $mod = _find_encoding($2);
872 return [ 'PerlIO.pm', $mod ] if $1 and $mod;
875 return $1 if /(?:^|\s)(?:do|require)\s+[^"]*"(.*?)"/;
876 return $1 if /(?:^|\s)(?:do|require)\s+[^']*'(.*?)'/;
877 return $1 if /[^\$]\b([\w:]+)->\w/ and $1 ne 'Tk' and $1 ne 'shift';
878 return $1 if /\b(\w[\w:]*)::\w+\(/ and $1 ne 'main' and $1 ne 'SUPER';
882 while (/->\s*([A-Z]\w+)/g) {
883 push @modules, "Tk/$1.pm";
885 while (/->\s*Scrolled\W+([A-Z]\w+)/g) {
886 push @modules, "Tk/$1.pm";
887 push @modules, "Tk/Scrollbar.pm";
889 if (/->\s*setPalette/g) {
892 qw( Button Canvas Checkbutton Entry
893 Frame Label Labelframe Listbox
894 Menubutton Menu Message Radiobutton
895 Scale Scrollbar Spinbox Text );
904 return unless defined($module);
905 return wantarray ? @$module : $module->[0] if ref($module);
907 $module =~ s/^['"]//;
908 return unless $module =~ /^\w/;
911 $module =~ s/::/\//g;
912 return if $module =~ /^(?:[\d\._]+|'.*[^']|".*[^"])$/;
914 $module .= ".pm" unless $module =~ /\./;
919 return unless $] >= 5.008 and eval { require Encode; %Encode::ExtModule };
921 my $mod = $Encode::ExtModule{ Encode::find_encoding($_[0])->name }
929 my ($rv, $module, $file, $used_by, $type) = @args{qw/rv module file used_by type/};
931 return unless defined($module) and defined($file);
933 # Ensure file is always absolute
934 $file = File::Spec->rel2abs($file);
937 # Avoid duplicates that can arise due to case differences that don't actually
938 # matter on a case tolerant system
939 if (File::Spec->case_tolerant()) {
940 foreach my $key (keys %$rv) {
941 if (lc($key) eq lc($module)) {
946 if (defined($used_by)) {
947 if (lc($used_by) eq lc($module)) {
950 foreach my $key (keys %$rv) {
951 if (lc($key) eq lc($used_by)) {
966 if (defined($used_by) and $used_by ne $module) {
967 push @{ $rv->{$module}{used_by} }, $used_by
968 if ( (!File::Spec->case_tolerant() && !grep { $_ eq $used_by } @{ $rv->{$module}{used_by} })
969 or ( File::Spec->case_tolerant() && !grep { lc($_) eq lc($used_by) } @{ $rv->{$module}{used_by} }));
971 # We assume here that another _add_info will be called to provide the other parts of $rv->{$used_by}
972 push @{ $rv->{$used_by}{uses} }, $module
973 if ( (!File::Spec->case_tolerant() && !grep { $_ eq $module } @{ $rv->{$used_by}{uses} })
974 or ( File::Spec->case_tolerant() && !grep { lc($_) eq lc($module) } @{ $rv->{$used_by}{uses} }));
978 # This subroutine relies on not being called for modules that have already been visited
981 ((@_ and $_[0] =~ /^(?:modules|rv|used_by|warn_missing)$/)
983 : (rv => (ref($_[0]) ? shift(@_) : undef), modules => [@_]));
985 my $rv = $args{rv} || {};
986 my $skip = $args{skip} || {};
987 my $used_by = $args{used_by};
989 foreach my $module (@{ $args{modules} }) {
990 my $file = _find_in_inc($module)
991 or _warn_of_missing_module($module, $args{warn_missing}), next;
992 next if $skip->{$file};
994 if (exists $rv->{$module}) {
995 _add_info( rv => $rv, module => $module,
996 file => $file, used_by => $used_by,
1001 my $type = _gettype($file);
1002 _add_info( rv => $rv, module => $module,
1003 file => $file, used_by => $used_by,
1006 if ($module =~ /(.*?([^\/]*))\.p[mh]$/i) {
1007 my ($path, $basename) = ($1, $2);
1009 foreach (_glob_in_inc("auto/$path")) {
1010 next if $_->{file} =~ m{\bauto/$path/.*/}; # weed out subdirs
1011 next if $_->{name} =~ m/(?:^|\/)\.(?:exists|packlist)$/;
1013 $ext = lc($1) if $_->{name} =~ /(\.[^.]+)$/;
1015 next if $ext eq lc(lib_ext());
1016 $type = 'shared' if $ext eq lc(dl_ext());
1017 $type = 'autoload' if ($ext eq '.ix' or $ext eq '.al');
1021 _add_info( rv => $rv, module => "auto/$path/$_->{name}",
1022 file => $_->{file}, used_by => $module,
1032 return unless defined $file;
1034 foreach my $dir (grep !/\bBSDPAN\b/, @INC, @IncludeLibs) {
1035 return "$dir/$file" if -f "$dir/$file";
1038 # absolute file names
1039 return $file if -f $file;
1046 my $pm_only = shift;
1051 $subdir =~ s/\$CurrentPackage/$CurrentPackage/;
1053 foreach my $dir (map "$_/$subdir", grep !/\bBSDPAN\b/, @INC, @IncludeLibs) {
1054 next unless -d $dir;
1057 my $name = $File::Find::name;
1058 $name =~ s!^\Q$dir\E/!!;
1059 return if $pm_only and lc($name) !~ /\.p[mh]$/i;
1060 push @files, $pm_only
1062 : { file => $File::Find::name,
1074 # App::Packer compatibility functions
1077 my ($class, $self) = @_;
1078 return bless($self ||= {}, $class);
1083 foreach my $script (@_) {
1084 my ($vol, $dir, $file) = File::Spec->splitpath($script);
1095 foreach my $module (@{ $args{add_modules} }) {
1096 $module =~ s/::/\//g;
1097 $module .= '.pm' unless $module =~ /\.p[mh]$/i;
1098 my $file = _find_in_inc($module)
1099 or _warn_of_missing_module($module, $args{warn_missing}), next;
1100 $self->{files}{$module} = $file;
1104 sub calculate_info {
1107 'keys' => [ $self->{main}{key}, sort keys %{ $self->{files} }, ],
1108 files => [ $self->{main}{file},
1109 map { $self->{files}{$_} } sort keys %{ $self->{files} },
1115 main => { file => $self->{main}{file},
1116 store_as => $self->{main}{key},
1120 my %cache = ($self->{main}{key} => $info->{main});
1121 foreach my $key (sort keys %{ $self->{files} }) {
1122 my $file = $self->{files}{$key};
1124 $cache{$key} = $info->{modules}{$key} = {
1127 used_by => [ $self->{main}{key} ],
1131 foreach my $key (sort keys %{$rv}) {
1132 my $val = $rv->{$key};
1133 if ($cache{ $val->{key} }) {
1134 defined($val->{used_by}) or next;
1135 push @{ $info->{ $val->{type} }->{ $val->{key} }->{used_by} },
1136 @{ $val->{used_by} };
1139 $cache{ $val->{key} } = $info->{ $val->{type} }->{ $val->{key} } =
1140 { file => $val->{file},
1141 store_as => $val->{key},
1142 used_by => $val->{used_by},
1147 $self->{info} = { main => $info->{main} };
1149 foreach my $type (sort keys %{$info}) {
1150 next if $type eq 'main';
1153 if (UNIVERSAL::isa($info->{$type}, 'HASH')) {
1154 foreach my $val (sort values %{ $info->{$type} }) {
1155 @{ $val->{used_by} } = map $cache{$_} || "!!$_!!",
1156 @{ $val->{used_by} };
1161 $type = 'modules' if $type eq 'module';
1162 $self->{info}{$type} = \@val;
1168 return $self->{info};
1171 # scan_deps_runtime utility functions
1174 my ($perl, $file, $inchash, $dl_shared_objects, $incarray) = @_;
1176 my ($fhout, $fname) = File::Temp::tempfile("XXXXXX");
1177 my $fhin = FileHandle->new($file) or die "Couldn't open $file\n";
1179 my $line = do { local $/; <$fhin> };
1180 $line =~ s/use Module::ScanDeps::DataFeed.*?\n//sg;
1181 $line =~ s/^(.*?)((?:[\r\n]+__(?:DATA|END)__[\r\n]+)|$)/
1182 use Module::ScanDeps::DataFeed '$fname.out';
1187 $fhout->print($line);
1191 system($perl, $fname);
1193 _extract_info("$fname.out", $inchash, $dl_shared_objects, $incarray);
1195 unlink("$fname.out");
1199 my ($perl, $file, $inchash, $dl_shared_objects, $incarray, $firstflag) = @_;
1201 $DB::single = $DB::single = 1;
1202 my ($fhout, $fname) = File::Temp::tempfile("XXXXXX");
1203 $fname = _abs_path($fname);
1204 my $fhin = FileHandle->new($file) or die "Couldn't open $file";
1206 my $line = do { local $/; <$fhin> };
1207 $line =~ s/use Module::ScanDeps::DataFeed.*?\n//sg;
1208 $line = "use Module::ScanDeps::DataFeed '$fname.out';\n" . $line;
1209 $fhout->print($line);
1213 File::Path::rmtree( ['_Inline'], 0, 1); # XXX hack
1214 system($perl, (map { "-I$_" } @IncludeLibs), $fname) == 0 or die "SYSTEM ERROR in executing $file: $?";
1216 _extract_info("$fname.out", $inchash, $dl_shared_objects, $incarray);
1218 unlink("$fname.out");
1221 # create a new hashref, applying fixups
1223 my ($inchash, $dl_shared_objects, $inc_array) = @_;
1226 my @newinc = map(quotemeta($_), @$inc_array);
1227 my $inc = join('|', sort { length($b) <=> length($a) } @newinc);
1228 # don't pack lib/c:/ or lib/C:/
1229 $inc = qr/$inc/i if(is_insensitive_fs());
1234 foreach $key (keys(%$inchash)) {
1236 $newkey =~ s"^(?:(?:$inc)/?)""sg if File::Spec->file_name_is_absolute($newkey);
1240 'file' => $inchash->{$key},
1241 'type' => _gettype($inchash->{$key}),
1247 foreach $dl_file (@$dl_shared_objects) {
1249 $key =~ s"^(?:(?:$inc)/?)""s;
1263 my ($fname, $inchash, $dl_shared_objects, $incarray) = @_;
1265 use vars qw(%inchash @dl_shared_objects @incarray);
1266 my $fh = FileHandle->new($fname) or die "Couldn't open $fname";
1267 my $line = do { local $/; <$fh> };
1272 $inchash->{$_} = $inchash{$_} for keys %inchash;
1273 @$dl_shared_objects = @dl_shared_objects;
1274 @$incarray = @incarray;
1279 my $dlext = quotemeta(dl_ext());
1281 return 'autoload' if $name =~ /(?:\.ix|\.al)$/i;
1282 return 'module' if $name =~ /\.p[mh]$/i;
1283 return 'shared' if $name =~ /\.$dlext$/i;
1287 # merge all keys from $rv_sub into the $rv mega-ref
1289 my ($rv_sub, $rv) = @_;
1292 foreach $key (keys(%$rv_sub)) {
1294 if ($rv->{$key} and _not_dup($key, $rv, $rv_sub)) {
1295 warn "Different modules for file '$key' were found.\n"
1296 . " -> Using '" . _abs_path($rv_sub->{$key}{file}) . "'.\n"
1297 . " -> Ignoring '" . _abs_path($rv->{$key}{file}) . "'.\n";
1298 $rv->{$key}{used_by} = [
1300 @{ $rv->{$key}{used_by} },
1301 @{ $rv_sub->{$key}{used_by} })
1303 @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} };
1304 $rv->{$key}{file} = $rv_sub->{$key}{file};
1306 elsif ($rv->{$key}) {
1307 $rv->{$key}{used_by} = [
1309 @{ $rv->{$key}{used_by} },
1310 @{ $rv_sub->{$key}{used_by} })
1312 @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} };
1316 used_by => [ @{ $rv_sub->{$key}{used_by} } ],
1317 file => $rv_sub->{$key}{file},
1318 key => $rv_sub->{$key}{key},
1319 type => $rv_sub->{$key}{type}
1322 @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} };
1328 my ($key, $rv1, $rv2) = @_;
1329 if (File::Spec->case_tolerant()) {
1330 return lc(_abs_path($rv1->{$key}{file})) ne lc(_abs_path($rv2->{$key}{file}));
1333 return _abs_path($rv1->{$key}{file}) ne _abs_path($rv2->{$key}{file});
1340 Cwd::abs_path(File::Basename::dirname($_[0])),
1341 File::Basename::basename($_[0]),
1346 sub _warn_of_missing_module {
1349 return if not $warn;
1350 return if not $module =~ /\.p[ml]$/;
1351 warn "# Could not find source file '$module' in \@INC or \@IncludeLibs. Skipping it.\n"
1357 my $preload = $Preload{$pm} or return();
1358 if ($preload eq 'sub') {
1359 $pm =~ s/\.p[mh]$//i;
1360 $preload = [ _glob_in_inc($pm, 1) ];
1362 elsif (UNIVERSAL::isa($preload, 'CODE')) {
1363 $preload = [ $preload->($pm) ];
1373 L<scandeps.pl> is a bundled utility that writes C<PREREQ_PM> section
1374 for a number of files.
1376 An application of B<Module::ScanDeps> is to generate executables from
1377 scripts that contains prerequisite modules; this module supports two
1378 such projects, L<PAR> and L<App::Packer>. Please see their respective
1379 documentations on CPAN for further information.
1383 Audrey Tang E<lt>cpan@audreyt.orgE<gt>
1385 To a lesser degree: Steffen Mueller E<lt>smueller@cpan.orgE<gt>
1387 Parts of heuristics were deduced from:
1393 B<PerlApp> by ActiveState Tools Corp L<http://www.activestate.com/>
1397 B<Perl2Exe> by IndigoStar, Inc L<http://www.indigostar.com/>
1401 The B<scan_deps_runtime> function is contributed by Edward S. Peschko.
1403 L<http://par.perl.org/> is the official website for this module. You
1404 can write to the mailing list at E<lt>par@perl.orgE<gt>, or send an empty
1405 mail to E<lt>par-subscribe@perl.orgE<gt> to participate in the discussion.
1407 Please submit bug reports to E<lt>bug-Module-ScanDeps@rt.cpan.orgE<gt>.
1411 Copyright 2002-2008 by
1412 Audrey Tang E<lt>cpan@audreyt.orgE<gt>;
1413 2005-2009 by Steffen Mueller E<lt>smueller@cpan.orgE<gt>.
1415 This program is free software; you can redistribute it and/or modify it
1416 under the same terms as Perl itself.
1418 See L<http://www.perl.com/perl/misc/Artistic.html>