branch Attitude Adjustment packages
[12.09/packages.git] / lang / perl / files / Module / ScanDeps.pm
1 package Module::ScanDeps;
2
3 use 5.004;
4 use strict;
5 use vars qw( $VERSION @EXPORT @EXPORT_OK $CurrentPackage );
6
7 $VERSION   = '0.62';
8 @EXPORT    = qw( scan_deps scan_deps_runtime );
9 @EXPORT_OK = qw( scan_line scan_chunk add_deps scan_deps_runtime );
10
11 use Config;
12 use Exporter;
13 use base 'Exporter';
14 use constant dl_ext  => ".$Config{dlext}";
15 use constant lib_ext => $Config{lib_ext};
16 use constant is_insensitive_fs => (
17     -s $0 
18         and (-s lc($0) || -1) == (-s uc($0) || -1)
19         and (-s lc($0) || -1) == -s $0
20 );
21
22 use Cwd ();
23 use File::Path ();
24 use File::Temp ();
25 use File::Basename ();
26 use FileHandle;
27
28 =head1 NAME
29
30 Module::ScanDeps - Recursively scan Perl code for dependencies
31
32 =head1 VERSION
33
34 This document describes version 0.61 of Module::ScanDeps, released
35 June 30, 2006.
36
37 =head1 SYNOPSIS
38
39 Via the command-line program L<scandeps.pl>:
40
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
45
46 Used in a program;
47
48     use Module::ScanDeps;
49
50     # standard usage
51     my $hash_ref = scan_deps(
52         files   => [ 'a.pl', 'b.pl' ],
53         recurse => 1,
54     );
55
56     # shorthand; assume recurse == 1
57     my $hash_ref = scan_deps( 'a.pl', 'b.pl' );
58
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;
66
67 =head1 DESCRIPTION
68
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:
72
73     {
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     }
79
80 One function, C<scan_deps>, is exported by default.  Three other
81 functions (C<scan_line>, C<scan_chunk>, C<add_deps>) are exported upon
82 request.
83
84 Users of B<App::Packer> may also use this module as the dependency-checking
85 frontend, by tweaking their F<p2e.pl> like below:
86
87     use Module::ScanDeps;
88     ...
89     my $packer = App::Packer->new( frontend => 'Module::ScanDeps' );
90     ...
91
92 Please see L<App::Packer::Frontend> for detailed explanation on
93 the structure returned by C<get_files>.
94
95 =head2 B<scan_deps>
96
97     $rv_ref = scan_deps(
98         files   => \@files,     recurse => $recurse,
99         rv      => \%rv,        skip    => \%skip,
100         compile => $compile,    execute => $execute,
101     );
102     $rv_ref = scan_deps(@files); # shorthand, with recurse => 1
103
104 This function scans each file in C<@files>, registering their
105 dependencies into C<%rv>, and returns a reference to the updated
106 C<%rv>.  The meaning of keys and values are explained above.
107
108 If C<$recurse> is true, C<scan_deps> will call itself recursively,
109 to perform a breadth-first search on text files (as defined by the
110 -T operator) found in C<%rv>.
111
112 If the C<\%skip> is specified, files that exists as its keys are
113 skipped.  This is used internally to avoid infinite recursion.
114
115 If C<$compile> or C<$execute> is true, runs C<files> in either
116 compile-only or normal mode, then inspects their C<%INC> after
117 termination to determine additional runtime dependencies.
118
119 If C<$execute> is an array reference, runs the files contained
120 in it instead of C<@files>.
121
122 =head2 B<scan_deps_runtime>
123
124 Like B<scan_deps>, but skips the static scanning part.
125
126 =head2 B<scan_line>
127
128     @modules = scan_line($line);
129
130 Splits a line into chunks (currently with the semicolon characters), and
131 return the union of C<scan_chunk> calls of them.
132
133 If the line is C<__END__> or C<__DATA__>, a single C<__END__> element is
134 returned to signify the end of the program.
135
136 Similarly, it returns a single C<__POD__> if the line matches C</^=\w/>;
137 the caller is responsible for skipping appropriate number of lines
138 until C<=cut>, before calling C<scan_line> again.
139
140 =head2 B<scan_chunk>
141
142     $module = scan_chunk($chunk);
143     @modules = scan_chunk($chunk);
144
145 Apply various heuristics to C<$chunk> to find and return the module
146 name(s) it contains.  In scalar context, returns only the first module
147 or C<undef>.
148
149 =head2 B<add_deps>
150
151     $rv_ref = add_deps( rv => \%rv, modules => \@modules );
152     $rv_ref = add_deps( @modules ); # shorthand, without rv
153
154 Resolves a list of module names to its actual on-disk location, by
155 finding in C<@INC>; modules that cannot be found are skipped.
156
157 This function populates the C<%rv> hash with module/filename pairs, and
158 returns a reference to it.
159
160 =head1 CAVEATS
161
162 This module intentially ignores the B<BSDPAN> hack on FreeBSD -- the
163 additional directory is removed from C<@INC> altogether.
164
165 The static-scanning heuristic is not likely to be 100% accurate, especially
166 on modules that dynamically load other modules.
167
168 Chunks that span multiple lines are not handled correctly.  For example,
169 this one works:
170
171     use base 'Foo::Bar';
172
173 But this one does not:
174
175     use base
176         'Foo::Bar';
177
178 =cut
179
180 my $SeenTk;
181
182 # Pre-loaded module dependencies {{{
183 my %Preload = (
184     'AnyDBM_File.pm'  => [qw( SDBM_File.pm )],
185     'Authen/SASL.pm'  => 'sub',
186     'Bio/AlignIO.pm'  => 'sub',
187     'Bio/Assembly/IO.pm'  => 'sub',
188     'Bio/Biblio/IO.pm'  => 'sub',
189     'Bio/ClusterIO.pm'  => 'sub',
190     'Bio/CodonUsage/IO.pm'  => 'sub',
191     'Bio/DB/Biblio.pm'  => 'sub',
192     'Bio/DB/Flat.pm'  => 'sub',
193     'Bio/DB/GFF.pm'  => 'sub',
194     'Bio/DB/Taxonomy.pm'  => 'sub',
195     'Bio/Graphics/Glyph.pm'  => 'sub',
196     'Bio/MapIO.pm'  => 'sub',
197     'Bio/Matrix/IO.pm'  => 'sub',
198     'Bio/Matrix/PSM/IO.pm'  => 'sub',
199     'Bio/OntologyIO.pm'  => 'sub',
200     'Bio/PopGen/IO.pm'  => 'sub',
201     'Bio/Restriction/IO.pm'  => 'sub',
202     'Bio/Root/IO.pm'  => 'sub',
203     'Bio/SearchIO.pm'  => 'sub',
204     'Bio/SeqIO.pm'  => 'sub',
205     'Bio/Structure/IO.pm'  => 'sub',
206     'Bio/TreeIO.pm'  => 'sub',
207     'Bio/LiveSeq/IO.pm'  => 'sub',
208     'Bio/Variation/IO.pm'  => 'sub',
209     'Crypt/Random.pm' => sub {
210         _glob_in_inc('Crypt/Random/Provider', 1);
211     },
212     'Crypt/Random/Generator.pm' => sub {
213         _glob_in_inc('Crypt/Random/Provider', 1);
214     },
215     'DBI.pm' => sub {
216         grep !/\bProxy\b/, _glob_in_inc('DBD', 1);
217     },
218     'DBIx/SearchBuilder.pm' => 'sub',
219     'DBIx/ReportBuilder.pm' => 'sub',
220     'Device/ParallelPort.pm' => 'sub',
221     'Device/SerialPort.pm' => [ qw(
222         termios.ph asm/termios.ph sys/termiox.ph sys/termios.ph sys/ttycom.ph
223     ) ],
224     'ExtUtils/MakeMaker.pm' => sub {
225         grep /\bMM_/, _glob_in_inc('ExtUtils', 1);
226     },
227     'File/Basename.pm' => [qw( re.pm )],
228     'File/Spec.pm'     => sub {
229         require File::Spec;
230         map { my $name = $_; $name =~ s!::!/!g; "$name.pm" } @File::Spec::ISA;
231     },
232     'HTTP/Message.pm' => [ qw(
233         URI/URL.pm          URI.pm
234     ) ],
235     'IO.pm' => [ qw(
236         IO/Handle.pm        IO/Seekable.pm      IO/File.pm
237         IO/Pipe.pm          IO/Socket.pm        IO/Dir.pm
238     ) ],
239     'IO/Socket.pm'     => [qw( IO/Socket/UNIX.pm )],
240     'LWP/UserAgent.pm' => [ qw(
241         URI/URL.pm          URI/http.pm         LWP/Protocol/http.pm
242         LWP/Protocol/https.pm
243     ), _glob_in_inc("LWP/Authen", 1) ],
244     'Locale/Maketext/Lexicon.pm'    => 'sub',
245     'Locale/Maketext/GutsLoader.pm' => [qw( Locale/Maketext/Guts.pm )],
246     'Mail/Audit.pm'                => 'sub',
247     'Math/BigInt.pm'                => 'sub',
248     'Math/BigFloat.pm'              => 'sub',
249         'Math/Symbolic.pm'              => 'sub',
250     'Module/Build.pm'               => 'sub',
251     'Module/Pluggable.pm'           => sub {
252         _glob_in_inc('$CurrentPackage/Plugin', 1);
253     },
254     'MIME/Decoder.pm'               => 'sub',
255     'Net/DNS/RR.pm'                 => 'sub',
256     'Net/FTP.pm'                    => 'sub',
257     'Net/SSH/Perl.pm'               => 'sub',
258     'PDF/API2/Resource/Font.pm'     => 'sub',
259     'PDF/API2/Basic/TTF/Font.pm'    => sub {
260         _glob_in_inc('PDF/API2/Basic/TTF', 1);
261     },
262     'PDF/Writer.pm'                 => 'sub',
263     'POE'                           => [ qw(
264         POE/Kernel.pm POE/Session.pm
265     ) ],
266     'POE/Kernel.pm'                    => [
267         map "POE/Resource/$_.pm", qw(
268             Aliases Events Extrefs FileHandles
269             SIDs Sessions Signals Statistics
270         )
271     ],
272     'Parse/AFP.pm'                  => 'sub',
273     'Parse/Binary.pm'               => 'sub',
274     'Regexp/Common.pm'              => 'sub',
275     'SerialJunk.pm' => [ qw(
276         termios.ph asm/termios.ph sys/termiox.ph sys/termios.ph sys/ttycom.ph
277     ) ],
278     'SOAP/Lite.pm'                  => sub {
279         (($] >= 5.008 ? ('utf8.pm') : ()), _glob_in_inc('SOAP/Transport', 1));
280     },
281     'SQL/Parser.pm' => sub {
282         _glob_in_inc('SQL/Dialects', 1);
283     },
284     'SVK/Command.pm' => sub {
285         _glob_in_inc('SVK', 1);
286     },
287     'SVN/Core.pm' => sub {
288         _glob_in_inc('SVN', 1),
289         map "auto/SVN/$_->{name}", _glob_in_inc('auto/SVN'),
290     },
291     'Template.pm'      => 'sub',
292     'Term/ReadLine.pm' => 'sub',
293         'Test/Deep.pm'     => 'sub',
294     'Tk.pm'            => sub {
295         $SeenTk = 1;
296         qw( Tk/FileSelect.pm Encode/Unicode.pm );
297     },
298     'Tk/Balloon.pm'     => [qw( Tk/balArrow.xbm )],
299     'Tk/BrowseEntry.pm' => [qw( Tk/cbxarrow.xbm Tk/arrowdownwin.xbm )],
300     'Tk/ColorEditor.pm' => [qw( Tk/ColorEdit.xpm )],
301     'Tk/DragDrop/Common.pm' => sub {
302         _glob_in_inc('Tk/DragDrop', 1),
303     },
304     'Tk/FBox.pm'        => [qw( Tk/folder.xpm Tk/file.xpm )],
305     'Tk/Getopt.pm'      => [qw( Tk/openfolder.xpm Tk/win.xbm )],
306     'Tk/Toplevel.pm'    => [qw( Tk/Wm.pm )],
307     'URI.pm'            => sub {
308         grep !/.\b[_A-Z]/, _glob_in_inc('URI', 1);
309     },
310     'Win32/EventLog.pm'    => [qw( Win32/IPC.pm )],
311     'Win32/Exe.pm'         => 'sub',
312     'Win32/TieRegistry.pm' => [qw( Win32API/Registry.pm )],
313     'Win32/SystemInfo.pm'  => [qw( Win32/cpuspd.dll )],
314     'XML/Parser.pm'        => sub {
315         _glob_in_inc('XML/Parser/Style', 1),
316         _glob_in_inc('XML/Parser/Encodings', 1),
317     },
318     'XML/Parser/Expat.pm' => sub {
319         ($] >= 5.008) ? ('utf8.pm') : ();
320     },
321     'XML/SAX.pm' => [qw( XML/SAX/ParserDetails.ini ) ],
322     'XMLRPC/Lite.pm' => sub {
323         _glob_in_inc('XMLRPC/Transport', 1),;
324     },
325     'diagnostics.pm' => sub {
326         # shamelessly taken and adapted from diagnostics.pm
327         use Config;
328         my($privlib, $archlib) = @Config{qw(privlibexp archlibexp)};
329         if ($^O eq 'VMS') {
330             require VMS::Filespec;
331             $privlib = VMS::Filespec::unixify($privlib);
332             $archlib = VMS::Filespec::unixify($archlib);
333         }
334
335         for (
336               "pod/perldiag.pod",
337               "Pod/perldiag.pod",
338               "pod/perldiag-$Config{version}.pod",
339               "Pod/perldiag-$Config{version}.pod",
340               "pods/perldiag.pod",
341               "pods/perldiag-$Config{version}.pod",
342         ) {
343             return $_ if _find_in_inc($_);
344         }
345         
346         for (
347               "$archlib/pods/perldiag.pod",
348               "$privlib/pods/perldiag-$Config{version}.pod",
349               "$privlib/pods/perldiag.pod",
350         ) {
351             return $_ if -f $_;
352         }
353
354         return 'pod/perldiag.pod';
355     },
356     'utf8.pm' => [
357         'utf8_heavy.pl', do {
358             my $dir = 'unicore';
359             my @subdirs = qw( To );
360             my @files = map "$dir/lib/$_->{name}", _glob_in_inc("$dir/lib");
361
362             if (@files) {
363                 # 5.8.x
364                 push @files, (map "$dir/$_.pl", qw( Exact Canonical ));
365             }
366             else {
367                 # 5.6.x
368                 $dir = 'unicode';
369                 @files = map "$dir/Is/$_->{name}", _glob_in_inc("$dir/Is")
370                   or return;
371                 push @subdirs, 'In';
372             }
373
374             foreach my $subdir (@subdirs) {
375                 foreach (_glob_in_inc("$dir/$subdir")) {
376                     push @files, "$dir/$subdir/$_->{name}";
377                 }
378             }
379             @files;
380         }
381     ],
382     'charnames.pm' => [
383         _find_in_inc('unicore/Name.pl') ? 'unicore/Name.pl' : 'unicode/Name.pl'
384     ],
385 );
386
387 # }}}
388
389 my $Keys = 'files|keys|recurse|rv|skip|first|execute|compile';
390 sub scan_deps {
391     my %args = (
392         rv => {},
393         (@_ and $_[0] =~ /^(?:$Keys)$/o) ? @_ : (files => [@_], recurse => 1)
394     );
395
396     scan_deps_static(\%args);
397
398     if ($args{execute} or $args{compile}) {
399         scan_deps_runtime(
400             rv      => $args{rv},
401             files   => $args{files},
402             execute => $args{execute},
403             compile => $args{compile},
404             skip    => $args{skip}
405         );
406     }
407
408     return ($args{rv});
409 }
410
411 sub scan_deps_static {
412     my ($args) = @_;
413     my ($files, $keys, $recurse, $rv, $skip, $first, $execute, $compile) =
414       @$args{qw( files keys recurse rv skip first execute compile )};
415
416     $rv   ||= {};
417     $skip ||= {};
418
419     foreach my $file (@{$files}) {
420         my $key = shift @{$keys};
421         next if $skip->{$file}++;
422         next if is_insensitive_fs()
423           and $file ne lc($file) and $skip->{lc($file)}++;
424
425         local *FH;
426         open FH, $file or die "Cannot open $file: $!";
427
428         $SeenTk = 0;
429
430         # Line-by-line scanning
431         LINE:
432         while (<FH>) {
433             chomp(my $line = $_);
434             foreach my $pm (scan_line($line)) {
435                 last LINE if $pm eq '__END__';
436
437                 if ($pm eq '__POD__') {
438                     while (<FH>) { last if (/^=cut/) }
439                     next LINE;
440                 }
441
442                 $pm = 'CGI/Apache.pm' if /^Apache(?:\.pm)$/;
443
444                 add_deps(
445                     used_by => $key,
446                     rv      => $rv,
447                     modules => [$pm],
448                     skip    => $skip
449                 );
450
451                 my $preload = $Preload{$pm} or next;
452                 if ($preload eq 'sub') {
453                     $pm =~ s/\.p[mh]$//i;
454                     $preload = [ _glob_in_inc($pm, 1) ];
455                 }
456                 elsif (UNIVERSAL::isa($preload, 'CODE')) {
457                     $preload = [ $preload->($pm) ];
458                 }
459
460                 add_deps(
461                     used_by => $key,
462                     rv      => $rv,
463                     modules => $preload,
464                     skip    => $skip
465                 );
466             }
467         }
468         close FH;
469
470         # }}}
471     }
472
473     # Top-level recursion handling {{{
474     while ($recurse) {
475         my $count = keys %$rv;
476         my @files = sort grep -T $_->{file}, values %$rv;
477         scan_deps_static({
478             files   => [ map $_->{file}, @files ],
479             keys    => [ map $_->{key},  @files ],
480             rv      => $rv,
481             skip    => $skip,
482             recurse => 0,
483         }) or ($args->{_deep} and return);
484         last if $count == keys %$rv;
485     }
486
487     # }}}
488
489     return $rv;
490 }
491
492 sub scan_deps_runtime {
493     my %args = (
494         perl => $^X,
495         rv   => {},
496         (@_ and $_[0] =~ /^(?:$Keys)$/o) ? @_ : (files => [@_], recurse => 1)
497     );
498     my ($files, $rv, $execute, $compile, $skip, $perl) =
499       @args{qw( files rv execute compile skip perl )};
500
501     $files = (ref($files)) ? $files : [$files];
502
503     my ($inchash, $incarray, $dl_shared_objects) = ({}, [], []);
504     if ($compile) {
505         my $file;
506
507         foreach $file (@$files) {
508             ($inchash, $dl_shared_objects, $incarray) = ({}, [], []);
509             _compile($perl, $file, $inchash, $dl_shared_objects, $incarray);
510
511             my $rv_sub = _make_rv($inchash, $dl_shared_objects, $incarray);
512             _merge_rv($rv_sub, $rv);
513         }
514     }
515     elsif ($execute) {
516         my $excarray = (ref($execute)) ? $execute : [@$files];
517         my $exc;
518         my $first_flag = 1;
519         foreach $exc (@$excarray) {
520             ($inchash, $dl_shared_objects, $incarray) = ({}, [], []);
521             _execute(
522                 $perl, $exc, $inchash, $dl_shared_objects, $incarray,
523                 $first_flag
524             );
525             $first_flag = 0;
526         }
527
528         my $rv_sub = _make_rv($inchash, $dl_shared_objects, $incarray);
529         _merge_rv($rv_sub, $rv);
530     }
531
532     return ($rv);
533 }
534
535 sub scan_line {
536     my $line = shift;
537     my %found;
538
539     return '__END__' if $line =~ /^__(?:END|DATA)__$/;
540     return '__POD__' if $line =~ /^=\w/;
541
542     $line =~ s/\s*#.*$//;
543     $line =~ s/[\\\/]+/\//g;
544
545     foreach (split(/;/, $line)) {
546         if (/^\s*package\s+(\w+)/) {
547             $CurrentPackage = $1;
548             $CurrentPackage =~ s{::}{/}g;
549             return;
550         }
551         return if /^\s*(use|require)\s+[\d\._]+/;
552         if (my ($autouse) = /^\s*use\s+autouse\s+(["'].*?["']|\w+)/)    
553         {
554             $autouse =~ s/["']//g;
555             $autouse =~ s{::}{/}g;
556             return ("autouse.pm", "$autouse.pm");
557         }
558
559         if (my ($libs) = /\b(?:use\s+lib\s+|(?:unshift|push)\W+\@INC\W+)(.+)/)
560         {
561             my $archname =
562               defined($Config{archname}) ? $Config{archname} : '';
563             my $ver = defined($Config{version}) ? $Config{version} : '';
564             foreach (grep(/\w/, split(/["';() ]/, $libs))) {
565                 unshift(@INC, "$_/$ver")           if -d "$_/$ver";
566                 unshift(@INC, "$_/$archname")      if -d "$_/$archname";
567                 unshift(@INC, "$_/$ver/$archname") if -d "$_/$ver/$archname";
568             }
569             next;
570         }
571
572         $found{$_}++ for scan_chunk($_);
573     }
574
575     return sort keys %found;
576 }
577
578 sub scan_chunk {
579     my $chunk = shift;
580
581     # Module name extraction heuristics {{{
582     my $module = eval {
583         $_ = $chunk;
584
585         return [ 'base.pm',
586             map { s{::}{/}g; "$_.pm" }
587               grep { length and !/^q[qw]?$/ } split(/[^\w:]+/, $1) ]
588           if /^\s* use \s+ base \s+ (.*)/sx;
589
590         return [ 'Class/Autouse.pm',
591             map { s{::}{/}g; "$_.pm" }
592               grep { length and !/^:|^q[qw]?$/ } split(/[^\w:]+/, $1) ]
593           if /^\s* use \s+ Class::Autouse \s+ (.*)/sx
594               or /^\s* Class::Autouse \s* -> \s* autouse \s* (.*)/sx;
595
596         return [ 'POE.pm',
597             map { s{::}{/}g; "POE/$_.pm" }
598               grep { length and !/^q[qw]?$/ } split(/[^\w:]+/, $1) ]
599           if /^\s* use \s+ POE \s+ (.*)/sx;
600
601         return [ 'encoding.pm',
602             map { _find_encoding($_) }
603               grep { length and !/^q[qw]?$/ } split(/[^\w:-]+/, $1) ]
604           if /^\s* use \s+ encoding \s+ (.*)/sx;
605
606         return $1 if /(?:^|\s)(?:use|no|require)\s+([\w:\.\-\\\/\"\']+)/;
607         return $1
608           if /(?:^|\s)(?:use|no|require)\s+\(\s*([\w:\.\-\\\/\"\']+)\s*\)/;
609
610         if (   s/(?:^|\s)eval\s+\"([^\"]+)\"/$1/
611             or s/(?:^|\s)eval\s*\(\s*\"([^\"]+)\"\s*\)/$1/)
612         {
613             return $1 if /(?:^|\s)(?:use|no|require)\s+([\w:\.\-\\\/\"\']*)/;
614         }
615
616         return "File/Glob.pm" if /<[^>]*[^\$\w>][^>]*>/;
617         return "DBD/$1.pm"    if /\b[Dd][Bb][Ii]:(\w+):/;
618         if (/(?:(:encoding)|\b(?:en|de)code)\(\s*['"]?([-\w]+)/) {
619             my $mod = _find_encoding($2);
620             return [ 'PerlIO.pm', $mod ] if $1 and $mod;
621             return $mod if $mod;
622         }
623         return $1 if /(?:^|\s)(?:do|require)\s+[^"]*"(.*?)"/;
624         return $1 if /(?:^|\s)(?:do|require)\s+[^']*'(.*?)'/;
625         return $1 if /[^\$]\b([\w:]+)->\w/ and $1 ne 'Tk';
626         return $1 if /\b(\w[\w:]*)::\w+\(/;
627
628         if ($SeenTk) {
629             my @modules;
630             while (/->\s*([A-Z]\w+)/g) {
631                 push @modules, "Tk/$1.pm";
632             }
633             while (/->\s*Scrolled\W+([A-Z]\w+)/g) {
634                 push @modules, "Tk/$1.pm";
635                 push @modules, "Tk/Scrollbar.pm";
636             }
637             return \@modules;
638         }
639         return;
640     };
641
642     # }}}
643
644     return unless defined($module);
645     return wantarray ? @$module : $module->[0] if ref($module);
646
647     $module =~ s/^['"]//;
648     return unless $module =~ /^\w/;
649
650     $module =~ s/\W+$//;
651     $module =~ s/::/\//g;
652     return if $module =~ /^(?:[\d\._]+|'.*[^']|".*[^"])$/;
653
654     $module .= ".pm" unless $module =~ /\./;
655     return $module;
656 }
657
658 sub _find_encoding {
659     return unless $] >= 5.008 and eval { require Encode; %Encode::ExtModule };
660
661     my $mod = $Encode::ExtModule{ Encode::find_encoding($_[0])->name }
662       or return;
663     $mod =~ s{::}{/}g;
664     return "$mod.pm";
665 }
666
667 sub _add_info {
668     my ($rv, $module, $file, $used_by, $type) = @_;
669     return unless defined($module) and defined($file);
670
671     $rv->{$module} ||= {
672         file => $file,
673         key  => $module,
674         type => $type,
675     };
676
677     push @{ $rv->{$module}{used_by} }, $used_by
678       if defined($used_by)
679       and $used_by ne $module
680       and !grep { $_ eq $used_by } @{ $rv->{$module}{used_by} };
681 }
682
683 sub add_deps {
684     my %args =
685       ((@_ and $_[0] =~ /^(?:modules|rv|used_by)$/)
686         ? @_
687         : (rv => (ref($_[0]) ? shift(@_) : undef), modules => [@_]));
688
689     my $rv   = $args{rv}   || {};
690     my $skip = $args{skip} || {};
691     my $used_by = $args{used_by};
692
693     foreach my $module (@{ $args{modules} }) {
694         if (exists $rv->{$module}) {
695             _add_info($rv, undef, undef, $used_by, undef);
696             next;
697         }
698
699         my $file = _find_in_inc($module) or next;
700         next if $skip->{$file};
701         next if is_insensitive_fs() and $skip->{lc($file)};
702
703         my $type = 'module';
704         $type = 'data' unless $file =~ /\.p[mh]$/i;
705         _add_info($rv, $module, $file, $used_by, $type);
706
707         if ($module =~ /(.*?([^\/]*))\.p[mh]$/i) {
708             my ($path, $basename) = ($1, $2);
709
710             foreach (_glob_in_inc("auto/$path")) {
711                 next if $skip->{$_->{file}};
712                 next if is_insensitive_fs() and $skip->{lc($_->{file})};
713                 next if $_->{file} =~ m{\bauto/$path/.*/};  # weed out subdirs
714                 next if $_->{name} =~ m/(?:^|\/)\.(?:exists|packlist)$/;
715                 my $ext = lc($1) if $_->{name} =~ /(\.[^.]+)$/;
716                 next if $ext eq lc(lib_ext());
717                 my $type = 'shared' if $ext eq lc(dl_ext());
718                 $type = 'autoload' if $ext eq '.ix' or $ext eq '.al';
719                 $type ||= 'data';
720
721                 _add_info($rv, "auto/$path/$_->{name}", $_->{file}, $module,
722                     $type);
723             }
724         }
725     }
726
727     return $rv;
728 }
729
730 sub _find_in_inc {
731     my $file = shift;
732
733     # absolute file names
734     return $file if -f $file;
735
736     foreach my $dir (grep !/\bBSDPAN\b/, @INC) {
737         return "$dir/$file" if -f "$dir/$file";
738     }
739     return;
740 }
741
742 sub _glob_in_inc {
743     my $subdir  = shift;
744     my $pm_only = shift;
745     my @files;
746
747     require File::Find;
748
749     $subdir =~ s/\$CurrentPackage/$CurrentPackage/;
750
751     foreach my $dir (map "$_/$subdir", grep !/\bBSDPAN\b/, @INC) {
752         next unless -d $dir;
753         File::Find::find(
754             sub {
755                 my $name = $File::Find::name;
756                 $name =~ s!^\Q$dir\E/!!;
757                 return if $pm_only and lc($name) !~ /\.p[mh]$/i;
758                 push @files, $pm_only
759                   ? "$subdir/$name"
760                   : {             file => $File::Find::name,
761                     name => $name,
762                   }
763                   if -f;
764             },
765             $dir
766         );
767     }
768
769     return @files;
770 }
771
772 # App::Packer compatibility functions
773
774 sub new {
775     my ($class, $self) = @_;
776     return bless($self ||= {}, $class);
777 }
778
779 sub set_file {
780     my $self = shift;
781     foreach my $script (@_) {
782         my $basename = $script;
783         $basename =~ s/.*\///;
784         $self->{main} = {
785             key  => $basename,
786             file => $script,
787         };
788     }
789 }
790
791 sub set_options {
792     my $self = shift;
793     my %args = @_;
794     foreach my $module (@{ $args{add_modules} }) {
795         $module =~ s/::/\//g;
796         $module .= '.pm' unless $module =~ /\.p[mh]$/i;
797         my $file = _find_in_inc($module) or next;
798         $self->{files}{$module} = $file;
799     }
800 }
801
802 sub calculate_info {
803     my $self = shift;
804     my $rv   = scan_deps(
805         keys  => [ $self->{main}{key}, sort keys %{ $self->{files} }, ],
806         files => [ $self->{main}{file},
807             map { $self->{files}{$_} } sort keys %{ $self->{files} },
808         ],
809         recurse => 1,
810     );
811
812     my $info = {
813         main => {  file     => $self->{main}{file},
814             store_as => $self->{main}{key},
815         },
816     };
817
818     my %cache = ($self->{main}{key} => $info->{main});
819     foreach my $key (sort keys %{ $self->{files} }) {
820         my $file = $self->{files}{$key};
821
822         $cache{$key} = $info->{modules}{$key} = {
823             file     => $file,
824             store_as => $key,
825             used_by  => [ $self->{main}{key} ],
826         };
827     }
828
829     foreach my $key (sort keys %{$rv}) {
830         my $val = $rv->{$key};
831         if ($cache{ $val->{key} }) {
832             push @{ $info->{ $val->{type} }->{ $val->{key} }->{used_by} },
833               @{ $val->{used_by} };
834         }
835         else {
836             $cache{ $val->{key} } = $info->{ $val->{type} }->{ $val->{key} } =
837               {        file     => $val->{file},
838                 store_as => $val->{key},
839                 used_by  => $val->{used_by},
840               };
841         }
842     }
843
844     $self->{info} = { main => $info->{main} };
845
846     foreach my $type (sort keys %{$info}) {
847         next if $type eq 'main';
848
849         my @val;
850         if (UNIVERSAL::isa($info->{$type}, 'HASH')) {
851             foreach my $val (sort values %{ $info->{$type} }) {
852                 @{ $val->{used_by} } = map $cache{$_} || "!!$_!!",
853                   @{ $val->{used_by} };
854                 push @val, $val;
855             }
856         }
857
858         $type = 'modules' if $type eq 'module';
859         $self->{info}{$type} = \@val;
860     }
861 }
862
863 sub get_files {
864     my $self = shift;
865     return $self->{info};
866 }
867
868 # scan_deps_runtime utility functions
869
870 sub _compile {
871     my ($perl, $file, $inchash, $dl_shared_objects, $incarray) = @_;
872
873     my ($fhout, $fname) = File::Temp::tempfile("XXXXXX");
874     my $fhin  = FileHandle->new($file) or die "Couldn't open $file\n";
875
876     my $line = do { local $/; <$fhin> };
877     $line =~ s/use Module::ScanDeps::DataFeed.*?\n//sg;
878     $line =~ s/^(.*?)((?:[\r\n]+__(?:DATA|END)__[\r\n]+)|$)/
879 use Module::ScanDeps::DataFeed '$fname.out';
880 sub {
881 $1
882 }
883 $2/s;
884     $fhout->print($line);
885     $fhout->close;
886     $fhin->close;
887
888     system($perl, $fname);
889
890     _extract_info("$fname.out", $inchash, $dl_shared_objects, $incarray);
891     unlink("$fname");
892     unlink("$fname.out");
893 }
894
895 sub _execute {
896     my ($perl, $file, $inchash, $dl_shared_objects, $incarray, $firstflag) = @_;
897
898     $DB::single = $DB::single = 1;
899     my ($fhout, $fname) = File::Temp::tempfile("XXXXXX");
900     $fname = _abs_path($fname);
901     my $fhin  = FileHandle->new($file) or die "Couldn't open $file";
902
903     my $line = do { local $/; <$fhin> };
904     $line =~ s/use Module::ScanDeps::DataFeed.*?\n//sg;
905     $line = "use Module::ScanDeps::DataFeed '$fname.out';\n" . $line;
906     $fhout->print($line);
907     $fhout->close;
908     $fhin->close;
909
910     File::Path::rmtree( ['_Inline'], 0, 1); # XXX hack
911     system($perl, $fname) == 0 or die "SYSTEM ERROR in executing $file: $?";
912
913     _extract_info("$fname.out", $inchash, $dl_shared_objects, $incarray);
914     unlink("$fname");
915     unlink("$fname.out");
916 }
917
918 sub _make_rv {
919     my ($inchash, $dl_shared_objects, $inc_array) = @_;
920
921     my $rv = {};
922     my @newinc = map(quotemeta($_), @$inc_array);
923     my $inc = join('|', sort { length($b) <=> length($a) } @newinc);
924
925     require File::Spec;
926
927     my $key;
928     foreach $key (keys(%$inchash)) {
929         my $newkey = $key;
930         $newkey =~ s"^(?:(?:$inc)/?)""sg if File::Spec->file_name_is_absolute($newkey);
931
932         $rv->{$newkey} = {
933             'used_by' => [],
934             'file'    => $inchash->{$key},
935             'type'    => _gettype($inchash->{$key}),
936             'key'     => $key
937         };
938     }
939
940     my $dl_file;
941     foreach $dl_file (@$dl_shared_objects) {
942         my $key = $dl_file;
943         $key =~ s"^(?:(?:$inc)/?)""s;
944
945         $rv->{$key} = {
946             'used_by' => [],
947             'file'    => $dl_file,
948             'type'    => 'shared',
949             'key'     => $key
950         };
951     }
952
953     return $rv;
954 }
955
956 sub _extract_info {
957     my ($fname, $inchash, $dl_shared_objects, $incarray) = @_;
958
959     use vars qw(%inchash @dl_shared_objects @incarray);
960     my $fh = FileHandle->new($fname) or die "Couldn't open $fname";
961     my $line = do { local $/; <$fh> };
962     $fh->close;
963
964     eval $line;
965
966     $inchash->{$_} = $inchash{$_} for keys %inchash;
967     @$dl_shared_objects = @dl_shared_objects;
968     @$incarray          = @incarray;
969 }
970
971 sub _gettype {
972     my $name = shift;
973     my $dlext = quotemeta(dl_ext());
974
975     return 'autoload' if $name =~ /(?:\.ix|\.al|\.bs)$/i;
976     return 'module'   if $name =~ /\.p[mh]$/i;
977     return 'shared'   if $name =~ /\.$dlext$/i;
978     return 'data';
979 }
980
981 sub _merge_rv {
982     my ($rv_sub, $rv) = @_;
983
984     my $key;
985     foreach $key (keys(%$rv_sub)) {
986         my %mark;
987         if ($rv->{$key} and _not_dup($key, $rv, $rv_sub)) {
988             warn "Different modules for file '$key' were found.\n"
989                 . " -> Using '" . _abs_path($rv_sub->{$key}{file}) . "'.\n"
990                 . " -> Ignoring '" . _abs_path($rv->{$key}{file}) . "'.\n";
991             $rv->{$key}{used_by} = [
992                 grep (!$mark{$_}++,
993                     @{ $rv->{$key}{used_by} },
994                     @{ $rv_sub->{$key}{used_by} })
995             ];
996             @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} };
997             $rv->{$key}{file} = $rv_sub->{$key}{file};
998         }
999         elsif ($rv->{$key}) {
1000             $rv->{$key}{used_by} = [
1001                 grep (!$mark{$_}++,
1002                     @{ $rv->{$key}{used_by} },
1003                     @{ $rv_sub->{$key}{used_by} })
1004             ];
1005             @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} };
1006         }
1007         else {
1008             $rv->{$key} = {
1009                 used_by => [ @{ $rv_sub->{$key}{used_by} } ],
1010                 file    => $rv_sub->{$key}{file},
1011                 key     => $rv_sub->{$key}{key},
1012                 type    => $rv_sub->{$key}{type}
1013             };
1014
1015             @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} };
1016         }
1017     }
1018 }
1019
1020 sub _not_dup {
1021     my ($key, $rv1, $rv2) = @_;
1022     (_abs_path($rv1->{$key}{file}) ne _abs_path($rv2->{$key}{file}));
1023 }
1024
1025 sub _abs_path {
1026     return join(
1027         '/',
1028         Cwd::abs_path(File::Basename::dirname($_[0])),
1029         File::Basename::basename($_[0]),
1030     );
1031 }
1032
1033 1;
1034
1035 __END__
1036
1037 =head1 SEE ALSO
1038
1039 L<scandeps.pl> is a bundled utility that writes C<PREREQ_PM> section
1040 for a number of files.
1041
1042 An application of B<Module::ScanDeps> is to generate executables from
1043 scripts that contains prerequisite modules; this module supports two
1044 such projects, L<PAR> and L<App::Packer>.  Please see their respective
1045 documentations on CPAN for further information.
1046
1047 =head1 AUTHORS
1048
1049 Audrey Tang E<lt>autrijus@autrijus.orgE<gt>
1050
1051 Parts of heuristics were deduced from:
1052
1053 =over 4
1054
1055 =item *
1056
1057 B<PerlApp> by ActiveState Tools Corp L<http://www.activestate.com/>
1058
1059 =item *
1060
1061 B<Perl2Exe> by IndigoStar, Inc L<http://www.indigostar.com/>
1062
1063 =back
1064
1065 The B<scan_deps_runtime> function is contributed by Edward S. Peschko.
1066
1067 L<http://par.perl.org/> is the official website for this module.  You
1068 can write to the mailing list at E<lt>par@perl.orgE<gt>, or send an empty
1069 mail to E<lt>par-subscribe@perl.orgE<gt> to participate in the discussion.
1070
1071 Please submit bug reports to E<lt>bug-Module-ScanDeps@rt.cpan.orgE<gt>.
1072
1073 =head1 COPYRIGHT
1074
1075 Copyright 2002, 2003, 2004, 2005, 2006 by
1076 Audrey Tang E<lt>autrijus@autrijus.orgE<gt>.
1077
1078 This program is free software; you can redistribute it and/or modify it
1079 under the same terms as Perl itself.
1080
1081 See L<http://www.perl.com/perl/misc/Artistic.html>
1082
1083 =cut