ZetCode

Perl modules and CPAN

last modified November 7, 2026

CPAN, the Comprehensive Perl Archive Network, holds tens of thousands of distributions of Perl modules. Using them involves two questions: how Perl finds a module when a program says use Some::Module, and how to install modules so that each program gets the versions it needs. This article answers both. It covers the @INC search path, use lib and PERL5LIB, and the toolchain built around CPAN: the cpanm installer, local::lib for private module directories, the cpanfile dependency list, and Carton for reproducible installs. Writing modules of our own is covered in the Perl packages article.

The examples were run with Perl 5.44 installed by perlbrew on Linux, with cpanm 1.7049, local::lib 2.000029, and Carton 1.0.35. The shell on this machine has local::lib enabled for ~/perl5, which shows up in some of the paths below.

The @INC search path

When Perl loads a module, it looks through the directories listed in the @INC array. Each module it loads is recorded in the %INC hash, together with the file it came from.

inc.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use Path::Tiny;
use List::Util;

say 'Search path (@INC):';
say "  $_" for @INC;

say 'Loaded modules (%INC):';

for my $name (sort keys %INC) {
    next unless $name =~ m{^(Path/Tiny|List/Util|strict)\.pm$};
    say "  $name => $INC{$name}";
}

The first two directories come from the PERL5LIB environment variable set by local::lib; modules installed by the user go there. The next ones belong to the perl installation: site_perl for modules installed into the perl itself and the plain lib directory for the core modules that ship with Perl. The x86_64-linux directories hold modules with compiled C parts. The keys of %INC are relative file names and the values show where each module was actually found: Path::Tiny in the user directory, List::Util and strict in the core. Unlike older versions, Perl no longer includes the current directory, ., in @INC, for security reasons.

$ perl inc.pl
Search path (@INC):
  /home/jano/perl5/lib/perl5/x86_64-linux
  /home/jano/perl5/lib/perl5
  /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0/x86_64-linux
  /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0
  /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux
  /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0
Loaded modules (%INC):
  List/Util.pm => /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux/List/Util.pm
  Path/Tiny.pm => /home/jano/perl5/lib/perl5/Path/Tiny.pm
  strict.pm => /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/strict.pm

How a module is found

Perl turns a module name into a relative path by replacing each :: with a slash and appending .pm; thus My::Greeting becomes My/Greeting.pm. It then tries the directories of @INC in order and loads the first file that exists. To see this, we create two versions of the same module in two directories.

first/My/Greeting.pm
package My::Greeting;

use strict;
use warnings;

our $VERSION = '1.00';

sub hello { return 'hello from the first directory' }

1;

The module in the second directory is identical except that its $VERSION is 2.00 and its message says second directory. The program loads the module and reports which one it got.

greet.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use My::Greeting;

say My::Greeting::hello();
say 'version ', My::Greeting->VERSION;
say 'loaded from ', $INC{'My/Greeting.pm'};

The -I option adds directories to the front of @INC, in the order given, so the first -I wins. The PERL5LIB variable does the same from the environment. Without either, the module cannot be found and Perl lists all the directories it tried. Because the search stops at the first match, a stale copy of a module early in @INC silently hides a newer one later in the list; checking %INC is the quickest way to find out which file was really loaded.

$ perl -Ifirst -Isecond greet.pl
hello from the first directory
version 1.00
loaded from first/My/Greeting.pm
$ perl -Isecond -Ifirst greet.pl
hello from the second directory
version 2.00
loaded from second/My/Greeting.pm
$ PERL5LIB=second perl greet.pl
hello from the second directory
version 2.00
loaded from second/My/Greeting.pm
$ perl greet.pl
Can't locate My/Greeting.pm in @INC (you may need to install the My::Greeting module) (@INC entries checked: /home/jano/perl5/lib/perl5/x86_64-linux /home/jano/perl5/lib/perl5 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0) at greet.pl line 6.
BEGIN failed--compilation aborted at greet.pl line 6.

use lib and FindBin

A program that ships with its own modules should not rely on the user setting -I or PERL5LIB. The lib pragma adds directories to @INC at compile time, and FindBin finds the directory of the running script, so the path works from anywhere.

