Is there any way to report the missing modules used in the Perl file beforehand instead of getting as an error.
I have something like use Digest::MD5, use File::DosGlob modules in my Perl program. Whenever the users run the script they are getting an error if there is no specific module installed in their system. They could not understand the default error message given by #INC. So I would like to clearly tell them that these modules need to be installed to run the script.
You could build your own verification by using a BEGIN block. Those are run at compile time, just like use is. Keep in mind that use Foo is essentially nothing else as this:
BEGIN {
require Foo;
Foo->import;
}
The following code will replace all use statements with a single BEGIN and place them inside an eval. That's essentially like a try/catch mechanism.
We need the string eval (which is considered evil around here) because require only converts from package names with colons :: to paths if the argument is a bareword. But because we have the name in $module, it's a string, so we need to place it into an eval according to require's docs.
If that string eval fails, we die. That's caught by the outer eval block and $# is set. We can then check if it contains our module name, in which case we naively assume the failure was because that module is not installed. This check could be a bit more elaborate.
We keep track of any failures in $fails, and if there were any, we stop.
#!/usr/bin/perl
use strict;
use warnings;
# all our use statements go here
BEGIN {
my $fails;
foreach my $module ( qw/Digest::MD5 File::DosGlob ASDF/ ) {
eval {
eval "require $module" or die; # because $module is not a bareword
$module->import;
};
if ($# && $# =~ /$module/) {
warn "You need to install the $module module";
$fails++;
}
}
exit if $fails;
}
# ...
Above I included ASDF, which I don't have, so when run it will say
You need to install the ASDF module at /home/code/scratch.pl line 1335.
You might want to make that message a bit more verbose. If your users are not able to understand the default error message that Perl gives when it cannot find a module, it might be wise to include a guide on how to install stuff right there.
Note that both modules you listed have been included with Perl for a while (read: since March 2002). So why would you want to do this for those modules?
$ corelist Digest::MD5
Data for 2014-09-14
Digest::MD5 was first released with perl v5.7.3
$ corelist File::DosGlob
Data for 2014-09-14
File::DosGlob was first released with perl 5.00405
A better way would be ship your program as a distribution that can be installed, and include a Makefile or a cpanfile or something similar that lists dependencies. There is a guide in perlnewmod on how to start a new module. You'd not want to upload to CPAN obviously, but the basics are the same.
With this, your users would get all dependencies installed automatically.
You could use Devel::Modlist, it will list all the required module for your program.
perl -d:Modlist test.pl
There's another module Module::ScanDeps which comes with a utility scandeps.pl which you can use on your script as:
scandeps.pl test.pl
Note that sanity checking your Perl code using perl -c is dangerous, so use it carefully.
Your question isn't really clear about what "beforehand" means. To check if a Perl program's syntax is correct and directly included modules are resolvable, use
perl -c <perl-program.pl>
This checks the syntax of your file and ensures that any modules used by your code exist. However, it does not transitively check the entire dependency tree, only those mentioned in perl-program.pl.
Related
I have a test suite with hundreds of tests for a perl module I've yet to release which is a command line interface. Since it is a command line interface, the tests (until possibly now) are all written to drop code into a template and then call the template script using a system call.
I recently added an optional dependency on a 3rd party module that's not a part of core perl. I know my module works whether that module is installed or not because I have a computer with it installed and one without and the module works without error in each case. However, I'd like to be able to write a test to confirm that my module will work when the 3rd party module is absent - and I'd like that test to work even if the 3rd party module is installed, but behave as if it wasn't.
Ideally, I could use the structure I've put in place for testing which makes a system call to a template script. I know I could write a separate test script that manipulates #INC in the BEGIN block, imports the particular methods that use the module, and call them like a unit test. But I would like to know if there's a way I can use the test structure I've already got all my other tests using, which is to make a system call.
So is there a way to exclude a module from being imported via a perl command line option? I've tried -M-Module, but the code use Module still imports the module.
Incidentally, my module uses the 3rd party module inside an eval, which is how I made it optional.
I wrote Test::Without::Module for this exact case. It works by modifying #INC to prevent loading of modules that you name. For testing, you could either run the test from the command line:
perl -MTest::Without::Module=Some::Module -w -Iblib/lib t/SomeModule.t
Or allow/disallow loading the module from within your test suite:
use Test::Without::Module qw( My::Module );
# Now, loading of My::Module fails :
eval { require My::Module; };
warn $# if $#;
# Now it works again
eval q{ no Test::Without::Module qw( My::Module ) };
eval { require My::Module; };
print "Found My::Module" unless $#;
I have made my own perl modules(pm files),named test.pm
package test;
use Exporter;
use strict;
use File::Basename qw(basename dirname);
use Cwd qw(abs_path);
use File::Path qw(make_path);
use FindBin qw($Bin $Script);
BEGIN {
our #ISA = qw(Exporter);
our #EXPORT = qw(mkdirOrDie);
our $VERSION = 1.0;
}
sub mkdirOrDie
{
my ($dir) = #_ ;
if(!-d $dir){
make_path($dir);
$dir=abs_path($dir);
# timeLog("Directory Created: $dir");
}
}
and I tried to install this module as follows,
h2xs -AX -n test
perl Makefile.PL
make
make install
there is no error,and I copy the test.pm to /usr/lib64/perl5/5.10.0/,but when i call sub function using test, an error has occurred,
Undefined subroutine &main::mkdirOrDie called at /to/my/path/main.pl line 92
is there something i ignored?
It's unclear at which point things started to go wrong for you.
Firstly, test.pm is a bad name for a Perl module. Perl modules should have names that begin with upper case letters (and Test.pm is already taken).
You should run h2xs before writing your code - as it generates a module skeleton for you fill in. I hope it hasn't overwritten your code with an almost empty file! It's also worth noting that most people stopped using h2xs many years ago. These days we have tools like Module::Starter.
Then, running, make install (which you need to do with root permissions - so usually with sudo) is what installs your module into the system libraries. There should be no need to run that cp command afterwards.
As for why your code doesn't find the module, there are many possible reasons. Are you using Perl 5.10 or do you have other Perl versions installed? What does the code look like that you are trying to use? Does test.pm still include the code you think it does?
Need more information to be much help here.
I have a module in the parent directory of my script and I would like to 'use' it.
If I do
use '../Foo.pm';
I get syntax errors.
I tried to do:
push #INC, '..';
use EPMS;
and .. apparently doesn't show up in #INC
I'm going crazy! What's wrong here?
use takes place at compile-time, so this would work:
BEGIN {push #INC, '..'}
use EPMS;
But the better solution is to use lib, which is a nicer way of writing the above:
use lib '..';
use EPMS;
In case you are running from a different directory, though, the use of FindBin is recommended:
use FindBin; # locate this script
use lib "$FindBin::RealBin/.."; # use the parent directory
use EPMS;
There are several ways you can modify #INC.
set PERL5LIB, as documented in perlrun
use the -I switch on the command line, also documented in perlrun. You can also apply this automatically with PERL5OPT, but just use PERL5LIB if you are going to do that.
use lib inside your program, although this is fragile since another person on a different machine might have it in a different directory.
Manually modify #INC, making sure you do that at compile time if you want to pull in a module with use. That's too much work though.
require the filename directly. While this is possible, it doesn't allow that filename to load files in the same directory. This would definitely raise eyebrows in a code review.
Personally I prefer to keep my modules (those that I write for myself or for systems I can control) in a certain directory, and also to place them in a subdirectory. As in:
/www/modules/MyMods/Foo.pm
/www/modules/MyMods/Bar.pm
And then where I use them:
use lib qw(/www/modules);
use MyMods::Foo;
use MyMods::Bar;
As an aside.. when it comes to pushing, I prefer the fat-arrow comma:
push #array => $pushee;
But that's just a matter of preference.
'use lib' is the answer, as #ephemient mentioned earlier. One other option is to use require/import instead of use. It means the module wouldn't be loaded at compile time, but instead in runtime.
That will allow you to modify #INC as you tried there, or you could pass require a path to the file instead of the module name. From 'perldoc -f require':
If EXPR is a bareword, the require assumes a ".pm" extension and
replaces "::" with "/" in the filename for you, to make it easy to
load standard modules. This form of loading of modules does not risk
altering your namespace.
You have to have the push processed before the use is -- and use is processed early. So, you'll need a BEGIN { push #INC, ".."; } to have a chance, I believe.
As reported by "perldoc -f use":
It is exactly equivalent to
BEGIN { require Module; import Module LIST; }
except that Module must be a bareword.
Putting that another way, "use" is equivalent to:
running at compile time,
converting the package name to a file name,
require-ing that file name, and
import-ing that package.
So, instead of calling use, you can call require and import inside a BEGIN block:
BEGIN {
require '../EPMS.pm';
EPMS->import();
}
And of course, if your module don't actually do any symbol exporting or other initialization when you call import, you can leave that line out:
BEGIN {
require '../EPMS.pm';
}
Some IDEs don't work correctly with 'use lib', the favored answer. I found 'use lib::relative' works with my IDE, JetBrains' WebStorm.
see POD for lib::relative
The reason it's not working is because what you're adding to #INC is relative to the current working directory in the command line rather than the script's directory.
For example, if you're currently in:
a/b/
And the script you're running has this URL:
a/b/modules/tests/test1.pl
BEGIN {
unshift(#INC, "..");
}
The above will mean that .. results in directory a/ rather than a/b/modules.
Either you must change .. to ./modules in your code or do a cd modules/tests in the command line before running the script again.
How can one use the Environment Modules system* in Perl?
Running
system("load module <module>");
does not work, presumably because it forks to another environment.
* Not to be confused with Perl modules. According to the Wikipedia entry:
The Environment Modules system is a tool to help users manage their Unix or Linux shell environment, by allowing groups of related environment-variable settings to be made or removed dynamically.
It looks like the Perl module Env::Modulecmd will do what you want. From the documentation:
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.
Example usage:
use Env::Modulecmd { load => 'foo/1.0' };
Alternately, to do it less perl-namespace like and more environment module shell-like, you can source the Environment Modules initialization perl code like the other shells:
do( '/usr/share/Modules/init/perl');
module('load use.own');
print module('list');
For a one-line example:
perl -e "do ('/usr/share/Modules/init/perl');print module('list');"
(This problem, "source perl environment module" uses such generic words, that it is almost un-searchable.)
system("load module foo ; foo bar");
or, if that doesn't work, then
system("load module foo\nfoo bar");
I'm guessing it makes changes to the environment variables. To change Perl's environment variables, it would have to be executed within the Perl process. That's not going to work since it was surely only designed to be integrated into the shell. (It might not be too hard to port it, though.)
If you are ok with restarting the script after loading the module, you can use the following workaround:
use String::ShellQuote qw( shell_quote );
BEGIN {
if (!#ARGV || $ARGV[0] ne '!!foo_loaded!!') {
my $perl_cmd = shell_quote($^X, '--', $0, '!!foo_loaded!!', #ARGV);
exec("load module foo ; $perl_cmd")
or die $!;
}
shift(#ARGV);
}
I have a perl script written for version 5.6.1 and which has dependencies on Oracle packages, DBI packages and more stuff. Everything is correctly installed and works.
Recently perl version 5.8.4 got installed and because those dependencies are not correctly set so the script fails.
'perl' command points to /program/perl_v5.8.4/bin/perl -- latest version
So, when I have to run my perl script I have to manually specify in command prompt
/program/perl_v5.6.1/bin/perl scriptName.pl
I tried adding following lines in script:
/program/perl_v5.6.1/bin/perl
use v5.6.1;
But, this means script has to take Perl version > 5.6.1
I found couple of related question which suggested:
To export path. But, I want the script for all the users without have to export path
Specify version greater than. But, I want to use just one specific version for this script.
use/require commands. Same issue as above.
My question: How to specify in the script to only use a specific version of perl?
The problem is that the interpreter specified in the shebang line (#!/some/path/to/perl) is not used if perl script is called like this:
perl some_script.pl
... as the 'default' (to simplify) perl is chosen then. One should use the raw power of shebang instead, by executing the file itself:
./some_script.pl
Of course, that means this file should be made executable (with chmod a+x, for example).
I have in my code:
our $LEVEL = '5.10.1';
our $BRACKETLEVEL = sprintf "%d.%03d%03d", split/\./, $LEVEL;
if ($] != $currentperl::BRACKETLEVEL)
{
die sprintf "Must use perl %s, this is %vd!\n", $LEVEL, $^V;
}
These are actually two different modules, but that's the basic idea. I simply "use correctlevel" at the top of my script instead of use 5.10.1; and I get this die if a developer tries using the wrong level of perl for that product. It does not, however, do anything else that use 5.10.1; would do (enable strict, enable features like say, switch, etc.).