Including Perl Dependencies Within Your Application

Perl can be great at many things, but when it comes to doing something “real” (parsing XML, compression, logging with log4perl, handling Unix signals) you ultimately want to leverage CPAN modules … and generally, these are not “already installed, out of the box” within Linux distributions like Red Hat.

You may consider including in some installation How To notes on downloading modules from CPAN, and installing them … but, then you would have to worry about version conflicts on top of the hassle of possibly supporting people whom simply did not know how to do that. You are probably already delivering your application in a distribution package like RPM to simplify installation … so, why would you want to possibly complicate it again?

There is one way to navigate this which boils-down to including your dependencies in your package, within some application-specific library path for example. This is actually quite straight-forward:

  1. Download the CPAN modules you need and install them into something like /my/app/lib/
  2. Expand the include path within your code with:
    push(@INC, '/my/app/lib');

Now, what happens when you want to include binary library objects like *.so files? These, having been compiled for a system, are architecture-specific and usually Perl version-specific. We can extend our basic solution to support this as well by including architecture and version specific library paths. For example, we may have

  • /my/app/lib/perl58-linux-i386
  • /my/app/lib/perl58-linux-x86_64
  • /my/app/lib/perl510-linux-i386
  • /my/app/lib/perl510-linux-x86_64

The required step of downloading and installing the modules from CPAN is still a good start. This step now needs to be performed for each variation of Perl version and architecture we intend to support. So, you may have to do this on several OS and architecture combinations if you plan on supporting multiples (and save-off the resulting artifacts within source control so you can incorporate them into your build). Then, the code needs to be enhanced as well:

# Determine runtime version
# $] is a raw version string where examples are:
#     5.008008 for 5.8.8
#     5.010001 for 5.10.1
# So, major version is before the "."
#   then minor is the next 3
#   then patch the last 3
my $perlVersion = "$]";
if ($perlVersion =~ /^([\d]+)\.([\d]{3})([\d]{3})$/) {
  # quick hack of adding zero to the string to "convert" something
  #   like 008 to 8 and 010 to 10
  my $majorVersion = $1 + 0;
  my $minorVersion = $2 + 0;
  my $patchVersion = $3 + 0;
  # for our version and architecture specific lib path, we will
  #   use major and minor (example: 58 or 510)
  $perlVersion = "$majorVersion" . "$minorVersion"
} else {
  # Unexpected version format
  die "FATAL ERROR: Unknown Perl version found ('$perlVersion')\n";
}

# Determine if 32-bit or 64-bit
my $is64 = 'false';
foreach my $incPath (@INC) {
  if ($incPath =~ /64/) {
    $is64 = 'true';
    last;
  }
}

# Runtime-specific path
my $runtimeLibPath = "/my/app/lib/perl" . $perlVersion . "-linux-";
if ($is64 eq 'true') {
  $runtimeLibPath .= "x86_64";
} else {
  $runtimeLibPath .= "i386";
}

@ Add it to our include path
push(@INC, $runtimeLibPath);

Now that you see the pattern, you can expand this for other platforms beyond Linux if you wish also.