From 212433f27ac422f79924d03b5d047236f6cdd308 Mon Sep 17 00:00:00 2001 From: "mkanat%bugzilla.org" <> Date: Wed, 24 Oct 2007 00:33:30 +0000 Subject: Bug 262269: A tool to auto-install missing perl packages on non-Windows systems Patch By Max Kanat-Alexander (module owner) --- Bugzilla/Install/CPAN.pm | 250 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 Bugzilla/Install/CPAN.pm (limited to 'Bugzilla/Install/CPAN.pm') diff --git a/Bugzilla/Install/CPAN.pm b/Bugzilla/Install/CPAN.pm new file mode 100644 index 000000000..01eceafeb --- /dev/null +++ b/Bugzilla/Install/CPAN.pm @@ -0,0 +1,250 @@ +# -*- 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); + +# 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 => [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 -- cgit v1.2.3-24-g4f1b