You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1083 lines
31 KiB

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