local::lib and File::Spec
local::lib
is a great way of providing a contained set of Cpan modules for an application. But it can be awkward if you wish to use it to provide an updated File::Spec
.
I wished to use Path::Tiny
, which I installed using cpanm
to a local::lib
directory. That requires a newer version of File::Spec
than came with the Perl we're using, so cpanm
downloaded and installed that to the local::lib
directory too, just as you'd want.
Our programs specify their local::lib
directory, so they can be run by users without them having to fiddle around setting up environment variables (on Windows). Unfortunately, however, using it in our programs like this didn't work for Path::Tiny
:
use local::lib qw<Cpan>;
use Path::Tiny;
That complains “File::Spec version 3.4 required--this is only version 3.33”.
I'm grateful to Haarg on #perl-help for pointing out that local::lib
itself uses File::Spec
(which isn't at all surprising, when you think about it), so will have loaded the system-installed version of File::Spec
before it gets round to adding the directory where the new one is found to the search path. When Path::Tiny
then uses File::Spec
, the older one is already loaded, which means perl
doesn't go looking for a newer one.
Apparently what I should've been doing is this instead:
use lib qw<Cpan/lib/perl5/>;
use Path::Tiny;
That adds the relevant local::lib
directories first, without actually loading the local::lib
module or any of its dependencies such as File::Spec
.
This works even though File::Spec
is an XS module, so is actually found in a subdirectory called something like Cpan/lib/perl5/MSWin32-x86-multi-thread: the use lib
automatically adds the architecture-specific directory as well, which is handy.
(The above was my fourth attempt at a workaround. Not knowing about that lib
feature, my third attempt involved messing around with $Config{archname}
to build the subdirectory path.
My second attempt was to unload the File::Spec
that local::lib
had loaded:
use local::lib 'Cpan';
BEGIN
{
delete @INC{glob 'File/Spec{,/Unix,/Win32}.pm'};
no strict qw<refs>;
%{'File::Spec::'} = ();
}
use Path::Tiny;
It, erm, works, but doesn't exactly strike me as code we should have in production.
My first attempt tried to use Class::Unload
. I now think that would've worked, but I was copying the example in its synopsis, which uses Class::Inspector
to report whether the class has been unloaded — which itself uses File::Spec
!)
Updated Tweaked after MST explained a little more about local::lib
. And another workaround attempt added (and the others renumbered) after Ribasushi reminded me I'd tried Class::Unload
.
Personally I don't like to use 'lib' or 'local::lib' in my application scripts as I find they lead to unnecessary structural binding between your application and the directory structure.
Ideally you application would be decoupled from its dependency paths (or at least I think so). So I tend to do like:
perl -I $pathtolib bin/myapp.pl
or, if using local::lib
perl -Mlocal::lib=$pathtolib bin/mayapp.pl
I also wrote a tool App::local::lib::helper that may or may not be useful in encapsulating this idea.
If you hard code the path to your local::lib or otherwise into your application, sooner or later its going to cause you trouble. Just my thoughts, good luck!
jnap
That's fine. There are plenty of circumstances where I wouldn't do either.
In this particular case it's for an internal graphical application run by every member of staff in the organization, most of whom aren't particularly technical. Windows users run it by double-clicking on an icon on their desktop. They don't even need to know that it's written in Perl.
As such, the thing that that icon runs needs to be a fully encapsulated application, with all the information needed to run it, including library paths.
There is only one live copy of the application, on a shared network drive that everybody's desktop icon runs. No users ever need to install it anywhere else.
So I find it useful for the main program file to set the library paths. If I wish to change anything, I can far more easily deploy an update to that one file on a networked drive than I can to simultaneously update the icons on everybody's desktops. And of course the source file is under version control, which the icons aren't.
That's a very narrow-minded view. Yes, there are situations where it can cause trouble, but it doesn't follow that it's bad in all situations.
First note that the path I'm ‘hard-coding’ is actually a relative path, merely the name of a subdirectory. So I'm not tying the application to being installed at a particular location. Indeed I do have various dev and staging copies of it at other locations, all of which can have their own subdirectory relative to them.
Finally note that all
use lib
is doing is adding a directory to@INC
as one possible placeperl
will look for modules; it isn't instructing Perl that it must load the modules from there, and that it isn't allowed to look anywhere else for them. Indeed, it doesn't even matter if the directory doesn't exist.So having a single subdirectory added to the search path like this doesn't preclude running the software in one of the ways you suggest, passing in another path in some other way: that will just fine too — timtowtdi!