blob: 094784e1a4b7771ea77f1fe223a388642a092382 [file] [log] [blame]
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::Install::CPAN;
use 5.10.1;
use strict;
use warnings;
use parent qw(Exporter);
our @EXPORT = qw(
BZ_LIB
check_cpan_requirements
set_cpan_config
install_module
);
use Bugzilla::Constants;
use Bugzilla::Install::Requirements qw(have_vers);
use Bugzilla::Install::Util qw(bin_loc install_string);
use Config;
use CPAN;
use Cwd qw(abs_path);
use File::Path qw(rmtree);
# These are required for install-module.pl to be able to install
# all modules properly.
use constant REQUIREMENTS => (
{
module => 'CPAN',
package => 'CPAN',
version => '1.81',
},
{
# When Module::Build isn't installed, the YAML module allows
# CPAN to read META.yml to determine that Module::Build first
# needs to be installed to compile a module.
module => 'YAML',
package => 'YAML',
version => 0,
},
{
# Many modules on CPAN are now built with Dist::Zilla, which
# unfortunately means they require this version of EU::MM to install.
module => 'ExtUtils::MakeMaker',
package => 'ExtUtils-MakeMaker',
version => '6.31',
},
);
# We need the absolute path of ext_libpath, because CPAN chdirs around
# and so we can't use a relative directory.
#
# We need it often enough (and at compile time, in install-module.pl) so
# we make it a constant.
use constant BZ_LIB => abs_path(bz_locations()->{ext_libpath});
# CPAN requires nearly all of its parameters to be set, or it will start
# asking questions to the user. We want to avoid that, so we have
# defaults here for most of the required parameters we know about, in case
# any of them aren't set. The rest are handled by set_cpan_defaults().
use constant CPAN_DEFAULTS => {
auto_commit => 0,
# We always force builds, so there's no reason to cache them.
build_cache => 0,
build_requires_install_policy => 'yes',
cache_metadata => 1,
colorize_output => 1,
colorize_print => 'bold',
index_expire => 1,
scan_cache => 'atstart',
inhibit_startup_message => 1,
bzip2 => bin_loc('bzip2'),
curl => bin_loc('curl'),
gzip => bin_loc('gzip'),
links => bin_loc('links'),
lynx => bin_loc('lynx'),
make => bin_loc('make'),
pager => bin_loc('less'),
tar => bin_loc('tar'),
unzip => bin_loc('unzip'),
wget => bin_loc('wget'),
urllist => ['http://www.cpan.org/'],
};
sub check_cpan_requirements {
my ($original_dir, $original_args) = @_;
_require_compiler();
my @install;
foreach my $module (REQUIREMENTS) {
my $installed = have_vers($module, 1);
push(@install, $module) if !$installed;
}
return if !@install;
my $restart_required;
foreach my $module (@install) {
$restart_required = 1 if $module->{module} eq 'CPAN';
install_module($module->{module}, 1);
}
if ($restart_required) {
chdir $original_dir;
exec($^X, $0, @$original_args);
}
}
sub _require_compiler {
my @errors;
my $cc_name = $Config{cc};
my $cc_exists = bin_loc($cc_name);
if (!$cc_exists) {
push(@errors, install_string('install_no_compiler'));
}
my $make_name = $CPAN::Config->{make};
my $make_exists = bin_loc($make_name);
if (!$make_exists) {
push(@errors, install_string('install_no_make'));
}
die @errors if @errors;
}
sub install_module {
my ($name, $test) = @_;
my $bzlib = BZ_LIB;
# Make Module::AutoInstall install all dependencies and never prompt.
local $ENV{PERL_AUTOINSTALL} = '--alldeps';
# This makes Net::SSLeay not prompt the user, if it gets installed.
# It also makes any other MakeMaker prompts accept their defaults.
local $ENV{PERL_MM_USE_DEFAULT} = 1;
# Certain modules require special stuff in order to not prompt us.
my $original_makepl = $CPAN::Config->{makepl_arg};
# This one's a regex in case we're doing Template::Plugin::GD and it
# pulls in Template-Toolkit as a dependency.
if ($name =~ /^Template/) {
$CPAN::Config->{makepl_arg} .= " TT_ACCEPT=y TT_EXTRAS=n";
}
elsif ($name eq 'XML::Twig') {
$CPAN::Config->{makepl_arg} = "-n $original_makepl";
}
elsif ($name eq 'SOAP::Lite') {
$CPAN::Config->{makepl_arg} .= " --noprompt";
}
my $module = CPAN::Shell->expand('Module', $name);
if (!$module) {
die install_string('no_such_module', { module => $name }) . "\n";
}
print install_string('install_module',
{ module => $name, version => $module->cpan_version }) . "\n";
if ($test) {
CPAN::Shell->force('install', $name);
}
else {
CPAN::Shell->notest('install', $name);
}
# If it installed any binaries in the Bugzilla directory, delete them.
if (-d "$bzlib/bin") {
File::Path::rmtree("$bzlib/bin");
}
$CPAN::Config->{makepl_arg} = $original_makepl;
}
sub set_cpan_config {
my $do_global = shift;
my $bzlib = BZ_LIB;
# We set defaults before we do anything, otherwise CPAN will
# start asking us questions as soon as we load its configuration.
eval { require CPAN::Config; };
_set_cpan_defaults();
# Calling a senseless autoload that does nothing makes us
# automatically load any existing configuration.
# We want to avoid the "invalid command" message.
open(my $saveout, ">&", "STDOUT");
open(STDOUT, '>', '/dev/null');
eval { CPAN->ignore_this_error_message_from_bugzilla; };
undef $@;
close(STDOUT);
open(STDOUT, '>&', $saveout);
my $dir = $CPAN::Config->{cpan_home};
if (!defined $dir || !-w $dir) {
# If we can't use the standard CPAN build dir, we try to make one.
$dir = "$ENV{HOME}/.cpan";
mkdir $dir;
# If we can't make one, we finally try to use the Bugzilla directory.
if (!-w $dir) {
print STDERR install_string('cpan_bugzilla_home'), "\n";
$dir = "$bzlib/.cpan";
}
}
$CPAN::Config->{cpan_home} = $dir;
$CPAN::Config->{build_dir} = "$dir/build";
# We always force builds, so there's no reason to cache them.
$CPAN::Config->{keep_source_where} = "$dir/source";
# This is set both here and in defaults so that it's always true.
$CPAN::Config->{inhibit_startup_message} = 1;
# Automatically install dependencies.
$CPAN::Config->{prerequisites_policy} = 'follow';
# Unless specified, we install the modules into the Bugzilla directory.
if (!$do_global) {
require Config;
$CPAN::Config->{makepl_arg} .= " LIB=\"$bzlib\""
. " INSTALLMAN1DIR=\"$bzlib/man/man1\""
. " INSTALLMAN3DIR=\"$bzlib/man/man3\""
# The bindirs are here because otherwise we'll try to write to
# the system binary dirs, and that will cause CPAN to die.
. " INSTALLBIN=\"$bzlib/bin\""
. " INSTALLSCRIPT=\"$bzlib/bin\""
# INSTALLDIRS=perl is set because that makes sure that MakeMaker
# always uses the directories we've specified here.
. " INSTALLDIRS=perl";
$CPAN::Config->{mbuild_arg} = " --install_base \"$bzlib\""
. " --install_path lib=\"$bzlib\""
. " --install_path arch=\"$bzlib/$Config::Config{archname}\"";
$CPAN::Config->{mbuild_install_arg} = $CPAN::Config->{mbuild_arg};
# When we're not root, sometimes newer versions of CPAN will
# try to read/modify things that belong to root, unless we set
# certain config variables.
$CPAN::Config->{histfile} = "$dir/histfile";
$CPAN::Config->{use_sqlite} = 0;
$CPAN::Config->{prefs_dir} = "$dir/prefs";
# Unless we actually set PERL5LIB, some modules can't install
# themselves, like DBD::mysql, DBD::Pg, and XML::Twig.
my $current_lib = $ENV{PERL5LIB} ? $ENV{PERL5LIB} . ':' : '';
$ENV{PERL5LIB} = $current_lib . $bzlib;
}
}
sub _set_cpan_defaults {
# If CPAN hasn't been configured, we try to use some reasonable defaults.
foreach my $key (keys %{CPAN_DEFAULTS()}) {
$CPAN::Config->{$key} = CPAN_DEFAULTS->{$key}
if !defined $CPAN::Config->{$key};
}
my @missing;
# In newer CPANs, this is in HandleConfig. In older CPANs, it's in
# Config.
if (eval { require CPAN::HandleConfig }) {
@missing = CPAN::HandleConfig->missing_config_data;
}
else {
@missing = CPAN::Config->missing_config_data;
}
foreach my $key (@missing) {
$CPAN::Config->{$key} = '';
}
}
1;
__END__
=head1 NAME
Bugzilla::Install::CPAN - Routines to install Perl modules from CPAN.
=head1 SYNOPSIS
use Bugzilla::Install::CPAN;
set_cpan_config();
install_module('Module::Name');
=head1 DESCRIPTION
This is primarily used by L<install-module> to do the "hard work" of
installing CPAN modules.
=head1 SUBROUTINES
=over
=item C<set_cpan_config>
Sets up the configuration of CPAN for this session. Must be called
before L</install_module>. Takes one boolean parameter. If true,
L</install_module> will install modules globally instead of to the
local F<lib/> directory. On most systems, you have to be root to do that.
=item C<install_module>
Installs a module from CPAN. Takes two arguments:
=over
=item C<$name> - The name of the module, just like you'd pass to the
C<install> command in the CPAN shell.
=item C<$test> - If true, we run tests on this module before installing,
but we still force the install if the tests fail. This is only used
when we internally install a newer CPAN module.
=back
Note that calling this function prints a B<lot> of information to
STDOUT and STDERR.
=back
=head1 B<Methods in need of POD>
=over
=item check_cpan_requirements
=back