How do I automate CPAN configuration? - perl

The first time you run cpan from the command line, you are prompted for answers to various questions. How do you automate cpan and install modules non-interactively from the beginning?

Since it hasn't been mentioned yet, cpanminus is a zero-conf cpan installer. And you can download a self-contained executable if it isn't available for your version control.
The cpanm executable is easily installed (as documented in the executable itself) with:
curl -L http://cpanmin.us | perl - --self-upgrade
# or
wget -O - http://cpanmin.us | perl - --self-upgrade

I was looking for an easy solution for this as well and found that this works:
(echo y;echo o conf prerequisites_policy follow;echo o conf commit)|cpan
Just thought I would post it here in case anyone else comes along.

Make your own CPAN.pm config file. The recent versions of the cpan command have a -J switch to dump the current config and a -j switch to load whatever config you like.
I added these switches because we were distributing CPAN on a CD, back in the days when a MiniCPAN could fit in under 700Mb. You'd run cpan as normal but with an added option:
% cpan -j /CD/Volume/path/cpan_config.pm ....
In that example, the config would set the URL list to the CD path. I've long since lost the source files, but I recall there was a way that it figured out dynamically where it was, or maybe had a program that did and saved the config somewhere.

Recent versions of CPAN.pm ask as first question whether the rest of the configuration should be run automatically, so it is advisable to upgrade CPAN.pm (manually) first: tarballs, repo.