uselib.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use FindBin qw($RealBin);
use lib "$RealBin/second";
use My::Greeting 2.00;

say My::Greeting::hello();
say "version ", My::Greeting->VERSION;

The $RealBin variable holds the script's directory with symbolic links resolved. A relative path such as use lib 'second' would be resolved against the current working directory, which breaks when the program is started from elsewhere. The use lib line must come before the use statements that depend on it, since both run at compile time.

$ perl uselib.pl
hello from the second directory
version 2.00

Minimum versions

A version number after the module name in use demands at least that version. Perl calls the module's VERSION method, which compares it with the module's $VERSION variable.

minver.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use lib 'first';
use My::Greeting 2.00;

say My::Greeting::hello();

The first directory holds version 1.00, so the program stops at compile time with a clear message. Requiring a minimum version is better than failing later with an obscure error because a method or an option is missing.

$ perl minver.pl
My::Greeting version 2 required--this is only version 1.00 at minver.pl line 7.
BEGIN failed--compilation aborted at minver.pl line 7.

Optional modules

The use statement loads a module at compile time and dies if it is missing. The require function loads it at run time, so it can be wrapped in eval to make a module optional.

optional.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

for my $module (qw(JSON::PP Cpanel::JSON::XS JSON::MaybeXS)) {
    (my $file = "$module.pm") =~ s{::}{/}g;

    if (eval { require $file; 1 }) {
        say "$module ", $module->VERSION, " is installed";
    } else {
        say "$module is not installed";
    }
}

When require receives a string, it treats it as a file name, so the program converts the module name to a path first. The eval { ...; 1 } idiom returns true only if the block finished. The pattern is common for faster XS modules with a pure-Perl fallback: the program here finds only JSON::PP, which ships with Perl.

$ perl optional.pl
JSON::PP 4.16 is installed
Cpanel::JSON::XS is not installed
JSON::MaybeXS is not installed

Finding installed modules

A few one-liners answer the usual questions about an installed module: where it lives, which version it is, and what the latest release on CPAN is.

$ perldoc -l Path::Tiny List::Util
/home/jano/perl5/lib/perl5/Path/Tiny.pm
/home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux/List/Util.pm
$ perl -MPath::Tiny -E 'say Path::Tiny->VERSION'
0.150
$ cpanm --info Text::CSV
ISHIGAKI/Text-CSV-2.06.tar.gz

Installing modules with cpanm

Perl ships with the cpan client, but most developers use cpanm from the App::cpanminus distribution. It needs no configuration, prints little, and installs dependencies automatically. It can be installed with cpan App::cpanminus, through the system package manager, or with curl -L https://cpanmin.us | perl - App::cpanminus. Then cpanm Module::Name downloads, builds, tests, and installs a module; the -n option skips the tests.

Where modules go depends on the environment. With the system perl, a plain cpanm needs root access to write to the system directories, which can conflict with the distribution's own packages; with a perlbrew perl or with local::lib enabled, modules go to directories owned by the user. The -L option installs into a project directory instead. We create a small project that uses Text::Table::Tiny.

report.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;
use Text::Table::Tiny qw(generate_table);

my @rows = (
    ['Module', 'Purpose'],
    ['Path::Tiny', 'file paths'],
    ['Text::CSV', 'CSV files'],
    ['Mojolicious', 'web framework'],
);

say generate_table(rows => \@rows, header_row => 1);
say 'Text::Table::Tiny ', Text::Table::Tiny->VERSION;

The program renders a list of rows as a text table. Running cpanm -L extlib in the project directory installs the module and all of its dependencies into extlib; modules already present in the user's ~/perl5 are deliberately ignored, so the directory is self-contained. The output shows how cpanm resolves the dependency tree, installing the dependencies before the module that needs them.

