ETOOBUSY 🚀 minimal blogging for the impatient
Today I Learned: fatpack and PERL5LIB
TL;DR
Today I Learned that fatpack relies on absolute paths in
PERL5LIB
.
While attempting to fatpack the main program for skfold:
$ local/bin/fatpack pack bin/skf > skf
I got out a file without the embedded Perl modules. How come?!?
Due preliminary note
As you may have noticed, I installed fatpack inside local
, along
with the supporting modules for skfold. To make sure that the
modules there are found, I set this:
$ export PERL5LIB='local/lib/perl5'
The relative path does not really bother me - I’m not planning to call skfold from any other directory than the project’s root while hacking on it.
So let’s troubleshoot this
The command I called was the all-in-one, so I thought it best to break it down into the different components.
It turns out that there are four stages:
trace
: some magic figures out which modules are needed by our program and produces a list of their associated module files;packlists-for
: from a list of module files, a list of the files included in the package each was installed for is produced (these files are installed aspacklist
, this is why the command is calledpacklists-for
);tree
: from a list of.packlist
files, produce a sub-directoryfatlib
with all the associated files (including, as it seems, POD files);file
: from a file, alib
and afatlib
, produce the final fat-packed artifact.
Let’s start!
Trace
While the other sub-commands seem to work fine with the standard file
descriptors (standard input and standard output), the initial tracing
command defaults to producing a yadda-yadda-yadda.trace
file, that we
will eventually need to cat
anyway. Luckily, there’s an option to send
it to the standard output right away:
$ local/bin/fatpack trace --to=- bin/skf
...
Path/Tiny.pm
...
Template/Perlish.pm
...
Log/Log4perl/Tiny.pm
...
The list seems fine to me, and the target modules that are not part of CORE are there, so let’s move on.
Packlist files
The next step is producing the list of .packlist
files:
$ local/bin/fatpack packlists-for \
$(local/bin/fatpack trace --to=- bin/skf)
bin/skf syntax OK
/home/foo/skfold/local/lib/perl5/x86_64-linux-gnu-thread-multi/auto/Log/Log4perl/Tiny/.packlist
/home/foo/skfold/local/lib/perl5/x86_64-linux-gnu-thread-multi/auto/Path/Tiny/.packlist
/home/foo/skfold/local/lib/perl5/x86_64-linux-gnu-thread-multi/auto/Template/Perlish/.packlist
Again, it seems encouraging because it detected exactly the right modules that we need to include in our fat-packed version. Let’s move on!
Tree
Let’s call the tree
sub-command now:
$ local/bin/fatpack tree $(local/bin/fatpack packlists-for \
$(local/bin/fatpack trace --to=- bin/skf))
bin/skf syntax OK
$ find fatlib
fatlib
Ouch! Files are not copied to the fatlib
sub-directory, here’s why
they don’t appear in the overall file.
A quick look at the code is helpful (from here):
1 sub packlists_to_tree {
2 my ($self, $where, $packlists) = @_;
3 rmtree $where;
4 mkpath $where;
5 foreach my $pl (@$packlists) {
6 my ($vol, $dirs, $file) = splitpath $pl;
7 my @dir_parts = splitdir $dirs;
8 my $pack_base;
9 PART: foreach my $p (0 .. $#dir_parts) {
10 if ($dir_parts[$p] eq 'auto') {
11 # $p-2 normally since it's <wanted path>/$Config{archname}/auto but
12 # if the last bit is a number it's $Config{archname}/$version/auto
13 # so use $p-3 in that case
14 my $version_lib = 0+!!($dir_parts[$p-1] =~ /^[0-9.]+$/);
15 $pack_base = catpath $vol, catdir @dir_parts[0..$p-(2+$version_lib)];
16 last PART;
17 }
18 }
19 die "Couldn't figure out base path of packlist ${pl}" unless $pack_base;
20 foreach my $source (lines_of $pl) {
21 # there is presumably a better way to do "is this under this base?"
22 # but if so, it's not obvious to me in File::Spec
23 next unless substr($source,0,length $pack_base) eq $pack_base;
24 my $target = rel2abs( abs2rel($source, $pack_base), $where );
25 my $target_dir = catpath((splitpath $target)[0,1]);
26 mkpath $target_dir;
27 copy $source => $target;
28 }
29 }
30 }
The culprit is in line 23, excused by the comment in lines 21 and 22.
Setting PERL5LIB
to a relative path makes variable $pack_base
(declared in line 8, populated in line 15) hold a relative path too,
like (in our case) local/lib/perl5
.
On the other hand, paths inside the .packlist
files are listed with
absolute paths:
$ cat local/lib/perl5/x86_64-linux-gnu-thread-multi/auto/Path/Tiny/.packlist
/home/foo/skfold/local/lib/perl5/Path/Tiny.pm
For this reason, the comparison in line 23 is going to put an absolute
portion of a path on the left hand side (i.e. the output of the
substr
call) and a relative path on the right hand side.
Setting PERL5LIB
to an absolute value solves the problem:
$ export PERL5LIB="$PWD/local/lib/perl5"
$ local/bin/fatpack tree $(local/bin/fatpack packlists-for \
$(local/bin/fatpack trace --to=- bin/skf))
bin/skf syntax OK
$ find fatlib/
fatlib/
fatlib/Path
fatlib/Path/Tiny.pm
fatlib/Log
fatlib/Log/Log4perl
fatlib/Log/Log4perl/Tiny.pm
fatlib/Log/Log4perl/Tiny.pod
fatlib/Template
fatlib/Template/Perlish.pm
fatlib/Template/Perlish.pod
Conclusion
Needless to say… I’ll put items in PERL5LIB
as absolute paths from
now on!