# -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Mozilla Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Bugzilla Bug Tracking System. # # The Initial Developer of the Original Code is Everything Solved, Inc. # Portions created by Everything Solved are Copyright (C) 2007 # Everything Solved, Inc. All Rights Reserved. # # Contributor(s): Max Kanat-Alexander package Bugzilla::Install::CPAN; use strict; use base 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); use List::Util qw(shuffle); # 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 => [shuffle qw( http://cpan.pair.com/ http://mirror.hiwaay.net/CPAN/ ftp://ftp.dc.aleron.net/pub/CPAN/ http://mirrors.kernel.org/cpan/ http://mirrors2.kernel.org/cpan/)], }; 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"; } my $version = $module->cpan_version; my $module_name = $name; if ($name eq 'LWP::UserAgent' && $^V lt v5.8.8) { # LWP 6.x requires Perl 5.8.8 or newer. # As PAUSE only indexes the very last version of each module, # we have to specify the path to the tarball ourselves. $name = 'GAAS/libwww-perl-5.837.tar.gz'; # This tarball contains LWP::UserAgent 5.835. $version = '5.835'; } print install_string('install_module', { module => $module_name, version => $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 to do the "hard work" of installing CPAN modules. =head1 SUBROUTINES =over =item C Sets up the configuration of CPAN for this session. Must be called before L. Takes one boolean parameter. If true, L will install modules globally instead of to the local F directory. On most systems, you have to be root to do that. =item C 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 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 of information to STDOUT and STDERR. =back