How can I mock builtin `require` function? - perl

I'm going to refactoring large number of old perl scripts. (over 150k lines, no tests, no strict, no packages, no commit logs, no comments)
When I started to write some tests under t directory. I found almost all files require each others with absolute paths. So I tried mocking builtin require function to make them portable, but no luck:
t/001-require.t
use strict;
use warnings;
use Test::More;
use FindBin;
BEGIN {
my $root = "$FindBin::RealBin/../";
sub require {
$_[0] = $root . $_[0];
CORE::require(#_);
}
}
require_ok "foo.pl";
done_testing();
The above script gives me: Error: Can't locate foo.pl in #INC...
How can I prepend a root path before Perl requires them?
update
Thank you Axeman, I modified absolute paths using following hook.
my $root = "$RealBin/../";
unshift #INC, sub {
my ($coderef, $filename) = #_;
$filename =~ s/^\///;
open(FILE, '<', $root . $filename);
return *FILE;
};

Aren't you simply looking for
use FindBin qw( $RealBin );
use lib "$RealBin/..";

You don't have to.
When a module is required, either by require or use, perl looks through a list to see if it can locate the module by the list of (usually) directories set up as libraries for the installation. This list is stored in a variable called #INC.
However, #INC takes more than directories, it also takes "hooks", which are subroutines which can change the loading behavior for Perl modules. If you insert a hook (a subroutine) as the first entry into #INC, require will call your behavior.
You can find a more complete treatment at the perldoc on require. I just wanted to give a quick profile of a hook:
sub inc_hook {
my ( $ref_to_this_sub, $relative_module_path ) = #_;
# $relative_module_path will be in directory form: Root/Package.pm
# return nothing to pass it to standard behavior
return unless _i_want_to_handle( $relative_module_path );
# most commonly, return handle to source
return my $io_handle_to_source = handle_module( $relative_module_path );
}
unshift #INC, inc_hook;

Related

Perl, cannot require a module dynamicaly from #INC

So, i've got a problem with loading a module via require.
We have a working directory. The program loads a PACKAGE (bolded not to make you confused) (which is ok - thanks to correct and local namespaces), but it has to load another module from very different directory. So, as i've heard, it should be unshifted into #INC in BEGIN block. But....This begin should take a param (currently from initial programm), containing some path to configuration file, which containing parameter i need (path to module, which i need to unshift).
BEGIN inited, i check #INC - unshift seems to be succeed. Then, in PACKAGE methods we need to load this module, but when i try to do something like:
eval{
print STDERR "Trying...\n";
my $path = "path/to/module"; # contains "DIR" dir and "Module.pm",
# also tried to write this path as "path/to/module/DIR/Module.pm"
require $path;
DIR::Module->import();
print STDERR "Success\n";
1;
} or {print STDERR "Failed\n";}
my $module = DIR::Module->new();
And I got "Trying.." and "Failed". Tried use lib with fullpath - got nothing. What am i doing wrong?
You say $path is the path to the module, but you also say it's the path to the directory containing DIR/Module.pm. I'm assuming it's the latter because it needs to be the former.
my $path = "/path/to/lib";
require "$path/DIR/Module.pm";
Remember to use $RealBin if the path is relative to your script.
use FindBin qw( $RealBin );
my $path = "$RealBin/../lib";
require "$path/DIR/Module.pm";
Note that it rarely makes sense to import from a dynamically loaded module.
If there's no reason to avoid loading this module at compile-time, I'd go with
use lib qw( /path/to/lib );
use DIR::Module;
Again, remember to use $RealBin if the path is relative to your script.
use FindBin qw( $RealBin );
use lib "$RealBin/../lib";
use DIR::Module;

Calling script with the same #INC as the parent script