One way is to take the CPAN/Config.pm (or ~/.cpan/CPAN/MyConfig.pm) created after one run from one system, and install it as ~/.cpan/CPAN/MyConfig.pm on the system you want to automate. Another way is to run the following to create the MyConfig.pm file for you (one thing missing below is the actual values for the urllist parameter which you will have to fill in with appropriate values for CPAN mirrors):
#!/usr/bin/perl
use strict;
use Config;
$ENV{PERL_MM_USE_DEFAULT}=1;
$ENV{PERL_MM_NONINTERACTIVE}=1;
$ENV{AUTOMATED_TESTING}=1;
# get the path to the library
my $libpath = $Config{privlib};
# force CPAN::FirstTime to not default to manual
# setup, since initial CPAN setup needs to be automated
{
local #ARGV = "$libpath/CPAN/FirstTime.pm";
my #source = <>;
$source[72] =~ s/\byes\b/no/ or die "Could not auto configure CPAN";
eval join('', #source) or die "Error executing CPAN::FirstTime: $#";
}
CPAN::FirstTime::init("$libpath/CPAN/Config.pm");
delete $CPAN::Config->{links};
$CPAN::Config->{auto_commit} = '0';
$CPAN::Config->{check_sigs} = '0';
$CPAN::Config->{halt_on_failure} = '0';
$CPAN::Config->{make_install_make_command} = '/usr/bin/make';
$CPAN::Config->{mbuild_arg} = '';
$CPAN::Config->{mbuildpl_arg} = '';
$CPAN::Config->{mbuild_install_arg} = '';
$CPAN::Config->{show_upload_date} = '';
$CPAN::Config->{tar_verbosity} = '1';
$CPAN::Config->{trust_test_report_history} = '0';
$CPAN::Config->{use_sqlite} = '0';
$CPAN::Config->{yaml_load_code} = '0';
$CPAN::Config->{urllist}
= [qw(http://... ftp://... etc...)];
$CPAN::Config->{connect_to_internet_ok} = '1';
$CPAN::Config->{perl5lib_verbosity} = 'v';
$CPAN::Config->{prefer_installer} = 'MB';
$CPAN::Config->{build_requires_install_policy} = 'no';
$CPAN::Config->{term_ornaments} = '1';
$CPAN::Config->{mbuild_install_build_command} = './Build';
mkdir ".cpan/CPAN" or die "Can't create .cpan/CPAN: $!";
CPAN::Config->commit(".cpan/CPAN/MyConfig.pm");
CPAN::install('Bundle::CPAN');
CPAN::install('JSON');
CPAN::install('JSON::XS');
# etc.
exit 0;

Related

How to see if a plugin is already installed with Perl

I have a Perl script that requires a couple of plugins, for istance nmap. How can I see if the plugins are already installed and, in case they are not, install them? I tryed with the following code but it doesn't work very well, what I am trying to do is capture the "bash: nmap: command not found" output. I tryed with both stdout and stderr.
print "Checking nmap...\n";
my ($stdout, $stderr) = capture {
system("nmap");
};
if ($stdout=~m/command not found/) {
print "nmap not found, installing...\n";
system("rpm -i nmap-4.75-1.26.x86_64.rpm");
}
else {
print "nmap is already installed.\n";
}
How can I see if the plugins are already installed and, in case they are not, install them?
This is not a good idea, do not check for dependencies at run time. Instead you declare the dependencies in your distro meta file and check for them at build time and perhaps abort the build. The easiest way to do so is with requires_external_bin from Module::Install. This integrates nicely into the existing RPM infrastructure. - In other words, learn the basics of packaging and which problems this solves.
If you cannot rely on the user having permission to install it system-wide, create an Alien distro that downloads the source and installs it into the share tree. But once you go down that rabbit hole, be aware that it's deep - you would also need to take care of the deps of nmap itself somehow.
What about this?
my $cmd='which nmap';
my $output = `$cmd 2>&1`;
my $exit_value=$? >> 8;
if ($exit_value){
print "not found $cmd, error: $output\n";
}else{
print "Found $cmd at $output\n";
}
Like daxim pointed out requires_external_bin from Module::Install::External is a good way to ensure that your binary is installed.
If you can't use Module::Install in your application you might try searching the PATH environment variable like this:
use File::Spec;
$\="\n";
print installed($_) ? "$_ installed" : "$_ not installed" for qw/nmap ls cat nosuchfile less/;
sub installed {
my $name = shift;
foreach my $path (File::Spec->path()) {
my $bin = File::Spec->catfile($path, $name);
return $bin if -e -f -x $bin;
}
}
This should output something like this:
$ perl test.pl
nmap installed
ls installed
cat installed
nosuchfile not installed
less installed
The downside of this is of course, that the binary you are looking for has to reside in $PATH.

How does Perl interact with the scripts it is running?

I have a Perl script that runs a different utility (called Radmind, for those interested) that has the capability to edit the filesystem. The Perl script monitors output from this process, so it would be running throughout this whole situation.
What would happen if the utility being run by the script tried to edit the script file itself, that is, replace it with a newer version? Does Perl load the script and any linked libraries at the start of its execution and then ignore the script file itself unless told specifically to mess with it? Or perhaps, would all hell break loose, and executions might or might not fail depending on how the new file differed from the one being run?
Or maybe something else entirely? Apologies if this belongs on SuperUser—seems like a gray area to me.
It's not quite as simple as pavel's answer states, because Perl doesn't actually have a clean division of "first you compile the source, then you run the compiled code"[1], but the basic point stands: Each source file is read from disk in its entirety before any code in that file is compiled or executed and any subsequent changes to the source file will have no effect on the running program unless you specifically instruct perl to re-load the file and execute the new version's code[2].
[1] BEGIN blocks will run code during compilation, while commands such as eval and require will compile additional code at run-time
[2] Most likely by using eval or do, since require and use check whether the file has been loaded already and ignore it if it has.
For a fun demonstration, consider
#! /usr/bin/perl
die "$0: where am I?\n" unless -e $0;
unlink $0 or die "$0: unlink $0: $!\n";
print "$0: deleted!\n";
for (1 .. 5) {
sleep 1;
print "$0: still running!\n";
}
Sample run:
$ ./prog.pl
./prog.pl: deleted!
./prog.pl: still running!
./prog.pl: still running!
./prog.pl: still running!
./prog.pl: still running!
./prog.pl: still running!
Your Perl script will be compiled first, then run; so changing your script while it runs won't change the running compiled code.
Consider this example:
#!/usr/bin/perl
use strict;
use warnings;
push #ARGV, $0;
$^I = '';
my $foo = 42;
my $bar = 56;
my %switch = (
foo => 'bar',
bar => 'foo',
);
while (<ARGV>) {
s/my \$(foo|bar)/my \$$switch{$1}/;
print;
}
print "\$foo: $foo, \$bar: $bar\n";
and watch the result when run multiple times.
The script file is read once into memory. You can edit the file from another utility after that -- or from the Perl script itself -- if you wish.
As the others said, the script is read into memory, compiled and run. GBacon shows that you can delete the file and it will run. This code below shows that you can change the file and do it and get the new behavior.
use strict;
use warnings;
use English qw<$PROGRAM_NAME>;
open my $ph, '>', $PROGRAM_NAME;
print $ph q[print "!!!!!!\n";];
close $ph;
do $PROGRAM_NAME;
... DON'T DO THIS!!!
Perl scripts are simple text files that are read into memory, compiled in memory, and the text file script is not read again. (Exceptions are modules that come into lexical scope after compilation and do and eval statements in some cases...)
There is a well known utility that exploits this behavior. Look at CPAN and its many versions which is probably in your /usr/bin directory. There is a CPAN version for each version of Perl on your system. CPAN will sense when a new version of CPAN itself is available, ask if you want to install it, and if you say "y" it will download the newer version and respawn itself right where you left off without loosing any data.
The logic of this is not hard to follow. Read /usr/bin/CPAN and then follow the individualized versions related to what $Config::Config{version} would generate on your system.
Cheers.

How do I automate the initial CPAN configuration?

Here How do I automate CPAN configuration? I've found some answers which led to some questions.
I tried cpan -j config.pm, but as far as I can see it is meant for per installation usage, not for changing the config-file permanently.
With the $CPAN::Config-method the force CPAN::FirstTime to not default to manual-part didn't work here so I tried without it:
#!/usr/bin/perl
use strict;
use warnings;
use Config;
use CPAN;
use CPAN::FirstTime;
$ENV{PERL_MM_USE_DEFAULT}=1;
$ENV{PERL_MM_NONINTERACTIVE}=1;
$ENV{AUTOMATED_TESTING}=1;
my $cpan_home = '/home/me/.cpan';
mkdir $cpan_home or die $! if not -d $cpan_home;
mkdir "$cpan_home/CPAN" or die $! if not -d "$cpan_home/CPAN";
CPAN::FirstTime::init( "$cpan_home/CPAN/MyConfig.pm" );
delete $CPAN::Config->{links};
$CPAN::Config->{applypatch} = '';
# ...
$CPAN::Config->{build_dir} = "$cpan_home/build";
$CPAN::Config->{cpan_home} = $cpan_home;
$CPAN::Config->{histfile} = "$cpan_home/histfile";
$CP$CPAN::Config->{keep_source_where} = "$cpan_home/sources";
$CPAN::Config->{make_install_make_command} = 'sudo make';
$CPAN::Config->{mbuild_install_build_command} = 'sudo ./Build';
$CPAN::Config->{prefs_dir} = "$cpan_home/prefs";
# ...
$CPAN::Config->{yaml_module} = 'YAML';
CPAN::HandleConfig->commit("$cpan_home/CPAN/MyConfig.pm");
CPAN::install('Bundle::CPAN');
# ...
# etc.
exit 0;
Is this OK? The only bad thing that I have noticed so far is the waiting, until the cpan-mirror-urls are found.
And what is the delete $CPAN::Config->{links}; for?
It looks like you are doing a lot of work. What are you trying to accomplish?
If you want to change the configuration file permanently, just change the configuration file. It's Perl code, so I think you can do everything you need, such as setting the root directory, right in the config file without having to deal with CPAN.pm.

How can I get the version and location of an installed Perl module?

Is there a smart way to detect whether a certain Perl module has been installed in your system?
My old sutpid way is to write a Perl script in which the only thing I do is just to use the module. If nothing croaks when I run the detect script, then I know the module has been installed, although I still don't know which version and where the module has been installed .
thanks in advance.
Something like:
perl -MModule -e 'print "$Module::VERSION\n"' 2>/dev/null || echo "Not installed"
would give you the version of a given module, or tell you it isn't installed. Usage would look like:
perl -MXML::Parser -e 'print "$XML::Parser::VERSION\n"' 2>/dev/null || echo "Not installed"
To find the module path, you could examine #INC to find possible locations, or you could perhaps look into perlwhich. There is also pmpath from pmtools.
The shortest thing I know of that doesn't involve a script or shell alias:
$ perl -MFoo::Bar\ 99
Foo::Bar version 99 required--this is only version 1.234.
(or the usual message about not being in #INC if it's not installed)
For the curious, this is the same as perl -e 'use Foo::Bar 99'.
instmodsh
NAME
instmodsh - A shell to examine installed modules
SYNOPSIS
instmodsh
DESCRIPTION
A little interface to ExtUtils::Installed to examine installed modules, validate your packlists and even create a tarball from an installed module.
SEE ALSO
ExtUtils::Installed
Here's a program which does that:
#!/usr/bin/perl
# testmod - test to see if a module is available
use strict;
use warnings;
my $mod = (shift #ARGV) || die "usage: $0 module\n";
# convert module-name to path
my $file = $mod;
$file =~ s{::}{/}gsmx;
$file .= '.pm';
# Pull in the module, if it exists
eval { require $file }
or die "can't find module $mod\n";
# Get the version from the module, if defined
my $ver;
{ no strict 'refs';
$ver = ${$mod . "::VERSION"} || 'UNKNOWN';
}
# And its location
my $from = $INC{$file};
print "module $mod is version $ver loaded from $from\n";
Use pmvers. Like the name suggests, it shows the version of an installed module. If a module is not installed, it fails with the familiar error message: Can't locate … in #INC (#INC contains: …)
Use pmpath from the same distribution to find a module's installation path.
I use these bash function/Perl oneliners to find the version number and location of Perl modules:
# equivalent to perldoc -l <module>
perlwhere() {
perl -wle'eval "require $ARGV[0]" or die; ($mod = $ARGV[0]) =~ s|::|/|g; print $INC{"${mod}.pm"}' $1
}
perlversion() {
perl -M$1 -wle'print $ARGV[0]->VERSION' $1
}
: [ether ~].2$; perlwhere Test::More
/usr/lib/perl5/5.8.8/Test/More.pm
: [ether ~].2$; perlversion Test::More
0.94
I don't know if there is any smart way for this. But what I usually
do is to make use of '-l' or '-m' option of perldoc. For example :
%perldoc -l XML::Simple
and the output is something like below,which is the full path of module file
.../lib/XML/Simple.pm
The advantage with this approach compared to yours is that, if the module is installed
the output contains the path for module location. However when the module is not
installed
or if it doesn't has a perldoc the error message shown is "No documentation found for ...",
making it impossible to distinguish if the error is due to missing module or missing
documentation. In such scenario the -m option becomes handy since it prints entire
contents of the file along with the path.
The pmvers utility and the other pmtools will do what you need. Otherwise here is a one-liner to find a module version:
perl -le 'eval "require $ARGV[0]" and print $ARGV[0]->VERSION' Some::Module
If you're looking for a cross-platform CLI (Linux, OSX, Windows), consider my whichpm utility; e.g.:
# Locate the Data::Dumper module, and also print
# version information and core-module status.
$ whichpm -v Data::Dumper
Data::Dumper 2.145 core>=5.005 /usr/lib/perl/5.18/Data/Dumper.pm
It can also find accidental duplicates and list all installed modules.
If you happen to have Node.js / io.js installed, you can install it from the npm registry:
[sudo] npm install whichpm -g
For manual installation instructions and more, see the repo; here's a direct download link to the latest version (will stay current).

How do I find all modules used in a Perl script and install them?

I have been given a few Perl scripts to deploy.
What is the easiest way to find and install all modules used by these scripts?
EDIT:
From what I can find there are no conditional includes or includes in evals.
Does my Module::Extract::Use help? There's an extract_modules program in the examples directory:
$ examples/extract_modules -l some_program
File::Spec
File::Spec::Functions
strict
warning
You can pipe that list to cpan.
$ examples/extract_modules -l some_program | xargs cpan
Once you have that list, which is only the first level of dependencies, you can make a script distribution that allows people to use the normal CPAN toolchain to install everything.
If there's something that doesn't work for you, modify the program to handle that. If you think it would be useful to other people, send a pull request. :)
I was hoping Module::ScanDeps which provides the command line utility scandeps.pl would be useful here but, to my dismay, Module::ScanDeps is apparently not intended for this particular purpose as scandeps.pl either ignores missing modules or (with -c or -x) croaks when the script uses a module that is not installed.
Here is a quick'n'dirty Perl script that tries to execute the script using do until it succeeds:
#!/usr/bin/perl
use strict;
use warnings;
use Term::Prompt;
my ($script) = #ARGV;
die "Provide script file name on the command line\n"
unless defined $script;
until ( do $script ) {
my $ex = $#;
if ( my ($file) = $ex =~ /^Can't locate (.+?) in/ ) {
my $module = $file;
$module =~ s/\.(\w+)$//;
$module = join('::', split '/', $module);
print "Attempting to install '$module' via cpan\n";
system(cpan => $module);
last unless prompt(y => 'Try Again?', '', 'n');
}
else {
die $ex;
}
}
If you do not want the script to be run, you can run perl -c $script, capture stderr output of that and parse for missing module messages and call cpan for each such module found until perl -c $script outputs "Syntax OK". That gives you a cleaner loop too. I'll look at this later.
You might miss dependencies loaded at run time using this technique.
Well this is the very simplistic way I solved this.
In a bash shell:
cat *.pl | grep "^use " | tr ';' ' ' | while read a b c; do echo $b; done | sort -iu > modules.txt
This gave me a file with only the module names, one on each line.
I then used this
cat modules.txt | while read a; do cpan $a; done
to invoke cpan to each module name in the file. And then sat there answering yes to CPAN's questions to install dependencies as appropriate.
Not pretty but this time it got the job done.
Or let PAR's pp do the work for you in collecting together everything you need in a single executable.