$ cpanm -L extlib Text::Table::Tiny
--> Working on Text::Table::Tiny
Fetching http://www.cpan.org/authors/id/N/NE/NEILB/Text-Table-Tiny-1.03.tar.gz ... OK
Configuring Text-Table-Tiny-1.03 ... OK
==> Found dependencies: Ref::Util, Test::Fatal, String::TtyLength
--> Working on Ref::Util
Fetching http://www.cpan.org/authors/id/A/AR/ARC/Ref-Util-0.204.tar.gz ... OK
Configuring Ref-Util-0.204 ... OK
==> Found dependencies: Ref::Util::XS
--> Working on Ref::Util::XS
Fetching http://www.cpan.org/authors/id/X/XS/XSAWYERX/Ref-Util-XS-0.117.tar.gz ... OK
Configuring Ref-Util-XS-0.117 ... OK
Building and testing Ref-Util-XS-0.117 ... OK
Successfully installed Ref-Util-XS-0.117
Building and testing Ref-Util-0.204 ... OK
Successfully installed Ref-Util-0.204
--> Working on Test::Fatal
Fetching http://www.cpan.org/authors/id/R/RJ/RJBS/Test-Fatal-0.019.tar.gz ... OK
Configuring Test-Fatal-0.019 ... OK
==> Found dependencies: Try::Tiny
--> Working on Try::Tiny
Fetching http://www.cpan.org/authors/id/E/ET/ETHER/Try-Tiny-0.32.tar.gz ... OK
Configuring Try-Tiny-0.32 ... OK
Building and testing Try-Tiny-0.32 ... OK
Successfully installed Try-Tiny-0.32
Building and testing Test-Fatal-0.019 ... OK
Successfully installed Test-Fatal-0.019
--> Working on String::TtyLength
Fetching http://www.cpan.org/authors/id/N/NE/NEILB/String-TtyLength-0.03.tar.gz ... OK
Configuring String-TtyLength-0.03 ... OK
==> Found dependencies: Unicode::EastAsianWidth
--> Working on Unicode::EastAsianWidth
Fetching http://www.cpan.org/authors/id/A/AU/AUDREYT/Unicode-EastAsianWidth-12.0.tar.gz ... OK
Configuring Unicode-EastAsianWidth-12.0 ... OK
Building and testing Unicode-EastAsianWidth-12.0 ... OK
Successfully installed Unicode-EastAsianWidth-12.0
Building and testing String-TtyLength-0.03 ... OK
Successfully installed String-TtyLength-0.03
Building and testing Text-Table-Tiny-1.03 ... OK
Successfully installed Text-Table-Tiny-1.03
7 distributions installed

local::lib

Modules in extlib are not in @INC, so the program cannot find them yet. The local::lib module knows the directory layout that cpanm -L creates and sets up both @INC and the environment for it. Loading it with -M and the directory name adds extlib/lib/perl5 and its architecture subdirectory to @INC for one run.

$ perl report.pl
Can't locate Text/Table/Tiny.pm in @INC (you may need to install the Text::Table::Tiny module) (@INC entries checked: /home/jano/perl5/lib/perl5/x86_64-linux /home/jano/perl5/lib/perl5 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/site_perl/5.44.0 /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0/x86_64-linux /home/jano/perl5/perlbrew/perls/perl-5.44.0/lib/5.44.0) at report.pl line 6.
BEGIN failed--compilation aborted at report.pl line 6.
$ perl -Mlocal::lib=extlib report.pl
+-------------+---------------+
| Module      | Purpose       |
+-------------+---------------+
| Path::Tiny  | file paths    |
| Text::CSV   | CSV files     |
| Mojolicious | web framework |
+-------------+---------------+
Text::Table::Tiny 1.03

Setting up the shell with local::lib

Run without a program, local::lib prints shell commands instead. Evaluating them with eval "$(perl -Mlocal::lib=extlib)" sets PERL5LIB and PATH for the current shell and makes cpanm and cpan install into the directory. The same line with the default ~/perl5 directory in ~/.bashrc is the usual way to give a user a private module directory without root access.

$ perl -Mlocal::lib=extlib
PATH="/home/jano/report/extlib/bin${PATH:+:${PATH}}"; export PATH;
PERL5LIB="/home/jano/report/extlib/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
PERL_LOCAL_LIB_ROOT="/home/jano/report/extlib${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
PERL_MB_OPT="--install_base \"/home/jano/report/extlib\""; export PERL_MB_OPT;
PERL_MM_OPT="INSTALL_BASE=/home/jano/report/extlib"; export PERL_MM_OPT;

