use lib for paths relative to a module

2019-07-28 04:44发布

问题:

I'm having issues figuring out relative paths for use lib. Here is an example of what I am trying to do:

I have a directory call foo, which contains a script named myscript.pl. myscript.pl uses a module named Mod1.pm located in a subdirectory called bar, so at the top of myscript.pl I put the lines:

    use lib 'bar';
    use Mod1;

This works fine, until I want Mod1.pm to use another module, foo/bar/asdf/Mod2.pm. If I put a use lib 'asdf' at the top of Mod1.pm this won't work because it searches relative to the directory I'm running myscript.pl from. So I did some searching and found FindBin, and tried adding these lines to Mod1.pm:

    use FindBin;
    use lib "$FindBin::Bin/asdf";

This didn't work either, because FindBin finds the directory that the script I am running (myscript.pl) is located in, not the file that uses FindBin (in this case Mod1.pm). So my question is, is there a way to search relative to where Mod1.pm is located, so that it can find Mod2.pm no matter where the script that uses Mod1.pm is located?

EDIT: Further explanation of my situation to address some of your comments:

I'm working on code in a repository, and all CPAN libraries need to be installed in a directory in the repository. That means that the use libs can't have any absolute paths. For my project there is a central module "Mod1.pm" which usees quite a few CPAN modules that are located in various directories in the repository. All of the use lib statements are of the form use lib '../../foo/bar/asdf' so that they can be found relative to wherever you're running the script from, but this restricts where you can run scripts that use Mod1.pm, which I'm trying to avoid. Another annoying side effect is that I can't just perl -c Mod1.pm to make sure any edits I make to Mod1.pm are sane, unless I'm running it in the right place, which conveniently isn't where Mod1.pm is located. Oh the joys of joining a project that already has messy code in place... Another nasty side effect is that I can't use any CPAN modules that any of you are suggesting to fix my problems, because I'd still need a way to find those.

回答1:

Usually you want your main program to find all its libraries on its own, in which case you should do as Alec Chen answered.

In some situations, such as finding plug ins, you want a library to find other libraries relative to its location.

You can do this using __FILE__ which contains the location of the current file of source code.

package Mod1;

use strict;
use warnings;

use File::Basename;

# Must put this in a BEGIN block so it happens at "compile time" while the
# "use"s are being executed.  But the "my" declaration must happen outside
# the BEGIN block else it can't be seen.
my $module_dir;
BEGIN { $module_dir = dirname(__FILE__); }

use lib $module_dir;

use Mod2;

The funny way that's written above is because each Perl file has two (or more) stages of execution. "Compile time" and "run time". use statements happen at compile time. In order to use lib $module_dir, we must set $module_dir at compile time with a BEGIN block. Confused? There's easier ways...

It is bad form for a module to alter @INC globally. Loading a module should not change the global state of the program (unless that's part of its functionality). You would instead localize it.

package Mod1;

use strict;
use warnings;

use File::Basename;
my $module_dir = dirname(__FILE__);

{
    local @INC;
    push @INC, $module_dir;
    require Mod2;
}

But its probably simpler to just require it directly as a file.

package Mod1;

use strict;
use warnings;

use File::Basename;
use File::Spec;
my $module_dir = dirname(__FILE__);
require File::Spec->catfile($module_dir, "Mod2.pm");

And, this being Perl, there are modules to handle having plugins, Module::Pluggable being very popular.



回答2:

You should use lib in myscript.pl, which is a convention.

For example, your files are organized as:

.
├── bar
│   ├── Mod1.pm
│   └── asdf
│       └── Mod2.pm
└── myscript.pl

then use lib in myscript.pl:

use File::Spec;
use FindBin qw($Bin);
use lib File::Spec->catdir($Bin, 'bar');
use Mod1;

then use Mod1.pm and Mod2.pm just like using other CPAN modules. Just remember to add the dir name before the module name when using Mod2.pm.

in Mod1.pm:

package Mod1;

use asdf::Mod2;

1;

in Mod2.pm:

package asdf::Mod2;

1;