Barton Chittenden
Barton Chittenden

Reputation: 4416

How do I conditionally change @INC based on module version?

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?

Upvotes: 2

Views: 286

Answers (2)

mob
mob

Reputation: 118605

There is nothing new under the sun.

use only::latest 'Foo::Common'

Upvotes: 3

ikegami
ikegami

Reputation: 385789

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).

Upvotes: 2

Related Questions