# -*- 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(set_cpan_config install_module BZ_LIB); use Bugzilla::Constants; use Bugzilla::Install::Util qw(bin_loc install_string); use CPAN; use Cwd qw(abs_path); use File::Path qw(rmtree); use List::Util qw(shuffle); # 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, cache_metadata => 1, index_expire => 1, scan_cache => 'atstart', inhibit_startup_message => 1, mbuild_install_build_command => './Build', 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://perl.secsup.org/ http://mirrors.kernel.org/cpan/)], }; sub install_module { my ($name, $notest) = @_; my $bzlib = BZ_LIB; # 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 'Net::LDAP') { $CPAN::Config->{makepl_arg} .= " --skipdeps"; } elsif ($name eq 'SOAP::Lite') { $CPAN::Config->{makepl_arg} .= " --noprompt"; } my $module = CPAN::Shell->expand('Module', $name); print install_string('install_module', { module => $name, version => $module->cpan_version }) . "\n"; if ($notest) { CPAN::Shell->notest('install', $name); } else { CPAN::Shell->force('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 "WARNING: Using the Bugzilla directory as the CPAN 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) { $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\""; # 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', 1); =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<$notest> - If true, we skip running tests on this module. This can greatly speed up the installation time. =back Note that calling this function prints a B of information to STDOUT and STDERR. =back