When writing an application, or even "just" a module with a command line script, it is very useful to make sure every use statement will load the modules from a directory relative to where the main Perl program can be found.

This will make sure that when you are developing the code, it won't, by accident, load the old installed versions of the modules.

If you check out several copies of the whole project from your version control system. In each copy the scripts will use the module in the same copy. So you can work on several changes at the same time without them interfering with each other.

Directory layout and use-case

A common directory layout of a project would look like this:

.../
    project/
        script/
            app.pl
            code.pl
        lib/
            App/
                Module.pm
                Other/
                    Class.pm

Though there might be some other files and directories, this is what currently interests us.

There are several executable scripts like the app.pl and the code.pl in our case. They are all placed in the directory called script inside the project directory. (Though some people would use the name bin instead of script, this does not matter for us.)

Each one of these scripts start like this:

use strict;
use warnings;
use 5.010;

use App::Module;
use App::Other::Class;

...

If we run this script it will try to load the file implementing App::Module, and later the file implementing App::Other::Class.

In order to find App::Module perl will go over the content of the @INC array that has a list of file-system directory paths in it. It will look for a subdirectory called App and within that subdirectory a file called Module.pm.

The content of @INC was set when Perl was installed and while in another article we can see how to change @INC in several ways, but in all those cases we assumed we know the absolute location of the module within the file-system.

In our case we would like to make sure that the app.pl script will find the module in the lib directory next to it, regardless of where we put the whole project directory.

We would even like to be able to have 2 or more copies of the whole tree, and each copy of the app.pl script should find and load the module from the directory next to it.

This is especially important during development. We might make some changes to the code in one copy of the tree, and we might be working on a totally different change in the other copy. Maybe even on a totally different version of the application.

So we would like to make sure each script will find the module relative to the location of the script.

In other words we would like to add a directory relative to the location of the script to @INC.

Let me stress that. We are not trying to add a directory relative to the current working directory, but relative to the directory where the script can be found.

Solution 1 - FindBin

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

The FindBin module has a global variable called $Bin that we can access as $FindBin::Bin. It contains the path to the directory where the current script can be found. (In our case that would .../project/script.) The catdir method of the File::Spec module/class can concatenate parts of a file-system path in a platform specific way. It is almost like join except that the string it uses to join the parameters depends on the Operating System. On Unix/Linux that will be a slash (/). On Windows that will be back-slash (\). On some other operating systems it might be something else.

So File::Spec->catdir($FindBin::Bin, '..', 'lib') means take the path, go one directory up and then descend into the lib directory.

Passing that path as a parameter to use lib will add the path to the beginning of @INC. So our scripts will look like this:

use strict;
use warnings;
use 5.010;

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

use App::Module;
use App::Other::Class;

...

One of the advantages of this example is that both modules are standard.

Solution 2 - $0

In the second solution we rely on $0 that always contains the name of the current executable script.

use File::Basename;
use File::Spec;
use lib File::Spec->catdir(
            File::Basename::dirname(File::Spec->rel2abs($0)),
            '..',
            'lib');

Because $0 might contain just the name of the script we first need to ask the currently running Perl to calculate the absolute path. We do that using the rel2abs method of the File::Spec module. Once we have the absolute path, we get rid of the last part of the path which is the name of the file We do that by calling the dirname function of File::Basename Then, as before, we use catdir to go one directory up (..) and then descend to the lib directory.

This solution too uses only standard modules.

Solution 3 - Cwd

In the above example we could replace the call File::Spec->rel2abs($0) by a call to Cwd::abs_path($0). That would still work the same way.

Solution 4 - Dir::Self

Another solution I saw recently uses Dir::Self. When loading, this module will add a symbol __DIR__ to the name-space of the current code. This symbol contains the absolute path to the directory where the file is located. So we only need to compute the relative directory using catdir:

use Dir::Self;
use lib File::Spec->catdir(
            __DIR__,
            '..',
            'lib');

This solution already looks better, but it has the drawback of needing an external module. There is also this thing with the strange-looking _DIR__ symbol.

Under the hood, the __DIR__ function uses the following code-snippet to to calculate the current directory:

my $file = (caller 0)[1];
File::Spec->rel2abs(join '', (File::Spec->splitpath($file))[0, 1])

Solution 5 - Path::Tiny

The solution that I currently like the best, involves the use of the Path::Tiny module

use Path::Tiny qw(path);
use lib path($0)->absolute->parent(2)->child('lib')->stringify;

Path::Tiny by default exports the path function, but I like to be explicit - for the sake of the maintenance programmer who will need to figure out where does the path function come from -, so I load the module by writing use Path::Tiny qw(path);.

Given an absolute or relative path, the path function returns an object representing that thing in the file-system. We passed the $0 variable that contains the name of the script being executed.

The absolute method turns it into an absolute path even if it was a relative path originally.

That's important, because if we run the script from the same directory where the script is: perl programming.pl, then $0 will only contain programming.pl and it will be hard to find out what is the parent directory. With absolute paths, we don't have the problem.

The parent method will point to the parent directory. While it is longer than writing '..' but our intentions are clearer with parent, than if we wrote '..' or even if we called dirname.

In addition, though we don't need it for our purposes, you can pass a number to it (e.g. parent(2)) and go several levels up in one call.

Then we are calling the child method with the name of the specific child-directory.

This returns a Path::Tiny object so we still need to call the stringify method to turn it into a real string.

I think our intentions are much clearer with this code, and besides, the Path::Tiny module is very useful for other tasks as well. We used it in the article fetching whois records, for locating a file and reading the content into memory.

Solution 6 - Path::Tiny

As suggested by Yary H in the comments, we can even make it shorter and nicer by using the sibling method:

use Path::Tiny qw(path);
use lib path($0)->absolute->parent->sibling('lib')->stringify;

Solution 7 - rlib

I've never seen this before but Tony Edwardson has suggested rlib which looks great. It has not seen any release since 1998, but it very simple and it seems to "just work". (I've tried on Linux and OSX.)

use rlib '../lib';

Solution 8 - FindBin::libs

FindBin::libs is a bit magical. By default it looks around the location of the currently running script, finds the lib directory and adds it to @INC. You only need to load the module:

use FindBin::libs;

It will work with seeral directory layouts. I have tried the following two cases:

/bin/script.pl
/lib/Module.pm

or if the script is in the root directory of the project:

script.pl
/lib/Module.pm

Unlike the other solutions, if there is no lib directory, this won't add the path to a not-existing directory to @INC.

Conclusion

If you can install CPAN modules, and I really hope for your that you can, I'd recommend Path::Tiny, rlib, or FindBin::libs.