In a couple of projects I’ve been working on (about which I expect to write more in the future), I’ve found myself wanting to re-use some set of functions or classes I’ve defined elsewhere, but that are not yet fully ready for public release. Or wanting to use a version of an upstream module that I’ve patched locally.

It turns out this is not at all hard to do, and it works really well. The trick is to use Carton to bundle the dependencies, and then install them either using Carton itself, or a recent-enough version of cpanm.

Bundling your dependencies

You’ll need carton 1.0.32 or greater in order to generate the package index. Older versions might still work, but like it says in the changelog, this version is the first to write the bundled index to a file that can be read by plain cpanm, which will come in handy if you don’t want to depend on carton itself in order to install your project.

Once you have them, the first thing to do will be to specify your dependencies in a way carton can understand them. For that you’ll need to use a cpanfile (although you should probably always be using a cpanfile).

Once that is done, you can install your dependencies using carton install in the root of your project (where you have your cpanfile).

Running this command will read the dependencies from your cpanfile, fetch them, and install them into local. While doing this, it will also cache the tarballs of all the distributions it downloads into local/cache.

Once that is done, you can run carton bundle to store a copy of this cache in a vendor directory, which will look like this:

$ tree vendor/
vendor/
└── cache
    ├── authors
    │   └── id
    │       └── F
    │           └── FO
    │               └── FOOBAR
    │                   ├── Some-Dist-1.337.tar.gz
    │                   └── Another-Dist-0.001001.tar.gz
    └── modules
        └── 02packages.details.txt.gz

And that generated 02packages.details.txt.gz file will look like this:

File:         02packages.details.txt
URL:          http://www.perl.com/CPAN/modules/02packages.details.txt
Description:  Package names found in cpanfile.snapshot
Columns:      package name, version, path
Intended-For: Automated fetch routines, namespace documentation.
Written-By:   Carton v1.0.34
Line-Count:   2
Last-Updated: Mon Jan 27 12:34:56 2020

Some::Dist      1.337     F/FO/FOOBAR/Some-Dist-1.337.tar.gz
Another::Dist   0.001001  F/FO/FOOBAR/Another-Dist-0.001001.tar.gz

Note that this will include all the dependencies in your cpanfile. If you only want to bundle some dependencies with your project you’ll have to manually remove all the ones you don’t care about (from both vendor/cache and your 02packages.details.txt.gz). Alternatively, you can read a follow-up post where I investigate this in more detail and provide some different solutions.

Once that is done, this vendor directory can be added to your source control and distributed as normal.

Installing your dependencies

The good thing about this is that this process does not require carton on the installing side.

Of course, if you have Carton, you can tell it to install the dependencies of your project as normal, and give it the --cached option to tell it to use the bundled modules.

If not, starting from version 1.7016 cpanm includes a --from option which allows you to specify where it should get the packages to install. In order to fully emulate the carton install --cached you can use the following command:

cpanm -L local \
    --from "$PWD/vendor/cache" \
    --installdeps --notest --quiet .

Or, if you’re using cpm:

# Note that the --resolver option is marked experimental!
cpm install                                          \
    --resolver "02packages,file://$PWD/vendor/cache" \
    --resolver snapshot                              \
    --resolver metadb

All of these will install your dependencies under local, so you can use them like so:

# Using carton
carton exec some/path.pl

# Using plain perl
perl -Ilocal/lib/perl5 some/path.pl

Hope this helps! It has helped me a lot.