http://www.perlmonks.org?node_id=671761

First of all, don't be fooled by the length of this post, it doesn't reflect its importance of substance.

I'm working on an application which I decide to include required CPAN modules in its distribution. The modules will be pre-installed as private modules of the application under its own 'lib' directory. Let's put aside for a while the debate around including the prerequisite modules directly versus letting the implementor installs the modules from CPAN. I believe everyone has their own pros and cons toward either or both ways. I have my own reasons to support both myself.

My goal is to make sure that the eventually installed application really loads the CPAN modules under its 'lib' directory, not the ones (if any) in the standard Perl module directories as set in @INC. The easiest way may be just by using use lib '/path/to/private/libdir'; to unshift the private libdir to @INC. But it could miss one module or two because perl will skip the private libdir upon failure to find a requested module there (should I forget to include it), and otherwise successfully loads it from a standard directory (if any), so the overall application runs OK. While this is harmless in most cases, it's simply false positive to my goal.

Without thinking other possibilities too much, I decide to take a route which is hopefully not the most stupid one. My idea is restricting @INC to contain only two or three entries: the first is the application 'lib' directory, the rest is (are) where Perl core modules installed.

For testing purpose I write a dummy script (inc.pl) contains a bunch of use statements for both core and CPAN modules required by the application. Some of the core modules are strict, warnings, Carp, and Data::Dumper. Some of the CPAN modules are DBI, SQL::Abstract, HTML::Template, CGI::Application, CGI::Simple, and CGI::FormBuilder. The first run of the script results nothing to the output which means everything is fine as my Perl installation is sane and all required CPAN modules are installed in the standard directories as found in @INC.

I then start bashing @INC by setting it to an empty array before any use statement.

BEGIN { @INC = () }

Unsuprisingly the script dies complaining that it Can't locate strict.pm in @INC (@INC contains:) at ./inc.pl line 6.. I then add the private libdir to the array but I don't need to rerun the script because I know it would result the same with the difference on printed @INC content.

BEGIN { @INC = qw( /path/to/apps/lib ); }
Speaking of @INC, the original @INC on my system is:
$ perl -le 'print for @INC' /etc/perl /usr/local/lib/perl/5.8.8 /usr/local/share/perl/5.8.8 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.8 /usr/share/perl/5.8 /usr/local/lib/site_perl .

I always wonder why Linux distros (mine is currently Debian 4.0) throw so many stuffs at @INC. Manual installation of Perl (default configuration) is simpler, for example:

$ /opt/bin/perl -E 'say for @INC' /opt/lib/perl5/5.10.0/i686-linux /opt/lib/perl5/5.10.0 /opt/lib/perl5/site_perl/5.10.0/i686-linux /opt/lib/perl5/site_perl/5.10.0 .

Anyway, I need to determine which path holds the Perl core modules. Nothing is probably easier than taking a core module and inspecting its entry in %INC,

$ perl -Mstrict -le 'print $INC{q(strict.pm)}' /usr/share/perl/5.8/strict.pm

Bingo! It's /usr/share/perl/5.8. Tempted to use another way, or maybe just to convince myself, I fire up another test with File::Find and checking two modules at once.

$ perl -MFile::Find -le ' find { follow => 1, wanted => sub{ print $File::Find::name if $File::Find::name =~ /strict\.pm$/ || $File::Find::name =~ /Data\/Dumper\.pm$/; }}, @INC; ' /usr/lib/perl/5.8/Data/Dumper.pm /usr/share/perl/5.8/strict.pm

Bummer! Two paths? (NOTE: I use option follow as it turns out that /usr/lib/perl/5.8 and /usr/share/perl/5.8 are symlinks to /usr/lib/perl/5.8.8 and /usr/share/perl/5.8.8, respectively.)

$ ls -ld /usr/lib/perl/5.8 /usr/share/perl/5.8 lrwxrwxrwx 1 root root 5 2008-02-05 13:17 /usr/lib/perl/5.8 -> 5.8.8 lrwxrwxrwx 1 root root 5 2008-02-05 13:17 /usr/share/perl/5.8 -> 5.8.8

Now I have to apologize to all of you as it becomes this lengthy, much longer than I originally thought it would be. I mean, uncovenience by my simple discovery and the fact that I found two paths, I insist to backup myself with supporting documentation. Among the standard POD documentations and some related modules, I think I find the canonical answer in ExtUtils::MakeMaker and Module::Build modules.

The former mentions INSTALL_BASE with three installation layouts: core, site, and vendor. Two of the installation directories of the core layout that attract me are INSTALLPRIVLIB and INSTALLARCHLIB. OTOH, the latter mentions installdirs with equally three installation layouts: perl, site, and vendor. As before, installprivlib and installarchlib of perl layout get my attention. Both modules refers to Config.pm, so I open its manual page (man Config). Upon reading about installprivlib and privlibexp in turn</c>, as well as installarchlib and archlibexp in turn, I quit the pager and peek at the module source code with perldoc -m Config. There I see privlibexp => '/usr/share/perl/5.8' and archlibexp => '/usr/lib/perl/5.8' entries of %Config hash. OK, it's time to go back to the script. I promise.

Armed with two standard directories, I change the BEGIN block to,

BEGIN { @INC = qw( /path/to/apps/lib /usr/share/perl/5.8 /usr/lib/perl/5.8 ); }

Running the script again, I get the expected result,

$ ./inc.pl Can't locate DBI.pm in @INC (@INC contains: /path/to/apps/lib /usr/sha +re/perl/5.8 /usr/lib/perl/5.8) at ./inc.pl line 21. BEGIN failed--compilation aborted at ./inc.pl line 21.

The module DBI is the first entry in the list of use statements for the non-core modules. It means that the core modules load OK, and it's time for the boring task of installing a number of CPAN modules, one by one, following the list. I know I can automate the perl Makefile-make-make test-make install sequence, or using CPAN locally. But, I enjoy the hardway. Seeing the friendly "Can't locate SomeModule.pm" message is quite fun, if you know what I mean (hints: checkpoint). But maybe it's just me. Maybe I'm simply sick. Maybe. Anyway, here is the complete test script, in case you wonder.

$ cat inc.pl #!/usr/bin/perl BEGIN { @INC = qw( /path/to/apps/lib /usr/share/perl/5.8 /usr/lib/perl/5.8 ); } # Core modules, along with in which Perl version they're first include +d use strict; # 5 use warnings; # 5.006 use Carp; # 5 use File::Spec; # 5.00405 use CGI::Carp; # 5.004 use Exporter; # 5 use Data::Dumper; # 5.005 # CPAN modules, list everything you need, as complete as possible use DBI; use DBD::mysql; use SQL::Abstract; use CGI::Application; use CGI::Application::Plugin::Forward; use CGI::Application::Plugin::DBH; use CGI::Simple; use CGI::FormBuilder; use HTML::Template; use URI; use Data::Pageset;

Comments and suggestions are very much welcome. Thank you verymuch.

Update: added notion about use lib, per sundialsvc4.


Open source softwares? Share and enjoy. Make profit from them if you can. Yet, share and enjoy!