# $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__ =head1 NAME Env::Modulecmd - Interface to modulecmd from Perl =head1 SYNOPSIS # 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'); =head1 DESCRIPTION C provides an automated interface to C from Perl. The most straightforward use of Env::Modulecmd is for loading and unloading modules at compile time, although many other uses are provided. =head2 modulecmd Interface In general, C works by making a system call to 'C', under the assumption that C is in your PATH. If you set the environment variable C, C will use that value in place of C. If C is not found, the shell will return an error and the script will die. I, and a default setting for C, can be built into C when it's installed. See the C file in the source tree for more information.> Modules may, by convention, output warnings and informational messages; C directs these to standard error. If C outputs anything to standard error, C inspects that output and attempts to determine whether it represents a fatal error. If the output begins with "ERROR:", or if it matches C's typical error message format, C fails. Otherwise, C emits that output as a warning, but only if Perl warnings are enabled (C<-w>, or C). If there were no fatal errors, C's output (if any) is C'ed. If the C operation fails, C will fail. If you attempt to load a module which has already been loaded, or perform some other benign operation, C will generate neither output nor error; this condition is silently ignored. =head2 Compile-Time Usage You can specify compile-time arguments to C on the C 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, C 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: C, C) and each value is either a scalar (operate on one module) or an arrayref (operate on several modules). In the example given above, C and C will be loaded by implicit usage. C will be loaded explicitly, and C and C will be unloaded. =head2 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 C, and each call can include one or more modules to be processed. The example above will generate the following six calls to C: 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 =head1 SEE ALSO For more information about modules, see the F manpage or F. =head1 BUGS If you find any bugs, or if you have any suggestions for improvement, please contact the author. =head1 AUTHOR Ron Isaacson > =head1 COPYRIGHT Copyright (c) 2001-2004, Morgan Stanley Dean Witter and Co. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. A copy of the GNU General Public License was distributed with this program in a file called LICENSE. For additional copies, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. =cut