I want to call an external script with system ($script) or do $script. In my #INC i have some specific modules which I import. How can I call the $script and transfer it the same #INC?
Script1.pm
#importing some libs
#code
$script = "path_to_script";
system ($script);
Script2.pm
use LibFromScript1#INC;
And I get the Error :
Can't locate LibFromScript1 in #INC...
The easiest way is probably to set the PERL5LIB environment variable. That will add a list of directories to the child process's #INC array
Your code would look something like
$ENV{PERL5LIB} = join ':', #INC;
system $script;
This has the disadvantage that the standard directories will also be added to #INC. It shouldn't cause any problems, but it would be best to set PERL5LIB to just the custom directories if you know them at that point.
Note also that perl will ignore PERL5LIB if you are running under the taint flag.
Strictly answering your question you could do $script; although recommended way would be to separate your common program logic into module, and use/require it.
You could use Storable to save #INC in a file and then pick it in other script.
For example you could do something like below.
test.pl
#!/usr/bin/perl
use strict;
use warnings;
use Storable;
store (\#INC, "test2.dump") or die "could not store";
system("perl", "test2.pl", $$) == 0 or die "error";
test2.pl
#!/usr/bin/perl
use strict;
use warnings;
use Storable;
use Data::Dumper;
my $parentpid = shift;
my $ref = retrieve("test2.dump") or die "couldn't retrieve";
print Dumper $ref;
Once you get the #INC in test2.pl as $ref, you can modify #INC in test2.pl to take contents from $ref.

Perl - Relative path of a file inside a module

I am building up a website using Perl. I organized my files as follow:
/index.cgi
/perl/modules/databaseFunctions.pm
/perl/indexCheck.cgi
/database/database.xml
Inside databaseFunctions.pm I have a function X() that reads and writes on database.xml. In X() I have specified relative path of the database as follow:
sub X{
my $db_path='../../database/database.xml';
my $parser=XML::LibXML->new();
my $doc=$parser->parse_file($db_path);
....
....
}
Here is the problem:
I have to call X() from index.cgi and indexCheck.cgi but I get an error the following error:
Could not create file parser context for file "../../database/database.xml": No such file or directory at perl/modules/databaseFunctions.pm line 21.
I think the problem is that when I call X() inside index.cgi or inside /perl/indexCheck.cgi the relative path of the database is different but I don't know how to set a path that works for index.cgi and /perl/indexCheck.cgi.
I think the problem boils down to "How to find out the path of the current script (*.pl)?" and
"How to find out the path of the current module (*.pm)?".
Scripts
For scripts, there is a very convenient module, FindBin, that offers 4 variables for the current script's name and path with
either symlinks resolved or not. Usually $FindBin::Bin is what you are looking for. It's the path of the current script.
I often use it to enhance the #INC path so that my scripts find additional (own) modules like so:
use FindBin;
use lib "$FindBin::Bin/my_mod_path";
use MyModule;
In this case MyModule.pm is searched for in the directory my_mod_path below the current script's path. Very convenient.
The module is part of the core distribution, i.e. no further installation is neccessary.
Modules
FindBin may not safely be used from inside modules because then it depends who (script or module) makes the first use FindBin;.
So if you don't want to care about the order, don't use FindBin; in modules, only in scripts.
For modules, there is some trick. Use the perl function caller().
Depending on the context called in, it returns the $filename of the file where it actually was called.
Thus, in modules you can safely use the following to get the module's path:
use File::Basename;
my $path_of_this_module = File::Basename::dirname( eval { ( caller() )[1] } );
Given that path you can navigate relative to it in order to find the other files you need, e.g. "$path_of_this_module/../.." and so on.
EDIT
index.cgi:
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/perl/modules";
use databaseFunctions;
databaseFunctions::X( "called from index.cgi\n" );
perl/indexCheck.cgi:
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/modules";
use databaseFunctions;
databaseFunctions::X( "called from indexCheck.cgi\n" );
perl/modules/databaseFunctions.pm:
package databaseFunctions;
use File::Basename;
my $path_of_this_module = File::Basename::dirname( eval { ( caller() )[1] } );
sub X {
my $arg = shift;
my $db_path="$path_of_this_module/../../database/database.xml";
open(my $fh, '>>', $db_path) or die "cannot open $db_path: $!\n";
print $fh $arg;
close($fh);
}
1;
When I now call ./index.cgi and then ./perl/indexCheck.cgi, then I get the following:
database/database.xml:
called from index.cgi
called from indexCheck.cgi
Exactly, what I thought you were looking for.

How to change #INC lib contents by using no lib command in PERL

if (#arr =~ /env1/) #checking enviroment
{
# Want to flush the #INC contents using below no lib
# --> Command before the perl get the libraries of my choice at the time of compilation
no lib "//First/lib/"; #flush INC
no lib "//Second/lib/"; #flush INC
print log1 "INC before current- #INC\n";
#set library of my choice with either syntax
BEGIN{ unshift #INC, "//Third/lib/" };
# or
use lib "//third/lib/";
print log1 "INC after- #INC\n";
print log2 "INC after- %INC\n";
print log3 map {"$_ => $INC{$_}\n"} keys %INC;
use DirHandle;
use File::Find;
use File::Copy;
print map {"$_ => $INC{$_}\n"} keys %INC;
}
I want to know the above thing can be achieved in a simpler way. Do I need to include any Perl modules to use the (no lib and use lib commands). This is a requirement due to script running in multiple enviroment and when one of the enviroments is down the script stops running. I want to achieve this by passing the libraries of my choice to be used by perl.
Suggestions are welcome.
Edit:
Here's the actual code.
if (#temp[arr_index]=~ /env1/) #enviroment 1
{
BEGIN{ unshift #INC, "//server1/lib/" }; #server 1 for same app lower enviroment 1
BEGIN{ unshift #INC, "//server2/lib/" }; #server 2 for same app lower enviroment 1
use File::Copy;
}
elsif (#temp[arr_index]=~ /env2/) #enviroment 2
{
BEGIN{ unshift #INC, "//server1/lib/" }; #server 1 for same app lower enviroment 2
BEGIN{ unshift #INC, "//server2/lib/" }; #server 2 for same app lower enviroment 2
use File::Copy;
}
elsif (#temp[arr_index]=~ /env3/) #enviroment 3
{
BEGIN{ unshift #INC, "//server1/lib/" }; #server 1 for same app lower enviroment 3
BEGIN{ unshift #INC, "//server2/lib/" }; #server 2 for same app lower enviroment 3
use File::Copy;
}
#INC - Having #INC the values same for all enviroments which is causing issue. Hope the above adds some clarity to your understanding . THANKS.
I would suggest just pushing all of them into #INC in the order of your preferred environments. Perl will look at the contents of #INC. Perlvar says about it:
The array #INC contains the list of places that the do EXPR , require,
or use constructs look for their library files.
That means it will look at the list of paths from left to right, as described here. Thus, you can just push your library paths into #INC one after another:
#!/usr/bin/perl
use strict;
use warnings;
BEGIN {
use lib 'first/lib', 'second/lib', 'third/lib';
}
If you now go and require Some::Module, Perl will look in first/lib first. If it cannot find it there, maybe because the path doesn't exist in your environment (it won't complain about that), Perl will proceed to look in second/lib and third/lib one after the other. You don't have to do anything else.
--
On another note, you have mixed compile time and run time a lot in your above code. The three use parts and the BEGIN block will be called at compile time regardless of the if construct they are in. Everything else, including the no lib calls will only be called at run time if the match is true. See perlmod for a more detailed explanation of how this works.

How do I load libraries relative to the script location in Perl?

How can you get current script directory in Perl?
This has to work even if the script is imported from another script (require).
This is not the current directory
Example:
#/aaa/foo.pl
require "../bbb/foo.pl"
#/bbb/bar.pl
# I want to obtain my directory (`/bbb/`)
print($mydir)
The script foo.pl could be executed in any ways and from any directory, like perl /aaa/foo.pl, or ./foo.pl.
What people usually do is
use FindBin '$Bin';
and then use $Bin as the base-directory of the running script. However, this won't work if you do things like
do '/some/other/file.pl';
and then expect $Bin to contain /some/other/ within that. I'm sure someone thought of something incredibly clever to work this around and you'll find it on CPAN somewhere, but a better approach might be to not include a program within a program, but to use Perl's wonderful ways of code-reuse that are much nicer than do and similar constructs. Modules, for example.
Those generally shouldn't care about what directory they were loaded from. If they really need to operate on some path, you can just pass that path to them.
See Dir::Self CPAN module. This adds pseudo-constant __DIR__ to compliment __FILE__ & __LINE__.
use Dir::Self;
use lib __DIR__ . '/lib';
I use this snippet very often:
use Cwd qw(realpath);
use File::Basename;
my $cwd = dirname(realpath($0));
This will give you the real path to the directory containing the currently running script. "real path" means all symlinks, "." and ".." resolved.
Sorry for the other 4 responses but none of them worked, here is a solution that really works.
In below example that adds the lib directory to include path the $dirname will contain the path to the current script. This will work even if this script is included using require from another directory.
BEGIN {
use File::Spec;
use File::Basename;
$dirname = dirname(File::Spec->rel2abs( __FILE__ )) . "/lib/";
}
use lib $dirname;
From perlfaq8's answer to How do I add the directory my program lives in to the module/library search path?
(contributed by brian d foy)
If you know the directory already, you can add it to #INC as you would for any other directory. You might if you know the directory at compile time:
use lib $directory;
The trick in this task is to find the directory. Before your script does anything else (such as a chdir), you can get the current working directory with the Cwd module, which comes with Perl:
BEGIN {
use Cwd;
our $directory = cwd;
}
use lib $directory;
You can do a similar thing with the value of $0, which holds the script name. That might hold a relative path, but rel2abs can turn it into an absolute path. Once you have the
BEGIN {
use File::Spec::Functions qw(rel2abs);
use File::Basename qw(dirname);
my $path = rel2abs( $0 );
our $directory = dirname( $path );
}
use lib $directory;
The FindBin module, which comes with Perl, might work. It finds the directory of the currently running script and puts it in $Bin, which you can then use to construct the right library path:
use FindBin qw($Bin);
You can also use local::lib to do much of the same thing. Install modules using local::lib's settings then use the module in your program:
use local::lib; # sets up a local lib at ~/perl5
See the local::lib documentation for more details.
Let's say you're looking for script.pl. You may be running it, or you may have included it. You don't know. So it either lies in the %INC table in the first case or as $PROGRAM_NAME (aka $0) in the second.
use strict;
use warnings;
use English qw<$PROGRAM_NAME>;
use File::Basename qw<dirname>;
use File::Spec;
use List::Util qw<first>;
# Here we get the first entry that ends with 'script.pl'
my $key = first { defined && m/\bscript\.pl$/ } keys %INC, $PROGRAM_NAME;
die "Could not find script.pl!" unless $key;
# Here we get the absolute path of the indicated path.
print File::Spec->rel2abs( dirname( $INC{ $key } || $key )), "\n";
Link to File::Basename, File::Spec, and List::Util