summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Install/CPAN.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Install/CPAN.pm')
-rw-r--r--Bugzilla/Install/CPAN.pm250
1 files changed, 250 insertions, 0 deletions
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 <mkanat@bugzilla.org>
+
+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<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<$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<lot> of information to
+STDOUT and STDERR.
+
+=back