Ran across this little nugget... I think I have it figured out, but see if anyone can shed more light.
I have a module in my PERL5LIB
environment variable that I use for logging. It is in e:/Scripts/Log/Logger.pm
. Following is an excerpt:
package Log::Logger;
...
package Log::NullLogger;
use base 'Log::Logger';
...
package Log::FileLogger;
use base 'Log::Logger';
...
package Log::DateFileLogger;
use base 'Log::FileLogger';
...
package LogTester;
use Data::Dump 'dump';
test() unless caller();
sub test {
warn dump \%INC;
...
}
I wanted to make some changes to this module so I copied it to e:/Test/Log/Log/Logger.pm
and started making changes and testing... I added a new class and altered an inherited routine in the Log::Logger
package...
package BasicFormatter;
...
But my tests were very odd. Some of the new code was being used, some was not. The dump of %INC
had:
"Log/Logger.pm" => "e:/Scripts/Log/Logger.pm"
... the old module! From what I can tell, use base 'Log::Logger'
went out and re-loaded Log/Logger.pm
from PERL5LIB
, replacing only those packages in it (i.e. package BasicFormatter
was coming from the new module, but all other packages were from the old module).
I read about use parent
and when I replaced my use base
with use parent
I got compile errors on use parent 'Log::FileLogger'
ostensibly because there was no Log/FileLogger.pm
module. use base
never complained about this -- a weakness in use parent
in my opinion.
I added
use lib '..';
to the top of the module and everything worked with use base
but that was not a fix I like, because it would not be in the final module when I "publish" it back to e:/Scripts/Log/Logger.pm
. With the use lib '..'
, %INC
now showed
"Log/Logger.pm" => "../Log/Logger.pm"
which makes sense.
My next step was to remove the use lib '..'
and replace all use base...
with our @ISA=(...)
. When I did this, the code started working and %INC
didn't show an entry for Log/Logger.pm
at all.
Note, when I set up a test script in e:/Test/Log/TestLogger.pl
with
use lib '.';
in it, it picks up e:/Test/Log/Log/Logger.pm
fine and the use base
calls don't re-require the package.
So, my conclusion is this... when running the module as a modulino, the module isn't "loaded" as far as Perl is concerned. So the first attempt to use base
on that module does a require <module>
which then loads it from the @INC
path, which could replace packages already in the modulino being executed. Using @ISA
instead bypasses the calls to require
and so the module isn't loaded from @INC
.
Anyone else got more information on this... is testing a module as a modulino just a bad idea?
I think it would be useful to clarify a few things:
@ISA
is for defining a package's parent classes. It does not affect how packages are loaded. You can manipulate it directly, or you can use the parent
or the slightly older base
pragmas. The difference between manipulating @ISA
directly and these two pragmas is that the pragmas do a little more: use base qw/Foo Bar/;
and use parent qw/Foo Bar/;
are both roughly similar in effect to
BEGIN {
require Foo;
require Bar;
push @ISA, qw(Foo Bar);
}
meaning that not only do they manipulate @ISA
, they also load the package with require
. There is one exception, which is use parent -norequire, qw/Foo Bar/;
- this may be useful if, as you seem to be showing, all of your packages reside in the same file. However, both pragmas do not affect how require
operates, including where it looks, for that you need to manipulate @INC
.
@INC
is the place that Perl's use
, require
, and do
look for files (the latter with a few exceptions). There are several ways to manipulate @INC
:
perl -I/path/to/modules script.pl
(see perlrun)use lib '/path/to/modules';
, or by direct manipulation of @INC
PERL5LIB
environment variablesitecustomize.pl
(only when configured)Perl looks through @INC
in order. So in your case, if Perl is loading the wrong version of Log::Logger
, that means that Perl finds it earlier in @INC
than the version you actually wanted to load. I would recommend inspecting @INC
and making sure that you only include those paths from which you actually want to load modules, or placing the path for the "development" version of Log::Logger
earlier in @INC
. For example, when I develop modules, I usually specify the directory for the development version on the command line with e.g. perl -Ilib ...
.
Note that there was a change as of Perl 5.26: the current directory .
was removed as a default entry in @INC
. (Perl 5.24.1 also introduced some related changes.)
%INC
basically just keeps track of which modules have already been loaded, so that require
and use
don't load the same module twice. While it is possible to trick require
into not looking for a file by adding its entry to %INC
manually, there are usually better solutions than this.
parent
requires you to explicitly say whether you want it to actually load the parent module or just set up inheritance. This is a strength, not a weakness; base
had the weakness of always trying to load the module, leading to the problem you had.
Do:
use parent -norequire => 'Log::Logger';
...
use parent -norequire => 'Log::FileLogger';
etc.
That said, the problem base
had wouldn't have happened if you consistently used use
or require
to load your module; if you just run a module (e.g. perl Foo/Bar.pm
) or otherwise load it (use Test::Log::Logger
instead of use lib 'Test'; use Log::Logger
??) and your code also tries to use
it, you always risk multiple versions being found.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With