Why doesn't "use lib" take effect in this way? - perl

In my app, I put all the modules in one directory , let's just call it libx.
Since it's up to the user to choose where to deploy the app, I don't want to hardcode the lib path.
So at the beginning of myapp.pl, I wrote the following lines of code.
#! /usr/bin/perl -w
use strict;
my $curr_dir = $0;
my $curr_lib = $curr_dir;
$curr_lib =~ s/myapp\.pl/libx/;
use $curr_lib ;
Instead of getting what I'm expecting, I got compiling errors!
So what's wrong with my code? I don't want to hardcode the lib path when using use lib, how should I do this?
Sorry I forgot to mention that when the app is deployed, myapp.pl and libx are in the same directory.

use happens at compile-time, not run-time, so your variable hasn't been set yet.
You can do:
my $curr_lib;
BEGIN {
$curr_lib = $0;
$curr_lib =~ s/myapp\.pl/libx/;
}
use lib $curr_lib;
or you could:
use FindBin;
use lib "$FindBin::Bin/libx";

I had trouble with this before too. What's happening is that Perl makes two passes over your code: once to compile it, and the second time to run it. In you example, lines 4,5, and 6 don't execute until run time, but use takes effect during compile time.
One possibility is to put it all inside a BEGIN{} block, which will make the code execute at compile time. I've done this, but it's messy/ugly. (BTW, instead of $0, you need to use $ARGV[0] in Perl).
The best way to tell Perl where to pick up libraries is to use the -I flag. You can put it on the #! line, or if you are starting the script from another script, you can start it as
perl -I/your/directory/libx your_script.pl
You can use relative paths, so maybe
perl -I./libx script.pl
would work for you.

use xxx is a compile-time directive, not a run-time instruction. You cannot set it programmatically within the script you are running.
You may need to try require rather than use
If you need use, you need to set your $PERL5LIB environment variable to ensure your modules are correctly located and used, or change your shebang line to read #! /usr/local/perl -w -I/path/to/libx. There are other methods (local::lib etc.), but from your question it seems your control over installation is a bit limited for that sort of approach.

Related

identify a procedure and replace it with a different procedure

