tags:

views:

155

answers:

5

Hi Everyone,

I have an object-oriented web-app that is installed in multiple locations on my server. Once for "live", once for "beta", etc. Being object-oriented, it consists of many perl modules. In the main module, I must "use lib" the appropriate directory for all of the custom perl modules for that instance of the app.

This is no big deal, I have a BEGIN block that checks the location of the main program and sets the library directory appropriately. However I also have a lot of utility, command line programs that need to do the same thing. I don't want to cut and paste this code everywhere.

What is the best way to share this code snippet amongst the various programs that need it?

I can't "use" it because the libary path isn't set up yet. Maybe "do" or "require" would the be the right answer, but both of those will search @INC, which is inappropriate.

Maybe something like eval `cat GetLib.pl`; would be appropriate but it seems kind of clunky and fragile.

Here is the BEGIN block that I currently use:

BEGIN {
  use FindBin qw ($Bin);
  require lib;

  if ($Bin =~ /^\/home\/w\/myapp_live/) {
    lib->import('/home/w/myapp_live/lib');
    print STDERR "live site\n";
  }

  if ($Bin =~ /^\/home\/w\/myapp_beta/) {
    lib->import('/home/w/myapp_beta/lib');
    print STDERR "beta site\n";
  }

  if ($Bin =~ /^\/home\/w\/myapp_test/) {
    lib->import('/home/w/myapp_test/lib');
    print STDERR "testing site\n";
  }

}

Thank you!

+4  A: 

If you're running a program from the command line, don't programmatically set the lib: just pass it in as an argument, e.g.: perl -I/location/of/my/lib myprog.pl.

For your web app, why don't you make your library relative to the location of the script itself? Then just install it on each machine where the libraries live.

use FindBin;
use File::Spec::Functions;
use Cwd qw(abs_path getcwd);

BEGIN {
    my $curdir = getcwd;
    my $selfdir = $FindBin::Bin;
    my $libdir = abs_path(catdir($selfdir, 'lib'));

    chdir $libdir or die "can't chdir to $libdir: $@";
    use lib $libdir;
}

Of course, the easiest option of all is to not use different lib directories. Why can't you be consistent across all your environments?

Edit. Re your comment "The reason I have to use different lib directories is because the code running in the live site is different than the code running on the beta site... that's the point of having a beta site." -- why don't you handle this at the level of the installer, rather than making the code itself have to know whether it's live vs. beta? e.g. store your code in different directories in your source tree as you do now, but only install the relevant code to each box. After all, that's exactly what a good revision control system would do for you -- you only check out one branch at a time, and you should only be installing one version of code at a time (as brian d foy alluded to).

Ether
Your first suggestion is too unreliable, I'd much rather have it be automated. The second suggestion is pretty good for the webapp, but I don't want to have to rely on the hard-coded locations of my command-line scripts, which live in various subdirectories under */lib.
NXT
The reason I have to use different lib directories is because the code running in the live site is different than the code running on the beta site... that's the point of having a beta site.
NXT
If your "beta" site is different than your production site, you should fix that first. :)
brian d foy
Brian: What do you mean? The beta site is for testing new features, which are then published to the live site when we are happy with them.
NXT
@NXT: your beta site isn't very much of a beta if it has different behaviour than what will be pushed to production.
Ether
I'm sorry but how can you guys not get this? The beta site is new code, being tested. When we're done testing it, it gets copied (through subversion) to the the live site directory. What could possibly be wrong with that?
NXT
+1  A: 

The code you've shown looks reasonable. You could install one copy of that code into a system-wide location, and then the code to invoke it would boil down to

require '/path/to/findlib.pl';
findlib->import;

The form of require that takes a filename doesn't search @INC.

As an alternative if you wanted to change lots of things around you could look into deploying the app in a way that would be more friendly to local::lib usage.

hobbs
Your answer seems very logical. I'd rather keep the file under source control with the rest of the site, but now that I think about it it makes perfect sense for it to be common to all sites. My only concern is that I will have to be very careful when editing that file because it could break the live site. But I think it's probably the best solution.
NXT
+1  A: 

The different environments could have different settings for the environment variable $PERL5LIB.

mobrule
Thanks, but this is about adding the the path for the program's libraries, not for the entire Perl 5 libraries.
NXT
You can set PERL5LIB per process. It doesn't have to be global.
brian d foy
Brian I know it can be set per-process... But PERL5LIB is for finding the system libraries, not the custom libraries you wrote for your program.
NXT
Perl already populates `@INC` with paths to the system libraries (unless something is broken). One of the best uses for `$PERL5LIB` is to specify locations for non-system libraries.
mobrule
Mobrule, thank you for clarifying that, I missed in on my first look at PERL5LIB.
NXT
+3  A: 

I use the following in many of my scripts:

use strict;
use warnings;

use 5.008;

use FindBin;
use lib $FindBin::Bin;

That last line could be modified as such:

use FindBin;
use lib "$FindBin::Bin/lib";
Joe Casadonte
Thank you. If you look at my code again you'll notice that I'm testing for $Bin that STARTS WITH a certain path, not IS a certain path. That way it works no matter where the program is under that path.
NXT
How about symlinking a relative `lib` directory to each top-level lib directory, then?
Joe Casadonte
+3  A: 

FindBin::libs is excellent for that. I've used it for a while in a large system with no problems at all.

The default invocation looks like it'll work for you, simply:

use FindBin::libs;

This will search for all the ./lib dirs in all the parent directories of the current file's dir and use lib them. So, for example, if your script lives in /home/w/myapp_live/scripts/defurblise_widgets.pl (and use()es FindBin::libs), it will look for:

/home/w/myapp_live/scripts/lib
/home/w/myapp_live/lib
/home/w/lib
/home/lib
/lib       # (presumably!)

Any that it finds with be added to you @INC with use lib.

But, if that's not quite what you need, it's a very flexible module. I'd be surprised if you can't find a way to make it do what you want.

Dan
This is the best answer yet, I think.
NXT