Module-ScanDeps-1.37/0000755000175000017500000000000014715360221014550 5ustar roderichroderichModule-ScanDeps-1.37/script/0000755000175000017500000000000014715360221016054 5ustar roderichroderichModule-ScanDeps-1.37/script/scandeps.pl0000644000175000017500000001476714335652766020250 0ustar roderichroderich#!/usr/bin/perl $VERSION = '0.76'; use strict; use Config; use Getopt::Long qw(:config bundling no_ignore_case); use Module::ScanDeps; use ExtUtils::MakeMaker; use subs qw( _name _modtree ); my $usage = "Usage: $0 [ -B ] [ -V ] [ -T ] [ -x [ --xargs STRING ] | -c ] [ -R ] [-C FILE ] [ -e STRING | FILE ... ]\n"; my %opts; GetOptions(\%opts, "B|bundle", "C|cachedeps=s", "c|compile", "e|eval=s", "xargs=s", "R|no-recurse", "T|modtree", "V|verbose", "x|execute", ) or die $usage; my (%map, %skip); my $core = $opts{B}; my $verbose = $opts{V}; my $eval = $opts{e}; my $recurse = $opts{R} ? 0 : 1; my $modtree = {} unless $opts{T}; # i.e. disable it unless explicitly requested if ($eval) { require File::Temp; my ($fh, $filename) = File::Temp::tempfile( UNLINK => 1 ); print $fh $eval, "\n" or die $!; close $fh; push @ARGV, $filename; } if ($opts{x} && defined $opts{xargs}) { require Text::ParseWords; $opts{x} = [ Text::ParseWords::shellwords($opts{xargs}) ]; } die $usage unless @ARGV; my @files = @ARGV; while (<>) { next unless /^package\s+([\w:]+)/; $skip{$1}++; } my $map = scan_deps( files => \@files, recurse => $recurse, $opts{x} ? ( execute => $opts{x} ) : $opts{c} ? ( compile => 1 ) : (), $opts{V} ? ( warn_missing => 1 ) : (), $opts{C} ? ( cache_file => $opts{C}) : (), ); my $len = 0; my @todo; my (%seen, %dist, %core, %bin); foreach my $key (sort keys %$map) { my $mod = $map->{$key}; my $name = $mod->{name} = _name($key); print "# $key [$mod->{type}]\n" if $verbose; if ($mod->{type} eq 'shared') { $key =~ s!auto/!!; $key =~ s!/[^/]+$!!; $key =~ s!/!::!; $bin{$key}++; } next unless $mod->{type} eq 'module'; next if $skip{$name}; my $privPath = "$Config{privlibexp}/$key"; my $archPath = "$Config{archlibexp}/$key"; $privPath =~ s|\\|\/|og; $archPath =~ s|\\|\/|og; if ($mod->{file} eq $privPath or $mod->{file} eq $archPath) { next unless $core; $core{$name}++; } elsif (my $dist = _modtree->{$name}) { $seen{$name} = $dist{$dist->package}++; } $len = length($name) if $len < length($name); $mod->{used_by} ||= []; push @todo, $mod; } $len += 2; print "#\n# Legend: [C]ore [X]ternal [S]ubmodule [?]NotOnCPAN\n" if $verbose; foreach my $mod (sort { $a->{name} cmp $b->{name} } @todo ) { my $version = MM->parse_version($mod->{file}); if (!$verbose) { printf "%-${len}s => '$version',", "'$mod->{name}'" if $version; } else { printf "%-${len}s => '0', # ", "'$mod->{name}'"; my @used_by = sort map(_name($_), @{$mod->{used_by}}); print $seen{$mod->{name}} ? 'S' : ' '; print $bin{$mod->{name}} ? 'X' : ' '; print $core{$mod->{name}} ? 'C' : ' '; print _modtree && !_modtree->{$mod->{name}} ? '?' : ' '; print " # "; print "@used_by" if @used_by; } print "\n"; } warn "No modules found!\n" unless @todo; sub _name { my $str = shift; $str =~ s!/!::!g; $str =~ s!.pm$!!i; $str =~ s!^auto::(.+)::.*!$1!; return $str; } sub _modtree { $modtree ||= eval { require CPANPLUS::Backend; CPANPLUS::Backend->new->module_tree; } || {}; } 1; __END__ =head1 NAME scandeps.pl - Scan file prerequisites =head1 SYNOPSIS % scandeps.pl *.pm # Print PREREQ_PM section for *.pm % scandeps.pl -e 'STRING' # Scan an one-liner % scandeps.pl -B *.pm # Include core modules % scandeps.pl -V *.pm # Show autoload/shared/data files % scandeps.pl -R *.pm # Don't recurse % scandeps.pl -C CACHEFILE # use CACHEFILE to cache dependencies =head1 DESCRIPTION F is a simple-minded utility that prints out the C section needed by modules. If the option C<-T> is specified and you have B installed, modules that are part of an earlier module's distribution with be denoted with C; modules without a distribution name on CPAN are marked with C. Also, if the C<-B> option is specified, module belongs to a perl distribution on CPAN (and thus uninstallable by C or C) are marked with C. Finally, modules that has loadable shared object files (usually needing a compiler to install) are marked with C; with the C<-V> flag, those files (and all other files found) will be listed before the main output. Additionally, all module files that the scanned code depends on but were not found (and thus not scanned recursively) are listed. These may include genuinely missing modules or false positives. That means, modules your code does not depend on (on this particular platform) but that were picked up by the heuristic anyway. =head1 OPTIONS =over 4 =item B<-e>, B<--eval>=I Scan I as a string containing perl code. =item B<-c>, B<--compile> Compiles the code and inspects its C<%INC>, in addition to static scanning. =item B<-x>, B<--execute> Executes the code and inspects its C<%INC>, in addition to static scanning. You may use B<--xargs> to specify C<@ARGV> when executing the code. =item B<--xargs>=I If B<-x> is given, splits the C using the function C from L and passes the result as C<@ARGV> when executing the code. =item B<-B>, B<--bundle> Include core modules in the output and the recursive search list. =item B<-R>, B<--no-recurse> Only show dependencies found in the files listed and do not recurse. =item B<-V>, B<--verbose> Verbose mode: Output all files found during the process; show dependencies between modules and availability. Additionally, warns of any missing dependencies. If you find missing dependencies that aren't really dependencies, you have probably found false positives. =item B<-C>, B<--cachedeps>=I Use CACHEFILE to speed up the scanning process by caching dependencies. Creates CACHEFILE if it does not exist yet. =item B<-T>, B<--modtree> Retrieves module information from CPAN if you have B installed. =back =head1 SEE ALSO L, L, L =head1 ACKNOWLEDGMENTS Simon Cozens, for suggesting this script to be written. =head1 AUTHORS Audrey Tang Eautrijus@autrijus.orgE =head1 COPYRIGHT Copyright 2003, 2004, 2005, 2006 by Audrey Tang Eautrijus@autrijus.orgE. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L =cut Module-ScanDeps-1.37/META.yml0000664000175000017500000000177114715360221016031 0ustar roderichroderich--- abstract: 'Recursively scan Perl code for dependencies' author: - 'Audrey Tang ' build_requires: ExtUtils::MakeMaker: '0' IPC::Run3: '0.048' Test::More: '0' Test::Requires: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Module-ScanDeps no_index: directory: - t - inc package: - Module::ScanDeps::Cache - Module::ScanDeps::DataFeed requires: File::Spec: '0' File::Temp: '0' Getopt::Long: '0' List::Util: '1.33' Module::Metadata: '0' Text::ParseWords: '0' perl: '5.008009' version: '0' resources: MailingList: mailto:par@perl.org bugtracker: https://github.com/rschupp/Module-ScanDeps/issues repository: git://github.com/rschupp/Module-ScanDeps.git version: '1.37' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Module-ScanDeps-1.37/Makefile.PL0000644000175000017500000000517414521704026016531 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use ExtUtils::MakeMaker; WriteMakefile1( NAME => 'Module::ScanDeps', VERSION_FROM => 'lib/Module/ScanDeps.pm', ABSTRACT_FROM => 'lib/Module/ScanDeps.pm', LICENSE => 'perl_5', AUTHOR => [ 'Audrey Tang ' ], MIN_PERL_VERSION => '5.008009', PREREQ_PM => { 'File::Temp' => 0, 'File::Spec' => 0, 'Getopt::Long' => 0, 'List::Util' => '1.33', 'Module::Metadata' => 0, 'Text::ParseWords' => 0, 'version' => 0, }, TEST_REQUIRES => { 'Test::More' => 0, 'Test::Requires' => 0, 'IPC::Run3' => '0.048', }, EXE_FILES => [ 'script/scandeps.pl' ], META_MERGE => { "meta-spec" => { version => 2 }, resources => { repository => { type => 'git', url => 'git://github.com/rschupp/Module-ScanDeps.git', web => 'https://github.com/rschupp/Module-ScanDeps', }, MailingList => 'mailto:par@perl.org', bugtracker => { web => 'https://github.com/rschupp/Module-ScanDeps/issues' }, }, no_index => { package => [qw( Module::ScanDeps::Cache Module::ScanDeps::DataFeed )], }, }, ); sub WriteMakefile1 { #Compatibility code for old versions of EU::MM. Written by Alexandr Ciornii, version 0.23. Added by eumm-upgrade. my %params=@_; my $eumm_version=$ExtUtils::MakeMaker::VERSION; $eumm_version=eval $eumm_version; die "EXTRA_META is deprecated" if exists $params{EXTRA_META}; die "License not specified" if not exists $params{LICENSE}; if ($params{AUTHOR} and ref($params{AUTHOR}) eq 'ARRAY' and $eumm_version < 6.5705) { $params{META_ADD}->{author}=$params{AUTHOR}; $params{AUTHOR}=join(', ',@{$params{AUTHOR}}); } if ($params{TEST_REQUIRES} and $eumm_version < 6.64) { $params{BUILD_REQUIRES}={ %{$params{BUILD_REQUIRES} || {}} , %{$params{TEST_REQUIRES}} }; delete $params{TEST_REQUIRES}; } if ($params{BUILD_REQUIRES} and $eumm_version < 6.5503) { #EUMM 6.5502 has problems with BUILD_REQUIRES $params{PREREQ_PM}={ %{$params{PREREQ_PM} || {}} , %{$params{BUILD_REQUIRES}} }; delete $params{BUILD_REQUIRES}; } delete $params{CONFIGURE_REQUIRES} if $eumm_version < 6.52; delete $params{MIN_PERL_VERSION} if $eumm_version < 6.48; delete $params{META_MERGE} if $eumm_version < 6.46; delete $params{META_ADD} if $eumm_version < 6.46; delete $params{LICENSE} if $eumm_version < 6.31; WriteMakefile(%params); } Module-ScanDeps-1.37/META.json0000664000175000017500000000341714715360221016200 0ustar roderichroderich{ "abstract" : "Recursively scan Perl code for dependencies", "author" : [ "Audrey Tang " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Module-ScanDeps", "no_index" : { "directory" : [ "t", "inc" ], "package" : [ "Module::ScanDeps::Cache", "Module::ScanDeps::DataFeed" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "File::Spec" : "0", "File::Temp" : "0", "Getopt::Long" : "0", "List::Util" : "1.33", "Module::Metadata" : "0", "Text::ParseWords" : "0", "perl" : "5.008009", "version" : "0" } }, "test" : { "requires" : { "IPC::Run3" : "0.048", "Test::More" : "0", "Test::Requires" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/rschupp/Module-ScanDeps/issues" }, "repository" : { "type" : "git", "url" : "git://github.com/rschupp/Module-ScanDeps.git", "web" : "https://github.com/rschupp/Module-ScanDeps" }, "x_MailingList" : "mailto:par@perl.org" }, "version" : "1.37", "x_serialization_backend" : "JSON::PP version 4.16" } Module-ScanDeps-1.37/README0000644000175000017500000000234014713435562015440 0ustar roderichroderichThis is the README file for Module::ScanDeps, a module to recursively scan Perl programs for dependencies. An application of Module::ScanDeps is to generate executables from scripts that contains necessary modules; this module supports two such projects, PAR and App::Packer. Please see their respective documentations on CPAN for further information. * Installation Module::ScanDeps uses the standard perl module install process: perl Makefile.PL make make test make install * Source Repository You can check out the most recent revision from Module::ScanDeps' GitHub repository: https://github.com/rschupp/Module-ScanDeps * Contact You can write to the mailing list at (no subscription required). Archives of the mailing list are available at or . Please submit bug reports to . * Copyright Copyright 2002-2008 by Audrey Tang ; 2005-2009 by Steffen Mueller . All rights reserved. You can redistribute and/or modify this bundle under the same terms as Perl itself. See . Module-ScanDeps-1.37/LICENSE0000644000175000017500000004413113014631605015556 0ustar roderichroderichCopyright 2002-2008 by Audrey Tang ; 2005-2009 by Steffen Mueller . This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- Copyright 2002-2008 by Audrey Tang ; 2005-2009 by Steffen Mueller . This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- Copyright 2002-2008 by Audrey Tang ; 2005-2009 by Steffen Mueller . This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End Module-ScanDeps-1.37/Changes0000644000175000017500000010275414715360027016060 0ustar roderichroderich1.37 2024-11-14 - fix parsing of "use if ..." Fixes errors in PAR::Packer test t/90-rt59710.t - add test for _parse_libs() 1.36 2024-10-21 - Fix CVE-2024-10224: Unsanitized input leads to LPE - use three-argument open() - replace 'eval "..."' constructs Note: this version was not released on CPAN because of Coordinated Release Date for CVE - README: add "Source Repository" and "Contact" info switch "Please submit bug reports to ..." to GitHub issues - add preload rule for MooX::HandlesVia cf. https://github.com/rschupp/PAR-Packer/issues/88 1.35 2023-11-05 - massive speed up, esp. for scripts using stuff from the Moose ecosystem, thanks to @shawnlaffan: - add package level caches for INC searches (_find_in_inc, _glob_in_inc) - faster add_deps on case insensitive systems AKA Windows 1.34 2023-09-24 - Fix issue #19 (AKA rschupp/PAR-Packer#78): invalid paths in zip file - Restore behaviour from version 1.31 when using "pp --execute ..." or "scandeps.pl --execute ...". When using "scan_deps(execute => 1, ...)", %INC as gleaned from running the script must be sanitized. Contrary to documentation "The key is the filename you specified (with module names converted to pathnames)" %INC *may* contain keys that are *absolute pathnames* (or start with "./relativ/path" when "relative/path" is in @PATH). Examples are autosplitted modules (for autosplit.ix and *.al files). pp will pack these absolute paths into the zip (Archive::Zip doesn't complain) which results in strange error messages when the packed executable tries to unpack them under CACHEDIR/inc on Windows. Add t/19-autosplit.t to test for this. Add IPC::Run3 to TEST_REQUIRES, used in t/19-autosplit.t - Add GitHub CI 1.33 2023-08-04 - Recognize Moose/Moo/Mouse style inheritance ("extends") or composition ("with") statements. - Add %Preload entries for known dependants of XS::Parse::Keyword. Note: XS::Parse::Keyword is loaded from XS code, grep.metacpan.org for calls of boot_xs_parse_keyword() in *.xs files. 1.32 2023-07-05 - Ensure $inc gets removed from the start of $File::Find::name On Windows, if $inc contains backslashes then it won't always get removed from the start of $File::Find::name because the latter may be canonicalized to only contain forward slashes. - Provide dedicated test scripts for some tests instead of using the test scripts themselves: Test::More draws in all kinds of stuff, totally unpredictable - Rewrite test helpers in t/Utils.pm to use Test::More's subtest feature - Code cleanup; fix detection of 'do STRING' (cf PR #15) - scandeps.pl: sort items in "used by" column - Handle spaces after quote operator, e.g. eval qq {Some::Module} - Fixes #12: share dir not returned when require module is in an eval - Recognize constructs like "eval qq{require Inline::C}". - Recognize idioms like "if (eval { require Foo }) { ..." - Add %preload rules for some Mojo resource files Fixes rschupp/PAR-Packer#44 - Bump perl dependency to guard against ancient perls without FindBin::again() - Add tool to trace when (and from where) Perl searches for a module 1.31 2021-04-21 - Try to match more PerlIO ":layer(args)" in open() or binmode() e.g. Spreadsheet::ParseODS uses ":gzip(none)"; Thanks, @shawnlaffan, for the suggestion (cf. PR #12) - XML::Twig::XPath needs either XML::XPathEngine or XML::XPath XML::Twig may use URI if present - Moo may use Class::XSAccessor if present - Fixes #10 "Support IUP.pm Module" 1.30 2021-01-13 - change bugtracker to GitHub issues - guard against trailing slashes for paths in @INC - interprete more common "use lib" idioms 1.29 2020-08-16 - implement interpretation of stuff like use FindBin; use lib "$FindBin/../lib"; 1.28 2020-08-06 - placate cperl ("Attempt to change hash while iterating over it.") - make _find_encoding() more robust 1.27 2019-01-15 - fixes by Shawn Laffan - Process lines like "{ package foo; use if 1, "warnings"; } (#8) - Also handle 'do {use module; ...}' - some clean ups - scandeps.pl: sort output by module name - add_deps(): use _gettype() instead of inline code - _compile_or_execute(): require DynaLoader _before_ accessing its variables - t/7-check-dynaloader.t: improve diagnostics - drop Cwd from the list of potential XS modules - suppress warnings in some ancient Perls 1.26 2018-12-12 - Glue DLLs of XS modules should have type "shared" rather than "data" Detection broke on Windows where nowadays $Config{dlext} = "xs.dll" (i.e. it's not a simple suffix) - Mention some other modules: Module::ExtractUse and Perl::PrereqScanner::* family - Add %Preload rules for: - JSON::MaybeXS - HTTP::Entity::Parser - FFI::Platypus 1.25 2018-08-18 - Merge pull request #2 from shawnlaffan/master, thanx Shawn! continue scanning one-liners when use if, autouse or >5.010 found - Fix how data obtained from compiling or executing a file is incorporated (_info2rv). Sanitize all pathnames to use slash (instead of backslash): - members of @INC - keys and values of %INC - members of @dl_shared_objects This should make stripping @INC prefixes finally work. - Add %Preload rule for FFI::Platypus - Add bugtracker to META.yml 1.24 2017-06-28 - Merge pull request from Salvador FandiƱo (salva), thx! Specio::PartialDump uses unicore - Fix RT#119737: Problems with detecting DateTime::Format::Natural dependencies ... by adding a %Preload rule 1.23 2016-11-16 - add %Preload rules for List::SomeUtils and Pod::Simple::Transcode - get rid of Module::Install, use ExtUtils::MakeMaker 1.22 2016-09-17 - Fix RT#117887: Not parsing new release of Net::DNS::Resolver add %Preload rule for Net/DNS/Resolver.pm - Move to GitHub. Thanks, OpenFoundry, for years of service. 1.21 2016-04-05 - %Preload: add rules for List::MoreUtils and Log::Dispatch - %Preload: make the following modules require the unicore stuff: charnames.pm Unicode/Normalize.pm Unicode/UCD.pm - add helper _glob_in_inc_1() - remove all references to http://par.perl.org/, doesn't exist anymore 1.20 2015-10-04 - Fix RT #107304: Newer versions of File::Path cause warning "_Inline for _Inline: No such file or directory at Module/ScanDeps.pm line 1339." - drop the dubious call to rmtree() - Fix RT106142: Preload dependencies for PDL and PDL::NiceSlice - adopted from a patch by Shawn Laffan, thanks Shawn! - Fix RT#106144: Preload dependencies for File::BOM) - adopted from a patch by Shawn Laffan, thanks Shawn! - Revise our stance on utf8.pm: - A line of "use utf8;" just means "this file is encoded in UTF-8" and should _not_ result in scanning utf8.pm which will pull in the whole Unicode shebang (propery tables and what not). Yes, utf8.pm *does* contain "require utf8_heavy.pl", but only inside an AUTOLOAD() that is *not* triggered by calling functions like utf8::is_utf8(). - OTOH the innocently looking one-liner perl -ne 'print if /\pN/' implicitly loads utf8.pm and triggers the AUTOLAD(). - So prevent utf8.pm from being scanned and make utf8_heavy.pl the indicator for "I need the Unicode stuff" instead. - Cache the results of _get_preload('utf8_heavy.pl'). - Make %Preload "transitive" so that given my %Preload = ( 'Foo.pm' => [ 'Bar.pm' ], 'Bar.pm' => [ 'Quux.pm' ], ... ); scan_deps_static() registers a dependency on Bar.pm _and_ Quux.pm when it has seen "use Foo;" - Minor changes: - drop dubious %Preload of utf8.pm for SOAP::Lite and XML::Parser::Expat - drop code for Perl < 5.008 as we require 5.8.1 already - rework the implementation of -x/-c - add add_preload_rule() to dynamically add a %Preload rule - recognize constructs like "open FH, '<:via(Foo)', ..." - upgrade to Module::Install 1.16 1.19 2015-05-27 - add %Preload rule for LWP::MediaTypes: data file LWP/media.types - add %Preload entry for MIME::Types: data file MIME/types.db - add %Preload rule for AnyEvent - always add Encode.pm when fix encountering constructs like decode("klingon", ...) open FH, "<:encoding(klingon)", .. - add license - update OpenFoundry repository URL 1.18 2015-01-19 - Fix RT #101569: Incorrect module parsing if Moose is included 1.17 2014-10-31 - scandeps.pl: die if an option is not recognized - Reformat Changes file according to CPAN::Changes::Spec - Modify %Preload rule: let Unicode::UCD explicitly imply utf8.pm. This fixes PAR::Packer's self test. Previously Unicode::UCD implied utf8.pm implicitly because it contains calls to some utf8::foo() functions. - Add %Preload rule: Mozilla::CA requires its cacert.pem file - Recognize "do filename" constructs even if "do" isn't at the start of a chunk. - Upgrade to Module::Install 1.14 1.16 2014-09-28 - Fix RT#98938: recognize Module::Runtime module-loading functions - Fix a nasty typo that broke scandeps.pl option -E $ scandeps -E "some string" Unknown option: E Can't open some string: No such file or directory at scandeps.pl line 49. - also scandeps.pl: die if an option is not recognized - Remove some overzealous heuristics from scan_chunk() - they were looking for Foo::Bar->something Foo::Bar::whatever(...) _anywhere_ in programs to infer a dependency on Foo/Bar.pm. BEWARE: This might break some use cases, i.e. missing some dependencies. On the other hand, this causes hard to investigate problems like the one starting at http://www.mail-archive.com/par@perl.org/msg05531.html. While the former can easily be worked around by the user itself (just add a missing dependecy explicitly, e.g. using "pp -M ...") and typically can be solved in general by adding a %Preload rule, the latter just wastes people's times. - Recognize Test::More require_ok() and use_ok() - makes 3-static_oo_interface_real.t pass again (fallout from the above) - Upgrade to Module::Install 1.12 - Add option -T to request information from CPAN - don't access CPAN behind the user's back just because they have CPANPLUS installed (it was in the Perl core from 5.10 to 5.18) - it might not even have been configured (e.g. in a corporate internet) - only do this when explicitly requested 1.15 2014-08-23 - Fix RT #98203: Migrate from deprecated Module::Build::ModuleInfo to Module::Metadata - thanx Petr Pisar (ppisar@redhat.com) for the hint - add long option names to scandeps.pl - implement option --xargs for scandeps.pl - fix wrong version numbers in Changes 1.14 2014-08-03 - Fix RT #92860 (t/7-check-dynaloader.t doesn't handle systems with mod2fname), also RT #97519 (Fix for t/7-check-dynaloader.t on systems with DynaLoader::mod2fname) - applied patch from Brian Fraser (fraserbn@gmail.com), thanks! - lib/Module/ScanDeps/DataFeed.pm: apply here, too 1.13 2013-12-21 - Fix recognition of (open() arguments) "<:encoding(klingon)", implies modules PerlIO and PerlIO::encoding. 1.12 2013-12-01 - Fix RT #90869: Use of uninitialized value $module in substitution (s///) - Fix RT #87775: typo fixes, thanks dsteinbrunner@pobox.com - new %Preload rule for B::Hooks::EndOfScope - new %Preload rule for Pod::Usage - add a fake %Preload rule that warns if use of Module::Implementation or Module::Runtime is detected (coz' they're doing runtime loading) - change some tests to use Test::Requires instead of homegrown stuff; hence add it to "test_requires" - clean up some uses of Test::More 1.11 2013-09-28 - Fix RT #89000: test broken by indirect base.pm disuse - delete base.pm from list of expected deps, patch by Andrew Main (zefram@fysh.org) - new %Preload rule for Net::HTTPS (e.g. used by LWP::Protocol::https) - look for IO::Socket::SSL or Net::SSL - new %Preload rule for YAML::Any - try to figure out what YAML::Any would have used (using YAML::Any->implementation) - as fallback, include anything below YAML 1.10 2012-10-20 - add %Preload rule for Params::Validate to detect its PP and XS implementations - Fix RT #80276 Module DateTime::Format::ISO8601 generates error after being packaged - caused by failing to pack DateTime::Format::Builder::Parser::XXX modules needed by DateTime::Format::Builder::Parser - add a corresponding %Preload rule - update to Module::Install 1.06 1.09 2012-09-09 - teach Module::ScanDeps about "use if ..." constructs - fixes CPAN Testers failures for PAR::Packer with perl 5.17.1 and up (Roderich Schupp) - RT #79003: t/7-check-dynaloader.t failing when /usr/lib != /usr/lib64 - scrap the test for "$entry{file} starts with $expected_prefix" as its assumptions are flawed (Roderich Schupp) - Mojo::Base is a loader (Alexandr Ciornii) - Special case for Class::Load (Alexandr Ciornii) 1.08 2012-02-21 - RT #73785: scandeps -c fails on modules that depend on Getopt::Euclid - for "scandeps -c ..." switch from an INIT block to a CHECK block and call the augmented script with "perl -c" instaed of "perl" - RT#72954 ":encoding(UTF-8)" doesn't imply a dependency on Encode.pm - if scan_chunk sees ":encoding(FOO)" or similar, it goes to some length to find the "external" Encode module to handle FOO; but it forgets that Encode.pm itself is needed at runtime (esp. if FOO is an encoding "internally" handled by Encode.pm, e.g. "UTF-8") - %Preload: add rules for Gtk2.pm and Pango.pm - %Preload: fix a problem with Image::ExifTool 1.07 2011-11-29 - RT #72796: dynaloader test fails when the .so files are in the system lib dirs and local::lib is involved? Relax a check in t/7-check-dynaloader.t - Update Module::Install to 1.04 1.06 2011-11-28 - RT #72211: pp includes way too much modules (when using 'use strict;')? Rework regexes to detect "use MODULE ...": the following line from unicore/mktables my $unihan = 'Unihan properties are by default not enabled in the Perl core. Instead use CPAN: Unicode::Unihan'; would erroneously detect a dependency on CPAN.pm (which will in turn pull in a lot of modules) - Bump Perl version requirement to 5.8.1 (Schwern: The End Of 5.6 Is Nigh!) - Rewrite t/7-check-dynaloader.t to look for more candidates of dynamic modules that might be used as test cases 1.05 2011-11-02 - RT #72082: $FindBin::Bin issue on Moudel::ScanDeps 1.04 Make FindBin work (at least with option -c or -x) by spoofing $0 in the temp script generated for M:SD::DataFeed - RT #70134: patch suggestions for Module::ScanDeps 1.04: additional preload rules, used_via_preload attribute Add suggested %Preload rules from the attached patch (thanks, Markus Jansen) - Add %Preload rules for MozRepl - Special case for Package::Stash (Alexandr Ciornii) - Special case for Moose (Alexandr Ciornii) 1.04 2011-07-21 - Brown paper bag bug: fix option -x (execute) (broken by changes for -c) - While we're at it: honor option -I with -c 1.03 2011-07-18 - RT #69213: ScanDeps incompatible with AnyEvent (Perl 5.14, AnyEvent 5.34, PAR 1.00.2) For option -c (compile) M:SD used to wrap the file in one big sub and appended an END block where it dumps %INC etc; the outer sub causes problems with certain contructs. Instead we now use an INIT block prepended to the file. - RT #69471: Problem with "eval { require SomeModule }" constructions Module::ScanDeps::DataFeed now omits %INC pairs with an undefined value (these may be created by an unsuccessful "require" under certain conditions). Also omit CODE refs from @INC. - Fix for failing CPAN Testers report http://www.cpantesters.org/cpan/report/4208fa16-a5d1-11e0-a0bc-c71a7862a918: Perl 5.15.0 got rid of Shell.pm - Fix for failing CPAN Testers report http://www.cpantesters.org/cpan/report/772147dc-6c1f-1014-baf2-318eb63ba09a: - regex meta characters in filenames break consistency check - Simplify Module::ScanDeps::DataFeed somewhat by localizing %INC around "require Module::ScanDeps::DataFeed" and by using Data::Dumper for the actual dump. - Don't create the tempfiles for DataFeed in the working directory. - Purge all pod from Module::ScanDeps::DataFeed, advise the CPAN indexer not to bother with it; same for Module::ScanDeps::Cache. 1.02 2011-04-03 - %Preload: add _all_ *.pl file below .../unicore for utf8.pm 1.01 2011-03-26 - %Preload: add "unicore/version" for Unicode/UCD.pm (because it contains a call openunicode(..., "version")) 1.00 2011-02-19 - RT #65855: Special handling for POSIX requested (Roderich Schupp) - RT #65252: Temp files left when execute fails (Roderich Schupp) - add a %Preload rule for Log::Report::Dispatcher (Roderich Schupp) cf. http://www.nntp.perl.org/group/perl.par/2011/01/msg4871.html - add %Preload rule for Date::Manip (Roderich Schupp) - speed up scanning *significantly* by not re-constructing regexen for every line of input and reducing the no. of sub calls (Steffen Mueller) - add Eric Roode to AUTHORS (Steffen Mueller) - RT #61027: "use lib" does not work (Roderich Schupp) scan_line(): When handling "use lib '/some/dir'" we add "/some/dir/ARCHNAME", "/some/dir/VER" and "/some/dir/VER/ARCHNAME", but forgot to add "/some/dir" itself. While we're at it, improve parsing the argument list of "use lib". Simply eval the string, this should at least make all forms of quoted strings work correctly. - fix URI special case (clkao) - fix a regression reported by CPAN Testers (Roderich Schupp) - finally: bump version to 1.00 0.98 2010-07-26 - Make %Preload entry for "utf8.pm" lazy (Roderich Schupp) - Upgrade to Module::Install 1.00 (Roderich Schupp) - RT #58093: Par-Packer not including all dependencies (unicore/Heavy.pl) (Roderich Schupp) - Add %Preload rule for RPC::XML (Roderich Schupp) - RT #57494: add %Preload rule for JSON.pm (Roderich Schupp) 0.97 2010-04-10 - Pack the content of module/distribution sharedirs is automatically. (kmx) - RT #56020 - add data files used by Unicode::UCD (Roderich Schupp) - RT #55746 - remove bogus "... if %Config::Config" condition (Roderich Schupp) - Add special case for CGI::Application::Plugin::AutoRunmode (Alexandr Ciornii) - Add special case for CGI::Application::Plugin::Authentication (Alexandr Ciornii) - Add special case for DBIx::Perlish (Alexandr Ciornii) 0.96 2009-11-13 - perl 5.6.1 compatibility (Alexandr Ciornii) - Test for "use module version;" (Alexandr Ciornii) 0.95 2009-10-16 - Fix "uninitialized value" warnings (Dave Rolsky) - Add special case for Perl::Critic (Alexandr Ciornii) - Add special case for Event (Alexandr Ciornii) - Add special case for Wx.pm (Alexandr Ciornii) - Add special case for Log::Any 0.94 2009-08-10 - Add tests for scan_line (Alexandr Ciornii) - RT#48151 fixed, "require __PACKAGE__" should not die (Alexandr Ciornii) - OS/2 fixes (Ilya Zakharevich) 0.93 2009-07-19 - Implement caching of dependencies (Christoph Lamprecht) 0.92 2009-07-19 - Fix bug with {type} being set to unexpected values in some cases (Christoph Lamprecht) - Add tests for scan_chunk (Alexandr Ciornii) - Add special case for parent.pm (Alexandr Ciornii) - Fix for "use parent::something" (Alexandr Ciornii) - Add special case for Catalyst.pm (Alexandr Ciornii) 0.91 2009-06-22 - Add special case for Tk's setPalette call (Christoph Lamprecht) 0.90 2009-05-09 - Add special case for DateTime::Locale - Add special case for PAR::Repository and PAR::Repository::Client 0.89 2008-11-03 - Distribution fixes. - Do not use base Exporter. - Detection of 'asa' and 'only::matching'. 0.88 2008-10-28 - Add special case for File::HomeDir. 0.87 2008-10-28 - Add special case for PPI. 0.86 2008-10-23 - Fix the 'use prefork "Foo"' static detection. - Fix the detection of any of the module-loader modules such as prefork, autouse, etc. if invoked as 'use prefork"Foo"' (note the lack of a space). - Slightly refactor the loader-module scanning. (see above) - Support for "use maybe 'foo';" - Use (arch|priv)libexp instead of (arch|priv)lib in scandeps.pl (Mark Stosberg) - Update to Module::Install 0.77 0.85 2008-08-01 - Add special case for Net::Server. 0.84 2008-05-13 - Add special case for Class::MethodMaker. 0.83 2008-03-23 - Add special case for Image::ExifTool. 0.82 2008-01-08 - Add Test::More to build requirements (Alexandr Ciornii) - Add dependency on version.pm - Now correctly identifies feature.pm as a dependency if "use 5.10.0;" (and up) is found. 0.81 2007-12-07 - Fix for the case-insensitive-file-system-test. 0.80 2007-11-30 - Fix to avoid duplicated entries arising from used_by references with case differences. - Do not report input files themselves as dependencies. (Regression from 0.74 onwards) - Remove warning from ScanFileRE tests. 0.78 2007-11-17 - Fix ScanFileRE heuristics to allow for scanning files without suffixes. 0.77 2007-09-20 - Add support for prefork.pm (similar to how base.pm is detected). - Added uses field to hash descriptions returned by scan_deps + tests (Adrian Issott) - Added ScanFileRE to restrict the files scanned to .pl, .pm, .al and .t but allow the user to override + tests (Adrian Issott) 0.76 2007-07-21 - Fix special case for Term::ReadLine (should not rope in Tk) - New special case for Tcl::Tk (should not rope in Tk either!) - New special case for threads::shared ==> rope in attributes.pm - Fix to avoid duplicated entries that can arise due to case differences that don't actually matter on case-tolerant systems (Adrian Issott) - M::SD warnings now go to STDERR not STDOUT (Adrian Issott) - Fixed bug #24162: scandeps.(bat|pl) doesn't correctly identify Core Modules on Windows (Adrian Issott) - Now finds shared libraries for modules specified as input files. - Tests for finding shared libraries. 0.75 2007-06-24 - Fix special cases for POE. (Roderich Schupp) - Added exported path_to_inc_name subroutine (Adrian Issott) - Added Module::Build::ModuleInfo dependency (Adrian Issott) - Fixed bug where input files weren't scoped properly - Add new "check-for-dynaloader" test. (Eric Wilhelm) 0.74 2007-04-26 - Same as 0.73_01, but not a developer release. 0.73_01 2007-03-28 - Fixed bug "scan_deps doesn't show ALL the dependencies" - Ensured all file entries are given by absolute paths - Added a number of test artificial dependency trees as test data mainly for "scan_deps doesn't show ALL the dependencies" bug - Added tests for scandeps recurse option (all pass) - Added tests for scandeps skip option (all pass) - Added tests to show a duplicated dependency is in fact only shown once (all pass) - Added Utils.pm test module containing generic_scandeps_rv_test and compare_scandeps_rvs subroutines (Adrian Issott) 0.73 2007-03-25 - Now being a little cleverer for detecting globs in diamond operators. (Requiring a meta character within the <>.) 0.72 2007-02-03 - Case-insensitive @INC removal for case-insensitive filesystems (Eric Wilhelm) 0.71 2007-01-04 - Added special cases for Catalyst Class::MakeMethods Config::Any DBIx::Class Email::Send Log::Log4perl SQL::Translator - print() the "# Legend..." line instead of warn()ing it. 0.70 2006-11-21 - Added special case for Image::Info. 0.69 2006-11-07 - Additional corner cases for LWP::UserAgent and LWP::Parallel::UserAgent and friends. 0.68 2006-10-25 - Added special case for PerlIO.pm. If PerlIO.pm is needed, require PerlIO::scalar, too, because it can be used "under the hood". (Roderich Schupp) - Added some File::Spec'ness. (Steffen Mueller) - Refactored the %Preload mapping code into _get_preload so that the PAR -M %Preload fix would work. (Steffen Mueller) 0.67 2006-10-24 - Added @IncludeLibs which is used alongside @INC for searching modules. (David Romano) - Won't pick up Tk as a dependency for Term::ReadLine any more. You can stop laughing now! 0.66 2006-09-24 - Fixed another bug in Module::ScanDeps::Datafeed which would break run- and compile-time dependency scanners if $ENV{PERL5LIB} entries had trailing backslashes. Thanks to Steven Mackenzie for pointing this out. - Added some documentation and comments to M::SD::Datafeed for the sake of future maintainers. 0.65 2006-09-24 - Fixed bug in Module::ScanDeps::Datafeed which would die() in 0.64. 0.64 2006-09-22 - Upgraded to Module::Install 0.64 - Added warning of missing modules when -V is in effect (scandeps.pl). - Added warning of missing modules if "warn_missing=>1" specified as an option to scan_deps. 0.63 2006-08-27 - Upgraded to Module::Install 0.63 0.62 2006-07-16 - Better diagnostics.pm support for searching the related .pod file. 0.61 2006-06-30 - Now presenting more helpful (and correct) error messages when multiple versions of a module (files) are found. - Corrected a POD error. - Added test for POD correctness. 0.60 2006-05-23 - Fixed bug that prevented "use encoding 'utf-8';" from being picked up. This was because the -8 was stripped and thus, the encoding wasn't recognized. 0.59 2006-05-03 - Recovering 5.005 compatibility. (Hopefully!) - Using Module::Install 0.62 - Added a dependency on File::Temp for pre 5.6 perls. - Fixed broken Module::Pluggable support. 0.58 2006-04-16 - Added dependency for Test::Deep - Added dependency for Math::Symbolic 0.57 2006-03-03 - Applied Stephen Schulze's patch which fixes the problem that modules are reported as depended upon only once. 0.56 2006-02-20 - Added special dependency for Tk::Getopt. Suggested by Slaven Rezic. 0.55 2006-02-17 - Applied Roderich Schupp's patch to fix a problem with 'autouse'. - Now using Module::Install 0.56 0.54 2006-01-11 - Switch to File::Temp::tempfile() for more robust temporary file creation. Contributed by: Jesse Vincent - Update to latest Module::Install _again_ to fix Cygwin installation. Reported by: Matt S Trout 0.53 2006-01-10 - Update to latest Module::Install; no functional changes. 0.52 2005-12-12 - Support for autouse.pm. - Support for Tk::DragDrop. Reported by: Renee Baecker. 0.51 2005-01-08 - scandeps.pl is now usable without CPANPLUS.pm installed. Reported by: Rafael Garcia-Suarez 0.50 2004-10-03 - LWP::Authen::* is now bundled with LWP::UserAgent. Reported by: Marcus Rueckert - Properly sign the release with newer EU::MM. 0.49 2004-09-26 - Adds Class::Autouse support, as requested by Adam Kennedy. 0.48 2004-09-07 - Skip auto/ files too if explicitly specified. - Also check for lower-cased keys in %skip, if operating under a case-insensitive file system. 0.47 2004-09-07 - First version under svk management. - Support for Mail::Audit plugins; prompted by Andrew Lee. - Support for modules that use Module::Plugin; prompted by Brian Cassidy. - scandeps.pl now reports module versions, courtesy of Dan Friedman. - Delayed loading of CPANPLUS on scandeps.pl. 0.46 2004-07-02 - Doc fixes; update signature test; add Alan to authors. - add POE heuristics from: http://search.cpan.org/dist/POE/lib/POE/Preprocessor.pm 0.44 2004-06-08 - Consistently recognize .ph files and upper-cased .p[mh] files. - Support for PDF::Writer. - Patfch from Roderich Shupps to fix absolute filename detection on non-Unix systems. 0.43 2004-06-02 - Add preliminary support for BioPerl, as suggested by Nathan Haigh. - Support for Net::SSH::Perl was incorrectly specified. - Add some support for PDF::API2 -- note you still have to explicitly require "PDF::API2::Basic::TTF::Font" to get TrueType support. - add heuristics for Devel::ParallelPort, as reported by Jouke Visser. 0.42 2004-04-30 - add support for DBIx::SearchBuilder and DBIx::ReportBuilder. - oops, typo - add PerlIO.pm to :encoding. 0.41 2004-04-18 - correctly handle SVN::Core, courtesy of Robert Spiers. - handles SVK::Command properly. - add support for Parse::Binary-based modules 0.40 2004-02-23 - Malcolm Nooning noticed that _execute() and _compile() checks were failing under directories that contain spaces, due to a qw() misuse. - Add heuristics for XML::SAX and XML::Parser::Expat, reported by Darek Adamkiewicz and Iain Cass. 0.39 2004-01-25 - Merged Edward's patch to make DataFeed.pm work with pre-5.8.3 perl versions. 0.38 2004-01-08 - Switching back to ExtUtils::MakeMaker, hoping to make ActiveState's cpanrun happy. 0.37 2003-12-31 - Win32 does not take Cwd::abs_path() for filenames. - Detection for __END__ blocks was wrong in _compile(). 0.36 2003-12-31 - sorry, "scandeps.pl -r" should be "-x". 0.35 2003-12-31 - New "-c" and "-r" flags to scandeps.pl for additional compile- and runtime-checking of dependencies. - New "compile" and "execute" flags to scan_deps() for runtime scanning, using scan_deps_runtime(). - integrated Edward S. Peschko's massive runtime detection patch, as scan_deps_runtime(). 0.34 2003-12-30 - changes. 0.33 2003-12-21 - Upgrades to Module::Install 0.30 framework. - Nik's got a CPAN ID. 0.32 2003-10-26 - Support for Locale::Maketext::Guts, reported by Jouke Visser. - Support for XML::Parser, reported by Jan Dubois. - Support for :encoding(), encoding.pm, and encode()/decode(). 0.31 2003-10-17 - Jesse Schoch reports that LWP::Protocol::https is not properly detected. 0.30 2003-09-20 - "use base" was still incorrectly parsed. 0.29 2003-09-17 - Simon Andrews points out that Math::BigInt's heuristics is badly coded. Fixed, and added heuristics for Math::BigFloat. - More defense against hash randomisation by sorting all keys() and values(). 0.28 2003-08-17 - Move ScanDeps.pm to lib/Module/. - Suggestion from Matt Sergeant to recognize A::B from A::B::C only on functions like A::B::C(). - This be 0.27 for real. - "use base" was improperly detected. 0.27 2003-08-16 - more patch from Roderich Schupp: handles "use base" and fixed Tk::Scrolled. - add $SeenTk to control Tk-specific heuristics. - add_deps now takes (skip => \%skip) properly. - scan_chunk() can now return more than one files in list context. - bump version. 0.26 2003-08-11 - add link to http://par.perl.org/ and the mailing list. - don't append ".pm" to require '' lines if it already has an extension. (this is required for Win32API::Registry to work with .pc files.) 0.25 2003-08-10 - tidy up the source a little. - POD and END sections was also scanned. bad. - PAR::read_file() should not imply dependency on PAR.pm. 0.24 2003-08-10 - Add support for SOAP::Lite, XMLRPC::Lite and Win32::SystemInfo. 0.23 2003-08-08 - @File::Spec::ISA was crippled during scanning, thanks to Roderich Schupp for pointing out. 0.22 2003-08-07 - huge patch to include almost all heuristics deducible from PerlApp: Authen::SASL, Crypt::Random, DBI, File::Spec, HTTP::Message, Math::BigInt, MIME::Decoder, Net::DNS::RR, Net::FTP, Net::SSH::Perl, SQL::Parser, Template, Term::ReadLine, URI, XML::Parser::Expat, diagnostics. - now accepts uppercased "DBI:" in DSN strings. - fixed a typo on Tk::FBox's xpm file. 0.21 2003-07-30 - Jouke reports that Win32.pm pulls all Win32::* DLLs. - oops. - scandeps.pl now take -e to scan a perl expression - anydbm implies SDBM. - Bruce Winter says that this fix for SerialJunks is needed on his Red Hat Linux oh well. 0.19 2003-03-22 - Jess Robinson reported that the fix was not -w safe. 0.18 2003-03-20 - added logic for "utf8" and "charnames" needed by Germain Garand. - added logic for "Devel::SerialPort" needed by Bruce Winter. - POSIX.pm no longer pulls in utf8.pm anymore. - .ph files are now fully supported. - take unshift/push @INC into account, too. - add Nik to authors. - Nik Clayton's patch to properly handle 'use lib'. - IO.pm dependencies, courtesy of Jerry Veldhuis. 0.14 2003-01-19 - s/UNIVERSA/UNIVERSAL/; - test explicitly for a hashref for safety. - try to fix D.H.'s bug report about broken 5.6 and pseudohashfications. - add lathos and obra to authors. - mention scandeps.pl earlier in pod. 0.13 2003-01-18 - much more improved scandeps, as suggested by jesse vincent. - add #! for core; explains the symbols. - use cpanplus to tell apart redundant modules if possible. 0.12 2003-01-18 - adds script/scandeps.pl - new year. - add CAVEATS about the fact that we don't probe beyond @INC, as requested by crazyinsomniac. - M::B heuristics. - reflect SEE ALSO in README. 0.10 2002-11-04 - Now featuring an object-oriented syntax, conformant with App::Packer::Frontend. - added corresponding documentation and tests. 0.03 2002-11-03 - add AUTHORS. - last minute fix from merlyn's bug report. - New presets for Locale::Maketext::Lexicon, Term::ReadLine, Regexp::Common, File::Spec, ExtUtils::MakeMaker. - New heuristics for Module::Name->method, Module::Name::sub - Strings in comments were erroneously checked. Fixed. - Mention PerlApp as a source of inspiration. - Regexp::Common. 0.02 2002-11-02 - now performs testing by looking at the test file itself. - displays correct message when connection fails. - backported to 5.001. - was looking in POD sections; fixed. - thorough comments and documentations. - oops, Makefile shouldn't be in RCS. - written-from-scratch version of dependency finding algorithm. Module-ScanDeps-1.37/AUTHORS0000644000175000017500000000323113014631605015615 0ustar roderichroderichHere is a list of people and their CPAN id, extracted from the ChangeLog file and the mailing list archives. These people have either submitted patches or suggestions, or their bug reports or comments have inspired the appropriate patches. Corrections, additions, deletions welcome: Adam Kennedy (ADAMK) Adrian Issott Alan Stewart Alexandr Ciornii (CHORNY) Andrew Lee Brian Cassidy (BRICAS) Bruce Winter Chris Dolan (CDOLAN,CLOTHO) Christoph Lamprecht (LAMPRECHT) D. H. (PODMASTER) Dan Friedman (LAMECH) Darek Adamkiewicz (DADAMK) David Romano Dominique Quatravaux (DOMQ) Edward S. Peschko Eric J. Roode (ROODE) Eric Wilhelm Germain Garand (GGARAND) Iain Cass Indy Singh Jan Dubois (JDB) Jerry Veldhuis Jess Robinson Jesse Schoch Jesse Vincent (JESSE) Johan Vromans Jouke Visser (JOUKE) Malcolm Nooning Marcus Rueckert Mark Stosberg (MARKSTOS) Markus Jansen Matt S Trout (MSTROUT) Matt Sergeant (MSERGEANT) Nadim Ibn Hamouda El Khemir (NKH) Nathan Haigh Nik Clayton (NIKC) Rafael Garcia-Suarez (RGARCIA) Randal L. Schwartz (MERLYN) Renee Baecker (RENEEB) Robert Spier (RSPIER) Roderich Schupp (RSCHUPP) Scott Stanton Seth L. Blumberg (METAL) Simon Andrews Simon Cozens (SIMON) Slaven Rezic (SREZIC) Steffen Mueller (SMUELLER) Stephen Schulze Steve Pick Steven Mackenzie Module-ScanDeps-1.37/MANIFEST.SKIP0000644000175000017500000000250214713665012016451 0ustar roderichroderich #!start included /home/roderich/perl5/perlbrew/perls/perl-5.30.3/lib/5.30.3/ExtUtils/MANIFEST.SKIP # Avoid version control files. \bRCS\b \bCVS\b \bSCCS\b ,v$ \B\.svn\b \B\.git\b \B\.gitignore\b \b_darcs\b \B\.cvsignore$ \B\.github\b # Avoid VMS specific MakeMaker generated files \bDescrip.MMS$ \bDESCRIP.MMS$ \bdescrip.mms$ # Avoid Makemaker generated and utility files. \bMANIFEST\.bak \bMakefile$ \bblib/ \bMakeMaker-\d \bpm_to_blib\.ts$ \bpm_to_blib$ \bblibdirs\.ts$ # 6.18 through 6.25 generated this \b_eumm/ # 7.05_05 and above # Avoid Module::Build generated and utility files. \bBuild$ \b_build/ \bBuild.bat$ \bBuild.COM$ \bBUILD.COM$ \bbuild.com$ # and Module::Build::Tiny generated files \b_build_params$ # Avoid temp and backup files. ~$ \.old$ \#$ \b\.# \.bak$ \.tmp$ \.# \.rej$ \..*\.sw.?$ # Avoid OS-specific files/dirs # Mac OSX metadata \B\.DS_Store # Mac OSX SMB mount metadata files \B\._ # Avoid Devel::Cover and Devel::CoverX::Covered files. \bcover_db\b \bcovered\b # Avoid prove files \B\.prove$ # Avoid MYMETA files ^MYMETA\. # Temp files for new META ^META_new\.(?:json|yml) # Avoid travis-ci.org file ^\.travis\.yml # Avoid AppVeyor file ^\.?appveyor.yml #!end included /home/roderich/perl5/perlbrew/perls/perl-5.30.3/lib/5.30.3/ExtUtils/MANIFEST.SKIP ^MYMETA\. ^typescript$ ^wip/ Module-ScanDeps-1.37/t/0000755000175000017500000000000014715360221015013 5ustar roderichroderichModule-ScanDeps-1.37/t/5-pluggable_fake.t0000644000175000017500000000063314442277143020303 0ustar roderichroderich#!/usr/bin/perl use Module::ScanDeps; use strict; use warnings; use Test::More tests => 1; use Test::Requires qw( Module::Pluggable ); use lib qw(t t/data/pluggable); use Utils; my $rv = scan_deps( files => ['t/data/pluggable/Foo.pm'], recurse => 1, ); my @deps = qw(Module/Pluggable.pm Foo/Plugin/Bar.pm Foo/Plugin/Baz.pm); check_rv($rv, ['t/data/pluggable/Foo.pm'], \@deps); Module-ScanDeps-1.37/t/14-static_functional_cached.t0000644000175000017500000003530214442277232022433 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More 'no_plan'; use lib qw(t t/data/static); use Utils; use version; ############################################################## # Tests compilation of Module::ScanDeps ############################################################## BEGIN { use_ok( 'Module::ScanDeps' ); } ############################################################## # Static dependency check of a script that doesn't use # anything with basic cache_cb test added ############################################################## my @roots1 = qw(t/data/static/null.pl); my $expected_rv1 = { "null.pl" => { file => generic_abs_path("t/data/static/null.pl"), key => "null.pl", type => "data", }, }; expected_cache_cb_args({key => 'null.pl', file => 't/data/static/null.pl', }); my $rv1 = scan_deps(files => \@roots1, cache_cb => \&cache_cb ); compare_rv($rv1, $expected_rv1, \@roots1); ### check if we can use M::SD::Cache my $skip_cache_tests = 1; eval {require Module::ScanDeps::Cache;}; unless ($@){ $skip_cache_tests = Module::ScanDeps::Cache::prereq_missing(); warn $skip_cache_tests, "\n"; } my $cache_file = 'deps_cache.dat'; for my $t(qw/write_cache use_cache/){ SKIP: { skip "Skipping M:SD::Cache tests" , 289 if $skip_cache_tests; ############################################################## # Static dependency check of a circular dependency: # ___ # |/_ \ # M _M # \____/| # ############################################################## my @roots2 = qw(t/data/static/egg.pm); my $expected_rv2 = { "chicken.pm" => { file => generic_abs_path("t/data/static/chicken.pm"), key => "chicken.pm", type => "module", used_by => ["egg.pm"], uses => ["egg.pm"], }, "egg.pm" => { file => generic_abs_path("t/data/static/egg.pm"), key => "egg.pm", type => "module", used_by => ["chicken.pm"], uses => ["chicken.pm"], }, }; # Functional i/f my $rv2 = scan_deps(files => \@roots2, cache_file => $cache_file, recurse => 1, ); compare_rv($rv2, $expected_rv2, \@roots2); ############################################################## # Static dependency check of the following dependency tree # # M # /|\ # / | \ # / | \ # / M \ # / / \ \ # / / \ \ # M M M M # \ \ / / # \ \ / / # \ M / # \ | / # \ | / # M # # With dependencies always going from the top downwards ############################################################## my @roots3 = qw(t/data/static/outer_diamond_N.pm); my $expected_rv3 = { "inner_diamond_E.pm" => { file => generic_abs_path("t/data/static/inner_diamond_E.pm"), key => "inner_diamond_E.pm", type => "module", used_by => ["inner_diamond_N.pm"], uses => ["inner_diamond_S.pm"], }, "inner_diamond_N.pm" => { file => generic_abs_path("t/data/static/inner_diamond_N.pm"), key => "inner_diamond_N.pm", type => "module", used_by => ["outer_diamond_N.pm"], uses => ["inner_diamond_E.pm", "inner_diamond_W.pm"], }, "inner_diamond_S.pm" => { file => generic_abs_path("t/data/static/inner_diamond_S.pm"), key => "inner_diamond_S.pm", type => "module", used_by => ["inner_diamond_W.pm", "inner_diamond_E.pm"], uses => ["outer_diamond_S.pm"], }, "inner_diamond_W.pm" => { file => generic_abs_path("t/data/static/inner_diamond_W.pm"), key => "inner_diamond_W.pm", type => "module", used_by => ["inner_diamond_N.pm"], uses => ["inner_diamond_S.pm"], }, "outer_diamond_E.pm" => { file => generic_abs_path("t/data/static/outer_diamond_E.pm"), key => "outer_diamond_E.pm", type => "module", used_by => ["outer_diamond_N.pm"], uses => ["outer_diamond_S.pm"], }, "outer_diamond_N.pm" => { file => generic_abs_path("t/data/static/outer_diamond_N.pm"), key => "outer_diamond_N.pm", type => "module", uses => ["inner_diamond_N.pm", "outer_diamond_E.pm", "outer_diamond_W.pm"], }, "outer_diamond_S.pm" => { file => generic_abs_path("t/data/static/outer_diamond_S.pm"), key => "outer_diamond_S.pm", type => "module", used_by => ["outer_diamond_E.pm", "outer_diamond_W.pm", "inner_diamond_S.pm"], }, "outer_diamond_W.pm" => { file => generic_abs_path("t/data/static/outer_diamond_W.pm"), key => "outer_diamond_W.pm", type => "module", used_by => ["outer_diamond_N.pm"], uses => ["outer_diamond_S.pm"], }, }; # Functional i/f my $rv3 = scan_deps(cache_file => $cache_file, recurse => 1, files => \@roots3); compare_rv($rv3, $expected_rv3, \@roots3); ############################################################## # Static dependency check of the following dependency tree # (i.e. multiple inputs) # # InputA.pl InputB.pl InputC.pl # / \ \ / # / \ \ / # / \ \ / # TestA.pm TestB.pm TestC.pm / # \ / # \ / # TestD.pm # ############################################################## my @roots4 = qw(t/data/static/InputA.pl t/data/static/InputB.pl t/data/static/InputC.pl); my $expected_rv4 = { "InputA.pl" => { file => generic_abs_path("t/data/static/InputA.pl"), key => "InputA.pl", type => "data", uses => ["TestA.pm", "TestB.pm"], }, "InputB.pl" => { file => generic_abs_path("t/data/static/InputB.pl"), key => "InputB.pl", type => "data", uses => ["TestC.pm"], }, "InputC.pl" => { file => generic_abs_path("t/data/static/InputC.pl"), key => "InputC.pl", type => "data", uses => ["TestD.pm"], }, "TestA.pm" => { file => generic_abs_path("t/data/static/TestA.pm"), key => "TestA.pm", type => "module", used_by => ["InputA.pl"], }, "TestB.pm" => { file => generic_abs_path("t/data/static/TestB.pm"), key => "TestB.pm", type => "module", used_by => ["InputA.pl"], }, "TestC.pm" => { file => generic_abs_path("t/data/static/TestC.pm"), key => "TestC.pm", type => "module", used_by => ["InputB.pl"], uses => ["TestD.pm"], }, "TestD.pm" => { file => generic_abs_path("t/data/static/TestD.pm"), key => "TestD.pm", type => "module", used_by => ["InputC.pl", "TestC.pm"], }, }; # Functional i/f my $rv4 = scan_deps(cache_file => $cache_file, recurse => 1, files => \@roots4); compare_rv($rv4, $expected_rv4, \@roots4); ############################################################## # Static dependency check of the following dependency tree # Tests the .pm only lists the .pl once in it's used_by entries # # Duplicator.pl # / \ # / \ # / \ # \ / # \ / # \ / # Duplicated.pm # ############################################################## my @roots5 = qw(t/data/static/Duplicator.pl); my $expected_rv5 = { "Duplicated.pm" => { file => generic_abs_path("t/data/static/Duplicated.pm"), key => "Duplicated.pm", type => "module", used_by => ["Duplicator.pl"], }, "Duplicator.pl" => { file => generic_abs_path("t/data/static/Duplicator.pl"), key => "Duplicator.pl", type => "data", uses => ["Duplicated.pm"], }, }; # Functional i/f my $rv5 = scan_deps(cache_file => $cache_file, recurse => 1, files => \@roots5); compare_rv($rv5, $expected_rv5, \@roots5); } ### SKIP block wrapping M::SD::Cache tests } ### end of for (qw/write_cache use_cache/) ### cache testing helper functions ### { my ($cb_args, $expecting_write); sub expected_cache_cb_args{ $cb_args = shift; } sub cache_cb{ my %args = @_; is($args{key}, $cb_args->{key}, "check arg 'key' in cache_cb."); is($args{file}, $cb_args->{file}, "check arg 'file' in cache_cb."); if ( $expecting_write ){ is($args{action}, 'write', "expecting write action"); } if ($args{action} eq 'read'){ $expecting_write = 1; return 0; } elsif ( $args{action} eq 'write' ){ $expecting_write = 0; return 1 } my $action = $args{action}; ok( 0, "wrong action: got [$action] must be 'read' or 'write'"); } }### end cache testing helper functions ### ### test Module::ScanDeps::Cache.pm SKIP: { skip "Skipping M:SD::Cache tests" , 9 if $skip_cache_tests; my %files = ('file1.pl' => "use TestModule;\n", 'file2.pl' => "use TestModule;\n", 'file3.pl' => "use TestModule;\n return 0;\n"); for my $name (keys %files){ open my $fh, '>', $name or die "Can not open file $name: $!"; print $fh $files{$name}; close $fh or die "Can not close file $name: $!"; } my $cb = Module::ScanDeps::Cache::get_cache_cb(); my $mod = []; my $ret = $cb->(key => 'testfile', file => 'file1.pl', action => 'read', modules => $mod ); is( $ret, 0, "File not present in cache"); $ret = $cb->(key => 'testfile', file => 'file1.pl', modules => [qw /TestModule.pm/], action => 'write', ); is( $ret, 1, "Writing file to cache"); $ret = $cb->(key => 'testfile', file => 'file1.pl', action => 'read', modules => $mod ); is( $ret, 1, "File is present in cache"); is( $mod->[0], 'TestModule.pm', "cache_cb sets modules 1"); $mod = []; $ret = $cb->(key => 'testfile', file => 'file2.pl', action => 'read', modules => $mod ); is( $ret, 1, "Identical file returns the same dependencies from cache"); is( $mod->[0], 'TestModule.pm', "cache_cb sets modules 2"); $mod = []; $ret = $cb->(key => 'testfile', file => 'file3.pl', action => 'read', modules => $mod ); is( $ret, 0, "No cached deps returned for file with different content"); is( @$mod, 0, "cache_cb does not set modules if no deps found"); eval {$cb->(action => 'foo')}; ok ($@ =~ /must be read or write/, "cache_cb dies on wrong action"); for my $name (keys %files){ unlink $name or die "Could not unlink file $name: $!"; } } unlink( $cache_file ); __END__ Module-ScanDeps-1.37/t/4-static_functional_interface_options_fake.t0000644000175000017500000003776114442277250025657 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More tests => 8; use lib qw(t t/data/static); use Utils; ############################################################## # Tests compilation of Module::ScanDeps ############################################################## BEGIN { use_ok( 'Module::ScanDeps' ); } ############################################################## # RECURSE OPTION TESTS ############################################################## # Using the following dependency tree # # M # /|\ # / | \ # / | \ # / M \ # / / \ \ # / / \ \ # M M M M # \ \ / / # \ \ / / # \ M / # \ | / # \ | / # M # # With dependencies always going from the top downwards ############################################################## my @roots1 = qw(t/data/static/outer_diamond_N.pm); my $expected_rv1 = { "inner_diamond_N.pm" => { file => generic_abs_path("t/data/static/inner_diamond_N.pm"), key => "inner_diamond_N.pm", type => "module", used_by => ["outer_diamond_N.pm"], }, "outer_diamond_E.pm" => { file => generic_abs_path("t/data/static/outer_diamond_E.pm"), key => "outer_diamond_E.pm", type => "module", used_by => ["outer_diamond_N.pm"], }, "outer_diamond_N.pm" => { file => generic_abs_path("t/data/static/outer_diamond_N.pm"), key => "outer_diamond_N.pm", type => "module", uses => ["inner_diamond_N.pm", "outer_diamond_E.pm", "outer_diamond_W.pm"], }, "outer_diamond_W.pm" => { file => generic_abs_path("t/data/static/outer_diamond_W.pm"), key => "outer_diamond_W.pm", type => "module", used_by => ["outer_diamond_N.pm"], }, }; my $rv1 = scan_deps( files => \@roots1, recurse => 0, ); compare_rv($rv1, $expected_rv1, \@roots1); ############################################################## # Using the following dependency tree # # InputA.pl InputB.pl InputC.pl # / \ \ / # / \ \ / # / \ \ / # TestA.pm TestB.pm TestC.pm / # \ / # \ / # TestD.pm # ############################################################## my @roots2 = qw(t/data/static/InputA.pl t/data/static/InputB.pl t/data/static/InputC.pl); my $expected_rv2 = { "InputA.pl" => { file => generic_abs_path("t/data/static/InputA.pl"), key => "InputA.pl", type => "data", uses => ["TestA.pm", "TestB.pm"], }, "InputB.pl" => { file => generic_abs_path("t/data/static/InputB.pl"), key => "InputB.pl", type => "data", uses => ["TestC.pm"], }, "InputC.pl" => { file => generic_abs_path("t/data/static/InputC.pl"), key => "InputC.pl", type => "data", uses => ["TestD.pm"], }, "TestA.pm" => { file => generic_abs_path("t/data/static/TestA.pm"), key => "TestA.pm", type => "module", used_by => ["InputA.pl"], }, "TestB.pm" => { file => generic_abs_path("t/data/static/TestB.pm"), key => "TestB.pm", type => "module", used_by => ["InputA.pl"], }, "TestC.pm" => { file => generic_abs_path("t/data/static/TestC.pm"), key => "TestC.pm", type => "module", used_by => ["InputB.pl"], }, "TestD.pm" => { file => generic_abs_path("t/data/static/TestD.pm"), key => "TestD.pm", type => "module", used_by => ["InputC.pl"], # No "TestC.pm" used_by entry }, }; my $rv2 = scan_deps( files => \@roots2, recurse => 0, ); compare_rv($rv2, $expected_rv2, \@roots2); ############################################################## # SKIP OPTION TESTS ############################################################## # Dependency tree for tests # # InputA.pl InputB.pl InputC.pl # / \ \ / # / \ \ / # / \ \ / # TestA.pm TestB.pm TestC.pm / # \ / # \ / # TestD.pm # ############################################################## my @roots_ABC = qw(t/data/static/InputA.pl t/data/static/InputB.pl t/data/static/InputC.pl); ############################################################## my $expected_rv_ABC_skip_TestA = { "InputA.pl" => { file => generic_abs_path("t/data/static/InputA.pl"), key => "InputA.pl", type => "data", uses => ["TestA.pm", "TestB.pm"], }, "InputB.pl" => { file => generic_abs_path("t/data/static/InputB.pl"), key => "InputB.pl", type => "data", uses => ["TestC.pm"], }, "InputC.pl" => { file => generic_abs_path("t/data/static/InputC.pl"), key => "InputC.pl", type => "data", uses => ["TestD.pm"], }, # It's OK to have this despite TestA.pm being skipped since this entry only shows # InputA.pl has been parsed and shown to depend on TestA.pm "TestA.pm" => { file => generic_abs_path("t/data/static/TestA.pm"), key => "TestA.pm", type => "module", used_by => ["InputA.pl"], }, "TestB.pm" => { file => generic_abs_path("t/data/static/TestB.pm"), key => "TestB.pm", type => "module", used_by => ["InputA.pl"], }, "TestC.pm" => { file => generic_abs_path("t/data/static/TestC.pm"), key => "TestC.pm", type => "module", used_by => ["InputB.pl"], uses => ["TestD.pm"], }, "TestD.pm" => { file => generic_abs_path("t/data/static/TestD.pm"), key => "TestD.pm", type => "module", used_by => ["InputC.pl", "TestC.pm"], }, }; my $rv3 = scan_deps( files => \@roots_ABC, skip => { generic_abs_path("t/data/static/TestA.pm") => 1 }, recurse => 1, ); compare_rv($rv3, $expected_rv_ABC_skip_TestA, \@roots_ABC); ############################################################## my $expected_rv_ABC_skip_TestC = { "InputA.pl" => { file => generic_abs_path("t/data/static/InputA.pl"), key => "InputA.pl", type => "data", uses => ["TestA.pm", "TestB.pm"], }, "InputB.pl" => { file => generic_abs_path("t/data/static/InputB.pl"), key => "InputB.pl", type => "data", uses => ["TestC.pm"], }, "InputC.pl" => { file => generic_abs_path("t/data/static/InputC.pl"), key => "InputC.pl", type => "data", uses => ["TestD.pm"], }, "TestA.pm" => { file => generic_abs_path("t/data/static/TestA.pm"), key => "TestA.pm", type => "module", used_by => ["InputA.pl"], }, "TestB.pm" => { file => generic_abs_path("t/data/static/TestB.pm"), key => "TestB.pm", type => "module", used_by => ["InputA.pl"], }, # It's OK to have this despite TestC.pm being skipped since this entry only shows # InputB.pl has been parsed and shown to depend on TestC.pm "TestC.pm" => { file => generic_abs_path("t/data/static/TestC.pm"), key => "TestC.pm", type => "module", used_by => ["InputB.pl"], }, "TestD.pm" => { file => generic_abs_path("t/data/static/TestD.pm"), key => "TestD.pm", type => "module", used_by => ["InputC.pl"], # No TestC used_by }, }; my $rv4 = scan_deps( files => \@roots_ABC, skip => { generic_abs_path("t/data/static/TestC.pm") => 1 }, recurse => 1, ); compare_rv($rv4, $expected_rv_ABC_skip_TestC, \@roots_ABC); ############################################################## # Test multiple skip entries my $expected_rv_ABC_skip_TestA_TestC = { "InputA.pl" => { file => generic_abs_path("t/data/static/InputA.pl"), key => "InputA.pl", type => "data", uses => ["TestA.pm", "TestB.pm"], }, "InputB.pl" => { file => generic_abs_path("t/data/static/InputB.pl"), key => "InputB.pl", type => "data", uses => ["TestC.pm"], }, "InputC.pl" => { file => generic_abs_path("t/data/static/InputC.pl"), key => "InputC.pl", type => "data", uses => ["TestD.pm"], }, # It's OK to have this despite TestA.pm being skipped since this entry only shows # InputA.pl has been parsed and shown to depend on TestA.pm "TestA.pm" => { file => generic_abs_path("t/data/static/TestA.pm"), key => "TestA.pm", type => "module", used_by => ["InputA.pl"], }, "TestB.pm" => { file => generic_abs_path("t/data/static/TestB.pm"), key => "TestB.pm", type => "module", used_by => ["InputA.pl"], }, # It's OK to have this despite TestC.pm being skipped since this entry only shows # InputB.pl has been parsed and shown to depend on TestC.pm "TestC.pm" => { file => generic_abs_path("t/data/static/TestC.pm"), key => "TestC.pm", type => "module", used_by => ["InputB.pl"], }, "TestD.pm" => { file => generic_abs_path("t/data/static/TestD.pm"), key => "TestD.pm", type => "module", used_by => ["InputC.pl"], # No TestC used_by }, }; my $rv5 = scan_deps( files => \@roots_ABC, skip => { generic_abs_path("t/data/static/TestA.pm") => 1, generic_abs_path("t/data/static/TestC.pm") => 1, }, recurse => 1, ); compare_rv($rv5, $expected_rv_ABC_skip_TestA_TestC, \@roots_ABC); ############################################################## my @roots_AB = qw(t/data/static/InputA.pl t/data/static/InputB.pl); my $expected_rv_AB_skip_TestC = { "InputA.pl" => { file => generic_abs_path("t/data/static/InputA.pl"), key => "InputA.pl", type => "data", uses => ["TestA.pm", "TestB.pm"], }, "InputB.pl" => { file => generic_abs_path("t/data/static/InputB.pl"), key => "InputB.pl", type => "data", uses => ["TestC.pm"], }, "TestA.pm" => { file => generic_abs_path("t/data/static/TestA.pm"), key => "TestA.pm", type => "module", used_by => ["InputA.pl"], }, "TestB.pm" => { file => generic_abs_path("t/data/static/TestB.pm"), key => "TestB.pm", type => "module", used_by => ["InputA.pl"], }, # It's OK to have this despite TestC.pm being skipped since this entry only shows # InputB.pl has been parsed and shown to depend on TestC.pm "TestC.pm" => { file => generic_abs_path("t/data/static/TestC.pm"), key => "TestC.pm", type => "module", used_by => ["InputB.pl"], }, # # No TestD entry # }; my $rv6 = scan_deps( files => \@roots_AB, skip => { generic_abs_path("t/data/static/TestC.pm") => 1 }, recurse => 1, ); compare_rv($rv6, $expected_rv_AB_skip_TestC, \@roots_AB); ############################################################## my $expected_rv_AB_skip_TestD = { "InputA.pl" => { file => generic_abs_path("t/data/static/InputA.pl"), key => "InputA.pl", type => "data", uses => ["TestA.pm", "TestB.pm"], }, "InputB.pl" => { file => generic_abs_path("t/data/static/InputB.pl"), key => "InputB.pl", type => "data", uses => ["TestC.pm"], }, "TestA.pm" => { file => generic_abs_path("t/data/static/TestA.pm"), key => "TestA.pm", type => "module", used_by => ["InputA.pl"], }, "TestB.pm" => { file => generic_abs_path("t/data/static/TestB.pm"), key => "TestB.pm", type => "module", used_by => ["InputA.pl"], }, "TestC.pm" => { file => generic_abs_path("t/data/static/TestC.pm"), key => "TestC.pm", type => "module", used_by => ["InputB.pl"], }, # # No TestD entry # }; my $rv7 = scan_deps( files => \@roots_AB, skip => { "t/data/static/TestD.pm" => 1 }, recurse => 1, ); #is_deeply($rv7, $expected_rv_AB_skip_TestD); compare_rv($rv7, $expected_rv_AB_skip_TestD, \@roots_AB); Module-ScanDeps-1.37/t/18-findbin.t0000644000175000017500000000063513716217376017057 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More tests => 3; use FindBin; BEGIN { use_ok( 'Module::ScanDeps' ); } my $saved_bin = $FindBin::Bin; my $deps = scan_deps(files => [ 't/data/use-findbin.pl' ], recurse => 1); ok($deps->{"Net/FTP.pm"}, q[Net::FTP seen in module found via 'use lib "$FindBin::Bin/..."']); is($FindBin::Bin, $saved_bin, '$FindBin::Bin unchanged after call to scan_deps()'); Module-ScanDeps-1.37/t/16-scan_line.t0000644000175000017500000000650014335652752017373 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More; use Module::ScanDeps qw/scan_line/; my @tests = ( { chunk => 'use strict;', expected => 'strict.pm', }, { chunk => 'require 5.10;', expected => 'feature.pm', }, { # use 5.010 in one-liners was only returning feature.pm (actually, 5.9.5 or higher) chunk => 'use 5.010; use MyModule::PlaceHolder1;', expected => 'feature.pm MyModule/PlaceHolder1.pm', comment => 'got more than just feature.pm when "use 5.010" in one-liner', }, { # use 5.009 in one-liners should not return feature.pm chunk => 'use 5.009; use MyModule::PlaceHolder1;', expected => 'MyModule/PlaceHolder1.pm', comment => 'did not get feature.pm when "use 5.009" in one-liner', }, { # avoid early return when pragma is found in one-liners chunk => 'use if 1, MyModule::PlaceHolder2; use MyModule::PlaceHolder1;', expected => 'if.pm MyModule/PlaceHolder1.pm MyModule/PlaceHolder2.pm', comment => 'if-pragma used in one-liner', }, { # avoid early return when pragma is found in one-liners chunk => 'use autouse "MyModule::PlaceHolder2"; use MyModule::PlaceHolder1;', expected => 'autouse.pm MyModule/PlaceHolder1.pm MyModule/PlaceHolder2.pm', comment => 'autouse pragma used in one-liner', }, { chunk => "{ package foo; use if 1, 'warnings' }", expected => 'if.pm warnings.pm', }, { chunk => "{ use if 1, 'warnings' }", expected => 'if.pm warnings.pm', }, { chunk => " do { use if 1, 'warnings' }", expected => 'if.pm warnings.pm', }, { chunk => " do { use foo }", expected => 'foo.pm', }, { chunk => " eval { require Win32::WinError };", expected => 'Win32/WinError.pm', }, { chunk => ' if ( eval { require Win32::WinError } ) {', expected => 'Win32/WinError.pm', }, { chunk => " eval { require Win32::WinError; 1 };", expected => 'Win32/WinError.pm', }, { chunk => ' if ( eval { require Win32::WinError; 1 } ) {', expected => 'Win32/WinError.pm', }, { chunk => ' eval "require Win32::WinError; 1";', expected => 'Win32/WinError.pm', }, { chunk => ' eval q(require Win32::WinError; 1);', expected => 'Win32/WinError.pm', }, { chunk => ' eval qq[require Win32::WinError; 1];', expected => 'Win32/WinError.pm', }, { chunk => ' eval qq {require Win32::WinError; 1};', expected => 'Win32/WinError.pm', }, { chunk => ' if ( eval "require Win32::WinError; 1" ) {', expected => 'Win32/WinError.pm', }, { chunk => ' eval "require Win32::WinError";', expected => 'Win32/WinError.pm', }, { chunk => ' if ( eval "require Win32::WinError" ) {', expected => 'Win32/WinError.pm', }, ); plan tests => 1+@tests; # RT#48151 eval { scan_line('require __PACKAGE__ . "SomeExt.pm";') }; is($@,''); foreach my $t (@tests) { my @got = scan_line($t->{chunk}); my @exp = split(' ', $t->{expected}); is_deeply([sort @got], [sort @exp], $t->{comment} || $t->{chunk}); } Module-ScanDeps-1.37/t/17-private_methods.t0000644000175000017500000000034613014631605020623 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More tests => 1; use Module::ScanDeps (); my @deps = Module::ScanDeps::_get_preload('Event.pm'); ok(grep {$_ eq 'Event/idle.pm'} @deps) or diag(join(', ',@deps)); Module-ScanDeps-1.37/t/1-static_functional_interface_real.t0000644000175000017500000000156214442574442024107 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use lib 't'; use Test::More tests => 2; use Utils; ############################################################## # Tests compilation of Module::ScanDeps ############################################################## BEGIN { use_ok( 'Module::ScanDeps' ); } ############################################################## # Tests static dependency scanning on a real set of modules. # This exercises the scanning functionality but because the # majority of files scanned aren't fixed, the checks are # necessarily loose. ############################################################## my $root = "t/data/minimal.pl"; my @deps = qw( Carp.pm Exporter.pm XSLoader.pm DynaLoader.pm strict.pm warnings.pm Data/Dumper.pm ); # Functional i/f my $rv = scan_deps($root); check_rv($rv, [$root], \@deps); Module-ScanDeps-1.37/t/data/0000755000175000017500000000000014715360221015724 5ustar roderichroderichModule-ScanDeps-1.37/t/data/static/0000755000175000017500000000000014715360221017213 5ustar roderichroderichModule-ScanDeps-1.37/t/data/static/chicken.pm0000644000175000017500000000005313014631605021151 0ustar roderichroderichpackage chicken; use egg; 1; __END__Module-ScanDeps-1.37/t/data/static/InputB.pl0000644000175000017500000000001513014631605020743 0ustar roderichroderichuse TestC.pm;Module-ScanDeps-1.37/t/data/static/null.pl0000644000175000017500000000000013014631605020506 0ustar roderichroderichModule-ScanDeps-1.37/t/data/static/TestD.pm0000644000175000017500000000003513014631605020570 0ustar roderichroderichpackage TestD; 1; __END__Module-ScanDeps-1.37/t/data/static/TestB.pm0000644000175000017500000000003513014631605020566 0ustar roderichroderichpackage TestB; 1; __END__Module-ScanDeps-1.37/t/data/static/inner_diamond_E.pm0000644000175000017500000000007713014631605022625 0ustar roderichroderichpackage inner_diamond_E; use inner_diamond_S; 1; __END__Module-ScanDeps-1.37/t/data/static/inner_diamond_N.pm0000644000175000017500000000012513014631605022630 0ustar roderichroderichpackage inner_diamond_N; use inner_diamond_E; use inner_diamond_W; 1; __END__Module-ScanDeps-1.37/t/data/static/egg.pm0000644000175000017500000000005313014631605020307 0ustar roderichroderichpackage egg; use chicken; 1; __END__Module-ScanDeps-1.37/t/data/static/outer_diamond_E.pm0000644000175000017500000000007713014631605022650 0ustar roderichroderichpackage outer_diamond_E; use outer_diamond_S; 1; __END__Module-ScanDeps-1.37/t/data/static/InputC.pl0000644000175000017500000000001513014631605020744 0ustar roderichroderichuse TestD.pm;Module-ScanDeps-1.37/t/data/static/TestA.pm0000644000175000017500000000003513014631605020565 0ustar roderichroderichpackage TestA; 1; __END__Module-ScanDeps-1.37/t/data/static/Duplicator.pl0000644000175000017500000000004613014631605021654 0ustar roderichroderichuse Duplicated.pm; use Duplicated.pm;Module-ScanDeps-1.37/t/data/static/useVERSION.pm0000644000175000017500000000006013014631605021405 0ustar roderichroderichpackage useVERSION; use 5.010; 1; __END__ Module-ScanDeps-1.37/t/data/static/inner_diamond_W.pm0000644000175000017500000000007713014631605022647 0ustar roderichroderichpackage inner_diamond_W; use inner_diamond_S; 1; __END__Module-ScanDeps-1.37/t/data/static/outer_diamond_W.pm0000644000175000017500000000007713014631605022672 0ustar roderichroderichpackage outer_diamond_W; use outer_diamond_S; 1; __END__Module-ScanDeps-1.37/t/data/static/TestC.pm0000644000175000017500000000005313014631605020567 0ustar roderichroderichpackage TestC; use TestD; 1; __END__Module-ScanDeps-1.37/t/data/static/outer_diamond_N.pm0000644000175000017500000000015313014631605022654 0ustar roderichroderichpackage outer_diamond_N; use outer_diamond_E; use outer_diamond_W; use inner_diamond_N; 1; __END__Module-ScanDeps-1.37/t/data/static/Duplicated.pm0000644000175000017500000000004213014631605021621 0ustar roderichroderichpackage Duplicated; 1; __END__Module-ScanDeps-1.37/t/data/static/inner_diamond_S.pm0000644000175000017500000000007713014631605022643 0ustar roderichroderichpackage inner_diamond_S; use outer_diamond_S; 1; __END__Module-ScanDeps-1.37/t/data/static/outer_diamond_S.pm0000644000175000017500000000004713014631605022663 0ustar roderichroderichpackage outer_diamond_S; 1; __END__Module-ScanDeps-1.37/t/data/static/InputA.pl0000644000175000017500000000003413014631605020743 0ustar roderichroderichuse TestA.pm; use TestB.pm;Module-ScanDeps-1.37/t/data/prefork.pl0000644000175000017500000000006314442573346017743 0ustar roderichroderichuse strict; use warnings; use prefork qw( less ); Module-ScanDeps-1.37/t/data/file-glob-yes.pl0000644000175000017500000000021013014631605020706 0ustar roderichroderichmy $serial_asn1; my $i; package Foo; sub length {1}; package main; bless $serial_asn1 => 'Foo'; for ($i=0; $i++,$_=<*.txt>; $i++) { } Module-ScanDeps-1.37/t/data/autosplit/0000755000175000017500000000000014715360221017750 5ustar roderichroderichModule-ScanDeps-1.37/t/data/autosplit/auto/0000755000175000017500000000000014715360221020720 5ustar roderichroderichModule-ScanDeps-1.37/t/data/autosplit/auto/Foo/0000755000175000017500000000000014715360221021443 5ustar roderichroderichModule-ScanDeps-1.37/t/data/autosplit/auto/Foo/barnie.al0000644000175000017500000000035514503007110023212 0ustar roderichroderich# NOTE: Derived from Foo.pm. # Changes made here will be lost when autosplit is run again. # See AutoSplit.pm. package Foo; #line 19 "Foo.pm (autosplit into auto/Foo/barnie.al)" sub barnie { print "barnie!\n" }; 1; # end of Foo::barnie Module-ScanDeps-1.37/t/data/autosplit/auto/Foo/autosplit.ix0000644000175000017500000000016014503007110024014 0ustar roderichroderich# Index created by AutoSplit for Foo.pm # (file acts as timestamp) package Foo; sub fred ; sub barnie ; 1; Module-ScanDeps-1.37/t/data/autosplit/auto/Foo/fred.al0000644000175000017500000000034514503007110022671 0ustar roderichroderich# NOTE: Derived from Foo.pm. # Changes made here will be lost when autosplit is run again. # See AutoSplit.pm. package Foo; #line 18 "Foo.pm (autosplit into auto/Foo/fred.al)" sub fred { print "fred!\n" }; # end of Foo::fred 1; Module-ScanDeps-1.37/t/data/autosplit/Foo.pm0000644000175000017500000000040514503007135021025 0ustar roderichroderichpackage Foo; use strict; use warnings; use AutoLoader 'AUTOLOAD'; sub blab { my @blab = @_; print "begin blab\n"; print "$_\n" foreach @blab; print "end blab\n"; } 1; __END__ sub fred { print "fred!\n" }; sub barnie { print "barnie!\n" }; Module-ScanDeps-1.37/t/data/use-findbin.pl0000644000175000017500000000014513716212734020471 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use FindBin; use lib "$FindBin::Bin/modules"; use Foo; Module-ScanDeps-1.37/t/data/duplicated_entries/0000755000175000017500000000000014715360221021573 5ustar roderichroderichModule-ScanDeps-1.37/t/data/duplicated_entries/Scoped/0000755000175000017500000000000014715360221023010 5ustar roderichroderichModule-ScanDeps-1.37/t/data/duplicated_entries/Scoped/Package.pm0000644000175000017500000000004713014631605024700 0ustar roderichroderichpackage Scoped::Package; 1; __END__Module-ScanDeps-1.37/t/data/duplicated_entries/use_scoped_package.pl0000644000175000017500000000005113014631605025726 0ustar roderichroderich#!/usr/bin/perl use Scoped::Package; Module-ScanDeps-1.37/t/data/use_lib.pl0000644000175000017500000000011013014631605017671 0ustar roderichroderich#!/usr/bin/perl use lib "t/data/check_path_to_inc_name"; use Some; Module-ScanDeps-1.37/t/data/case-insensitive-keys/0000755000175000017500000000000014715360221022146 5ustar roderichroderichModule-ScanDeps-1.37/t/data/case-insensitive-keys/Foo2.pm0000644000175000017500000000005414443556266023326 0ustar roderichroderichpackage Foo2; use Cwd; $foo->cwd->foo(); 1; Module-ScanDeps-1.37/t/data/case-insensitive-keys/Foo.pm0000644000175000017500000000002414443556266023241 0ustar roderichroderichpackage Foo; 1; Module-ScanDeps-1.37/t/data/case-insensitive-keys/that_case.pl0000644000175000017500000000001614443556266024451 0ustar roderichroderichuse foo; 1; Module-ScanDeps-1.37/t/data/case-insensitive-keys/this_case.pl0000644000175000017500000000001614443556266024460 0ustar roderichroderichuse Foo; 1; Module-ScanDeps-1.37/t/data/check_path_to_inc_name/0000755000175000017500000000000014715360221022350 5ustar roderichroderichModule-ScanDeps-1.37/t/data/check_path_to_inc_name/Scoped/0000755000175000017500000000000014715360221023565 5ustar roderichroderichModule-ScanDeps-1.37/t/data/check_path_to_inc_name/Scoped/Package.pm0000644000175000017500000000004713014631605025455 0ustar roderichroderichpackage Scoped::Package; 1; __END__Module-ScanDeps-1.37/t/data/check_path_to_inc_name/use_scoped_package.pl0000644000175000017500000000005113014631605026503 0ustar roderichroderich#!/usr/bin/perl use Scoped::Package; Module-ScanDeps-1.37/t/data/check_path_to_inc_name/Some.pm0000644000175000017500000000003413014631605023604 0ustar roderichroderichpackage Some; 1; __END__Module-ScanDeps-1.37/t/data/modules/0000755000175000017500000000000014715360221017374 5ustar roderichroderichModule-ScanDeps-1.37/t/data/modules/Foo.pm0000644000175000017500000000004013716212747020460 0ustar roderichroderichpackage Foo; use Net::FTP; 1; Module-ScanDeps-1.37/t/data/ScanFileRE/0000755000175000017500000000000014715360221017637 5ustar roderichroderichModule-ScanDeps-1.37/t/data/ScanFileRE/auto/0000755000175000017500000000000014715360221020607 5ustar roderichroderichModule-ScanDeps-1.37/t/data/ScanFileRE/auto/example/0000755000175000017500000000000014715360221022242 5ustar roderichroderichModule-ScanDeps-1.37/t/data/ScanFileRE/auto/example/example.h0000644000175000017500000000002013014631605024034 0ustar roderichroderichuse example_too;Module-ScanDeps-1.37/t/data/ScanFileRE/example.pm0000644000175000017500000000003013014631605021617 0ustar roderichroderichpackage example; 1; Module-ScanDeps-1.37/t/data/ScanFileRE/example_too.pm0000644000175000017500000000003413014631605022504 0ustar roderichroderichpackage example_too; 1; Module-ScanDeps-1.37/t/data/pluggable/0000755000175000017500000000000014715360221017666 5ustar roderichroderichModule-ScanDeps-1.37/t/data/pluggable/Foo/0000755000175000017500000000000014715360221020411 5ustar roderichroderichModule-ScanDeps-1.37/t/data/pluggable/Foo/Plugin/0000755000175000017500000000000014715360221021647 5ustar roderichroderichModule-ScanDeps-1.37/t/data/pluggable/Foo/Plugin/Bar.pm0000644000175000017500000000004113014631605022702 0ustar roderichroderichpackage Foo::Plugin::Bar; 1; Module-ScanDeps-1.37/t/data/pluggable/Foo/Plugin/Baz.pm0000644000175000017500000000004113014631605022712 0ustar roderichroderichpackage Foo::Plugin::Baz; 1; Module-ScanDeps-1.37/t/data/pluggable/Foo.pm0000644000175000017500000000005613014631605020746 0ustar roderichroderichpackage Foo; use Module::Pluggable; 1; Module-ScanDeps-1.37/t/data/file-glob-no.pl0000644000175000017500000000022013014631605020523 0ustar roderichroderichmy $serial_asn1; my $i; package Foo; sub length {1}; package main; bless $serial_asn1 => 'Foo'; for ($i=0; $i<$serial_asn1->length; $i++) { } Module-ScanDeps-1.37/t/data/minimal.pl0000644000175000017500000000005414442574356017723 0ustar roderichroderichuse strict; use warnings; use Data::Dumper; Module-ScanDeps-1.37/t/data/rt90869.pl0000644000175000017500000000032413014631605017323 0ustar roderichroderich# some forms of "use autouse ..." use autouse TestA => qw(foo bar); use autouse "TestB", qw(foo bar); # "use if ..." (note the function call in COND) sub frobnicate { 1 } use if frobnicate(), TestC => qw(quux); Module-ScanDeps-1.37/t/7-check-dynaloader.t0000644000175000017500000000626114002064072020540 0ustar roderichroderich#!perl use strict; use Test::More; use Config; use Module::ScanDeps; use DynaLoader; use File::Temp; use Data::Dumper; plan skip_all => "No dynamic loading available in your version of perl" unless $Config{usedl}; my @try_mods = qw( File::Glob Data::Dumper List::Util Time::HiRes Compress::Raw::Zlib ); my @dyna_mods = grep { my $mod = $_; eval("require $mod; 1") && grep { $_ eq $mod } @DynaLoader::dl_modules } @try_mods; plan skip_all => "No dynamic module found (tried @try_mods)" unless @dyna_mods; my $extra_verbose = ($ENV{TEST_VERBOSE}||0) > 1; diag "dynamic modules used for test: @dyna_mods"; if ($extra_verbose) { diag "\@DynaLoader::dl_modules = @DynaLoader::dl_modules"; diag "\@DynaLoader::dl_shared_objects = @DynaLoader::dl_shared_objects"; } plan tests => 4 * 2 * @dyna_mods; foreach my $module (@dyna_mods) { # cf. DynaLoader.pm my @modparts = split(/::/,$module); my $modfname = defined &DynaLoader::mod2fname ? DynaLoader::mod2fname(\@modparts) : $modparts[-1]; my $auto_path = join('/', 'auto', @modparts, "$modfname.$Config{dlext}"); check_bundle_path(static => $module, $auto_path, sub { scan_deps( files => [ $_[0] ], recurse => 0); }, ".pl", <<"...", use $module; 1; ... ); check_bundle_path(compile => $module, $auto_path, sub { scan_deps_runtime( files => [ $_[0] ], recurse => 0, compile => 1); }, ".pm", <<"...", package Bar; use $module; 1; ... ); check_bundle_path(execute => $module, $auto_path, sub { scan_deps_runtime( files => [ $_[0] ], recurse => 0, execute => 1); }, ".pl", <<"...", # no way in hell can this detected by static analysis :) my \$req = join("", qw( r e q u i r e )); eval "\$req $module"; exit(0); ... ); check_bundle_path(execute_with_args => $module, $auto_path, sub { scan_deps_runtime( files => [ $_[0] ], recurse => 0, execute => [ $module ]); }, ".pl", <<"...", # no way in hell can this detected by static analysis :) my \$req = join("", qw( r e q u i r e )); eval "\$req \$_" foreach \@ARGV; exit(0); ... ); } exit(0); # NOTE: check_bundle_path runs 2 tests sub check_bundle_path { my ($tag, $module, $auto_path, $scan, $suffix, $source) = @_; my ($fh, $filename) = File::Temp::tempfile( UNLINK => 1, SUFFIX => $suffix ); print $fh $source, "\n" or die $!; close $fh; my $rv = $scan->($filename); diag("check_bundle_path:$tag for $module ..."); diag(Dumper($rv)) if $extra_verbose; my ( $entry ) = grep { /^\Q$auto_path\E$/ } keys %$rv; ok($entry, "check_bundle_path:$tag for $module: ". "found some key that looks like it pulled in its shared lib (auto_path=$auto_path)"); # Actually we accept anything that ends with $auto_path. ok($rv->{$entry}{file} =~ m{/\Q$auto_path\E$}, "check_bundle_path:$tag for $module: ". "the full bundle path we got \"$rv->{$entry}{file}\" looks legit"); } Module-ScanDeps-1.37/t/3-static_oo_interface_real.t0000644000175000017500000000216614364254312022357 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More; my $rv; ############################################################## # Tests static dependency scanning on a real set of modules. # This exercises the scanning functionality but because the # majority of files scanned aren't fixed, the checks are # necessarily loose. ############################################################## my @deps = qw( Carp.pm Config.pm Exporter.pm Test/More.pm constant.pm strict.pm vars.pm Module/ScanDeps.pm ); plan tests => @deps + 3; ############################################################## # Tests compilation of Module::ScanDeps ############################################################## use_ok( 'Module::ScanDeps' ); my $obj = Module::ScanDeps->new; $obj->set_file($0); $obj->calculate_info; ok($rv = $obj->get_files); foreach my $mod (@deps) { ok(grep {$_->{store_as} eq $mod } @{$rv->{modules}}); }; use File::Basename qw/basename/; my $basename = basename($0); ok(not(grep {$_->{store_as} =~ /\Q$basename\E/} @{$rv->{modules}})); __END__ Module-ScanDeps-1.37/t/19-autosplit.t0000644000175000017500000000666414511012472017463 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use File::Temp; use File::Spec; use IPC::Run3; use Test::More; use lib 't/data/autosplit'; BEGIN { use_ok( 'Module::ScanDeps' ); } sub create_script { my ($text) = @_; my ($fh, $filename) = File::Temp::tempfile( UNLINK => 1, SUFFIX => '.pl' ); print $fh $text; close $fh; return $filename; } sub test_autosplit { my ($tag, $scan, $expected, $text) = @_; diag($tag); my $filename = create_script($text); my $rv = $scan->($filename); foreach my $mod (@$expected) { ok($rv->{$mod}, "$mod detected"); } my @bogus = grep { File::Spec->file_name_is_absolute($_) or m|^\.[/\\]| } keys %$rv; is("@bogus", "", "no bogus keys in \$rv"); } test_autosplit( 'use autosplitted module - static scan', sub { scan_deps(files => [$_[0]], recurse => 1); }, [qw(AutoLoader.pm Foo.pm auto/Foo/autosplit.ix auto/Foo/barnie.al auto/Foo/fred.al)], 'use Foo'); test_autosplit( 'use autosplitted module - runtime scan, absolute search path', sub { my $rv = eval # scan_deps_runtime() may die { scan_deps_runtime(files => [$_[0]], recurse => 1, execute => [qw(fee fo fum)]); }; if ($@) { print STDERR "scan_deps_runtime died: $@\n" if $@; return {}; } return $rv; }, [qw(AutoLoader.pm Foo.pm auto/Foo/autosplit.ix auto/Foo/barnie.al)], << '...'); use Cwd; use lib getcwd().'/t/data/autosplit'; # "use" that can't be resolved by static analysis my $Foo = "Foo"; eval "use $Foo"; die qq["use $Foo" failed: $@] if $@; Foo::blab(@ARGV); Foo::barnie(); ... test_autosplit( 'use autosplitted module - runtime scan, relative search path', sub { my $rv = eval # scan_deps_runtime() may die { scan_deps_runtime(files => [$_[0]], recurse => 1, execute => 1); }; if ($@) { print STDERR "scan_deps_runtime died: $@\n" if $@; return {}; } return $rv; }, [qw(AutoLoader.pm Foo.pm auto/Foo/autosplit.ix auto/Foo/barnie.al)], << '...'); use lib 't/data/autosplit'; # "use" that can't be resolved by static analysis my $Foo = "Foo"; eval "use $Foo"; die qq["use $Foo" failed: $@] if $@; Foo::blab(@ARGV); Foo::barnie(); ... my $scanner = create_script(<< '...'); use Module::ScanDeps; my ($file, @args) = @ARGV; scan_deps_runtime(files => [$file], recurse => 1, execute => \@args); ... my $file = create_script(<< '...'); use lib 't/data/autosplit'; # "use" that can't be resolved by static analysis my $Foo = "Foo"; eval "use $Foo"; die qq["use $Foo" failed: $@] if $@; Foo::blab(@ARGV); Foo::barnie(); ... my @args = qw(fee fo fum); # run $file with @args once and capture its output my ($out, $err); run3([$^X, $file, @args], \undef, \$out, \$err); is($?, 0, "script ran successfully") or diag("stdout:$out\nstderr:$err\n"); my $exp = $out; my $rx = join(".*", map { quotemeta($_) } @args, "barnie!"); like($exp, qr/$rx/s, "script output"); # run $scanner on $file with @args run3([$^X, "-Mblib", $scanner, $file, @args], \undef, \$out, \$err); is($?, 0, "scanner ran successfully"); is($out, $exp, "scanner output"); done_testing(); Module-ScanDeps-1.37/t/13-static_prefork_test.t0000644000175000017500000000145714443556221021513 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More tests => 2; use Test::Requires qw( prefork ); use lib 't'; use Utils; ############################################################## # Tests compilation of Module::ScanDeps ############################################################## BEGIN { use_ok( 'Module::ScanDeps' ); } ############################################################## # Tests static dependency scanning with the prefork module. # This was broken until Module::ScanDeps 0.85 ############################################################## my $root = "t/data/prefork.pl"; my @deps = qw( Carp.pm Config.pm Exporter.pm strict.pm warnings.pm prefork.pm less.pm ); # Functional i/f my $rv = scan_deps($root); check_rv($rv, [$root], \@deps); Module-ScanDeps-1.37/t/rt90869.t0000644000175000017500000000053113014631605016242 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More; use Module::ScanDeps qw(scan_deps); use lib qw(t/data/static); my @expected_modules = qw( TestA TestB TestC ); plan tests => scalar @expected_modules; my $rv = scan_deps("t/data/rt90869.pl"); foreach (@expected_modules) { ok(exists $rv->{"$_.pm"}, "expected module $_ found"); } Module-ScanDeps-1.37/t/9-check_path_to_inc_name.t0000644000175000017500000000265713014631605022002 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Cwd; use Test::More tests => 7; ############################################################## # Tests compilation of Module::ScanDeps ############################################################## BEGIN { use_ok( 'Module::ScanDeps', qw(path_to_inc_name scan_deps) ); } my $name; my $basepath; my $warn = 1; # Absolute path tests $basepath = cwd().'/t/data/check_path_to_inc_name/'; $name = 'Some.pm'; is(path_to_inc_name($basepath.$name, $warn), $name, "$name correctly returned by path_to_inc_name($basepath$name)"); $name = 'Scoped/Package.pm'; is(path_to_inc_name($basepath.$name, $warn), $name, "$name correctly returned by path_to_inc_name($basepath$name)"); # Relative path tests $basepath = 't/data/check_path_to_inc_name/'; $name = 'Some.pm'; is(path_to_inc_name($basepath.$name, $warn), $name, "$name correctly returned by path_to_inc_name($basepath$name)"); $name = 'Scoped/Package.pm'; is(path_to_inc_name($basepath.$name, $warn), $name, "$name correctly returned by path_to_inc_name($basepath$name)"); # script test $basepath = 't/data/check_path_to_inc_name/'; $name = 'use_scoped_package.pl'; is(path_to_inc_name($basepath.$name, $warn), $name, "$name correctly returned by path_to_inc_name($basepath$name)"); # 'use lib ...' my $rv = scan_deps("t/data/use_lib.pl"); ok(exists $rv->{"Some.pm"}, "'use lib ...' correctly interpreted"); __END__ Module-ScanDeps-1.37/t/8-check_duplicated_entries.t0000644000175000017500000000241614442277252022364 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More tests => 2; use lib qw(t t/data/duplicated_entries); use Utils; ############################################################## # Tests compilation of Module::ScanDeps ############################################################## BEGIN { use_ok( 'Module::ScanDeps' ); } my @roots = qw(t/data/duplicated_entries/use_scoped_package.pl t/data/duplicated_entries/Scoped/Package.pm); my $expected_rv = { "use_scoped_package.pl" => { file => generic_abs_path("t/data/duplicated_entries/use_scoped_package.pl"), key => "use_scoped_package.pl", type => "data", uses => ["Scoped/Package.pm"], }, "Scoped/Package.pm" => { file => generic_abs_path("t/data/duplicated_entries/Scoped/Package.pm"), key => "Scoped/Package.pm", type => "module", used_by => ["use_scoped_package.pl"], }, }; # Functional i/f my $rv = scan_deps(@roots); compare_rv($rv, $expected_rv, \@roots); __END__ Module-ScanDeps-1.37/t/2-static_functional_interface_fake.t0000644000175000017500000002405414442277240024070 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More tests => 8; use lib qw(t t/data/static); use Utils; use version; ############################################################## # Tests compilation of Module::ScanDeps ############################################################## BEGIN { use_ok( 'Module::ScanDeps' ); } ############################################################## # Static dependency check of a script that doesn't use # anything ############################################################## my @roots1 = qw(t/data/static/null.pl); my $expected_rv1 = { "null.pl" => { file => generic_abs_path("t/data/static/null.pl"), key => "null.pl", type => "data", }, }; # Functional i/f my $rv1 = scan_deps(@roots1); compare_rv($rv1, $expected_rv1, \@roots1); ############################################################## # Static dependency check of a circular dependency: # ___ # |/_ \ # M _M # \____/| # ############################################################## my @roots2 = qw(t/data/static/egg.pm); my $expected_rv2 = { "chicken.pm" => { file => generic_abs_path("t/data/static/chicken.pm"), key => "chicken.pm", type => "module", used_by => ["egg.pm"], uses => ["egg.pm"], }, "egg.pm" => { file => generic_abs_path("t/data/static/egg.pm"), key => "egg.pm", type => "module", used_by => ["chicken.pm"], uses => ["chicken.pm"], }, }; # Functional i/f my $rv2 = scan_deps(@roots2); compare_rv($rv2, $expected_rv2, \@roots2); ############################################################## # Static dependency check of the following dependency tree # # M # /|\ # / | \ # / | \ # / M \ # / / \ \ # / / \ \ # M M M M # \ \ / / # \ \ / / # \ M / # \ | / # \ | / # M # # With dependencies always going from the top downwards ############################################################## my @roots3 = qw(t/data/static/outer_diamond_N.pm); my $expected_rv3 = { "inner_diamond_E.pm" => { file => generic_abs_path("t/data/static/inner_diamond_E.pm"), key => "inner_diamond_E.pm", type => "module", used_by => ["inner_diamond_N.pm"], uses => ["inner_diamond_S.pm"], }, "inner_diamond_N.pm" => { file => generic_abs_path("t/data/static/inner_diamond_N.pm"), key => "inner_diamond_N.pm", type => "module", used_by => ["outer_diamond_N.pm"], uses => ["inner_diamond_E.pm", "inner_diamond_W.pm"], }, "inner_diamond_S.pm" => { file => generic_abs_path("t/data/static/inner_diamond_S.pm"), key => "inner_diamond_S.pm", type => "module", used_by => ["inner_diamond_W.pm", "inner_diamond_E.pm"], uses => ["outer_diamond_S.pm"], }, "inner_diamond_W.pm" => { file => generic_abs_path("t/data/static/inner_diamond_W.pm"), key => "inner_diamond_W.pm", type => "module", used_by => ["inner_diamond_N.pm"], uses => ["inner_diamond_S.pm"], }, "outer_diamond_E.pm" => { file => generic_abs_path("t/data/static/outer_diamond_E.pm"), key => "outer_diamond_E.pm", type => "module", used_by => ["outer_diamond_N.pm"], uses => ["outer_diamond_S.pm"], }, "outer_diamond_N.pm" => { file => generic_abs_path("t/data/static/outer_diamond_N.pm"), key => "outer_diamond_N.pm", type => "module", uses => ["inner_diamond_N.pm", "outer_diamond_E.pm", "outer_diamond_W.pm"], }, "outer_diamond_S.pm" => { file => generic_abs_path("t/data/static/outer_diamond_S.pm"), key => "outer_diamond_S.pm", type => "module", used_by => ["outer_diamond_E.pm", "outer_diamond_W.pm", "inner_diamond_S.pm"], }, "outer_diamond_W.pm" => { file => generic_abs_path("t/data/static/outer_diamond_W.pm"), key => "outer_diamond_W.pm", type => "module", used_by => ["outer_diamond_N.pm"], uses => ["outer_diamond_S.pm"], }, }; # Functional i/f my $rv3 = scan_deps(@roots3); compare_rv($rv3, $expected_rv3, \@roots3); ############################################################## # Static dependency check of the following dependency tree # (i.e. multiple inputs) # # InputA.pl InputB.pl InputC.pl # / \ \ / # / \ \ / # / \ \ / # TestA.pm TestB.pm TestC.pm / # \ / # \ / # TestD.pm # ############################################################## my @roots4 = qw(t/data/static/InputA.pl t/data/static/InputB.pl t/data/static/InputC.pl); my $expected_rv4 = { "InputA.pl" => { file => generic_abs_path("t/data/static/InputA.pl"), key => "InputA.pl", type => "data", uses => ["TestA.pm", "TestB.pm"], }, "InputB.pl" => { file => generic_abs_path("t/data/static/InputB.pl"), key => "InputB.pl", type => "data", uses => ["TestC.pm"], }, "InputC.pl" => { file => generic_abs_path("t/data/static/InputC.pl"), key => "InputC.pl", type => "data", uses => ["TestD.pm"], }, "TestA.pm" => { file => generic_abs_path("t/data/static/TestA.pm"), key => "TestA.pm", type => "module", used_by => ["InputA.pl"], }, "TestB.pm" => { file => generic_abs_path("t/data/static/TestB.pm"), key => "TestB.pm", type => "module", used_by => ["InputA.pl"], }, "TestC.pm" => { file => generic_abs_path("t/data/static/TestC.pm"), key => "TestC.pm", type => "module", used_by => ["InputB.pl"], uses => ["TestD.pm"], }, "TestD.pm" => { file => generic_abs_path("t/data/static/TestD.pm"), key => "TestD.pm", type => "module", used_by => ["InputC.pl", "TestC.pm"], }, }; # Functional i/f my $rv4 = scan_deps(@roots4); compare_rv($rv4, $expected_rv4, \@roots4); ############################################################## # Static dependency check of the following dependency tree # Tests the .pm only lists the .pl once in it's used_by entries # # Duplicator.pl # / \ # / \ # / \ # \ / # \ / # \ / # Duplicated.pm # ############################################################## my @roots5 = qw(t/data/static/Duplicator.pl); my $expected_rv5 = { "Duplicated.pm" => { file => generic_abs_path("t/data/static/Duplicated.pm"), key => "Duplicated.pm", type => "module", used_by => ["Duplicator.pl"], }, "Duplicator.pl" => { file => generic_abs_path("t/data/static/Duplicator.pl"), key => "Duplicator.pl", type => "data", uses => ["Duplicated.pm"], }, }; # Functional i/f my $rv5 = scan_deps(@roots5); compare_rv($rv5, $expected_rv5, \@roots5); ############################################################## # Static dependency check of a module that does a # use 5.010; # Note that this doesn't test as much as the other tests # since feature.pm ropes in all kinds of things. ############################################################## SKIP: { skip "Skipping 'use VERSION' tests on pre-5.10.0", 2 if version->new($]) < version->new("5.10.0"); my @roots1 = qw(t/data/static/useVERSION.pm); # Functional i/f my $rv1 = scan_deps(@roots1); ok(exists $rv1->{"useVERSION.pm"}, "use VERSION: source file included"); ok(exists $rv1->{"feature.pm"}, "use VERSION: feature.pm included"); } __END__ Module-ScanDeps-1.37/t/0-pod.t0000644000175000017500000000014013014631605016110 0ustar roderichroderichuse strict; use Test::More; use Test::Requires { "Test::Pod" => "1.00" }; all_pod_files_ok(); Module-ScanDeps-1.37/t/Utils.pm0000644000175000017500000001162314442574740016466 0ustar roderichroderichpackage Utils; use strict; use warnings; require Exporter; use Module::ScanDeps qw(path_to_inc_name); use Test::More; use Data::Dumper; our @ISA = qw(Exporter); our $VERSION = '0.1'; our @EXPORT = qw( check_rv compare_rv generic_abs_path dump_rv ); # runs 1 (toplevel) test sub check_rv { my ($rv, $input_keys, $known_deps, $name) = @_; $name ||= "Check rv"; return subtest($name, sub { # sanity check input { my %i = map { ($_, 1) } @$input_keys; die "\@input_keys overlaps with \@known_deps\n" if grep { $i{$_} } @$known_deps; } isa_ok($rv, "HASH", "\$rv is a HASH") or return; # check all input files and known deps correspond to an entry in rv my @input_keys = map { path_to_inc_name($_, 1) } @$input_keys; my @known_deps = @$known_deps; # make a copy map {$_ =~ s:\\:/:g} (@input_keys, @known_deps); ok(exists $rv->{$_}, "$_ is in \$rv") foreach (@input_keys, @known_deps); # Check general properties of the keys foreach my $k (keys %$rv) { my $v = $rv->{$k}; ok(exists($v->{key}) && $k eq $v->{key}, qq[key $k matches field "key"]); ok(exists($v->{file}) && $v->{file} =~ /(?:^|[\/\\])\Q$k\E$/ && File::Spec->file_name_is_absolute($v->{file}), qq[key $k: field "file" has been verified]); ok(exists($v->{type}) && $v->{type} =~ /^(?:module|autoload|data|shared)$/, qq[key $k: field "type" matches module|autoload|data|shared]); if (exists($v->{used_by})) { ok(@{$v->{used_by}} > 0, qq[key $k: field "used_by" isn't empty if it exists]); my %dup; ok(!(grep { ++$dup{$_} > 1 } @{$v->{used_by}}), qq[key $k: field "used by" has no duplicates]); ok(!(grep { !exists $rv->{$_} } @{$v->{used_by}}), qq[key $k: all entries in field "used by" are themselves in \$rv]); foreach my $u (@{$v->{used_by}}) { # check corresponding uses field ok(exists($rv->{$u}{uses}), qq[\$rv contains a matching "uses" field for the "used_by" entry $u for key $k]) or next; ok(scalar(grep { $_ eq $k } @{$rv->{$u}{uses}}), qq[\$rv contains a matching "uses" field for the "used_by" entry $u for key $k]); } } else { ok(scalar(grep { $_ eq $k} @input_keys), # XXX || $k =~ /Plugin/, qq[key $k: field "used by" doesn't exist so $k must be one of the input files]) } if (exists($v->{uses})) { # check corresponding used_by field foreach my $u (@{$v->{uses}}) { ok(exists($rv->{$u}{used_by}), qq[\$rv contains a matching "used_by" field for the "uses" entry $u for key $k]) or next; ok(scalar(grep { $_ eq $k } @{$rv->{$u}{used_by}}), qq[\$rv contains a matching "used_by" field for the "uses" entry $u for key $k]); } } } }); } # runs 1 (toplevel) test sub compare_rv { my ($rv_got, $rv_expected, $input_keys, $name) = @_; $name ||= "Compare rvs"; return subtest($name, sub { check_rv($rv_expected, $input_keys, []); # validate test data isa_ok($rv_got, "HASH", "\$rv_got is a HASH") or return; if (( my @missing = grep { !exists $rv_got->{$_} } keys %$rv_expected ) || ( my @surplus = grep { !exists $rv_expected->{$_} } keys %$rv_got )) { fail("missing keys in \$rv_got: @missing") if @missing; fail("surplus keys in \$rv_got: @surplus") if @surplus; return; } foreach my $k (keys %$rv_expected) { my $expected = $rv_expected->{$k}; my $got = $rv_got->{$k}; for (qw( key file type )) { ok((exists $got->{$_}) && $got->{$_} eq $expected->{$_}, qq[key $k: field "$_" matches]); } for (qw( used_by uses )) { if (exists $expected->{$_}) { ok(exists($got->{$_}), qq[key $k: field "$_" exists]) or next; is_deeply( [sort @{$got->{$_}}], [sort @{$expected->{$_}}], qq[key $k: field "$_" matches]); } } } }); } sub generic_abs_path { my ($file) = @_; $file = File::Spec->rel2abs($file); $file =~ s:\\:/:g; return $file; } sub dump_rv { my ($name, $rv) = @_; return Data::Dumper->Dump([$rv], [$name]); } 1; Module-ScanDeps-1.37/t/12-ScanFileRE.t0000644000175000017500000000333013014631605017330 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use File::Temp; use Test::More tests => 8; use lib 't/data/ScanFileRE'; BEGIN { use_ok( 'Module::ScanDeps' ); } # Test that ScanFileRE is applied to the input files my ($fh, $filename) = File::Temp::tempfile( UNLINK => 1, SUFFIX => '.na' ); ok(defined $Module::ScanDeps::ScanFileRE, "ScanFileRE is accessible outside Module::ScanDeps"); ok($filename !~ $Module::ScanDeps::ScanFileRE, "$filename does not match"); my $rv = scan_deps(files => [$filename]); ok( !(scalar grep { /\Q$filename\E/ } keys %$rv), "ScanFileRE removed non-matching input files" ); my ($fh2, $filename2) = File::Temp::tempfile( UNLINK => 1 ); ok($filename2 =~ $Module::ScanDeps::ScanFileRE, "$filename2 does match"); my $rv2 = scan_deps(files => [$filename2]); my $basename = $filename2; $basename =~ s/^.*(?:\/|\\)([^\\\/]+)$/$1/; ok( (scalar grep { /\Q$basename\E/ } keys %$rv2) == 1, "ScanFileRE did not remove matching input files" ); # The next two tests rely on t/data/ScanFileRE/auto/example/example.h using t/data/ScanFileRE/example_too.pm # Test that the default ScanFileRE is applied to the used files $rv = scan_deps(files => ['t/data/ScanFileRE/example.pm'], recurse => 1); ok( !(scalar grep { /example_too\.pm/ } keys %$rv), "ScanFileRE only scanned matching files in the dependency tree" ); # Test that ScanFileRE can be changed to now pick up all files in the dependency tree $Module::ScanDeps::ScanFileRE = qr/.*/; $rv = scan_deps(files => ['t/data/ScanFileRE/example.pm'], recurse => 1); ok( (scalar grep { /example_too\.pm/ } keys %$rv), "M::SD recognised the new ScanFileRE and scanned all files in the dependency tree" ); __END__ Module-ScanDeps-1.37/t/20-_parse_libs.t0000644000175000017500000000127714713664622017722 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More; use Module::ScanDeps; my $libs = <<'EOF'; "/1//foo/bar", '/2/foo/bar', "C:\\3\\foo\\bar", q"/4/foo/bar", qq"/5/foo/bar", q:/6/foo/bar:, qq:/7/foo/bar:, q/\/8\/foo\/bar/, qq/\\9\/foo\/bar/, q[/10/foo/bar], qq(/11/foo/bar), q[/12/foo/bar], qq(/13/foo/bar), q[/14/foo/bar(1)], ("/15/foo", '/16/bar', q/17quux/), qw(fred barnie), qw/wilma pebbles betty bamm-bamm/, EOF my @expected = eval "($libs)"; plan tests => scalar @expected; #diag("---\n$libs---"); #diag("@expected"); while ((my $got, $libs) = Module::ScanDeps::_parse_libs($libs)) { is $got, shift @expected; } Module-ScanDeps-1.37/t/10-case-insensitive-keys.t0000644000175000017500000000376514443556266021671 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use File::Spec; use Test::More; BEGIN { if(!File::Spec->case_tolerant()) { plan skip_all => 'Test irrelevant on case-sensitive systems'; } else { plan tests => 3; } } use lib qw(t t/data/case-insensitive-keys); use Utils; ############################################################## # Tests compilation of Module::ScanDeps ############################################################## BEGIN { use_ok( 'Module::ScanDeps' ); } ############################################################## # Static dependency check of scripts that reference the same # module but in different cases ############################################################## my @roots1 = qw(t/data/case-insensitive-keys/this_case.pl t/data/case-insensitive-keys/that_case.pl); my $expected_rv1 = { "Foo.pm" => { file => generic_abs_path("t/data/case-insensitive-keys/Foo.pm"), key => "Foo.pm", type => "module", used_by => ["this_case.pl", "that_case.pl"], }, "that_case.pl" => { file => generic_abs_path("t/data/case-insensitive-keys/that_case.pl"), key => "that_case.pl", type => "data", uses => ["Foo.pm"], }, "this_case.pl" => { file => generic_abs_path("t/data/case-insensitive-keys/this_case.pl"), key => "this_case.pl", type => "data", uses => ["Foo.pm"], }, }; my $rv1 = scan_deps(@roots1); compare_rv($rv1, $expected_rv1, \@roots1); # Check that only one entry for Cwd is created. my @roots2 = qw(t/data/case-insensitive-keys/Foo2.pm); my $rv2 = scan_deps(@roots2); my @keys = grep { lc($_) eq "cwd.pm" } keys %$rv2; ok(@keys == 1, "contains only one match"); __END__ Module-ScanDeps-1.37/t/14-scan_chunk.t0000644000175000017500000000366414462676714017567 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More; use Module::ScanDeps qw/scan_chunk/; my @tests = ( { chunk => 'use strict;', expected => 'strict.pm', }, { chunk => 'use base qw(strict);', expected => 'base.pm strict.pm', }, { chunk => 'use parent qw(strict);', expected => 'parent.pm strict.pm', }, { chunk => 'use parent "Foo::Bar"', expected => 'parent.pm Foo/Bar.pm', }, { chunk => 'use parent qw(Fred Wilma);', expected => 'parent.pm Fred.pm Wilma.pm', }, { chunk => 'use parent "Foo::Bar", qw(Fred Wilma);', expected => 'parent.pm Foo/Bar.pm Fred.pm Wilma.pm', }, { chunk => 'use parent::doesnotexist qw(strict);', expected => 'parent/doesnotexist.pm', }, { chunk => 'use Mojo::Base "strict";', expected => 'Mojo/Base.pm strict.pm', comment => 'Mojo::Base', }, { chunk => 'use Catalyst qw/-Debug ConfigLoader Session::State::Cookie/', expected => 'Catalyst.pm Catalyst/Plugin/ConfigLoader.pm Catalyst/Plugin/Session/State/Cookie.pm', comment => '-Debug should be skipped', }, { chunk => 'use Catalyst qw/URI +My::Catalyst::Stuff/', expected => 'Catalyst.pm Catalyst/Plugin/URI.pm My/Catalyst/Stuff.pm', }, { chunk => 'with "Some::Role1", "Some::Role2"', expected => 'Some/Role1.pm Some/Role2.pm', }, { chunk => 'with qw(Some::Role1 Some::Role2)', expected => 'Some/Role1.pm Some/Role2.pm', }, { chunk => 'use I18N::LangTags 0.30 ();', expected => 'I18N/LangTags.pm', }, ); plan tests => 0+@tests; foreach my $t (@tests) { my @got = scan_chunk($t->{chunk}); my @exp = split(' ', $t->{expected}); is_deeply([sort @got], [sort @exp], $t->{comment}); } Module-ScanDeps-1.37/t/6-file-glob.t0000644000175000017500000000055613014631605017207 0ustar roderichroderich#!/usr/bin/perl use strict; use warnings; use Test::More tests => 2; use Module::ScanDeps; use lib qw(t/data); my $map = scan_deps( files => ['t/data/file-glob-no.pl'], recurse => 1, ); ok(not exists $map->{'File/Glob.pm'}); $map = scan_deps( files => ['t/data/file-glob-yes.pl'], recurse => 1, ); ok(exists $map->{'File/Glob.pm'}); __END__ Module-ScanDeps-1.37/MANIFEST0000644000175000017500000000440614715360221015705 0ustar roderichroderichAUTHORS Changes lib/Module/ScanDeps.pm lib/Module/ScanDeps/Cache.pm LICENSE Makefile.PL MANIFEST This list of files MANIFEST.SKIP README script/scandeps.pl t/0-pod.t t/1-static_functional_interface_real.t t/10-case-insensitive-keys.t t/12-ScanFileRE.t t/13-static_prefork_test.t t/14-scan_chunk.t t/14-static_functional_cached.t t/16-scan_line.t t/17-private_methods.t t/18-findbin.t t/19-autosplit.t t/2-static_functional_interface_fake.t t/20-_parse_libs.t t/3-static_oo_interface_real.t t/4-static_functional_interface_options_fake.t t/5-pluggable_fake.t t/6-file-glob.t t/7-check-dynaloader.t t/8-check_duplicated_entries.t t/9-check_path_to_inc_name.t t/data/autosplit/auto/Foo/autosplit.ix t/data/autosplit/auto/Foo/barnie.al t/data/autosplit/auto/Foo/fred.al t/data/autosplit/Foo.pm t/data/case-insensitive-keys/Foo.pm t/data/case-insensitive-keys/Foo2.pm t/data/case-insensitive-keys/that_case.pl t/data/case-insensitive-keys/this_case.pl t/data/check_path_to_inc_name/Scoped/Package.pm t/data/check_path_to_inc_name/Some.pm t/data/check_path_to_inc_name/use_scoped_package.pl t/data/duplicated_entries/Scoped/Package.pm t/data/duplicated_entries/use_scoped_package.pl t/data/file-glob-no.pl t/data/file-glob-yes.pl t/data/minimal.pl t/data/modules/Foo.pm t/data/pluggable/Foo.pm t/data/pluggable/Foo/Plugin/Bar.pm t/data/pluggable/Foo/Plugin/Baz.pm t/data/prefork.pl t/data/rt90869.pl t/data/ScanFileRE/auto/example/example.h t/data/ScanFileRE/example.pm t/data/ScanFileRE/example_too.pm t/data/static/chicken.pm t/data/static/Duplicated.pm t/data/static/Duplicator.pl t/data/static/egg.pm t/data/static/inner_diamond_E.pm t/data/static/inner_diamond_N.pm t/data/static/inner_diamond_S.pm t/data/static/inner_diamond_W.pm t/data/static/InputA.pl t/data/static/InputB.pl t/data/static/InputC.pl t/data/static/null.pl t/data/static/outer_diamond_E.pm t/data/static/outer_diamond_N.pm t/data/static/outer_diamond_S.pm t/data/static/outer_diamond_W.pm t/data/static/TestA.pm t/data/static/TestB.pm t/data/static/TestC.pm t/data/static/TestD.pm t/data/static/useVERSION.pm t/data/use-findbin.pl t/data/use_lib.pl t/rt90869.t t/Utils.pm META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) Module-ScanDeps-1.37/lib/0000755000175000017500000000000014715360221015316 5ustar roderichroderichModule-ScanDeps-1.37/lib/Module/0000755000175000017500000000000014715360221016543 5ustar roderichroderichModule-ScanDeps-1.37/lib/Module/ScanDeps/0000755000175000017500000000000014715360221020243 5ustar roderichroderichModule-ScanDeps-1.37/lib/Module/ScanDeps/Cache.pm0000644000175000017500000000467713014631605021620 0ustar roderichroderichpackage Module::ScanDeps::Cache; use strict; use warnings; my $has_DMD5; eval { require Digest::MD5 }; $has_DMD5 = 1 unless $@; my $has_Storable; eval { require Storable }; $has_Storable = 1 unless $@; my $cache; my $cache_file; my $cache_dirty; sub prereq_missing{ my @missing; push @missing, 'Digest::MD5' unless $has_DMD5; push @missing, 'Storable' unless $has_Storable; return @missing; } sub init_from_file{ my $c_file = shift; return 0 if prereq_missing(); eval{$cache = Storable::retrieve($c_file)}; #warn $@ if ($@); unless ($cache){ warn "Couldn't retrieve data from file $c_file. Building new cache.\n"; $cache = {}; } $cache_file = $c_file; return 1; } sub store_cache{ my $c_file = shift || $cache_file; # no need to store to the file we retrieved from # unless we have seen changes written to the cache return unless ($cache_dirty || $c_file ne $cache_file); Storable::nstore($cache, $c_file) or warn "Could not store cache to file $c_file!"; } sub get_cache_cb{ return sub{ my %args = @_; if ( $args{action} eq 'read' ){ return _read_cache( %args ); } elsif ( $args{action} eq 'write' ){ return _write_cache( %args ); } die "action in cache_cb must be read or write!"; }; } ### check for existence of the entry ### check for identity of the file ### pass cached value in $mod_aref ### return true in case of a hit sub _read_cache{ my %args = @_; my ($key, $file, $mod_aref) = @args{qw/key file modules/}; return 0 unless (exists $cache->{$key}); my $entry = $cache->{$key}; my $checksum = _file_2_md5($file); if ($entry->{checksum} eq $checksum){ @$mod_aref = @{$entry->{modules}}; return 1; } return 0; } sub _write_cache{ my %args = @_; my ($key, $file, $mod_aref) = @args{qw/key file modules/}; my $entry = $cache->{$key} ||= {}; my $checksum = _file_2_md5($file); $entry->{checksum} = $checksum; $entry->{modules} = [@$mod_aref]; $cache_dirty = 1; return 1; } sub _file_2_md5{ my $file = shift; open my $fh, '<', $file or die "can't open $file: $!"; my $md5 = Digest::MD5->new; $md5->addfile($fh); close $fh or die "can't close $file: $!"; return $md5->hexdigest; } 1; Module-ScanDeps-1.37/lib/Module/ScanDeps.pm0000644000175000017500000017024714715355425020626 0ustar roderichroderichpackage Module::ScanDeps; use 5.008001; use strict; use warnings; use vars qw( $VERSION @EXPORT @EXPORT_OK @ISA $CurrentPackage @IncludeLibs $ScanFileRE ); $VERSION = '1.37'; @EXPORT = qw( scan_deps scan_deps_runtime ); @EXPORT_OK = qw( scan_line scan_chunk add_deps scan_deps_runtime path_to_inc_name ); use Config; require Exporter; our @ISA = qw(Exporter); use version; use File::Path (); use File::Temp (); use FileHandle; use Module::Metadata; use List::Util qw ( any first ); # NOTE: Keep the following imports exactly as specified, even if the Module::ScanDeps source # doesn't reference some of them. See '"use lib" idioms' for the reason. use Cwd (qw(abs_path)); use File::Spec; use File::Spec::Functions; use File::Basename; use constant is_insensitive_fs => File::Spec->case_tolerant(); $ScanFileRE = qr/(?:^|\\|\/)(?:[^.]*|.*\.(?i:p[ml]|t|al))$/; my %_glob_cache; my %_file_cache; my $_cached_inc = ""; =head1 NAME Module::ScanDeps - Recursively scan Perl code for dependencies =head1 SYNOPSIS Via the command-line program L: % scandeps.pl *.pm # Print PREREQ_PM section for *.pm % scandeps.pl -e "use utf8" # Read script from command line % scandeps.pl -B *.pm # Include core modules % scandeps.pl -V *.pm # Show autoload/shared/data files Used in a program; use Module::ScanDeps; # standard usage my $hash_ref = scan_deps( files => [ 'a.pl', 'b.pl' ], recurse => 1, ); # shorthand; assume recurse == 1 my $hash_ref = scan_deps( 'a.pl', 'b.pl' ); # App::Packer::Frontend compatible interface # see App::Packer::Frontend for the structure returned by get_files my $scan = Module::ScanDeps->new; $scan->set_file( 'a.pl' ); $scan->set_options( add_modules => [ 'Test::More' ] ); $scan->calculate_info; my $files = $scan->get_files; =head1 DESCRIPTION This module scans potential modules used by perl programs, and returns a hash reference; its keys are the module names as appears in C<%INC> (e.g. C); the values are hash references with this structure: { file => '/usr/local/lib/perl5/5.8.0/Test/More.pm', key => 'Test/More.pm', type => 'module', # or 'autoload', 'data', 'shared' used_by => [ 'Test/Simple.pm', ... ], uses => [ 'Test/Other.pm', ... ], } One function, C, is exported by default. Other functions such as (C, C, C, C) are exported upon request. Users of B may also use this module as the dependency-checking frontend, by tweaking their F like below: use Module::ScanDeps; ... my $packer = App::Packer->new( frontend => 'Module::ScanDeps' ); ... Please see L for detailed explanation on the structure returned by C. =head2 B $rv_ref = scan_deps( files => \@files, recurse => $recurse, rv => \%rv, skip => \%skip, compile => $compile, execute => $execute, ); $rv_ref = scan_deps(@files); # shorthand, with recurse => 1 This function scans each file in C<@files>, registering their dependencies into C<%rv>, and returns a reference to the updated C<%rv>. The meaning of keys and values are explained above. If C<$recurse> is true, C will call itself recursively, to perform a breadth-first search on text files (as defined by the -T operator) found in C<%rv>. If the C<\%skip> is specified, files that exists as its keys are skipped. This is used internally to avoid infinite recursion. If C<$compile> or C<$execute> is true, runs C in either compile-only or normal mode, then inspects their C<%INC> after termination to determine additional runtime dependencies. If C<$execute> is an array reference, passes C<@$execute> as arguments to each file in C<@files> when it is run. If performance of the scanning process is a concern, C can be set to a filename. The scanning results will be cached and written to the file. This will speed up the scanning process on subsequent runs. Additionally, an option C is recognized. If set to true, C issues a warning to STDERR for every module file that the scanned code depends but that wasn't found. Please note that this may also report numerous false positives. That is why by default, the heuristic silently drops all dependencies it cannot find. =head2 B Like B, but skips the static scanning part. =head2 B @modules = scan_line($line); Splits a line into chunks (currently with the semicolon characters), and return the union of C calls of them. If the line is C<__END__> or C<__DATA__>, a single C<__END__> element is returned to signify the end of the program. Similarly, it returns a single C<__POD__> if the line matches C; the caller is responsible for skipping appropriate number of lines until C<=cut>, before calling C again. =head2 B $module = scan_chunk($chunk); @modules = scan_chunk($chunk); Apply various heuristics to C<$chunk> to find and return the module name(s) it contains. In scalar context, returns only the first module or C. =head2 B $rv_ref = add_deps( rv => \%rv, modules => \@modules ); $rv_ref = add_deps( @modules ); # shorthand, without rv Resolves a list of module names to its actual on-disk location, by finding in C<@INC> and C<@Module::ScanDeps::IncludeLibs>; modules that cannot be found are skipped. This function populates the C<%rv> hash with module/filename pairs, and returns a reference to it. =head2 B $perl_name = path_to_inc_name($path, $warn) Assumes C<$path> refers to a perl file and does it's best to return the name as it would appear in %INC. Returns undef if no match was found and a prints a warning to STDERR if C<$warn> is true. E.g. if C<$path> = perl/site/lib/Module/ScanDeps.pm then C<$perl_name> will be Module/ScanDeps.pm. =head1 NOTES =head2 B<@Module::ScanDeps::IncludeLibs> You can set this global variable to specify additional directories in which to search modules without modifying C<@INC> itself. =head2 B<$Module::ScanDeps::ScanFileRE> You can set this global variable to specify a regular expression to identify what files to scan. By default it includes all files of the following types: .pm, .pl, .t and .al. Additionally, all files without a suffix are considered. For instance, if you want to scan all files then use the following: C<$Module::ScanDeps::ScanFileRE = qr/./> =head1 CAVEATS This module intentionally ignores the B hack on FreeBSD -- the additional directory is removed from C<@INC> altogether. The static-scanning heuristic is not likely to be 100% accurate, especially on modules that dynamically load other modules. Chunks that span multiple lines are not handled correctly. For example, this one works: use base 'Foo::Bar'; But this one does not: use base 'Foo::Bar'; =cut my $SeenTk; my %SeenRuntimeLoader; # match "use LOADER LIST" chunks; sets $1 to LOADER and $2 to LIST my $LoaderRE = qr/^ use \s+ ( asa | base | parent | prefork | POE | encoding | maybe | only::matching | Mojo::Base | Catalyst )(?!\:) \b \s* (.*) /sx; # Pre-loaded module dependencies {{{ my %Preload = ( 'AnyDBM_File.pm' => [qw( SDBM_File.pm )], 'AnyEvent.pm' => 'sub', 'Authen/SASL.pm' => 'sub', 'B/Hooks/EndOfScope.pm' => [qw( B/Hooks/EndOfScope/PP.pm B/Hooks/EndOfScope/XS.pm )], 'Bio/AlignIO.pm' => 'sub', 'Bio/Assembly/IO.pm' => 'sub', 'Bio/Biblio/IO.pm' => 'sub', 'Bio/ClusterIO.pm' => 'sub', 'Bio/CodonUsage/IO.pm' => 'sub', 'Bio/DB/Biblio.pm' => 'sub', 'Bio/DB/Flat.pm' => 'sub', 'Bio/DB/GFF.pm' => 'sub', 'Bio/DB/Taxonomy.pm' => 'sub', 'Bio/Graphics/Glyph.pm' => 'sub', 'Bio/MapIO.pm' => 'sub', 'Bio/Matrix/IO.pm' => 'sub', 'Bio/Matrix/PSM/IO.pm' => 'sub', 'Bio/OntologyIO.pm' => 'sub', 'Bio/PopGen/IO.pm' => 'sub', 'Bio/Restriction/IO.pm' => 'sub', 'Bio/Root/IO.pm' => 'sub', 'Bio/SearchIO.pm' => 'sub', 'Bio/SeqIO.pm' => 'sub', 'Bio/Structure/IO.pm' => 'sub', 'Bio/TreeIO.pm' => 'sub', 'Bio/LiveSeq/IO.pm' => 'sub', 'Bio/Variation/IO.pm' => 'sub', 'Catalyst.pm' => sub { return ('Catalyst/Runtime.pm', 'Catalyst/Dispatcher.pm', _glob_in_inc('Catalyst/DispatchType', 1)); }, 'Catalyst/Engine.pm' => 'sub', 'CGI/Application/Plugin/Authentication.pm' => [qw( CGI/Application/Plugin/Authentication/Store/Cookie.pm )], 'CGI/Application/Plugin/AutoRunmode.pm' => [qw( Attribute/Handlers.pm )], 'charnames.pm' => \&_unicore, 'Class/Load.pm' => [qw( Class/Load/PP.pm )], 'Class/MakeMethods.pm' => 'sub', 'Class/MethodMaker.pm' => 'sub', 'Class/Plain.pm' => [qw( XS/Parse/Keyword.pm )], 'Config/Any.pm' =>'sub', 'Crypt/Random.pm' => sub { _glob_in_inc('Crypt/Random/Provider', 1); }, 'Crypt/Random/Generator.pm' => sub { _glob_in_inc('Crypt/Random/Provider', 1); }, 'Date/Manip.pm' => [qw( Date/Manip/DM5.pm Date/Manip/DM6.pm )], 'Date/Manip/Base.pm' => sub { _glob_in_inc('Date/Manip/Lang', 1); }, 'Date/Manip/TZ.pm' => sub { return (_glob_in_inc('Date/Manip/TZ', 1), _glob_in_inc('Date/Manip/Offset', 1)); }, 'DateTime/Format/Builder/Parser.pm' => 'sub', 'DateTime/Format/Natural.pm' => 'sub', 'DateTime/Locale.pm' => 'sub', 'DateTime/TimeZone.pm' => 'sub', 'DBI.pm' => sub { grep !/\bProxy\b/, _glob_in_inc('DBD', 1); }, 'DBIx/Class.pm' => 'sub', 'DBIx/SearchBuilder.pm' => 'sub', 'DBIx/Perlish.pm' => [qw( attributes.pm )], 'DBIx/ReportBuilder.pm' => 'sub', 'Device/ParallelPort.pm' => 'sub', 'Device/SerialPort.pm' => [qw( termios.ph asm/termios.ph sys/termiox.ph sys/termios.ph sys/ttycom.ph )], 'diagnostics.pm' => sub { # shamelessly taken and adapted from diagnostics.pm use Config; my($privlib, $archlib) = @Config{qw(privlibexp archlibexp)}; if ($^O eq 'VMS') { require VMS::Filespec; $privlib = VMS::Filespec::unixify($privlib); $archlib = VMS::Filespec::unixify($archlib); } for ( "pod/perldiag.pod", "Pod/perldiag.pod", "pod/perldiag-$Config{version}.pod", "Pod/perldiag-$Config{version}.pod", "pods/perldiag.pod", "pods/perldiag-$Config{version}.pod", ) { return $_ if _find_in_inc($_); } for ( "$archlib/pods/perldiag.pod", "$privlib/pods/perldiag-$Config{version}.pod", "$privlib/pods/perldiag.pod", ) { return $_ if -f $_; } return 'pod/perldiag.pod'; }, 'Email/Send.pm' => 'sub', 'Event.pm' => sub { map "Event/$_.pm", qw( idle io signal timer var ); }, 'ExtUtils/MakeMaker.pm' => sub { grep /\bMM_/, _glob_in_inc('ExtUtils', 1); }, 'FFI/Platypus.pm' => 'sub', 'File/Basename.pm' => [qw( re.pm )], 'File/BOM.pm' => [qw( Encode/Unicode.pm )], 'File/HomeDir.pm' => 'sub', 'File/Spec.pm' => sub { require File::Spec; map { my $name = $_; $name =~ s!::!/!g; "$name.pm" } @File::Spec::ISA; }, 'Future/AsyncAwait.pm' => [qw( XS/Parse/Keyword.pm )], 'Future/AsyncAwait/Hooks.pm' => [qw( XS/Parse/Keyword.pm )], 'Gtk2.pm' => [qw( Cairo.pm )], # Gtk2.pm does: eval "use Cairo;" 'HTTP/Entity/Parser.pm' => 'sub', 'HTTP/Message.pm' => [qw( URI/URL.pm URI.pm )], 'Image/ExifTool.pm' => sub { return( (map $_->{name}, _glob_in_inc('Image/ExifTool', 0)), # also *.pl files qw( File/RandomAccess.pm ), ); }, 'Image/Info.pm' => sub { return( _glob_in_inc('Image/Info', 1), qw( Image/TIFF.pm ), ); }, 'IO.pm' => [qw( IO/Handle.pm IO/Seekable.pm IO/File.pm IO/Pipe.pm IO/Socket.pm IO/Dir.pm )], 'IO/Socket.pm' => [qw( IO/Socket/UNIX.pm )], 'IUP.pm' => 'sub', 'JSON.pm' => sub { # add JSON/PP*.pm, JSON/PP/*.pm # and ignore other JSON::* modules (e.g. JSON/Syck.pm, JSON/Any.pm); # but accept JSON::XS, too (because JSON.pm might use it if present) return( grep /^JSON\/(PP|XS)/, _glob_in_inc('JSON', 1) ); }, 'JSON/MaybeXS.pm' => [qw( Cpanel/JSON/XS.pm JSON/XS.pm JSON/PP.pm )], 'List/Keywords.pm' => [qw( XS/Parse/Keyword.pm )], 'List/MoreUtils.pm' => 'sub', 'List/SomeUtils.pm' => 'sub', 'Locale/Maketext/Lexicon.pm' => 'sub', 'Locale/Maketext/GutsLoader.pm' => [qw( Locale/Maketext/Guts.pm )], 'Log/Any.pm' => 'sub', 'Log/Dispatch.pm' => 'sub', 'Log/Log4perl.pm' => 'sub', 'Log/Report/Dispatcher.pm' => 'sub', 'LWP/MediaTypes.pm' => [qw( LWP/media.types )], 'LWP/Parallel.pm' => sub { _glob_in_inc( 'LWP/Parallel', 1 ), qw( LWP/ParallelUA.pm LWP/UserAgent.pm LWP/RobotPUA.pm LWP/RobotUA.pm ), }, 'LWP/Parallel/UserAgent.pm' => [qw( LWP/Parallel.pm )], 'LWP/UserAgent.pm' => sub { return( qw( URI/URL.pm URI/http.pm LWP/Protocol/http.pm ), _glob_in_inc("LWP/Authen", 1), _glob_in_inc("LWP/Protocol", 1), ); }, 'Mail/Audit.pm' => 'sub', 'Math/BigInt.pm' => 'sub', 'Math/BigFloat.pm' => 'sub', 'Math/Symbolic.pm' => 'sub', 'MIME/Decoder.pm' => 'sub', 'MIME/Types.pm' => [qw( MIME/types.db )], 'Module/Build.pm' => 'sub', 'Module/Pluggable.pm' => sub { _glob_in_inc('$CurrentPackage/Plugin', 1); }, 'Moo.pm' => [qw( Class/XSAccessor.pm )], 'Moose.pm' => sub { _glob_in_inc('Moose', 1), _glob_in_inc('Class/MOP', 1), }, 'MooseX/AttributeHelpers.pm' => 'sub', 'MooseX/POE.pm' => sub { _glob_in_inc('MooseX/POE', 1), _glob_in_inc('MooseX/Async', 1), }, 'MooX/HandlesVia.pm' => sub { _glob_in_inc('Data/Perl', 1) }, 'Mozilla/CA.pm' => [qw( Mozilla/CA/cacert.pem )], 'MozRepl.pm' => sub { qw( MozRepl/Log.pm MozRepl/Client.pm Module/Pluggable/Fast.pm ), _glob_in_inc('MozRepl/Plugin', 1), }, 'Module/Implementation.pm' => \&_warn_of_runtime_loader, 'Module/Runtime.pm' => \&_warn_of_runtime_loader, 'Mojo/Util.pm' => sub { # html_entities.txt map { $_->{name} } _glob_in_inc('Mojo/resources', 0) }, 'Mojo/IOLoop/TLS.pm' => sub { # server.{crt,key} map { $_->{name} } _glob_in_inc('Mojo/IOLoop/resources', 0) }, 'Net/DNS/Resolver.pm' => 'sub', 'Net/DNS/RR.pm' => 'sub', 'Net/FTP.pm' => 'sub', 'Net/HTTPS.pm' => [qw( IO/Socket/SSL.pm Net/SSL.pm )], 'Net/Server.pm' => 'sub', 'Net/SSH/Perl.pm' => 'sub', 'Object/Pad.pm' => [qw( XS/Parse/Keyword.pm )], 'Object/Pad/Keyword/Accessor.pm' => [qw( XS/Parse/Keyword.pm )], 'Package/Stash.pm' => [qw( Package/Stash/PP.pm Package/Stash/XS.pm )], 'Pango.pm' => [qw( Cairo.pm )], # Pango.pm does: eval "use Cairo;" 'PAR/Repository.pm' => 'sub', 'PAR/Repository/Client.pm' => 'sub', 'Params/Validate.pm' => 'sub', 'Parse/AFP.pm' => 'sub', 'Parse/Binary.pm' => 'sub', 'PDF/API2/Resource/Font.pm' => 'sub', 'PDF/API2/Basic/TTF/Font.pm' => sub { _glob_in_inc('PDF/API2/Basic/TTF', 1); }, 'PDF/Writer.pm' => 'sub', 'PDL/NiceSlice.pm' => 'sub', 'Perl/Critic.pm' => 'sub', #not only Perl/Critic/Policy 'PerlIO.pm' => [qw( PerlIO/scalar.pm )], 'Pod/Simple/Transcode.pm' => [qw( Pod/Simple/TranscodeDumb.pm Pod/Simple/TranscodeSmart.pm )], 'Pod/Usage.pm' => sub { # from Pod::Usage (as of 1.61) $] >= 5.005_58 ? 'Pod/Text.pm' : 'Pod/PlainText.pm' }, 'POE.pm' => [qw( POE/Kernel.pm POE/Session.pm )], 'POE/Component/Client/HTTP.pm' => sub { _glob_in_inc('POE/Component/Client/HTTP', 1), qw( POE/Filter/HTTPChunk.pm POE/Filter/HTTPHead.pm ), }, 'POE/Kernel.pm' => sub { _glob_in_inc('POE/XS/Resource', 1), _glob_in_inc('POE/Resource', 1), _glob_in_inc('POE/XS/Loop', 1), _glob_in_inc('POE/Loop', 1), }, 'POSIX.pm' => sub { map $_->{name}, _glob_in_inc('auto/POSIX/SigAction', 0), # *.al files _glob_in_inc('auto/POSIX/SigRt', 0), # *.al files }, 'PPI.pm' => 'sub', 'Regexp/Common.pm' => 'sub', 'RPC/XML/ParserFactory.pm' => sub { _glob_in_inc('RPC/XML/Parser', 1); }, 'SerialJunk.pm' => [qw( termios.ph asm/termios.ph sys/termiox.ph sys/termios.ph sys/ttycom.ph )], 'SOAP/Lite.pm' => sub { _glob_in_inc('SOAP/Transport', 1), _glob_in_inc('SOAP/Lite', 1), }, 'Socket/GetAddrInfo.pm' => 'sub', 'Specio/PartialDump.pm' => \&_unicore, 'SQL/Parser.pm' => sub { _glob_in_inc('SQL/Dialects', 1); }, 'SQL/Translator/Schema.pm' => sub { _glob_in_inc('SQL/Translator', 1); }, 'Sub/Exporter/Progressive.pm' => [qw( Sub/Exporter.pm )], 'SVK/Command.pm' => sub { _glob_in_inc('SVK', 1); }, 'SVN/Core.pm' => sub { _glob_in_inc('SVN', 1), map { $_->{name} } _glob_in_inc('auto/SVN', 0), # *.so, *.bs files }, 'Syntax/Keyword/Combine/Keys.pm' => [qw( XS/Parse/Keyword.pm )], 'Syntax/Keyword/Defer.pm' => [qw( XS/Parse/Keyword.pm )], 'Syntax/Keyword/Dynamically.pm' => [qw( XS/Parse/Keyword.pm )], 'Syntax/Keyword/Inplace.pm' => [qw( XS/Parse/Keyword.pm )], 'Syntax/Keyword/Match.pm' => [qw( XS/Parse/Keyword.pm )], 'Syntax/Keyword/Try.pm' => [qw( XS/Parse/Keyword.pm )], 'Template.pm' => 'sub', 'Term/ReadLine.pm' => 'sub', 'Test/Deep.pm' => 'sub', 'threads/shared.pm' => [qw( attributes.pm )], # anybody using threads::shared is likely to declare variables # with attribute :shared 'Tk.pm' => sub { $SeenTk = 1; qw( Tk/FileSelect.pm Encode/Unicode.pm ); }, 'Tk/Balloon.pm' => [qw( Tk/balArrow.xbm )], 'Tk/BrowseEntry.pm' => [qw( Tk/cbxarrow.xbm Tk/arrowdownwin.xbm )], 'Tk/ColorEditor.pm' => [qw( Tk/ColorEdit.xpm )], 'Tk/DragDrop/Common.pm' => sub { _glob_in_inc('Tk/DragDrop', 1), }, 'Tk/FBox.pm' => [qw( Tk/folder.xpm Tk/file.xpm )], 'Tk/Getopt.pm' => [qw( Tk/openfolder.xpm Tk/win.xbm )], 'Tk/Toplevel.pm' => [qw( Tk/Wm.pm )], 'Unicode/Normalize.pm' => \&_unicore, 'Unicode/UCD.pm' => \&_unicore, 'URI.pm' => sub { grep !/urn/, _glob_in_inc('URI', 1) }, 'utf8_heavy.pl' => \&_unicore, 'Win32/EventLog.pm' => [qw( Win32/IPC.pm )], 'Win32/Exe.pm' => 'sub', 'Win32/TieRegistry.pm' => [qw( Win32API/Registry.pm )], 'Win32/SystemInfo.pm' => [qw( Win32/cpuspd.dll )], 'Wx.pm' => [qw( attributes.pm )], 'XML/Parser.pm' => sub { _glob_in_inc('XML/Parser/Style', 1), _glob_in_inc('XML/Parser/Encodings', 1), }, 'XML/SAX.pm' => [qw( XML/SAX/ParserDetails.ini ) ], 'XML/Twig.pm' => [qw( URI.pm )], # or URI::File or LWP 'XML/Twig/XPath.pm' => [qw( XML/XPathEngine.pm XML/XPath.pm )], 'XMLRPC/Lite.pm' => sub { _glob_in_inc('XMLRPC/Transport', 1); }, 'XS/Parse/Keyword/FromPerl.pm' => [qw( XS/Parse/Keyword.pm )], 'YAML.pm' => [qw( YAML/Loader.pm YAML/Dumper.pm )], 'YAML/Any.pm' => sub { # try to figure out what YAML::Any would have used my $impl = eval "use YAML::Any; YAML::Any->implementation;"; return _mod2pm($impl) unless $@; _glob_in_inc('YAML', 1); # fallback }, ); # }}} sub path_to_inc_name($$) { my $path = shift; my $warn = shift; my $inc_name; if ($path =~ m/\.pm$/io) { die "$path doesn't exist" unless (-f $path); my $module_info = Module::Metadata->new_from_file($path); die "Module::Metadata error: $!" unless defined($module_info); $inc_name = $module_info->name(); if (defined($inc_name)) { $inc_name =~ s|\:\:|\/|og; $inc_name .= '.pm'; } else { warn "# Couldn't find include name for $path\n" if $warn; } } else { # Bad solution! (my $vol, my $dir, $inc_name) = File::Spec->splitpath($path); } return $inc_name; } my $Keys = 'files|keys|recurse|rv|skip|first|execute|compile|warn_missing|cache_cb|cache_file'; sub scan_deps { my %args = ( rv => {}, (@_ and $_[0] =~ /^(?:$Keys)$/o) ? @_ : (files => [@_], recurse => 1) ); if (!defined($args{keys})) { $args{keys} = [map {path_to_inc_name($_, $args{warn_missing})} @{$args{files}}]; } my $cache_file = $args{cache_file}; my $using_cache; if ($cache_file) { require Module::ScanDeps::Cache; $using_cache = Module::ScanDeps::Cache::init_from_file($cache_file); if( $using_cache ){ $args{cache_cb} = Module::ScanDeps::Cache::get_cache_cb(); }else{ my @missing = Module::ScanDeps::Cache::prereq_missing(); warn join(' ', "Can not use cache_file: Needs Modules [", @missing, "]\n",); } } my ($type, $path); foreach my $input_file (@{$args{files}}) { if ($input_file !~ $ScanFileRE) { warn "Skipping input file $input_file" . " because it doesn't match \$Module::ScanDeps::ScanFileRE\n" if $args{warn_missing}; next; } $type = _gettype($input_file); $path = $input_file; if ($type eq 'module') { # necessary because add_deps does the search for shared libraries and such add_deps( used_by => undef, rv => $args{rv}, modules => [path_to_inc_name($path, $args{warn_missing})], skip => undef, warn_missing => $args{warn_missing}, ); } else { _add_info( rv => $args{rv}, module => path_to_inc_name($path, $args{warn_missing}), file => $path, used_by => undef, type => $type, ); } } { ## "use lib" idioms # # We want to correctly interprete stuff like # # use FindBin; # use lib "$FindBin/../lib"; # # Find out what $FindBin::Bin etc would have been if "use FindBin" had been # called in the first file to analyze. # # Notes: # (1) We don't want to reimplement FindBin, hence fake $0 locally (as the path of the # first file analyzed) and call FindBin::again(). # (2) If the caller of scan_deps() itself uses FindBin, we don't want to overwrite # the value of "their" $FindBin::Bin. # # Other idioms seen sometimes: # # use lib "$ENV{FOO}/path"; # use lib File::Spec->catdir($FindBin::Bin, qw[.. qqlib] ); # use lib catdir(dirname($0), "perl"); # use lib dirname(abs_path($0)); # # In order to correctly interprete these, the modules referenced have to be imported. require FindBin; local $FindBin::Bin; #local $FindBin::RealBin; #local $FindBin::Script; #local $FindBin::RealScript; my $_0 = $args{files}[0]; local *0 = \$_0; FindBin->again(); scan_deps_static(\%args); } if ($args{execute} or $args{compile}) { scan_deps_runtime( rv => $args{rv}, files => $args{files}, execute => $args{execute}, compile => $args{compile}, skip => $args{skip} ); } if ( $using_cache ){ Module::ScanDeps::Cache::store_cache(); } # do not include the input files themselves as dependencies! delete $args{rv}{$_} foreach @{$args{files}}; return ($args{rv}); } sub scan_deps_static { my ($args) = @_; my ($files, $keys, $recurse, $rv, $skip, $first, $execute, $compile, $cache_cb, $_skip) = @$args{qw( files keys recurse rv skip first execute compile cache_cb _skip )}; $rv ||= {}; $_skip ||= { %{$skip || {}} }; foreach my $file (@{$files}) { my $key = shift @{$keys}; next if $_skip->{$file}++; next if is_insensitive_fs() and $file ne lc($file) and $_skip->{lc($file)}++; next unless $file =~ $ScanFileRE; my @pm; my $found_in_cache; if ($cache_cb){ my $pm_aref; # cache_cb populates \@pm on success $found_in_cache = $cache_cb->(action => 'read', key => $key, file => $file, modules => \@pm, ); unless( $found_in_cache ){ @pm = scan_file($file); $cache_cb->(action => 'write', key => $key, file => $file, modules => \@pm, ); } }else{ # no caching callback given @pm = scan_file($file); } foreach my $pm (@pm){ add_deps( used_by => $key, rv => $args->{rv}, modules => [$pm], skip => $args->{skip}, warn_missing => $args->{warn_missing}, ); my @preload = _get_preload($pm) or next; add_deps( used_by => $key, rv => $args->{rv}, modules => \@preload, skip => $args->{skip}, warn_missing => $args->{warn_missing}, ); } } # Top-level recursion handling {{{ # prevent utf8.pm from being scanned $_skip->{$rv->{"utf8.pm"}{file}}++ if $rv->{"utf8.pm"}; while ($recurse) { my $count = keys %$rv; my @files = sort grep { defined $_->{file} && -T $_->{file} } values %$rv; scan_deps_static({ files => [ map $_->{file}, @files ], keys => [ map $_->{key}, @files ], rv => $rv, skip => $skip, recurse => 0, cache_cb => $cache_cb, _skip => $_skip, }); last if $count == keys %$rv; } # }}} return $rv; } sub scan_deps_runtime { my %args = ( rv => {}, (@_ and $_[0] =~ /^(?:$Keys)$/o) ? @_ : (files => [@_], recurse => 1) ); my ($files, $rv, $execute, $compile) = @args{qw( files rv execute compile )}; $files = (ref($files)) ? $files : [$files]; if ($compile) { foreach my $file (@$files) { next unless $file =~ $ScanFileRE; _merge_rv(_info2rv(_compile_or_execute($file)), $rv); } } elsif ($execute) { foreach my $file (@$files) { $execute = [] unless ref $execute; # make sure it's an array ref _merge_rv(_info2rv(_compile_or_execute($file, $execute)), $rv); } } return ($rv); } sub scan_file{ my $file = shift; my %found; open my $fh, "<", $file or die "Cannot open $file: $!"; $SeenTk = 0; # Line-by-line scanning LINE: while (my $line = <$fh>) { chomp($line); foreach my $pm (scan_line($line)) { last LINE if $pm eq '__END__'; if ($pm eq '__POD__') { while ($line = <$fh>) { next LINE if $line =~ /^=cut/; } } # Skip Tk hits from Term::ReadLine and Tcl::Tk my $pathsep = qr/\/|\\|::/; if ($pm =~ /^Tk\b/) { next if $file =~ /(?:^|${pathsep})Term${pathsep}ReadLine\.pm$/; next if $file =~ /(?:^|${pathsep})Tcl${pathsep}Tk\W/; } $SeenTk ||= $pm =~ /Tk\.pm$/; $found{$pm}++; } } close $fh or die "Cannot close $file: $!"; return keys %found; } sub scan_line { my $line = shift; my %found; return '__END__' if $line =~ /^__(?:END|DATA)__$/; return '__POD__' if $line =~ /^=\w/; $line =~ s/\s*#.*$//; CHUNK: foreach (split(/;/, $line)) { s/^\s*//; s/^\w+:\s*//; # remove LABEL: s/^(?:do\s*)?\{\s*//; # handle single line blocks like 'do { package foo; use xx; }' s/\s*\}$//; if (/^package\s+(\w+)/) { $CurrentPackage = $1; $CurrentPackage =~ s{::}{/}g; next CHUNK; } # use VERSION: if (/^(?:use|require)\s+v?(\d[\d\._]*)/) { # include feature.pm if we have 5.9.5 or better if (version->new($1) >= version->new("5.9.5")) { # seems to catch 5.9, too (but not 5.9.4) $found{"feature.pm"}++; } next CHUNK; } if (my ($pragma, $args) = /^(?:use|no) \s+ (autouse|if) \s+ (.+)/x) { # NOTE: There are different ways the MODULE may # be specified for the "autouse" and "if" pragmas, e.g. # use autouse Module => qw(func1 func2); # use autouse "Module", qw(func1); my $module; if ($pragma eq "autouse") { ($module) = _parse_module_list($args); } else { # The syntax of the "if" pragma is # use if COND, MODULE => ARGUMENTS # NOTE: This works only for simple conditions. $args =~ s/.*? (?:,|=>) \s*//x; ($module) = _parse_module_list($args); } $found{_mod2pm($pragma)}++; $found{_mod2pm($module)}++ if $module; next CHUNK; } if (my ($how, $libs) = /^(use \s+ lib \s+ | (?:unshift|push) \s+ \@INC \s*,\s*) (.+)/x) { my $archname = defined($Config{archname}) ? $Config{archname} : ''; my $ver = defined($Config{version}) ? $Config{version} : ''; while ((my $dir, $libs) = _parse_libs($libs)) { next unless defined $dir; my @dirs = $dir; push @dirs, "$dir/$ver", "$dir/$archname", "$dir/$ver/$archname" if $how =~ /lib/; foreach (@dirs) { unshift(@INC, $_) if -d $_; } } next CHUNK; } $found{$_}++ for scan_chunk($_); } return sort keys %found; } # convert module name to file name sub _mod2pm { my $mod = shift; $mod =~ s!::!/!g; return "$mod.pm"; } # parse a comma-separated list of module names (as string literals or qw() lists) sub _parse_module_list { my $list = shift; # split $list on anything that's not a word character or ":" # and ignore "q", "qq" and "qw" return grep { length and !/^:|^q[qw]?$/ } split(/[^\w:]+/, $list); } # incrementally parse a comma separated list library paths: # returning a pair: the contents of the first strings literal and the remainder of the string # - for "string", 'string', q/string/, qq/string/ also unescape \\ and \) # - for qw(foo bar quux) return ("foo", qw(bar quux)) # - otherwise skip over the first comma and return (undef, "remainder") # - return () if the string is exhausted # - as a special case, if the string starts with $FindBin::Bin, replace it with our $Bin sub _parse_libs { local $_ = shift; s/^[\s,()]*//; return if $_ eq ""; if (s/^(['"]) ((?:\\.|.)*?) \1//x) { return (_unescape($1, $2), $_); } if (s/^qq? \s* (\W)//x) { my $opening_delim = $1; (my $closing_delim = $opening_delim) =~ tr:([{<:)]}>:; s/^((?:\\.|.)*?) \Q$closing_delim\E//x; return (_unescape($opening_delim, $1), $_); } if (s/^qw \s* (\W)//x) { my $opening_delim = $1; (my $closing_delim = $opening_delim) =~ tr:([{<:)]}>:; s/^((?:\\.|.)*?) \Q$closing_delim\E//x; my $contents = $1; my @list = split(" ", $contents); return (undef, $_) unless @list; my $first = shift @list; return (_unescape($opening_delim, $first), @list ? "qw${opening_delim}@list${closing_delim}$_" : $_); } # nothing recognizable in the first list item, skip to the next if (s/^.*? ,//x) { return (undef, $_); } return; # list exhausted } sub _unescape { my ($delim, $str) = @_; $str =~ s/\\([\\\Q$delim\E])/$1/g; $str =~ s/^\$FindBin::Bin\b/$FindBin::Bin/; return $str; } sub scan_chunk { my $chunk = shift; # Module name extraction heuristics {{{ my $module = eval { local $_ = $chunk; s/^\s*//; # "if", "while" etc: analyze the expression s/^(?:if|elsif|unless|while|until) \s* \( \s*//x; # "eval" with a block: analyze the block s/^eval \s* \{ \s*//x; # "eval" with an expression that's a string literal: # analyze the string s/^eval \s+ (?:['"]|qq?\s*\W) \s*//x; # "use LOADER LIST" # TODO: There's many more of these "loader" type modules on CPAN! if (my ($loader, $list) = $_ =~ $LoaderRE) { my @mods = _parse_module_list($list); if ($loader eq "Catalyst") { # "use Catalyst 'Foo'" looks for "Catalyst::Plugin::Foo", # but "use Catalyst +Foo" looks for "Foo" @mods = map { ($list =~ /([+-])\Q$_\E(?:$|[^\w:])/) ? ($1 eq "-" ? () # "-Foo": it's a flag, eg. "-Debug", skip it : $_) # "+Foo": look for "Foo" : "Catalyst::Plugin::$_" # "Foo": look for "Catalyst::Plugin::Foo" } @mods; } return [ map { _mod2pm($_) } $loader, @mods ]; } if (/^use \s+ Class::Autouse \b \s* (.*)/sx or /^Class::Autouse \s* -> \s* autouse \s* (.*)/sx) { return [ map { _mod2pm($_) } "Class::Autouse", _parse_module_list($1) ]; } # generic "use ..." if (s/^(?:use|no) \s+//x) { my ($mod) = _parse_module_list($_); # just the first word return _mod2pm($mod); } if (s/^(require|do) [\s(]+//x) { return ($1 eq "require" && /^([\w:]+)/) ? _mod2pm($1) # bareword ("require" only) : $_; # maybe string literal? } if (/(<[^>]*[^\$\w>][^>]*>)/) { my $diamond = $1; return "File/Glob.pm" if $diamond =~ /[*?\[\]{}~\\]/; } return "DBD/$1.pm" if /\bdbi:(\w+):/i; # Moose/Moo/Mouse style inheritance or composition if (s/^(with|extends)\s+//) { return [ map { _mod2pm($_) } _parse_module_list($_) ]; } # check for stuff like # decode("klingon", ...) # open FH, "<:encoding(klingon)", ... if (my ($args) = /\b(?:open|binmode)\b(.*)/) { my @mods; push @mods, qw( PerlIO.pm PerlIO/encoding.pm Encode.pm ), _find_encoding($1) if $args =~ /:encoding\((.*?)\)/; while ($args =~ /:(\w+)(?:\((.*?)\))?/g) { push @mods, "PerlIO/$1.pm"; push @mods, "Encode.pm", _find_encoding($2) if $1 eq "encoding"; } push @mods, "PerlIO.pm" if @mods; return \@mods if @mods; } if (/\b(?:en|de)code\(\s*['"]?([-\w]+)/) { return [qw( Encode.pm ), _find_encoding($1)]; } if ($SeenTk) { my @modules; while (/->\s*([A-Z]\w+)/g) { push @modules, "Tk/$1.pm"; } while (/->\s*Scrolled\W+([A-Z]\w+)/g) { push @modules, "Tk/$1.pm"; push @modules, "Tk/Scrollbar.pm"; } if (/->\s*setPalette/g) { push @modules, map { "Tk/$_.pm" } qw( Button Canvas Checkbutton Entry Frame Label Labelframe Listbox Menubutton Menu Message Radiobutton Scale Scrollbar Spinbox Text ); } return \@modules; } # Module::Runtime return $_ if s/^(?:require_module|use_module|use_package_optimistically) \s* \( \s*//x; # Test::More return $_ if s/^(?:require_ok|use_ok) \s* \( \s*//x; return; }; # }}} return unless defined($module); return wantarray ? @$module : $module->[0] if ref($module); # extract contents from string literals if ($module =~ /^(['"]) (.*?) \1/x) { $module = $2; } elsif ($module =~ s/^qq? \s* (\W)//x) { (my $closing = $1) =~ tr:([{<:)]}>:; $module =~ s/\Q$closing\E.*//; } $module =~ s/::/\//g; return if $module =~ /^(?:[\d\._]+|'.*[^']|".*[^"])$/; $module .= ".pm" unless $module =~ /\./; return $module; } sub _find_encoding { my ($enc) = @_; return unless $] >= 5.008 and eval { require Encode; %Encode::ExtModule }; my $mod = eval { $Encode::ExtModule{ Encode::find_encoding($enc)->name } } or return; return _mod2pm($mod); } sub _add_info { my %args = @_; my ($rv, $module, $file, $used_by, $type) = @args{qw/rv module file used_by type/}; return unless defined($module) and defined($file); # Ensure file is always absolute $file = File::Spec->rel2abs($file); $file =~ s|\\|\/|go; # Avoid duplicates that can arise due to case differences that don't actually # matter on a case tolerant system if (is_insensitive_fs) { if (!exists $rv->{$module}) { my $lc_module = lc $module; my $key = first {lc($_) eq $lc_module} keys %$rv; if (defined $key) { $module = $key }; } if (defined($used_by)) { if (lc($used_by) eq lc($module)) { $used_by = $module; } else { if (!exists $rv->{$used_by}) { my $lc_used_by = lc $used_by; my $key = first {lc($_) eq $lc_used_by} keys %$rv; if (defined $key) { $used_by = $key }; } } } } $rv->{$module} ||= { file => $file, key => $module, type => $type, }; if (defined($used_by) and $used_by ne $module) { if (is_insensitive_fs) { my $lc_used_by = lc $used_by; my $lc_module = lc $module; push @{ $rv->{$module}{used_by} }, $used_by if !any { lc($_) eq $lc_used_by } @{ $rv->{$module}{used_by} }; # We assume here that another _add_info will be called to provide the other parts of $rv->{$used_by} push @{ $rv->{$used_by}{uses} }, $module if !any { lc($_) eq $lc_module } @{ $rv->{$used_by}{uses} }; } else { push @{ $rv->{$module}{used_by} }, $used_by if !any { $_ eq $used_by } @{ $rv->{$module}{used_by} }; # We assume here that another _add_info will be called to provide the other parts of $rv->{$used_by} push @{ $rv->{$used_by}{uses} }, $module if !any { $_ eq $module } @{ $rv->{$used_by}{uses} }; } } } # This subroutine relies on not being called for modules that have already been visited sub add_deps { my %args = ((@_ and $_[0] =~ /^(?:modules|rv|used_by|warn_missing)$/) ? @_ : (rv => (ref($_[0]) ? shift(@_) : undef), modules => [@_])); my $rv = $args{rv} || {}; my $skip = $args{skip} || {}; my $used_by = $args{used_by}; foreach my $module (@{ $args{modules} }) { my $file = _find_in_inc($module) or _warn_of_missing_module($module, $args{warn_missing}), next; next if $skip->{$file}; if (exists $rv->{$module}) { _add_info( rv => $rv, module => $module, file => $file, used_by => $used_by, type => undef ); next; } _add_info( rv => $rv, module => $module, file => $file, used_by => $used_by, type => _gettype($file) ); if ((my $path = $module) =~ s/\.p[mh]$//i) { foreach (_glob_in_inc("auto/$path")) { next if $_->{name} =~ m{^auto/$path/.*/}; # weed out subdirs next if $_->{name} =~ m{/(?:\.exists|\.packlist)$|\Q$Config{lib_ext}\E$}; _add_info( rv => $rv, module => $_->{name}, file => $_->{file}, used_by => $module, type => _gettype($_->{name}) ); } ### Now, handle module and distribution share dirs # convert 'Module/Name' to 'Module-Name' my $modname = $path; $modname =~ s|/|-|g; # TODO: get real distribution name related to module name my $distname = $modname; foreach (_glob_in_inc("auto/share/module/$modname")) { _add_info( rv => $rv, module => $_->{name}, file => $_->{file}, used_by => $module, type => 'data' ); } foreach (_glob_in_inc("auto/share/dist/$distname")) { _add_info( rv => $rv, module => $_->{name}, file => $_->{file}, used_by => $module, type => 'data' ); } } } # end for modules return $rv; } # invalidate %_file_cache and %_glob_cache in case @INC changes sub _validate_cached_inc { my $inc = join("\0", @INC, @IncludeLibs); return if $inc eq $_cached_inc; # blow away the caches %_file_cache = (); %_glob_cache = (); $_cached_inc = $inc; } sub _find_in_inc { my $file = shift; return unless defined $file; _validate_cached_inc(); my $cached_val = $_file_cache{$file}; return $cached_val if $cached_val; foreach my $dir (grep !/\bBSDPAN\b/, @INC, @IncludeLibs) { if (-f "$dir/$file") { $_file_cache{$file} = "$dir/$file"; return "$dir/$file" }; } # absolute file names return $file if -f $file; return; } sub _glob_in_inc { my ($subdir, $pm_only) = @_; require File::Find; $subdir =~ s/\$CurrentPackage/$CurrentPackage/; _validate_cached_inc(); my $cached_val = $_glob_cache{$subdir}; return @$cached_val if $cached_val; my @files; foreach my $inc (grep !/\bBSDPAN\b/, @INC, @IncludeLibs) { my $dir = "$inc/$subdir"; next unless -d $dir; # canonicalize $inc (ie. use "/" as filename separator exclusively) # as newer versions of File::Find return a canonicalized $File::Find::name (my $canon = $inc) =~ s|\\|/|g; File::Find::find( sub { return unless -f $_; return if $pm_only and !/\.p[mh]$/i; (my $file = $File::Find::name) =~ s|\\|/|g; (my $name = $file) =~ s|^\Q$canon\E/||; push @files, $pm_only ? $name : { file => $file, name => $name }; }, $dir ); } $_glob_cache{$subdir} = \@files; return @files; } # like _glob_in_inc, but looks only at the first level # (i.e. the children of $subdir) # NOTE: File::Find has no public notion of the depth of the traversal # in its "wanted" callback, so it's not helpful sub _glob_in_inc_1 { my ($subdir, $pm_only) = @_; $subdir =~ s/\$CurrentPackage/$CurrentPackage/; my @files; foreach my $inc (grep !/\bBSDPAN\b/, @INC, @IncludeLibs) { my $dir = "$inc/$subdir"; next unless -d $dir; opendir(my $dh, $dir) or next; my @names = map { "$subdir/$_" } grep { -f "$dir/$_" } readdir $dh; closedir $dh; push @files, $pm_only ? ( grep { /\.p[mh]$/i } @names ) : ( map { { file => "$inc/$_", name => $_ } } @names ); } return @files; } my $unicore_stuff; sub _unicore { $unicore_stuff ||= [ 'utf8_heavy.pl', map $_->{name}, _glob_in_inc('unicore', 0) ]; return @$unicore_stuff; } # App::Packer compatibility functions sub new { my ($class, $self) = @_; return bless($self ||= {}, $class); } sub set_file { my $self = shift; my $script = shift; my ($vol, $dir, $file) = File::Spec->splitpath($script); $self->{main} = { key => $file, file => $script, }; } sub set_options { my $self = shift; my %args = @_; foreach my $module (@{ $args{add_modules} }) { $module = _mod2pm($module) unless $module =~ /\.p[mh]$/i; my $file = _find_in_inc($module) or _warn_of_missing_module($module, $args{warn_missing}), next; $self->{files}{$module} = $file; } } sub calculate_info { my $self = shift; my $rv = scan_deps( 'keys' => [ $self->{main}{key}, sort keys %{ $self->{files} }, ], files => [ $self->{main}{file}, map { $self->{files}{$_} } sort keys %{ $self->{files} }, ], recurse => 1, ); my $info = { main => { file => $self->{main}{file}, store_as => $self->{main}{key}, }, }; my %cache = ($self->{main}{key} => $info->{main}); foreach my $key (sort keys %{ $self->{files} }) { my $file = $self->{files}{$key}; $cache{$key} = $info->{modules}{$key} = { file => $file, store_as => $key, used_by => [ $self->{main}{key} ], }; } foreach my $key (sort keys %{$rv}) { my $val = $rv->{$key}; if ($cache{ $val->{key} }) { defined($val->{used_by}) or next; push @{ $info->{ $val->{type} }->{ $val->{key} }->{used_by} }, @{ $val->{used_by} }; } else { $cache{ $val->{key} } = $info->{ $val->{type} }->{ $val->{key} } = { file => $val->{file}, store_as => $val->{key}, used_by => $val->{used_by}, }; } } $self->{info} = { main => $info->{main} }; foreach my $type (sort keys %{$info}) { next if $type eq 'main'; my @val; if (UNIVERSAL::isa($info->{$type}, 'HASH')) { foreach my $val (sort values %{ $info->{$type} }) { @{ $val->{used_by} } = map $cache{$_} || "!!$_!!", @{ $val->{used_by} }; push @val, $val; } } $type = 'modules' if $type eq 'module'; $self->{info}{$type} = \@val; } } sub get_files { my $self = shift; return $self->{info}; } sub add_preload_rule { my ($pm, $rule) = @_; die qq[a preload rule for "$pm" already exists] if $Preload{$pm}; $Preload{$pm} = $rule; } # scan_deps_runtime utility functions # compile $file if $execute is undef, # otherwise execute $file with arguments @$execute sub _compile_or_execute { my ($file, $execute) = @_; local $ENV{MSD_ORIGINAL_FILE} = $file; my ($ih, $instrumented_file) = File::Temp::tempfile(UNLINK => 1); my (undef, $data_file) = File::Temp::tempfile(UNLINK => 1); local $ENV{MSD_DATA_FILE} = $data_file; # spoof $0 (to $file) so that FindBin works as expected # NOTE: We don't directly assign to $0 as it has magic (i.e. # assigning has side effects and may actually fail, cf. perlvar(1)). # Instead we alias *0 to a package variable holding the correct value. print $ih <<'...'; BEGIN { my $_0 = $ENV{MSD_ORIGINAL_FILE}; *0 = \$_0; } ... # NOTE: When compiling the block will run as the last CHECK block; # when executing the block will run as the first END block and # the programs continues. print $ih $execute ? "END\n" : "CHECK\n", <<'...'; { require DynaLoader; my @_dl_shared_objects = @DynaLoader::dl_shared_objects; my @_dl_modules = @DynaLoader::dl_modules; # save %INC etc so that requires below don't pollute them my %_INC = %INC; my @_INC = @INC; require Cwd; require Data::Dumper; require Config; my $dlext = $Config::Config{dlext}; foreach my $k (keys %_INC) { # NOTES: # (1) An unsuccessful "require" may store an undefined value into %INC. # (2) If a key in %INC was located via a CODE or ARRAY ref or # blessed object in @INC the corresponding value in %INC contains # the ref from @INC. # (3) Some modules (e.g. Moose) fake entries in %INC, e.g. # "Class/MOP/Class/Immutable/Moose/Meta/Class.pm" => "(set by Moose)" # On some architectures (e.g. Windows) Cwd::abs_path() will throw # an exception for such a pathname. my $v = $_INC{$k}; if (defined $v && !ref $v && -e $v) { $_INC{$k} = Cwd::abs_path($v); } else { delete $_INC{$k}; } } # drop refs from @_INC @_INC = grep { !ref $_ } @_INC; my @dlls = grep { defined $_ && -e $_ } Module::ScanDeps::DataFeed::_dl_shared_objects(); my @shared_objects = @dlls; push @shared_objects, grep { -e $_ } map { (my $bs = $_) =~ s/\.\Q$dlext\E$/.bs/; $bs } @dlls; # write data file my $data_file = $ENV{MSD_DATA_FILE}; open my $fh, ">", $data_file or die "Couldn't open $data_file: $!\n"; print $fh Data::Dumper::Dumper( { '%INC' => \%_INC, '@INC' => \@_INC, dl_shared_objects => \@shared_objects, }); close $fh; sub Module::ScanDeps::DataFeed::_dl_shared_objects { if (@_dl_shared_objects) { return @_dl_shared_objects; } elsif (@_dl_modules) { return map { Module::ScanDeps::DataFeed::_dl_mod2filename($_) } @_dl_modules; } return; } sub Module::ScanDeps::DataFeed::_dl_mod2filename { my $mod = shift; return if $mod eq 'B'; return unless defined &{"$mod\::bootstrap"}; # cf. DynaLoader.pm my @modparts = split(/::/, $mod); my $modfname = defined &DynaLoader::mod2fname ? DynaLoader::mod2fname(\@modparts) : $modparts[-1]; my $modpname = join('/', @modparts); foreach my $dir (@_INC) { my $file = "$dir/auto/$modpname/$modfname.$dlext"; return $file if -e $file; } return; } } # END or CHECK ... # append the file to compile or execute { open my $fh, "<", $file or die "Couldn't open $file: $!"; print $ih qq[#line 1 "$file"\n], <$fh>; close $fh; } close $ih; # run the instrumented file my $rc = system( $^X, $execute ? () : ("-c"), (map { "-I$_" } @IncludeLibs), $instrumented_file, $execute ? @$execute : ()); die $execute ? "SYSTEM ERROR in executing $file @$execute: $rc" : "SYSTEM ERROR in compiling $file: $rc" unless $rc == 0; my $info = do $data_file or die "error extracting info from -c/-x file: ", ($@ || "can't read $data_file: $!"); return $info; } # create a new hashref, applying fixups sub _info2rv { my ($info) = @_; my $rv = {}; my $incs = join('|', sort { length($b) <=> length($a) } map { s|\\|/|g; s|/+$||; quotemeta($_) } @{ $info->{'@INC'} }); my $i = is_insensitive_fs() ? "i" : ""; my $strip_inc_prefix = qr{^(?$i:$incs)/}; require File::Spec; foreach my $key (keys %{ $info->{'%INC'} }) { (my $path = $info->{'%INC'}{$key}) =~ s|\\|/|g; # NOTE: %INC may contain (as keys) absolute pathnames, # e.g. for autosplit .ix and .al files. In the latter case, # the key may also start with "./" if found via a relative path in @INC. $key =~ s|\\|/|g; $key =~ s|^\./||; $key =~ s/$strip_inc_prefix//; $rv->{$key} = { 'used_by' => [], 'file' => $path, 'type' => _gettype($path), 'key' => $key }; } foreach my $path (@{ $info->{dl_shared_objects} }) { $path =~ s|\\|/|g; (my $key = $path) =~ s/$strip_inc_prefix//; $rv->{$key} = { 'used_by' => [], 'file' => $path, 'type' => 'shared', 'key' => $key }; } return $rv; } sub _gettype { my $name = shift; return 'autoload' if $name =~ /\.(?:ix|al|bs)$/i; return 'module' if $name =~ /\.p[mh]$/i; return 'shared' if $name =~ /\.\Q$Config{dlext}\E$/i; return 'data'; } # merge all keys from $rv_sub into the $rv mega-ref sub _merge_rv { my ($rv_sub, $rv) = @_; my $key; foreach $key (keys(%$rv_sub)) { my %mark; if ($rv->{$key} and _not_dup($key, $rv, $rv_sub)) { warn "Different modules for file '$key' were found.\n" . " -> Using '" . abs_path($rv_sub->{$key}{file}) . "'.\n" . " -> Ignoring '" . abs_path($rv->{$key}{file}) . "'.\n"; $rv->{$key}{used_by} = [ grep (!$mark{$_}++, @{ $rv->{$key}{used_by} }, @{ $rv_sub->{$key}{used_by} }) ]; @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} }; $rv->{$key}{file} = $rv_sub->{$key}{file}; } elsif ($rv->{$key}) { $rv->{$key}{used_by} = [ grep (!$mark{$_}++, @{ $rv->{$key}{used_by} }, @{ $rv_sub->{$key}{used_by} }) ]; @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} }; } else { $rv->{$key} = { used_by => [ @{ $rv_sub->{$key}{used_by} } ], file => $rv_sub->{$key}{file}, key => $rv_sub->{$key}{key}, type => $rv_sub->{$key}{type} }; @{ $rv->{$key}{used_by} } = grep length, @{ $rv->{$key}{used_by} }; } } } sub _not_dup { my ($key, $rv1, $rv2) = @_; if (is_insensitive_fs) { return lc(abs_path($rv1->{$key}{file})) ne lc(abs_path($rv2->{$key}{file})); } else { return abs_path($rv1->{$key}{file}) ne abs_path($rv2->{$key}{file}); } } sub _warn_of_runtime_loader { my $module = shift; return if $SeenRuntimeLoader{$module}++; $module =~ s/\.pm$//; $module =~ s|/|::|g; warn "# Use of runtime loader module $module detected. Results of static scanning may be incomplete.\n"; return; } sub _warn_of_missing_module { my $module = shift; my $warn = shift; return if not $warn; return if not $module =~ /\.p[ml]$/; warn "# Could not find source file '$module' in \@INC or \@IncludeLibs. Skipping it.\n" if not -f $module; } sub _get_preload1 { my $pm = shift; my $preload = $Preload{$pm} or return(); if ($preload eq 'sub') { $pm =~ s/\.p[mh]$//i; return _glob_in_inc($pm, 1); } elsif (UNIVERSAL::isa($preload, 'CODE')) { return $preload->($pm); } return @$preload; } sub _get_preload { my ($pm, $seen) = @_; $seen ||= {}; $seen->{$pm}++; my @preload; foreach $pm (_get_preload1($pm)) { next if $seen->{$pm}; $seen->{$pm}++; push @preload, $pm, _get_preload($pm, $seen); } return @preload; } 1; __END__ =head1 SEE ALSO L is a bundled utility that writes C section for a number of files. An application of B is to generate executables from scripts that contains prerequisite modules; this module supports two such projects, L and L. Please see their respective documentations on CPAN for further information. Other modules which accomplish the same goal with different approach: L, L, L, L. =head1 AUTHORS Audrey Tang Ecpan@audreyt.orgE To a lesser degree: Steffen Mueller Esmueller@cpan.orgE Parts of heuristics were deduced from: =over 4 =item * B by ActiveState Tools Corp L =item * B by IndigoStar, Inc L =back The B function is contributed by Edward S. Peschko. You can write to the mailing list at Epar@perl.orgE, or send an empty mail to Epar-subscribe@perl.orgE to participate in the discussion. Archives of the mailing list are available at Ehttps://www.mail-archive.com/par@perl.org/E or Ehttps://groups.google.com/g/perl.parE. Please submit bug reports to Ehttps://github.com/rschupp/Module-ScanDeps/issuesE. =head1 COPYRIGHT Copyright 2002-2008 by Audrey Tang Ecpan@audreyt.orgE; 2005-2010 by Steffen Mueller Esmueller@cpan.orgE. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L =cut