Env::Modulecmd - Interface to modulecmd from Perl


Env-Modulecmd documentation Contained in the Env-Modulecmd distribution.

Index


Code Index:

NAME

Top

Env::Modulecmd - Interface to modulecmd from Perl

SYNOPSIS

Top

  # import bootstraps, executed at compile-time

    # explicit operations

    use Env::Modulecmd { load => 'foo/1.0',
                         unload => ['bar/1.0', 'baz/1.0'],
                       };

    # implied loading

    use Env::Modulecmd qw(quux/1.0 quuux/1.0);

    # hybrid

    use Env::Modulecmd ('bazola/1.0', 'ztesch/1.0',
                        { load => 'oogle/1.0',
                          unload => [qw(foogle/1.0 boogle/1.0)],
                        }
                       );

  # implicit functions, executed at run-time

    Env::Modulecmd::load (qw(fred/1.0 jim/1.0 sheila/barney/1.0));
    Env::Modulecmd::unload ('corge/grault/1.0', 'flarp/1.0');
    Env::Modulecmd::pippo ('pluto/paperino/1.0');

DESCRIPTION

Top

Env::Modulecmd provides an automated interface to modulecmd from Perl. The most straightforward use of Env::Modulecmd is for loading and unloading modules at compile time, although many other uses are provided.

modulecmd Interface

In general, Env::Modulecmd works by making a system call to 'modulecmd perl [cmd] [module]', under the assumption that modulecmd is in your PATH. If you set the environment variable PERL_MODULECMD, Env::Modulecmd will use that value in place of modulecmd. If modulecmd is not found, the shell will return an error and the script will die.

Note: a default path to modulecmd, and a default setting for MODULEPATH, can be built into Env::Modulecmd when it's installed. See the README file in the source tree for more information.

Modules may, by convention, output warnings and informational messages; modulecmd directs these to standard error. If modulecmd outputs anything to standard error, Env::Modulecmd inspects that output and attempts to determine whether it represents a fatal error. If the output begins with "ERROR:", or if it matches modulecmd's typical error message format, Env::Modulecmd fails. Otherwise, Env::Modulecmd emits that output as a warning, but only if Perl warnings are enabled (-w, or use warnings).

If there were no fatal errors, modulecmd's output (if any) is eval'ed. If the eval operation fails, Env::Modulecmd will fail.

If you attempt to load a module which has already been loaded, or perform some other benign operation, modulecmd will generate neither output nor error; this condition is silently ignored.

Compile-Time Usage

You can specify compile-time arguments to Env::Modulecmd on the use line, as follows:

  use Env::Modulecmd ('bazola/1.0', 'ztesch/1.0',
                      { load => 'oogle/1.0',
                        unload => [qw(foogle/1.0 boogle/1.0)],
                      }
                     );

Each argument is assumed to be either a scalar or a hashref. If it's a scalar, Env::Modulecmd assumes it's the name of a module you want to load. If it's a hashref, then each key is the name of a modulecmd operation (ie: load, unload) and each value is either a scalar (operate on one module) or an arrayref (operate on several modules).

In the example given above, bazola/1.0 and ztesch/1.0 will be loaded by implicit usage. oogle/1.0 will be loaded explicitly, and foogle/1.0 and boogle/1.0 will be unloaded.

Run-Time Usage

Additional module operations can be performed at run-time by using implicit functions. For example:

  Env::Modulecmd::load (qw(fred/1.0 jim/1.0 sheila/barney/1.0));
  Env::Modulecmd::unload ('corge/grault/1.0', 'flarp/1.0');
  Env::Modulecmd::pippo ('pluto/paperino/1.0');

Each function name is passed as a command name to modulecmd, and each call can include one or more modules to be processed. The example above will generate the following six calls to modulecmd:

  modulecmd perl load fred/1.0
  modulecmd perl load jim/1.0
  modulecmd perl load sheila/barney/1.0
  modulecmd perl unload corge/grault/1.0
  modulecmd perl unload flarp/1.0
  modulecmd perl pippo pluto/paperino/1.0

SEE ALSO

Top

For more information about modules, see the module(1) manpage or http://www.modules.org.

BUGS

Top

If you find any bugs, or if you have any suggestions for improvement, please contact the author.

AUTHOR

Top

Ron Isaacson <Ron.Isaacson@morganstanley.com>

COPYRIGHT

Top


Env-Modulecmd documentation Contained in the Env-Modulecmd distribution.

# $Id: Modulecmd.pm,v 4.1 2004/05/07 15:42:15 ronisaac Exp $

# Copyright (c) 2001-2004, Morgan Stanley Dean Witter and Co.
# Distributed under the terms of the GNU General Public License.
# Please see the copyright notice at the end of this file for more information.

package Env::Modulecmd;

BEGIN {
  # defaults: if Env::Modulecmd is built using perl5.005 or later, the
  # magic strings below are replaced with values supplied to 'make' at
  # build time

  my $modulecmd  = '@@DEFAULT_PERL_MODULECMD@@';
  my $modulepath = '@@DEFAULT_MODULEPATH@@';

  $ENV{PERL_MODULECMD} ||= $modulecmd  unless ($modulecmd  =~ /^\@\@/);
  $ENV{MODULEPATH}     ||= $modulepath unless ($modulepath =~ /^\@\@/);
}

use strict;
use Carp;
use vars qw($VERSION $AUTOLOAD);

use IPC::Open3;
use IO::Handle;

