in reply to Question: Best practices for packaging scripts

I would try to isolate your application as much as possible from the system it is being installed in. I view applications as guests on a machine - they should leave everything the way they found it.

This is clearly easier to do if everything used by your application is self-contained. When items are shared, uninstalling an application can be really difficult. Unless your application is being installed by a package manager that keeps track of how many applications are using module X (e.g. you wrapped your CPAN modules in a .deb or .rpm), you won't be able to tell if a shared module X might be in use by some other application.

Keeping every thing separate also saves you from a nightmare of version compatibility issues. You don't have to worry about your application breaking if other applications need older or newer versions of a module.

Yes, this means you might have more than one copy of module X, but at least you won't be in Perl's version of DDL/JAR hell. A common store of non-core modules for all applications is generally only a good idea if you are running a server box or test machine dedicated to a set of applications or administration scripts where you have full control over what versions will need.

Manually setting up PERL5LIB is a pain. When I produce self-contained applications, I generally put the modules in a sub-directory. I always know its location relative to the script and hard code that relative path. When the script starts up, I get the location of the script using some script magic (see below), and then convert the hard coded relative path into a real path and add the real path to @INC

Getting the actual location of the script is a bit tricky. Two things can get in the way: symbolic links and mod_perl. In *nix systems there is a long standing practice of placing a symbolic link to scripts in either /usr/bin or /usr/local/bin. Thus __FILE__ contains a symbolic link rather than the actual location of the file. To get the scripts actual location from the script alone, one has to get to the real file behind the symbolic link. To simplify the process of finding the real script, you can use the module Find::Bin

I haven't done much work with mod_perl (we use FastCGI), but as I understand it mod_perl presents another problem: scripts are loaded as modules. This means that __FILE__ is often only a partial path. To find the module's actual location, one needs to look up the module in %INC.

The script below illustrates a way to find the location of your module directory (e.g. myapp/lib) from your script file's location:

# placed in a begin block so it gets executed before we # try to load any modules. use XXX statements for non-core # modules should be placed after this begin block BEGIN { # Taint mode will complain unless you set these paths. $ENV{PATH} = ""; $ENV{CDPATH} = ""; sub calcLib { if (caller(3)) { #this is loaded as module: caller(3) returns the files caller #can't use __FILE__ between {} my $sModuleFile = __FILE__; $sModuleFile = $INC{ $sModuleFile }; return $sModuleFile =~ /bin\/[^\/]+$/ ? "$sModuleFile/../../lib" : "$sModuleFile/.."; } else { #this is the main entry point: caller(3) returns no caller #use FindBin to get the full file path of this directory #untaint FindBin::Bin my $sModuleDir = $1 if ($FindBin::Bin =~ m/^(.*)$/); return $sModuleDir =~ m/\/bin$/ ? $sModuleDir . "/../lib" : $sModuleDir; } } use lib (calcLib); }

Replies are listed 'Best First'.
Re^2: Question: Best practices for packaging scripts
by jujiro_eb (Sexton) on Mar 27, 2009 at 14:24 UTC
    Awesome! Thanks, this is the kind of input I was looking. I agree with you 100% as the applications being a guest on a machine. I do not care about duplication of modules. Disk space these days is cheaper than oil!!!

    You mentioned, "Getting the actual location of the script is a bit tricky".

    Can't I use "./lib" as the location of modules? It works both on Windows and Unix.

    Ash

      Use "./lib" only works if the following two statements are true

      • the current working directory is set to appdir
      • the modules are stored in appdir/lib

      The only way to make sure that the current working directory is set to the application directory is to write a script that changes to "known" directory location. But how are you to know that directory location? You could force your user to store that application in a hard coded location, but that really isn't very guest-like.

      You could let your user choose a location and then set an environment variable during registration, but then you have no way of making sure that the application location and the environment variable stay in sync. You could educate your user about the environment variable, but now you have yet one more thing to teach and your users have yet one more thing that could go wrong.

      But even if your users were happy with the environment variable, would they be happy with being forced to change directories to the application directory every time the script starts up? And maybe the system is set up so that users can read and execute script files but they are not allowed to go exploring in application directories (e.g. they don't have permission to make the application directory their current working directory)?

      So in the main it is just more reliable to locate your application script without relying on cd or the current working directory. The script above avoids this by using some tricks to deduce the fully qualified path to the application. Then it appends a relative path.

      The actual relative path will depend on the layout of the application directory. In the script above, the layout is

      • appdir/bin/myscript.pl - location of script
      • appdir/lib - location of modules

      The relative path is "../lib". Thus in "$fullPathOfScript/../lib", ".." moves up from the script's own directory to its parent directory, i.e. from appdir/bin to appdir. "lib" moves back down again to appdir/lib

      Hope that helps, beth