Can a Perl script install its own CPAN dependencies? - perl

I have a Perl script that has two dependencies that exist in CPAN. What I'd like to do is have the script itself prompt the user to install the necessary dependencies so the script will run properly. If the user needs to enter in some kind of authentication to install the dependencies that's fine: what I'm trying to avoid is the following workflow:
Run script -> Watch it fail -> Scour CPAN aimlessly -> Lynch the script writer
Instead I'm hoping for something like:
Run script -> Auto-download script dependencies (authenticating as necessary) -> Script succeeds -> Buy the script writer a beer
Can this be done?

Each of the standard build paradigms has their own way of specifying dependencies. In all of these cases, the build process will attempt to install your dependencies, automatically in some contexts.
In ExtUtils::MakeMaker, you pass a hash reference in the PREREQ_PM field to WriteMakefile:
# Makefile.PL for My::Module
use ExtUtils::MakeMaker;
WriteMakefile (
NAME => 'My::Module',
AUTHOR => ...,
...,
PREREQ_PM => {
'Some::Dependency' => 0, # any version
'Some::Other::Dependency' => 0.42, # at least version 0.42
...
},
...
);
In Module::Build, you pass a hashref to the build_requires field:
# Build.PL
use Module::Build;
...
my $builderclass = Module::Build->subclass( ... customizations ... );
my $builder = $builderclass->new(
module_name => 'My::Module',
...,
build_requires => {
'Some::Dependency' => 0,
'Some::Other::Dependency' => 0.42,
},
...
);
$builderclass->create_build_script();
In Module::Install, you execute one or more requires commands before calling the command to write the Makefile:
# Makefile.PL
use inc::Module::Install;
...
requires 'Some::Dependency' => 0;
requires 'Some::Other::Dependency' => 0.42;
test_requires 'Test::More' => 0.89;
...
WriteAll;

You can probably just execute this from inside your script.
perl -MCPAN -e 'install MyModule::MyDepends'

Related

How do I make Alien use an existing tarball instead of downloading?