The cpanfile

Listing dependencies in a file is better than remembering them. A cpanfile declares the modules a project needs, with minimum versions, in a small Perl DSL.

cpanfile
requires 'perl', '5.034';

requires 'Text::Table::Tiny', '1.03';
requires 'Path::Tiny', '0.150';

on 'test' => sub {
    requires 'Test::More', '1.302';
};

The requires function names a module and an optional minimum version; perl itself can be required as well. The on 'test' block lists modules needed only to run the tests; there are also develop, build, and configure phases, and recommends for optional modules. Running cpanm --installdeps . installs everything the file lists; here, with -L extlib, only Path::Tiny was still missing.

$ cpanm -L extlib --installdeps .
--> Working on .
Configuring /home/jano/report ... OK
==> Found dependencies: Path::Tiny
--> Working on Path::Tiny
Fetching http://www.cpan.org/authors/id/D/DA/DAGOLDEN/Path-Tiny-0.150.tar.gz ... OK
Configuring Path-Tiny-0.150 ... OK
Building and testing Path-Tiny-0.150 ... OK
Successfully installed Path-Tiny-0.150
<== Installed dependencies for .. Finishing.
1 distribution installed

Carton

A cpanfile states minimum versions, so two installs at different times can end up with different module versions. Carton, Perl's equivalent of Bundler or npm, records the exact versions that were installed and can reproduce them on another machine. It is installed with cpanm Carton. The carton install command reads the cpanfile, installs the modules into the local directory, and writes cpanfile.snapshot. Test-only dependencies such as Test::Fatal are not installed this time, because Carton does not run the modules' test suites.

$ carton install
Installing modules using /home/jano/report/cpanfile
Successfully installed Ref-Util-XS-0.117
Successfully installed Ref-Util-0.204
Successfully installed Unicode-EastAsianWidth-12.0
Successfully installed String-TtyLength-0.03
Successfully installed Text-Table-Tiny-1.03
Successfully installed Path-Tiny-0.150
6 distributions installed
Complete! Modules were installed into /home/jano/report/local

carton exec and carton list

To run the program with exactly the installed modules, we use carton exec, which sets up @INC to see local and the core modules only. The carton list command prints the installed distributions.

$ carton exec perl report.pl
+-------------+---------------+
| Module      | Purpose       |
+-------------+---------------+
| Path::Tiny  | file paths    |
| Text::CSV   | CSV files     |
| Mojolicious | web framework |
+-------------+---------------+
Text::Table::Tiny 1.03
$ carton list
Path-Tiny-0.150
Ref-Util-0.204
Ref-Util-XS-0.117
String-TtyLength-0.03
Text-Table-Tiny-1.03
Unicode-EastAsianWidth-12.0

The snapshot file

The snapshot file pins each distribution to an exact release and records the modules it provides and requires. The listing shows its first lines.

$ head -12 cpanfile.snapshot
# carton snapshot format: version 1.0
DISTRIBUTIONS
  Path-Tiny-0.150
    pathname: D/DA/DAGOLDEN/Path-Tiny-0.150.tar.gz
    provides:
      Path::Tiny 0.150
      Path::Tiny::Error 0.150
    requirements:
      Carp 0
      Cwd 0
      Digest 1.03
      Digest::SHA 5.45

Deploying with Carton

Both cpanfile and cpanfile.snapshot belong in version control; the local directory does not. On a server or in a CI job, carton install --deployment installs the versions from the snapshot and fails if the snapshot is out of date, instead of resolving newer ones. The carton bundle command stores the distribution tarballs in the project, for machines without access to CPAN.

Best practices

In this article we have looked at how Perl searches @INC for modules, how -I, PERL5LIB, and use lib change the search path, how to require versions and load optional modules, and how to install and pin dependencies with cpanm, local::lib, cpanfile, and Carton.

Source

App::cpanminus · local::lib · cpanfile · Carton · perlvar: @INC

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Perl tutorials.