2

Background: I have a perl module -- let's call it Foo::Common which has been installed on one of our file servers (for the sake of simplicity, I will call thie the 'global' version). The module is included by somewhere on the order of 1000 perl scripts which are launched every 15 minutes. The module is several versions old, but Updating it is going to take a lot of testing over the course of several months.

I'm writing a new perl script which will run on the same processing server, but I need functionality which is only included in the most recent version of Foo::Common. If I simply want to use the newer version of Foo::Common, I can place Foo/Common.pm in a ./libdirectory where the script is being run, then use the following code:

my $lib = '';
BEGIN {
    $lib = $ENV{FOO_ENV_HOME} || '';
}
use lib "$lib/core/bin/";
use lib './lib';
use Foo::Common;

Because use lib adds directories to the beginning of @INC, the newer version of Foo::Common will be found and used first.

This is all well and good, but I don't want my script dependent on the local version of Foo::Common. The global version of Foo::Common has our $VERSION = '1.1.2', and the version that I want to install locally is at 1.1.7. If I deploy the code above, and then we upgrade the global version to the not-yet-written 1.2.0, my script will be stuck running 1.1.7.

According to perldoc -f require:

`require $filename` Has semantics similar to the following subroutine: 

sub require {
   my ($filename) = @_;
   if (exists $INC{$filename}) {
       return 1 if $INC{$filename};
       die "Compilation failed in require";
   }
   my ($realfilename,$result);
   ITER: {
       foreach $prefix (@INC) {
           $realfilename = "$prefix/$filename";
           if (-f $realfilename) {
               $INC{$filename} = $realfilename;
               $result = do $realfilename;
               last ITER;
           }
       }
       die "Can't find $filename in \@INC";
   }
   if ($@) {
       $INC{$filename} = undef;
       die $@;
   } elsif (!$result) {
       delete $INC{$filename};
       die "$filename did not return true value";
   } else {
       return $result;
   }
}

I'm not clear on how this interacts with the use MODULE VERSION syntax, although discussion in the answer to What does Perl do when two versions of a module are installed? indicates that use Module Version is not enough.

I presume that I'm going to manipulate @INC, but I'm not sure how to do that based on each module's $VERSION, especially because use has an implicit BEGINstatement.

How do I approach this?

Community
  • 1
  • 1
Barton Chittenden
  • 4,238
  • 2
  • 27
  • 46

2 Answers2

4

There is nothing new under the sun.

use only::latest 'Foo::Common'

mob
  • 117,087
  • 18
  • 149
  • 283
2

You can't make the check based on the version since $VERSION doesn't exist until after it has been loaded aka executed.


So you want the "global" version used if present, and the local version used otherwise? Append the local path to @INC instead of prepending it.

BEGIN {
   push @INC, "$ENV{FOO_ENV_HOME}/core/bin";
      if $ENV{FOO_ENV_HOME};
}

use Foo::Common;

If you can't rearrange @INC because it would affect something else, try to load Foo::Common before changing @INC.

BEGIN { eval { require Foo::Common; }; }
use lib ...;
use Foo::Common;
use ...;

The use Foo::Common; won't load the module if it was already loaded earlier, and it will import from whichever version did get loaded (no matter where it got loaded).

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • No, not quite... I want to use `$ENV{FOO_ENV_HOME}/core/bin/Foo/Common.pm` if and only if it's `$VERSION` is greater than or equal to the `$VERSION` of `./lib/Foo/Common.pm`. – Barton Chittenden Oct 03 '12 at 18:05
  • What if I run `eval { require Foo::Common; };` check it's `$VERSION`, then change @INC and re-run `eval { require Foo::Common; };` if the previous `$VERSION` wasn't high enough? (I'm not exporting anything from the Foo::Common namespace, so I don't *need* to use `use`) – Barton Chittenden Oct 03 '12 at 18:16
  • Executing both modules is going to lead the problems, and it's totally unnecessary. – ikegami Oct 03 '12 at 18:18
  • Please confirm that my analysis of the question is correct (*So you want the "global" version used if present, and the local version used otherwise?*) – ikegami Oct 03 '12 at 18:19
  • That's a really weird thing to want, btw. – ikegami Oct 03 '12 at 18:21
  • The "global" version is *always* going to exist. It's currently at a version that I can't use, so I want to include a local version which implements the features that I need... however, I don't want to be tied to the local version if we upgrade the global version in the future. – Barton Chittenden Oct 03 '12 at 18:38