The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 032
MANIFEST 02
META.yml 23
Makefile.PL 44
lib/Module/ScanDeps.pm 65123
t/0-pod.t 22
t/13-static_prefork_test.t 97
t/3-static_oo_interface_real.t 1115
t/5-pluggable_fake.t 128
t/6-file-glob.t 33
t/data/rt90869.pl 07
t/rt90869.t 017
12 files changed (This is a version diff) 108223
@@ -1,3 +1,35 @@
+[Changes for 1.12 - 2013-12-21]
+* Fix recognition of (open() arguments) "<:encoding(klingon)",
+  implies modules PerlIO and PerlIO::encoding.
+
+[Changes for 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
+
+[Changes for 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
+
 [Changes for 1.10 - 2012-10-20]
 * add %Preload rule for Params::Validate to detect 
   its PP and XS implementations
@@ -48,6 +48,7 @@ t/data/file-glob-yes.pl
 t/data/pluggable/Foo.pm
 t/data/pluggable/Foo/Plugin/Bar.pm
 t/data/pluggable/Foo/Plugin/Baz.pm
+t/data/rt90869.pl
 t/data/ScanFileRE/auto/example/example.h
 t/data/ScanFileRE/example.pm
 t/data/ScanFileRE/example_too.pm
@@ -73,4 +74,5 @@ t/data/static/TestC.pm
 t/data/static/TestD.pm
 t/data/static/useVERSION.pm
 t/data/use_lib.pl
+t/rt90869.t
 t/Utils.pm
@@ -5,6 +5,7 @@ author:
 build_requires:
   ExtUtils::MakeMaker: 6.59
   Test::More: 0
+  Test::Requires: 0
 configure_requires:
   ExtUtils::MakeMaker: 6.59
 distribution_type: module
@@ -19,7 +20,7 @@ no_index:
   directory:
     - inc
     - t
-  module:
+  package:
     - Module::ScanDeps::Cache
     - Module::ScanDeps::DataFeed
 requires:
@@ -31,4 +32,4 @@ requires:
 resources:
   license: http://dev.perl.org/licenses/
   repository: http://svn.openfoundry.org/par/Module-ScanDeps/trunk/
-version: 1.10
+version: 1.13
@@ -11,12 +11,12 @@ requires        'File::Temp'                => '0';
 requires        'File::Spec'                => '0';
 requires        'Module::Build::ModuleInfo' => '0';
 requires        'version'                   => '0';
-build_requires  'Test::More'                => '0';
+test_requires   'Test::More'                => '0';
+test_requires   'Test::Requires'            => '0';
 
 install_script  'script/scandeps.pl';
 
-no_index        module  => 'Module::ScanDeps::Cache';
-no_index        module  => 'Module::ScanDeps::DataFeed';
+no_index        package => qw( Module::ScanDeps::Cache 
+                               Module::ScanDeps::DataFeed );
 
-#sign;
 WriteAll;
@@ -4,7 +4,7 @@ use strict;
 use warnings;
 use vars qw( $VERSION @EXPORT @EXPORT_OK @ISA $CurrentPackage @IncludeLibs $ScanFileRE );
 
-$VERSION   = '1.10';
+$VERSION   = '1.13';
 @EXPORT    = qw( scan_deps scan_deps_runtime );
 @EXPORT_OK = qw( scan_line scan_chunk add_deps scan_deps_runtime path_to_inc_name );
 
@@ -200,7 +200,7 @@ C<$Module::ScanDeps::ScanFileRE = qr/./>
 
 =head1 CAVEATS
 
-This module intentially ignores the B<BSDPAN> hack on FreeBSD -- the
+This module intentionally ignores the B<BSDPAN> hack on FreeBSD -- the
 additional directory is removed from C<@INC> altogether.
 
 The static-scanning heuristic is not likely to be 100% accurate, especially
@@ -219,12 +219,14 @@ But this one does not:
 =cut
 
 my $SeenTk;
+my %SeenRuntimeLoader;
 
 # Pre-loaded module dependencies {{{
 my %Preload;
 %Preload = (
     'AnyDBM_File.pm'  => [qw( SDBM_File.pm )],
     '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',
@@ -256,7 +258,9 @@ my %Preload;
     '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' => sub {
+        _find_in_inc('unicore/Name.pl') ? 'unicore/Name.pl' : 'unicode/Name.pl'
+    },
     'Class/Load.pm' => [qw( Class/Load/PP.pm )],
     'Class/MakeMethods.pm' => 'sub',
     'Class/MethodMaker.pm' => 'sub',
@@ -289,6 +293,37 @@ my %Preload;
     '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' => [ map "Event/$_.pm", qw(idle io signal timer var)],
     'ExtUtils/MakeMaker.pm' => sub {
@@ -327,16 +362,11 @@ my %Preload;
         # but accept JSON::XS, too (because JSON.pm might use it if present)
         return( grep /^JSON\/(PP|XS)/, _glob_in_inc('JSON', 1) );
     },
-    'Log/Log4perl.pm' => 'sub',
+    'Locale/Maketext/Lexicon.pm'    => 'sub',
+    'Locale/Maketext/GutsLoader.pm' => [qw( Locale/Maketext/Guts.pm )],
     'Log/Any.pm' => 'sub',
+    'Log/Log4perl.pm' => 'sub',
     'Log/Report/Dispatcher.pm' => 'sub',
-    '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),
-        );
-    },
     'LWP/Parallel.pm' => sub {
         _glob_in_inc( 'LWP/Parallel', 1 ),
         qw(
@@ -348,17 +378,22 @@ my %Preload;
         qw( LWP/Parallel.pm ),
         @{ _get_preload('LWP/Parallel.pm') }
     },
-    'Locale/Maketext/Lexicon.pm'    => 'sub',
-    'Locale/Maketext/GutsLoader.pm' => [qw( Locale/Maketext/Guts.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',
     'Module/Build.pm'               => 'sub',
     'Module/Pluggable.pm'           => sub {
         _glob_in_inc('$CurrentPackage/Plugin', 1);
     },
-    'MIME/Decoder.pm'               => 'sub',
     'Moose.pm'                      => sub {
         _glob_in_inc('Moose', 1),
         _glob_in_inc('Class/MOP', 1),
@@ -372,8 +407,11 @@ my %Preload;
         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,
     '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',
     'Package/Stash.pm'              => [qw( Package/Stash/PP.pm Package/Stash/XS.pm )],
@@ -383,13 +421,16 @@ my %Preload;
     'Params/Validate.pm'            => 'sub',
     'Parse/AFP.pm'                  => 'sub',
     'Parse/Binary.pm'               => 'sub',
-    'Perl/Critic.pm'                => 'sub', #not only Perl/Critic/Policy
-    'PerlIO.pm'                     => [ 'PerlIO/scalar.pm' ],
     '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',
+    'Perl/Critic.pm'                => 'sub', #not only Perl/Critic/Policy
+    'PerlIO.pm'                     => [ 'PerlIO/scalar.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),
@@ -436,6 +477,9 @@ my %Preload;
     '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 );
@@ -457,6 +501,11 @@ my %Preload;
     'URI.pm'            => sub {
         grep !/urn/, _glob_in_inc('URI', 1);
     },
+    'utf8.pm' => sub {
+        # Perl 5.6.x: "unicode", Perl 5.8.x and up: "unicore"
+        my $unicore = _find_in_inc('unicore/Name.pl') ? 'unicore' : 'unicode';
+        return ('utf8_heavy.pl', map $_->{name}, _glob_in_inc($unicore, 0));
+    },
     'Win32/EventLog.pm'    => [qw( Win32/IPC.pm )],
     'Win32/Exe.pm'         => 'sub',
     'Win32/TieRegistry.pm' => [qw( Win32API/Registry.pm )],
@@ -473,48 +522,16 @@ my %Preload;
     'XMLRPC/Lite.pm' => sub {
         _glob_in_inc('XMLRPC/Transport', 1),;
     },
-    'YAML.pm' => [qw( YAML/Loader.pm YAML/Dumper.pm )],
-    '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);
+    '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;";
+        unless ($@) 
+        { 
+            $impl =~ s!::!/!g; 
+            return "$impl.pm"; 
         }
-
-        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';
-    },
-    'threads/shared.pm' => [qw( attributes.pm )],
-    # anybody using threads::shared is likely to declare variables
-    # with attribute :shared
-    'utf8.pm' => sub {
-        # Perl 5.6.x: "unicode", Perl 5.8.x and up: "unicore"
-        my $unicore = _find_in_inc('unicore/Name.pl') ? 'unicore' : 'unicode';
-        return ('utf8_heavy.pl', map $_->{name}, _glob_in_inc($unicore, 0));
-    },
-    'charnames.pm' => sub {
-        _find_in_inc('unicore/Name.pl') ? 'unicore/Name.pl' : 'unicode/Name.pl'
+        _glob_in_inc('YAML', 1);        # fallback
     },
 );
 
@@ -813,8 +830,34 @@ sub scan_line {
 
         if (my ($pragma, $args) = /^use \s+ (autouse|if) \s+ (.+)/x)
         {
-            my @args = do { no strict; no warnings; eval $args };
-            my $module = $pragma eq "autouse" ? $args[0] : $args[1];
+            # 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);
+            # To avoid to parse them ourself, we simply try to eval the 
+            # string after the pragma (in a list context). The MODULE
+            # should be the first ("autouse") or second ("if") element
+            # of the list.
+            my $module;
+            { 
+                no strict; no warnings; 
+                if ($pragma eq "autouse") {
+                    ($module) = eval $args;
+                }
+                else {
+                    # The syntax of the "if" pragma is
+                    #   use if COND, MODULE => ARGUMENTS
+                    # The COND may contain undefined functions (i.e. undefined
+                    # in Module::ScanDeps' context) which would throw an 
+                    # exception. Sneak  "1 || " in front of COND so that
+                    # COND will not be evaluated. This will work in most
+                    # cases, but there are operators with lower precedence
+                    # than "||" which will cause this trick to fail.
+                    (undef, $module) = eval "1 || $args";
+                }
+                # punt if there was a syntax error
+                return if $@ or !defined $module;
+            };
             $module =~ s{::}{/}g;
             return ("$pragma.pm", "$module.pm");
         }
@@ -917,14 +960,20 @@ sub scan_chunk {
             my $diamond = $1;
             return "File/Glob.pm" if $diamond =~ /[*?\[\]{}~\\]/;
         }
+
         return "DBD/$1.pm"    if /\b[Dd][Bb][Ii]:(\w+):/;
-        if (/(?:(:encoding)|\b(?:en|de)code)\(\s*['"]?([-\w]+)/) {
-            my $mod = _find_encoding($2);
-            my @mods = ( 'Encoding.pm' );       # always needed
-            push @mods, 'PerlIO.pm' if $1;      # needed for ":encoding(...)"
+
+        # check for stuff like
+        #   decode("klingon", ...)
+        #   open FH, "<:encoding(klingon)", ...
+        if (my ($io_layer, $encoding) = /(?:(:encoding)|\b(?:en|de)code)\(\s*['"]?([-\w]+)/) {
+            my @mods;
+            my $mod = _find_encoding($encoding);
             push @mods, $mod if $mod;           # "external" Encode module
+            push @mods, qw( PerlIO.pm PerlIO/encoding.pm ) if $io_layer;
             return \@mods;
         }
+
         return $1 if /^(?:do|require)\s+[^"]*"(.*?)"/;
         return $1 if /^(?:do|require)\s+[^']*'(.*?)'/;
         return $1 if /[^\$]\b([\w:]+)->\w/ and $1 ne 'Tk' and $1 ne 'shift';
@@ -1076,7 +1125,7 @@ sub add_deps {
                            type   => $type );
             }
 
-            ### Now, handle module and distribudion share dirs
+            ### Now, handle module and distribution share dirs
             # convert 'Module/Name' to 'Module-Name'
             my $modname = $path;
             $modname =~ s|/|-|g;
@@ -1268,7 +1317,7 @@ sub _compile_or_execute {
                        [ $INC{"Module/ScanDeps/DataFeed.pm"}, $dump_file ],
                        [ qw( datafeedpm dump_file ) ]);
 
-    # save %INC etc so that further requires dont't pollute them
+    # save %INC etc so that further requires don't pollute them
     print $feed_fh <<'...';
     %Module::ScanDeps::DataFeed::_INC = %INC;
     @Module::ScanDeps::DataFeed::_INC = @INC;
@@ -1428,6 +1477,15 @@ sub _abs_path {
 }
 
 
+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;
@@ -1,6 +1,6 @@
 use strict;
 use Test::More;
-eval "use Test::Pod 1.00";
-plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
+use Test::Requires { "Test::Pod" => "1.00" };
+
 all_pod_files_ok();
 
@@ -3,21 +3,19 @@
 use strict;
 use warnings;
 
+use Test::More;
+use Test::Requires qw( prefork );
+
 use lib 't';
+use Utils;
+
 BEGIN {
-  require Test::More;
-  if (not eval "require prefork; 1;" or $@) {
-    Test::More->import(skip_all => "This test requires prefork.pm which is not installed. Skipping.");
-    exit(0);
-  }
-  else {
     # Mwuahahaha!
     delete $INC{"prefork.pm"};
     %prefork:: = ();
-  }
+
+    plan 'no_plan'; # no_plan because the number of objects in the dependency tree (and hence the number of tests) can change
 }
-use Test::More qw(no_plan); # no_plan because the number of objects in the dependency tree (and hence the number of tests) can change
-use Utils;
 
 my $rv;
 my $root;
@@ -3,16 +3,9 @@
 use strict;
 use warnings;
 
-use Test::More tests => 12;
+use Test::More;
 
 my $rv;
-my $root;
-
-##############################################################
-# Tests compilation of Module::ScanDeps
-##############################################################
-BEGIN { use_ok( 'Module::ScanDeps' ); }
-
 
 ##############################################################
 # Tests static dependency scanning on a real set of modules.
@@ -20,12 +13,23 @@ BEGIN { use_ok( 'Module::ScanDeps' ); }
 # majority of files scanned aren't fixed, the checks are
 # necessarily loose.
 ##############################################################
-$root = $0;
 my @deps = qw(
-    Carp.pm Config.pm	Exporter.pm Test/More.pm
-    base.pm constant.pm	strict.pm   vars.pm
+    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);
@@ -5,21 +5,17 @@ use strict;
 use warnings;
 
 use Test::More qw(no_plan); # no_plan because the number of objects in the dependency tree (and hence the number of tests) can change
+use Test::Requires qw( Module::Pluggable );
+
 use lib qw(t t/data/pluggable);
 use Utils;
 
-if (eval {require Module::Pluggable}) {
-   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);
-   generic_scandeps_rv_test($rv, ['t/data/pluggable/Foo.pm'], \@deps);
+my $rv = scan_deps(
+  files   => ['t/data/pluggable/Foo.pm'],
+  recurse => 1,
+);
 
-} else {
-   diag("Module::Pluggable not installed, skipping all tests");
-   pass("Marking test as passed because Module::Pluggable is not available.");
-}
+my @deps = qw(Module/Pluggable.pm Foo/Plugin/Bar.pm Foo/Plugin/Baz.pm);
+generic_scandeps_rv_test($rv, ['t/data/pluggable/Foo.pm'], \@deps);
 
 __END__
@@ -1,10 +1,10 @@
 #!/usr/bin/perl
 
-use Test;
-BEGIN { plan tests => 2 }
+use strict;
+use warnings;
 
+use Test::More tests => 2;
 use Module::ScanDeps;
-
 use lib qw(t/data);
 
 my $map = scan_deps(
@@ -0,0 +1,7 @@
+# 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);
@@ -0,0 +1,17 @@
+#!/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");
+}