$VERSION = 1.2;

my $modulecmd = $ENV{'PERL_MODULECMD'} || 'modulecmd';

sub import {
  my @args = @_;
  shift @args;

  # import just dispatches commands to _modulecmd

  foreach my $arg (@args) {
    if (ref ($arg) eq "HASH") {
      my %hash = %{$arg};
      foreach my $key (keys %hash) {
        my $val = $hash{$key};
        if (ref ($val) eq "ARRAY") {
          _modulecmd ($key, $_) for @{$val};
        } else {
          _modulecmd ($key, $val);
        }
      }
    } else {
      _modulecmd ('load', $arg);
    }
  }
}

sub AUTOLOAD {
  my @modules = @_;

  # AUTOLOAD, like import, calls _modulecmd with the requested function

  my $fun = $AUTOLOAD;
  $fun =~ s/^.*:://;

  _modulecmd ($fun, $_) for @modules;
}

sub _indent {
  my ($str) = @_;

  $str =~ s/\n$//;
  $str =~ s/\n/\n -> /g;
  $str = " -> $str\n";

  return ($str);
}

sub _modulecmd {
  my ($fun, $module) = @_;

  # here's where the actual work gets done. first we build a command
  # string and send it to open3 for execution. we're not sending any
  # input, but we want to catch both its standard output and standard
  # error, so a simple piped open won't work.

  my @cmd = ($modulecmd, "perl", $fun, $module);
  my $cmd = join (" ", @cmd);

  my $pid = 0;
  my $out = '';
  my $err = '';

  {
    # need to turn off all warnings here, or else we get a double
    # error from open3 if the exec fails

    local $^W = 0;

    my $IN  = IO::Handle->new;
    my $OUT = IO::Handle->new;
    my $ERR = IO::Handle->new;

    $pid = open3 ($IN, $OUT, $ERR, @cmd);

    # slurp all output

    undef local $/;

    $out = <$OUT>;
    $err = <$ERR>;
  }

  waitpid ($pid, 0);
  my $retcode = $? >> 8;

  # if the process sent anything to standard error, or if it exited
  # with a non-zero return code, it may have "failed"

  if ($err || $retcode) {
    my $croak = 0;

    # attempt to guess whether the stderr output is a real error
    # generated by modulecmd, or just an informational message output
    # by the module itself. error messages from modulecmd (like
    # "Couldn't find modulefile ... in MODULEPATH") fall into two
    # categories: they either (a) start with "ERROR:", or (b) start
    # and end with a row of dashes, and contain the message shown
    # below. (note that "occurred" is misspelled as "occured" in the
    # modulecmd source.)

    my $error_from_modulecmd =
      (($err =~ /^ERROR:/) or
       ($err =~ /^-----/ and $err =~ /-----\s*$/ and
        $err =~ /An error occur*ed while processing your module command/));

    $croak = 1
      if $error_from_modulecmd;

    # now check for an exec failure. open3 obviously doesn't attempt
    # to exec modulecmd until after it forks; if the exec fails, it
    # croaks from the child process. we could check the STDERR output
    # for "open3: exec of ... failed", except that on win32, the exec
    # NEVER fails. (this is because exec's on win32 are done via
    # system(), which uses cmd.exe, and the running cmd.exe always
    # succeeds, leaving the child process to print an error message,
    # with no indication whether the error came from cmd.exe or from
    # modulecmd itself.)
    #
    # a non-zero return code is the best way to detect an exec
    # failure, and modulecmd itself will hardly ever exit with a
    # non-zero return code. however, there are two cases where it
    # will: (a) invalid syntax, like "modulecmd no-such-shell list";
    # and (b) "modulecmd perl load /no/such/directory". in these
    # cases, we attempt to determine, using the pattern above, whether
    # this is an error message from modulecmd. if not, we assume it's
    # a message about a failure to exec modulecmd in the first place.

    if ($retcode) {
      $croak = 1;

      unless ($error_from_modulecmd) {

        # if we're on win32, we'll actually get a semi-useful error
        # message from cmd.exe, such as "The system cannot find the
        # path specified." on unix, it's just "open3: exec of ...
        # failed at Modulecmd.pm line 123"; there's no detailed
        # reason, and the line number in Modulecmd.pm doesn't help
        # anybody. so if the error output begins with "open3:", we
        # assume that it's useless and build our own message.

        croak "Unable to execute '$cmd'" .
          ($err =~ /^open3:/ ? "\n" : ":\n" . _indent ($err)) .
          "Error loading module $module";
      }
    }

    # now, if $croak is set, it's a fatal error, so croak on it.
    # otherwise, issue a warning, but only if -w is in effect.

    if ($croak) {
      croak
        ("Errors from '$cmd':\n" .
         _indent ($err) .
         "Error loading module $module");
    } else {
      carp
        ("Messages from '$cmd':\n" .
         _indent ($err) .
         "Possible error loading module $module")
          if $^W;
    }
  }

  # if we got here, then the command didn't fail. if it did generate
  # output, then we have something to eval.

  if ($out) {

    # what if we try to eval something that's not valid perl? in this
    # case, eval will die, with a message indicating what went wrong.
    # we want to catch this and nicely print out the error.

    eval $out;

    croak
      ("'$cmd' generated output:\n" .
       _indent ($out) .
       "Error evaluating:\n" .
       _indent ($@) .
       "Error loading module $module")
        if $@;
  }
}

1;

__END__