Bundling vendored modules
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.