Self-contained applications
In No-Dependency Land
While the proliferation of solutions like local::lib and cpanminus has made it a breeze to manage dependencies, there are still some rare occassions in which we need to be able to ship code that has no external non-core dependencies.
There are a few existing solutions for them, but we're going to concentrate on a new one called FatPacker.
Our application
Of course, we just happen to have a sample application we want to pack. It downloads various pages from our website and compiles a statistics report. It uses HTTP::Tiny as a user agent. Our application begins with the lines:
#!/usr/bin/perl
use strict;
use warnings;
use HTTP::Tiny;
Our app is, surprisingly, saved as the file ourapp.pl.
Packing the deps
App::FatPacker comes with an application called fatpack. You'll use fatpack to get at all of App::FatPacker's features. There are four simple steps for packing your dependencies. Let's go over them.
Tracing
To find out what dependencies our code has, we trace
our app. This will create a file called fatpacker.trace, which includes a list of modules that fatpack has discovered.
$ fatpack trace ourapp.pl
In case some modules aren't successfully traced, you can ask fatpack
to include them:
$ fatpack trace --use=Additional::Module ourapp.pl
If we open the fatpacker.trace file, we can see it collected a few modules, including both HTTP/Tiny.pm and Carp.pm (which HTTP::Tiny uses).
Gathering packlists
Packlists are files that distributions install. They contain information on which modules are included in the distribution. FatPacker needs to find the packlist for each module in order to make sure it includes all dependencies recursively and does not miss anything. One module is likely to use another module, which might use another module in turn, and so on.
We can call packlists-for
with a list of modules, or we can feed it the content of the trace output we created with the previous command. It will print out a list of all the packlists, which we'll simply redirect to a file so we can reuse this information.
$ fatpack packlists-for `cat fatpacker.trace` > packlists
The packlists file will include the path to the packlists of Carp and HTTP::Tiny.
Forming the tree
In this step FatPacker collects all the dependencies recursively into a directory called fatlib, which it will then be able to pack together.
tree
needs a list of packlists. Lucky for us, we saved the packlists that our previous command has found in a file called packlists. Let's just call tree
and feed it that file.
$ fatpack tree `cat packlists`
Taking a look at our fatlib directory, we'll see the following structure:
fatlib/
├── Carp
│ └── Heavy.pm
├── Carp.pm
└── HTTP
└── Tiny.pm
You can clearly see it added HTTP::Tiny and Carp, but you can also see it added Carp::Heavy which comes with Carp. This is what recursively copying dependencies means.
Packing dependencies
Once we have all our dependencies in a directory, we can finally pack it all nicely using the last command: file
. This command packs all the modules in the current fatlib directory. It will also try to pack any lib directory that exists in the current directory. If none is present, you will need to create it.
Since the command only packs the modules, we're still missing our code that uses them, so we will concatenate that as well. We will also print this to a new file so we could ship it.
$ (fatpack file; cat ourapp.pl) > ourapp.packed.pl
Stick a shebang line at the top of ourapp.packed.pl and that's all there is to it!
You can now ship ourapp.packed.pl to any location, and it will include all dependencies recursively.
You can open our newly-packed application file and see the way it has packed everything together:
BEGIN {
my %fatpacked;
$fatpacked{"Carp.pm"} = <<'CARP';
... # entire Carp
CARP
$fatpacked{"Carp/Heavy.pm"} = <<'CARP_HEAVY';
... # entire Carp::Heavy
CARP_HEAVY
$fatpacked{"HTTP/Tiny.pm"} = <<'HTTP_TINY';
... # entire HTTP::Tiny
HTTP_TINY
# fixing of @INC to load these
...
} # END OF FATPACK CODE
#!perl
use strict;
use warnings;
use HTTP::Tiny;
# rest of our code
...CARP
It's already being used!
There is at least one famous project which uses this method to create a self-contained program: cpanminus proved this method to be useful for beginners and seasoned system administrators in providing a self-contained full-fledged CPAN client, always available at your finger-tips without any installations required (other than having a Perl interpreter, of course).
You can always download a packed cpanminus
program and use it, wherever you are, using the following command:
$ curl -kL cpanmin.us > cpanm
$ perl cpanm Some::Module
Caveat
There are some considerations still:
Compile time code will be run
If you have any compile-time code (think BEGIN
blocks), they will be run as part of the tracing step. Generally, these aren't recommended for most use cases anyway.
If you have any compile-time code which shouldn't run upon tracing, you might want to consider refactoring it into run-time code.
Lazily loaded modules won't be found
Any modules that are loaded lazily (such as require
statements) will not be traced successfully. You can, however, provide them as additional modules for the trace
command, as described above.
XS modules are not supported
App::FatPacker only supports Pure-Perl modules, so if you're using any XS modules, you'll need to have them installed remotely.