My distro does not offer any gsl <2.6 any more.
Given:
Alien::GSL 1.01
/tmp/gsl-2.5.tar.gz
How do I force it to compile that gsl instead of downloading from GNU FTP version 2.6, which I already have on the system anyway but is not delectable to Math::GSL 0.40?
I unsuccessfully tried:
copying the tarball into the unpacked Alien::GSL base directory
messing with alien_repository
This is for a throw-away project. I'm okay with manual installation instructions and patching toolchain code.
ikegami found the decisive hint:
It looks like you can set protocol of local to use a local file
Tested step-by-step instructions, plus some additional work-arounds; to me it looks like the build systems of the two modules are buggy/insufficiently tested:
cpanm --look Alien::GSL
patch Build.PL
diff --git a/Build.PL b/Build.PL
index 32f3057..6537138 100644
--- a/Build.PL
+++ b/Build.PL
## -20,10 +20,9 ## my $builder = Alien::Base::ModuleBuild->new(
alien_name => 'gsl',
alien_repository => [
{
- protocol => 'ftp',
- host => 'ftp.gnu.org',
- location => '/gnu/gsl',
- pattern => qr/^gsl-([\d\.]+)\.tar\.gz$/,
+ protocol => 'local',
+ location => '/tmp',
+ pattern => 'gsl-2.5.tar.gz',
},
],
meta_merge => {
--
2.23.0
perl Build.PL
./Build
Pay attention to the generated configure/libtool commands here, they match the Perl configuration. A manual installation without those various options is not guaranteed to be compatible or usable. (This is not superstition: a similar problem historically shows up when installing mod_perl2 and libapreq2 from source on a system httpd; perl needs to be compiled first, then httpd to match, then the other packages, otherwise it won't work.) This shows the value of installing through Alien, since it delegates to M::B, the correct options will be figured out. It's above my level of knowledge to accurately create them from scratch.
./Build test
gsl-config in blib now erroneously contains build paths, not install paths, fix:
perl -MConfig -i -lpe'
s|/.*(/auto/share/dist/Alien-GSL)|$Config{installsitelib}$1|
' blib/lib/auto/share/dist/Alien-GSL/bin/gsl-config
./Build install
exit # cpanm
cpanm --look Math::GSL
# let it pick up gsl-config on PATH
export PATH=$PATH:$(perl -mAlien::GSL -e'print Alien::GSL->bin_dir')
perl Build.PL
./Build
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(gsl-config --libs | perl -lne'/-L(\S+) / && print $1')
./Build test
./Build install
exit # cpanm
Finally rëexport the variables whenever you want to use Math::GSL.

Make sure the prerequisite module are installed on the unix/linux os

I am cpan module which have prerequisite on other module. Is there any process to make sure the module can be installed only after the installation of the prerequisite module while
manual installation of the module.
perl MakeFile.pl
make
make test
make install
Yes. Use a standard build management module like
ExtUtils::MakeMaker or Module::Build, which have conventions to handle prerequisites.
For ExtUtils::MakeMaker, the convention is to pass prerequisite information in the PREREQ_PM argument to the WriteMakefile function. Here's what it might look like:
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'My::Module',
AUTHOR => 'Me',
VERSION_FROM => 'lib/My/Module.pm',
PREREQ_PM => {
'Some::Module' => 0.42, # need >=v0.42 of Some::Module
'Some::Other::Module' => 0, # but any version of Some::Other::Module is ok
}
dist => ... ,
clean => ... ,
...
);

'make installdeps' fails with Net::FTP-related error

I am writing Catalyst application which uses Module::Install for module building. I keep all project-related modules in separate directory with local::lib:
eval $(perl -Mlocal::lib=$HOME/work/projects/freevideo)
Recently I've moved to amd64 environment, and 'make installdeps' stopped working:
nikita#freevideo-dev:~/fv$ make installdeps
/usr/bin/perl "-Iinc" Makefile.PL --config= \
--installdeps=Text::Table,0,Catalyst::Log::Log4perl,1.04,\
Catalyst::Plugin::Session,0,Catalyst::Plugin::Session::State::Cookie,0,\
Catalyst::Plugin::Session::Store::FastMmap,0,Catalyst::Plugin::Authentication,0,\
Catalyst::Plugin::Authorization::Roles,0,Catalyst::Plugin::Unicode,0,\
Catalyst::Plugin::SmartURI,0,Catalyst::Model::DBIC::Schema,0.5,\
Catalyst::View::JSON,0.33,Catalyst::View::TT,0.37,\
Template::Plugin::ListUtil,0.02,Template::Stash::ForceUTF8,0.03,\
Encode,2.43,DateTime,0.70,DateTime::Format::MySQL,0.04,\
Mail::RFC822::Address,0.3,Digest::MD5,2.51,Data::Dumper,2.131,DBIx::Class,0.08194
include /home/nikita/work/projects/freevideo/FreeVideo/inc/Module/Install.pm
include inc/Module/Install/Metadata.pm
include inc/Module/Install/Base.pm
include inc/Module/Install/Makefile.pm
Cannot determine perl version info from lib/FV.pm
Cannot determine license info from lib/FV.pm
include inc/Module/Install/Catalyst.pm
include inc/Module/Install/Include.pm
include inc/File/Copy/Recursive.pm
*** Module::Install::Catalyst
Please run "make catalyst_par" to create the PAR package!
*** Module::Install::Catalyst finished.
include inc/Module/Install/Scripts.pm
include inc/Module/Install/AutoInstall.pm
include inc/Module/AutoInstall.pm
*** Installing dependencies...
Can't use an undefined value as a symbol reference at \
/usr/share/perl/5.10/Net/FTP/dataconn.pm line 54.
Compilation failed in require at \
/usr/share/perl5/Module/Install/AutoInstall.pm line 37.
make: *** [installdeps] Error 111
Here's my Makefile.PL:
# IMPORTANT: if you delete this file your app will not work as
# expected. you have been warned
use inc::Module::Install;
use strict;
use warnings;
name 'FV';
all_from 'lib/FV.pm';
requires 'Moose'; # Moose is required for Catalyst
# Catalyst components
requires 'Catalyst::Runtime' => '5.7015';
requires 'Catalyst::Log::Log4perl' => '1.04';
requires 'Catalyst::Plugin::ConfigLoader';
requires 'Catalyst::Plugin::Static::Simple';
requires 'Catalyst::Plugin::Session';
requires 'Catalyst::Plugin::Session::State::Cookie';
requires 'Catalyst::Plugin::Session::Store::FastMmap';
requires 'Catalyst::Plugin::Authentication';
requires 'Catalyst::Plugin::Authorization::Roles';
requires 'Catalyst::Plugin::Unicode';
requires 'Catalyst::Plugin::SmartURI';
requires 'Catalyst::Action::RenderView';
requires 'Catalyst::Model::DBIC::Schema' => '0.5';
requires 'Catalyst::View::JSON' => '0.33';
requires 'Catalyst::View::TT' => '0.37';
# pragmas
requires 'parent';
requires 'utf8' => '1.07';
requires 'feature' => '1.13';
requires 'Config::General'; # This should reflect the config file format you've chosen
# See Catalyst::Plugin::ConfigLoader for supported formats
# Template Toolkit
requires 'Template' => '2.22';
requires 'Template::Plugin::ListUtil' => '0.02';
requires 'Template::Stash::ForceUTF8' => '0.03';
# other
requires 'POSIX' => '1.17';
requires 'Exporter' => '5.63';
requires 'Encode' => '2.43';
requires 'Data::GUID' => '0.046';
requires 'DateTime' => '0.70';
requires 'DateTime::Format::MySQL' => '0.04';
requires 'Mail::RFC822::Address' => '0.3';
requires 'Digest::MD5' => '2.51';
requires 'Data::Dumper' => '2.131';
requires 'File::Copy' => '2.14';
requires 'DBIx::Class' => '0.08194';
test_requires 'Test::More';
test_requires 'Test::Exception';
test_requires 'Text::Table';
catalyst;
install_script glob('script/*.pl');
auto_install;
WriteAll;
Everything runs on Ubuntu Natty amd64. Is there any workaround for this?
Solved! CPANPLUS had FTP mirrors written in its configuration, and I was too lazy to properly configure my ftp proxy. After replacing all ftp mirrors with http dependencies were successfully installed.
I had a similar error message : Can't use an undefined value as a symbol reference at /System/Library/Perl/5.12/Net/FTP/dataconn.pm line 54.
From reading Nikita answer, I made these changes to fix it:
sudo vim ~/.cpan/CPAN/MyConfig.pm
in the 'urllist' line: replace ftp by http

Why does Perl's Net::SFTP->new complain about "Not an ARRAY reference"?

I am trying to use Net::SFTP to get connected to remote server.
My script is:
my %args = (
ssh_args => {
user => 'canneu_scp',
identity_files => [ '/home/home1/cgrshah/responsys/capgemini.private' ],
debug => 1,
} );
my $targetserver='files.responsys.net';
my $sftp = Net::SFTP->new($targetserver, %args)
or die "could not open connection to $targetserver\n";
But when I run this, I get an error stating:
Not an ARRAY reference at /usr/lib/perl5/site_perl/5.8.1/Net/SFTP.pm line 36.
Can anyone help me with this?
This is just a wild shot in the dark, but the user option should not be in the hash handed to ssh_args, it is at the same level. Try using this code instead:
my $sftp = Net::SFTP->new(
$targetserver,
user => 'canneu_scp',
ssh_args => {
identity_files => [ '/home/home1/cgrshah/responsys/capgemini.private' ],
debug => 1,
}
) or die "could not open connection to $targetserver\n";
It sounds like the code above got you further along, and now you are having problems because your version of Math::BigInt is too old. I see three ways to move forward:
switch to an RSA key instead of a DSA key
find an RPM of Math::BigInt version 1.78 or later
manually install a copy of Math::BigInt
The third option has many pitfalls, and if you decide to go with it I would suggest the following steps:
install App::cpanminus
make sure you have a gcc installed
run wget -O- http://cpanmin.us | perl - --local-lib=~/perl5 App::cpanminus
add ~/perl5/bin to your path
install Math::BigInt into your home directory with cpanm --local-lib=~/perl5 Math::BigInt
add use lib "$ENV{HOME}/perl5"; to the start of your script so it can find the new modules

How do I find the standard site_perl directory for Perl?

How can I find the standard site_perl (non-arch specific) location? Is it safe to just loop over #INC and find the path ending with "site_perl", or is there a standard way to do this?
The reason for trying to find this, is I have a very large project built up from hundreds of individual modules, all with their own Makefile.PL files (pretty much every .pm file has been built as its own CPAN style module). Along with this, each module may have artifacts (templates, .cgi's, etc), in various locations, all which need to be deployed to various locations, nothing is standard. This is the first step in trying to get this under control, basically having a single Makefile which can find and deploy everything, the next step will be getting it in sensible layout in version control.
I've spent time trying to do this with standard installation tools, but have had no luck.
C:\Temp> perl -MConfig -e "print qq{$_ => $Config{$_}\n} for grep { /site/ } keys %Config"
d_sitearch => define
installsitearch => C:\opt\perl\site\lib
installsitebin => C:\opt\perl\site\bin
installsitehtml1dir =>
installsitehtml3dir =>
installsitelib => C:\opt\perl\site\lib
installsiteman1dir =>
installsiteman3dir =>
installsitescript => C:\opt\perl\site\bin
sitearch => C:\opt\perl\site\lib
sitearchexp => C:\opt\perl\site\lib
sitebin => C:\opt\perl\site\bin
sitebinexp => C:\opt\perl\site\bin
sitehtml1dir =>
sitehtml1direxp =>
sitehtml3dir =>
sitehtml3direxp =>
sitelib => C:\opt\perl\site\lib
sitelib_stem =>
sitelibexp => C:\opt\perl\site\lib
siteman1dir =>
siteman1direxp =>
siteman3dir =>
siteman3direxp =>
siteprefix => C:\opt\perl\site
siteprefixexp => C:\opt\perl\site
sitescript =>
sitescriptexp =>
usesitecustomize => define
Or, as #ysth points out in comments, you can use:
C:\Temp> perl -V:.*site.*
on Windows and
$ perl '-V:.*site.*'
in *nix shells.
Is there a reason not to use one of the module installers (ExtUtils::MakeMaker, Module::Build, Module::Install)?
But if you must, the directory is available (after loading Config) as $Config::Config{'installsitelib'}. Note that some platforms may configure perl such that this directory doesn't literally appear in #INC, instead having some other directory that's symlinked to the installsitelib directory.
Just run perl -V. It will print the default #INC at the end.
It is not safe to loop over #INC as it can be modified by code or the environment, and, therefore, may contain multiple directories that end in site_perl.
If you are trying to determine where a given module is installed use %INC.