What I want to achieve:
###############CODE########
old_procedure(arg1, arg2);
#############CODE_END######
I have a huge code which has a old procedure in it. I want that the call to that old_procedure go to a call to a new procedure (new_procedure(arg1, arg2)) with the same arguments.
Now I know, the question seems pretty stupid but the trick is I am not allowed to change the code or the bad_function. So the only thing I can do it create a procedure externally which reads the code flow or something and then whenever it finds the bad_function, it replaces it with the new_function. They have a void type, so don't have to worry about the return values.
I am usng perl. If someone knows how to atleast start in this direction...please comment or answer. It would be nice if the new code can be done in perl or C, but other known languages are good too. C++, java.
EDIT: The code is written in shell script and perl. I cannot edit the code and I don't have location of the old_function, I mean I can find it...but its really tough. So I can use the package thing pointed out but if there is a way around it...so that I could parse the thread with that function and replace function calls. Please don't remove tags as I need suggestions from java, C++ experts also.
EDIT: #mirod
So I tried it out and your answer made a new subroutine and now there is no way of accessing the old one. I had created an variable which checks the value to decide which way to go( old_sub or new_sub)...is there a way to add the variable in the new code...which sends the control back to old_function if it is not set...
like:
use BadPackage; # sub is defined there
BEGIN
{ package BapPackage;
no warnings; # to avoid the "Subroutine bad_sub redefined" message
# check for the variable and send to old_sub if the var is not set
sub bad_sub
{ # good code
}
}
# Thanks #mirod
This is easier to do in Perl than in a lot of other languages, but that doesn't mean it's easy, and I don't know if it's what you want to hear. Here's a proof-of-concept:
Let's take some broken code:
# file name: Some/Package.pm
package Some::Package;
use base 'Exporter';
our #EXPORT = qw(forty_two nineteen);
sub forty_two { 19 }
sub nineteen { 19 }
1;
# file name: main.pl
use Some::Package;
print "forty-two plus nineteen is ", forty_two() + nineteen();
Running the program perl main.pl produces the output:
forty-two plus nineteen is 38
It is given that the files Some/Package.pm and main.pl are broken and immutable. How can we fix their behavior?
One way we can insert arbitrary code to a perl command is with the -M command-line switch. Let's make a repair module:
# file: MyRepairs.pm
CHECK {
no warnings 'redefine';
*forty_two = *Some::Package::forty_two = sub { 42 };
};
1;
Now running the program perl -MMyRepairs main.pl produces:
forty-two plus nineteen is 61
Our repair module uses a CHECK block to execute code in between the compile-time and run-time phase. We want our code to be the last code run at compile-time so it will overwrite some functions that have already been loaded. The -M command-line switch will run our code first, so the CHECK block delays execution of our repairs until all the other compile time code is run. See perlmod for more details.
This solution is fragile. It can't do much about modules loaded at run-time (with require ... or eval "use ..." (these are common) or subroutines defined in other CHECK blocks (these are rare).
If we assume the shell script that runs main.pl is also immutable (i.e., we're not allowed to change perl main.pl to perl -MMyRepairs main.pl), then we move up one level and pass the -MMyRepairs in the PERL5OPT environment variable:
PERL5OPT="-I/path/to/MyRepairs -MMyRepairs" bash the_immutable_script_that_calls_main_pl.sh
These are called automated refactoring tools and are common for other languages. For Perl though you may well be in a really bad way because parsing Perl to find all the references is going to be virtually impossible.
Where is the old procedure defined?
If it is defined in a package, you can switch to the package, after it has been used, and redefine the sub:
use BadPackage; # sub is defined there
BEGIN
{ package BapPackage;
no warnings; # to avoid the "Subroutine bad_sub redefined" message
sub bad_sub
{ # good code
}
}
If the code is in the same package but in a different file (loaded through a require), you can do the same thing without having to switch package.
if all the code is in the same file, then change it.
sed -i 's/old_procedure/new_procedure/g codefile
Is this what you mean?

How to test your own Perl modules while not depending on other modules in production?

First of all, I think somebody needs to rewrite my question, I know what I am asking, not how I should ask it precisely.
Assume I have some local module LOCAL::Commons and it has one subroutine called globalTrim. I am testing that subroutine below, and while LOCAL::Commons is installed under /usr/local/lib/perl, the module I am testing below is located in directory /home/me/perl/LOCAL/. See how I am using use lib ... to make sure I am not using LOCAL::Commons located in /usr/local/lib/perl (I have this directory on my path).
Now, if LOCAL::Commons is using another local module LOCAL::Cool (that is, not from cpan), and I have also made some changes to that module, how can I make sure my tests are using the correct modules? That is, I want LOCAL::Commons to use /home/me/perl/LOCAL/Cool and not /usr/local/lib/perl/LOCAL/Cool.
#!/usr/bin/perl
# test_local_commons.pl
# directory: /home/me/perl
use strict;
use warnings;
use Test::More 'no_plan';
use File::Temp qw( tempfile tempdir );
use Cwd qw();
use lib Cwd::abs_path();
# Testing
use LOCAL::Commons qw ( globalTrim );
sub newTest($) {
my $name = shift;
print "---------------------------------------------------\n";
print $name, "\n";
print "---------------------------------------------------\n";
}
sub testTraverse {
is(globalTrim(" - stackoverflow - ), "-stackoverflow-", "Passed" );
}
newTest "testTraverse"; testTraverse ();
If you run this like so:
perl -I/home/me/perl test_local_commons.pl
It should ensure your /home/me/perl version is checked first
After you load the files you want, put this:
die 'Included PRODUCTION Module!!!'
if grep { m{/usr/local/lib/perl/LOCAL/Cool} } values %INC
;
Of course you could die for any module you wanted.
For example, we've got this tool at work that works with Activestate's PerlEZ.dll. We don't want to deploy it with Perl installed. But we need to use some libraries, which we hide elsewhere. When I'm testing my code I include a module from our hidden path. It blows up anytime it sees a standard library path in %INC. We want to make sure it almost everything from the #INC hook and the special libraries.
I suggest using Perlbrew to make a completely separate Perl installation for testing. That way you control the testing environment and don't really have to worry about it.
use lib has process scope -- that is, it changes the value #main::INC with global visibility, just as a non-localized assignment to #INC would. That means that any modules that you load after saying use lib will check the include path you've set.
As to what path use lib sets, it explicitly adds to the front of #INC so that later calls to use lib will be considered earlier when searching for modules.
The upshot of which is that it looks to me like your code sample will just work. Note that I'm going to discourage you from checking that you're using the under-development version in your test. That would cause your test to fail based on something unrelated to whether the function under test actually works correctly. (But note also that you should also have written unit tests for LOCAL::Cool.)
You could just add a BEGIN{ #INC = qw(directories you want to allow)} block. While using PERL5LIB, use lib etc. just appends to the include path, this will completely replace it, thus eliminating the danger that the module is picked up later in the search path, e.g. because you forgot to install it /home/me/perl/.
But the real TDD answer is probably to use mock modules for everything that's not the module under test.

Having a perl script make use of one among several secondary scripts

I have a main program mytool.pl to be run from the command line. There are several auxillary scripts special1.pl, special2.pl, etc. which each contain a couple subroutines and a hash, all identically named across scripts. Let's suppose these are named MySpecialFunction(), AnotherSpecialFunction() and %SpecialData.
I'd like for mytool to include/use/import the contents of one of the special*.pl files, only one, according to a command line option. For example, the user will do:
bash> perl mytool.pl --specialcase=5
and mytools will use MySpecialFunction() from special5.pl, and ignore all other special*.pl files.
Is this possible and how to do it?
It's important to note that the selection of which special file to use is made at runtime, so adding a "use" at the top of mytool.pl probably isn't the right thing to do.
Note I am a long-time C programmer, not a perl expert; I may be asking something obvious.
This is for a one-off project that will turn to dust in only a month. Neither mytool.pl nor special?.pl (nor perl itself) will be of interest beyond the end of this short project. Therefore, we don't care for solutions that are elaborate or require learning some deep magic. Quick and dirty preferred. I'm guessing that Perl's module mechanism is overkill for this, but have no idea what the alternatives are.
You can use a hash or array to map values of specialcase to .pl files and require or do them as needed.
#!/usr/bin/env perl
use strict; use warnings;
my #handlers = qw(one.pl two.pl);
my ($case) = #ARGV;
$case = 0 unless defined $case;
# check that $case is within range
do $handlers[$case];
print special_function(), "\n";
When you use a module, Perl just require's the module in a BEGIN block (and imports the modules exported items). Since you want to change what script you load at runtime, call require yourself.
if ($special_case_1) {
require 'special1.pl';
# and go about your business
}
Here's a good reference on when to use use vs. require.

.pm file that's loaded on every invocation of the perl interpreter?

I thought I remember reading somewhere about where perl can be configured to automatically load a certain .pm file on start up.
I know about PERL5OPT, but to my recollection, this was a specific file that would be loaded if it exists.
Is it a compile option that can be set (i.e. via Configure)?
Reading through perldoc perlrun it looks like you are looking for what is talked about in the -f option:
-f
Disable executing $Config{sitelib}/sitecustomize.pl at startup.
Perl can be built so that it by default will try to execute
$Config{sitelib}/sitecustomize.pl at startup (in a BEGIN block). This
is a hook that allows the sysadmin to customize how Perl behaves. It
can for instance be used to add entries to the #INC array to make Perl
find modules in non-standard locations.
Perl actually inserts the following code:
BEGIN {
do { local $!; -f "$Config{sitelib}/sitecustomize.pl"; }
&& do "$Config{sitelib}/sitecustomize.pl";
}
Since it is an actual do (not a require), sitecustomize.pl doesn't
need to return a true value. The code is run in package main , in its
own lexical scope. However, if the script dies, $# will not be set.
The value of $Config{sitelib} is also determined in C code and not
read from Config.pm , which is not loaded.
The code is executed very early. For example, any changes made to #INC
will show up in the output of perl -V. Of course, END blocks will be
likewise executed very late.
To determine at runtime if this capability has been compiled in your
perl, you can check the value of $Config{usesitecustomize} .
I've never done this, but it looks like if you put what you want in $Config{sitelib}/sitecustomize.pl you'll get what you are looking for.
See:
http://perldoc.perl.org/perlrun.html
http://www.nntp.perl.org/group/perl.perl5.porters/2007/10/msg129926.html
I'm confused by what you mean by "on start up". If you mean when a script / CGI / whatever is "started", then just use the module in the script:
use Data::Dumper;
Or do you mean something else?

How can I "use lib" the appropriate directory depending on installation location?

I have an object-oriented web-app that is installed in multiple locations on my server. Once for "live", once for "beta", etc. Being object-oriented, it consists of many perl modules. In the main module, I must "use lib" the appropriate directory for all of the custom perl modules for that instance of the app.
This is no big deal, I have a BEGIN block that checks the location of the main program and sets the library directory appropriately. However I also have a lot of utility, command line programs that need to do the same thing. I don't want to cut and paste this code everywhere.
What is the best way to share this code snippet amongst the various programs that need it?
I can't "use" it because the libary path isn't set up yet. Maybe "do" or "require" would the be the right answer, but both of those will search #INC, which is inappropriate.
Maybe something like eval `cat GetLib.pl`; would be appropriate but it seems kind of clunky and fragile.
Here is the BEGIN block that I currently use:
BEGIN {
use FindBin qw ($Bin);
require lib;
if ($Bin =~ /^\/home\/w\/myapp_live/) {
lib->import('/home/w/myapp_live/lib');
print STDERR "live site\n";
}
if ($Bin =~ /^\/home\/w\/myapp_beta/) {
lib->import('/home/w/myapp_beta/lib');
print STDERR "beta site\n";
}
if ($Bin =~ /^\/home\/w\/myapp_test/) {
lib->import('/home/w/myapp_test/lib');
print STDERR "testing site\n";
}
}
Thank you!
FindBin::libs is excellent for that. I've used it for a while in a large system with no problems at all.
The default invocation looks like it'll work for you, simply:
use FindBin::libs;
This will search for all the ./lib dirs in all the parent directories of the current file's dir and use lib them. So, for example, if your script lives in /home/w/myapp_live/scripts/defurblise_widgets.pl (and use()es FindBin::libs), it will look for:
/home/w/myapp_live/scripts/lib
/home/w/myapp_live/lib
/home/w/lib
/home/lib
/lib # (presumably!)
Any that it finds with be added to you #INC with use lib.
But, if that's not quite what you need, it's a very flexible module. I'd be surprised if you can't find a way to make it do what you want.
If you're running a program from the command line, don't programmatically set the lib: just pass it in as an argument, e.g.: perl -I/location/of/my/lib myprog.pl.
For your web app, why don't you make your library relative to the location of the script itself? Then just install it on each machine where the libraries live.
use FindBin;
use File::Spec::Functions;
use Cwd qw(abs_path getcwd);
BEGIN {
my $curdir = getcwd;
my $selfdir = $FindBin::Bin;
my $libdir = abs_path(catdir($selfdir, 'lib'));
chdir $libdir or die "can't chdir to $libdir: $#";
use lib $libdir;
}
Of course, the easiest option of all is to not use different lib directories. Why can't you be consistent across all your environments?
Edit. Re your comment "The reason I have to use different lib directories is because the code running in the live site is different than the code running on the beta site... that's the point of having a beta site." -- why don't you handle this at the level of the installer, rather than making the code itself have to know whether it's live vs. beta? e.g. store your code in different directories in your source tree as you do now, but only install the relevant code to each box. After all, that's exactly what a good revision control system would do for you -- you only check out one branch at a time, and you should only be installing one version of code at a time (as brian d foy alluded to).
I use the following in many of my scripts:
use strict;
use warnings;
use 5.008;
use FindBin;
use lib $FindBin::Bin;
That last line could be modified as such:
use FindBin;
use lib "$FindBin::Bin/lib";
The different environments could have different settings for the environment variable $PERL5LIB.
The code you've shown looks reasonable. You could install one copy of that code into a system-wide location, and then the code to invoke it would boil down to
require '/path/to/findlib.pl';
findlib->import;
The form of require that takes a filename doesn't search #INC.
As an alternative if you wanted to change lots of things around you could look into deploying the app in a way that would be more friendly to local::lib usage.