3

I have a bunch of perl scripts, each of them needs to have an identical BEGIN section, which adds to @INC the path of perl modules we developed. Because it is not a sub, I cannot simply call it. Currently I inlcude this section in every perl script, which is obviously a headache to maintain. Is there a easier way to include the BEGIN section?

BEGIN
{
     my $current_script_dir = File::Basename::dirname(File::Spec::Functions::rel2abs($0));

     # Assume that the root of all libraries is two levels up from the directory of the 
     # script being run.

     my $all_libs_root = File::Spec->canonpath("$current_script_dir/../..");

     # Make sure the path is absolute,      
     $all_libs_root = File::Spec->rel2abs($all_libs_root);

     unshift(@INC, "$all_libs_root");
} 
ericyoung
  • 621
  • 2
  • 15
  • 21
  • 4
    You might think of setting `PERL5LIB` environment variable to point to your include path(s) - separated with `:`. Then you don't need to add it to all of your scripts, but to your environments where the scripts run. – Karsten S. Jul 15 '13 at 18:23
  • 3
    Or alternatively think about using some sort of package management solution (CPAN, or the OS's package manager) to drop your libraries into the interpreter's default locations in a robust and maintainable way. – Andy Ross Jul 15 '13 at 18:35

1 Answers1

8

Yes. You can simply put that code (that would go into BEGIN) into a new Perl module.

package MyIncPathMaintenance;
push @INC, "something_bvaluable";
1;

Then, all your scripts merely need to do:

use MyIncPathMaintenance;

as their first line after shebang and use strict

This works, because use XYZ is equivalent to BEGIN { require XYZ; XYZ->import() } (source); whereas that require will execute the module's code (including your @INC manipulation) as if it was eval-ed (source).

This approach has a number of benefits over BEGIN + @INC change:

  • It's a lot more maintainable going forward. Any changes to you path (or adding a new path) are encapsulated in the module, instead of being copied/pasted across tons of scripts

  • It encapsulates away somewhat advanced functionality that may be a bit dangerous to expose to junior developers who don't know what they are doing and can break it ("hey, I'll just add my home directory to this path so I can run my ad-hoc library version without core review/testing/releasing SDLC").

  • It allows more fancy logic in the script down the line (in our case, the module automagically prepends BETA library paths for BETA scripts).


To address the question of "where does MyIncPathMaintenance.pm go?", it depends on your environment. You can find a detailed discussion of how @INC is built here: How is Perl's @INC constructed? (aka What are all the ways of affecting where Perl modules are searched for?)

including what your Perl interpreter's default @INC is and how you can affect it via environmental variables.

Community
  • 1
  • 1
DVK
  • 126,886
  • 32
  • 213
  • 327
  • Then the question might be: where to put MyIncPathMainenance.pm that it's found by all scripts? – Karsten S. Jul 15 '13 at 18:24
  • @KarstenS. - that depends entirely on your environment. We have standard directories that all servers' Perl's compiled-in \@INC sees where I work. If you run `perl -V` you will see your default @INC. For more details of how \@INC is built (including environmental variables you can use to affect it) see here: http://stackoverflow.com/questions/2526804/how-is-perls-inc-constructed-aka-what-are-all-the-ways-of-affecting-where-pe – DVK Jul 15 '13 at 18:25
  • This just changes the problem from adding a `BEGIN` block to every program to adding a `use` statement to every program. Although it is slightly better, as any more common requirements can then just be added to the new module. – Borodin Jul 15 '13 at 18:28
  • @Borodin - it's also more maintainable (you won't have to change ALL your scripts if paths change). It also hides away \@INC magic from people who shouldn't be messin' with it. – DVK Jul 15 '13 at 18:30
  • In "push @INC, "something_bvaluable"; Is "something_bvaluable" an absolute path to the module? Our script will be tested locally and then be moved to the server. The absolute path might be different on local machine and on the server. – ericyoung Jul 15 '13 at 18:44
  • @ericyoung - absolute path. But you can push both path locations, e.g. "something_valuable_prod","something_valuable_dev" – DVK Jul 15 '13 at 18:45
  • Or you can have the module detect the environment and push the correct path (e.g. by checking directory existence via -e/-d). That's the beauty of the module - you only have to do it once – DVK Jul 15 '13 at 18:46
  • @DVK Could you give an clear example? I edited my quesiton. The begin section is what I want to do. – ericyoung Jul 15 '13 at 18:52
  • @ericyoung - you literally just take 100% of your code within your BEGIN block and move it as-is into the module, in between "package" line and "1;" line. – DVK Jul 15 '13 at 20:55
  • You should `use lib` to add paths to `@INC`. See `perldoc lib` http://perldoc.perl.org/lib.html – shawnhcorey Jul 15 '13 at 23:36
  • @shawnhcorey - if you read the linked post, there are 5 distinct major ways to add paths to `@INC`, and a boatload of heuristics for which way should be used when :) – DVK Jul 16 '13 at 01:00
  • @DVK, so? `use lib` was design to replace them all. – shawnhcorey Jul 16 '13 at 12:09