summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Install/Requirements.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Install/Requirements.pm')
-rw-r--r--Bugzilla/Install/Requirements.pm990
1 files changed, 767 insertions, 223 deletions
diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm
index 43c441d6b..27549ca41 100644
--- a/Bugzilla/Install/Requirements.pm
+++ b/Bugzilla/Install/Requirements.pm
@@ -1,9 +1,19 @@
-# 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/.
+# -*- Mode: perl; indent-tabs-mode: nil -*-
#
-# This Source Code Form is "Incompatible With Secondary Licenses", as
-# defined by the Mozilla Public License, v. 2.0.
+# 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.
+#
+# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+# Marc Schumann <wurblzap@gmail.com>
package Bugzilla::Install::Requirements;
@@ -13,37 +23,38 @@ package Bugzilla::Install::Requirements;
# Subroutines may "require" and "import" from modules, but they
# MUST NOT "use."
-use 5.10.1;
use strict;
-use warnings;
+use version;
use Bugzilla::Constants;
-use Bugzilla::Install::Util qw(install_string bin_loc success
- extension_requirement_packages);
+use Bugzilla::Install::Util qw(vers_cmp install_string bin_loc
+ success extension_requirement_packages);
use List::Util qw(max);
+use Safe;
use Term::ANSIColor;
-use CPAN::Meta;
-use CPAN::Meta::Prereqs;
-use CPAN::Meta::Requirements;
-use Module::Metadata;
-use parent qw(Exporter);
-use autodie;
+# Return::Value 1.666002 pollutes the error log with warnings about this
+# deprecated module. We have to set NO_CLUCK = 1 before loading Email::Send
+# in have_vers() to disable these warnings.
+BEGIN {
+ $Return::Value::NO_CLUCK = 1;
+}
+use base qw(Exporter);
+use autodie;
our @EXPORT = qw(
+ REQUIRED_MODULES
+ OPTIONAL_MODULES
FEATURE_FILES
- load_cpan_meta
- check_cpan_requirements
- check_cpan_feature
- check_all_cpan_features
- check_webdotbase
- check_font_file
+ check_requirements
+ check_graphviz
+ export_cpanfile
+ have_vers
+ install_command
map_files_to_features
);
-our $checking_for_indent = 0;
-
# This is how many *'s are in the top of each "box" message printed
# by checksetup.pl.
use constant TABLE_WIDTH => 71;
@@ -53,12 +64,10 @@ use constant TABLE_WIDTH => 71;
#
# The keys are the names of the modules, the values are what the module
# is called in the output of "apachectl -t -D DUMP_MODULES".
-use constant APACHE_MODULES => {
+use constant APACHE_MODULES => {
mod_headers => 'headers_module',
mod_env => 'env_module',
mod_expires => 'expires_module',
- mod_rewrite => 'rewrite_module',
- mod_version => 'version_module'
};
# These are all of the binaries that we could possibly use that can
@@ -72,131 +81,471 @@ use constant APACHE => qw(apachectl httpd apache2 apache);
# If we don't find any of the above binaries in the normal PATH,
# these are extra places we look.
use constant APACHE_PATH => [qw(
- /usr/sbin
+ /usr/sbin
/usr/local/sbin
/usr/libexec
/usr/local/libexec
)];
+# The below two constants are subroutines so that they can implement
+# a hook. Other than that they are actually constants.
+
+# "package" is the perl package we're checking for. "module" is the name
+# of the actual module we load with "require" to see if the package is
+# installed or not. "version" is the version we need, or 0 if we'll accept
+# any version.
+#
+# "blacklist" is an arrayref of regular expressions that describe versions that
+# are 'blacklisted'--that is, even if the version is high enough, Bugzilla
+# will refuse to say that it's OK to run with that version.
+sub REQUIRED_MODULES {
+ my $perl_ver = sprintf('%vd', $^V);
+ my @modules = (
+ {
+ package => 'CGI.pm',
+ module => 'CGI',
+ # 3.51 fixes a security problem that affects Bugzilla.
+ # (bug 591165)
+ version => '3.51',
+ },
+ {
+ package => 'Digest-SHA',
+ module => 'Digest::SHA',
+ version => 0
+ },
+ {
+ package => 'TimeDate',
+ module => 'Date::Format',
+ version => '2.21'
+ },
+ # 0.28 fixed some important bugs in DateTime.
+ {
+ package => 'DateTime',
+ module => 'DateTime',
+ version => '0.28'
+ },
+ # 0.79 is required to work on Windows Vista and Windows Server 2008.
+ # As correctly detecting the flavor of Windows is not easy,
+ # we require this version for all Windows installations.
+ # 0.71 fixes a major bug affecting all platforms.
+ {
+ package => 'DateTime-TimeZone',
+ module => 'DateTime::TimeZone',
+ version => ON_WINDOWS ? '0.79' : '0.71'
+ },
+ {
+ package => 'DBI',
+ module => 'DBI',
+ version => (vers_cmp($perl_ver, '5.13.3') > -1) ? '1.614' : '1.41'
+ },
+ # 2.22 fixes various problems related to UTF8 strings in hash keys,
+ # as well as line endings on Windows.
+ {
+ package => 'Template-Toolkit',
+ module => 'Template',
+ version => '2.22'
+ },
+ {
+ package => 'Email-Send',
+ module => 'Email::Send',
+ version => ON_WINDOWS ? '2.16' : '2.00',
+ blacklist => ['^2\.196$']
+ },
+ {
+ package => 'Email-MIME',
+ module => 'Email::MIME',
+ # This fixes a memory leak in walk_parts that affected jobqueue.pl.
+ version => '1.904'
+ },
+ {
+ package => 'URI',
+ module => 'URI',
+ # This version properly handles a semicolon as the delimiter
+ # in a URL query string.
+ version => '1.37',
+ },
+ {
+ package => 'List-MoreUtils',
+ module => 'List::MoreUtils',
+ version => 0.22,
+ },
+ {
+ package => 'Math-Random-ISAAC',
+ module => 'Math::Random::ISAAC',
+ version => '1.0.1',
+ },
+ );
+
+ if (ON_WINDOWS) {
+ push(@modules, {
+ package => 'Win32',
+ module => 'Win32',
+ # 0.35 fixes a memory leak in GetOSVersion, which we use.
+ version => 0.35,
+ },
+ {
+ package => 'Win32-API',
+ module => 'Win32::API',
+ # 0.55 fixes a bug with char* that might affect Bugzilla::RNG.
+ version => '0.55',
+ });
+ }
+
+ my $extra_modules = _get_extension_requirements('REQUIRED_MODULES');
+ push(@modules, @$extra_modules);
+ return \@modules;
+};
+
+sub OPTIONAL_MODULES {
+ my $perl_ver = sprintf('%vd', $^V);
+ my @modules = (
+ {
+ package => 'GD',
+ module => 'GD',
+ version => '1.20',
+ feature => [qw(graphical_reports new_charts old_charts)],
+ },
+ {
+ package => 'Chart',
+ module => 'Chart::Lines',
+ # Versions below 2.1 cannot be detected accurately.
+ # There is no 2.1.0 release (it was 2.1), but .0 is required to fix
+ # https://rt.cpan.org/Public/Bug/Display.html?id=28218.
+ version => '2.1.0',
+ feature => [qw(new_charts old_charts)],
+ },
+ {
+ package => 'Template-GD',
+ # This module tells us whether or not Template-GD is installed
+ # on Template-Toolkits after 2.14, and still works with 2.14 and lower.
+ module => 'Template::Plugin::GD::Image',
+ version => 0,
+ feature => ['graphical_reports'],
+ },
+ {
+ package => 'GDTextUtil',
+ module => 'GD::Text',
+ version => 0,
+ feature => ['graphical_reports'],
+ },
+ {
+ package => 'GDGraph',
+ module => 'GD::Graph',
+ version => 0,
+ feature => ['graphical_reports'],
+ },
+ {
+ package => 'MIME-tools',
+ # MIME::Parser is packaged as MIME::Tools on ActiveState Perl
+ module => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
+ version => '5.406',
+ feature => ['moving'],
+ },
+ {
+ package => 'libwww-perl',
+ module => 'LWP::UserAgent',
+ version => 0,
+ feature => ['updates'],
+ },
+ {
+ package => 'XML-Twig',
+ module => 'XML::Twig',
+ version => 0,
+ feature => ['moving', 'updates'],
+ },
+ {
+ package => 'PatchReader',
+ module => 'PatchReader',
+ # 0.9.6 fixes two notable bugs and significantly improves the UX.
+ version => '0.9.6',
+ feature => ['patch_viewer'],
+ },
+ {
+ package => 'perl-ldap',
+ module => 'Net::LDAP',
+ version => 0,
+ feature => ['auth_ldap'],
+ },
+ {
+ package => 'Authen-SASL',
+ module => 'Authen::SASL',
+ version => 0,
+ feature => ['smtp_auth'],
+ },
+ {
+ package => 'RadiusPerl',
+ module => 'Authen::Radius',
+ version => 0,
+ feature => ['auth_radius'],
+ },
+ {
+ package => 'SOAP-Lite',
+ module => 'SOAP::Lite',
+ # Fixes various bugs, including 542931 and 552353 + stops
+ # throwing warnings with Perl 5.12.
+ version => '0.712',
+ feature => ['xmlrpc'],
+ },
+ {
+ package => 'JSON-RPC',
+ module => 'JSON::RPC',
+ version => 0,
+ feature => ['jsonrpc', 'rest'],
+ },
+ {
+ package => 'JSON-XS',
+ module => 'JSON::XS',
+ # 2.0 is the first version that will work with JSON::RPC.
+ version => '2.0',
+ feature => ['jsonrpc_faster'],
+ },
+ {
+ package => 'Test-Taint',
+ module => 'Test::Taint',
+ version => 0,
+ feature => ['jsonrpc', 'xmlrpc', 'rest'],
+ },
+ {
+ # We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
+ package => 'HTML-Parser',
+ module => 'HTML::Parser',
+ version => (vers_cmp($perl_ver, '5.13.3') > -1) ? '3.67' : '3.40',
+ feature => ['html_desc'],
+ },
+ {
+ package => 'HTML-Scrubber',
+ module => 'HTML::Scrubber',
+ version => 0,
+ feature => ['html_desc'],
+ },
+ {
+ # we need version 2.21 of Encode for mime_name
+ package => 'Encode',
+ module => 'Encode',
+ version => 2.21,
+ feature => ['detect_charset'],
+ },
+ {
+ package => 'Encode-Detect',
+ module => 'Encode::Detect',
+ version => 0,
+ feature => ['detect_charset'],
+ },
+
+ # S3 attachments
+ {
+ package => 'Class-Accessor-Fast',
+ module => 'Class::Accessor::Fast',
+ version => 0,
+ feature => ['s3'],
+ },
+ {
+ package => 'XML-Simple',
+ module => 'XML::Simple',
+ version => 0,
+ feature => ['s3'],
+ },
+
+ # Inbound Email
+ {
+ package => 'Email-MIME-Attachment-Stripper',
+ module => 'Email::MIME::Attachment::Stripper',
+ version => 0,
+ feature => ['inbound_email'],
+ },
+ {
+ package => 'Email-Reply',
+ module => 'Email::Reply',
+ version => 0,
+ feature => ['inbound_email'],
+ },
+
+ # Mail Queueing
+ {
+ package => 'TheSchwartz',
+ module => 'TheSchwartz',
+ # 1.10 supports declining of jobs.
+ version => 1.10,
+ feature => ['jobqueue'],
+ },
+ {
+ package => 'Daemon-Generic',
+ module => 'Daemon::Generic',
+ version => 0,
+ feature => ['jobqueue'],
+ },
+
+ # mod_perl
+ {
+ package => 'mod_perl',
+ module => 'mod_perl2',
+ version => '1.999022',
+ feature => ['mod_perl'],
+ },
+ {
+ package => 'Apache-SizeLimit',
+ module => 'Apache2::SizeLimit',
+ # 0.96 properly determines process size on Linux.
+ version => '0.96',
+ feature => ['mod_perl'],
+ },
+
+ # memcached
+ {
+ package => 'URI-Escape',
+ module => 'URI::Escape',
+ version => 0,
+ feature => ['memcached', 's3'],
+ },
+ {
+ package => 'Cache-Memcached',
+ module => 'Cache::Memcached',
+ version => '0',
+ feature => ['memcached'],
+ },
+
+ # BMO - metrics
+ {
+ package => 'ElasticSearch',
+ module => 'ElasticSearch',
+ version => '0',
+ feature => ['elasticsearch'],
+ },
+
+ # multi factor auth - totp
+ {
+ package => 'Auth-GoogleAuth',
+ module => 'Auth::GoogleAuth',
+ version => '1.01',
+ feature => ['mfa'],
+ },
+ {
+ package => 'GD-Barcode-QRcode',
+ module => 'GD::Barcode::QRcode',
+ version => '0',
+ feature => ['mfa'],
+ },
+ # Documentation
+ {
+ package => 'File-Copy-Recursive',
+ module => 'File::Copy::Recursive',
+ version => 0,
+ feature => ['documentation'],
+ }
+ );
+
+ my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
+ push(@modules, @$extra_modules);
+ return \@modules;
+};
+
# This maps features to the files that require that feature in order
# to compile. It is used by t/001compile.t and mod_perl.pl.
use constant FEATURE_FILES => (
jsonrpc => ['Bugzilla/WebService/Server/JSONRPC.pm', 'jsonrpc.cgi'],
xmlrpc => ['Bugzilla/WebService/Server/XMLRPC.pm', 'xmlrpc.cgi',
'Bugzilla/WebService.pm', 'Bugzilla/WebService/*.pm'],
- rest => ['Bugzilla/API/Server.pm', 'rest.cgi', 'Bugzilla/API/*/*.pm',
- 'Bugzilla/API/*/Server.pm', 'Bugzilla/API/*/Resource/*.pm'],
- psgi => ['app.psgi'],
+ rest => ['Bugzilla/WebService/Server/REST.pm', 'rest.cgi'],
moving => ['importxml.pl'],
auth_ldap => ['Bugzilla/Auth/Verify/LDAP.pm'],
auth_radius => ['Bugzilla/Auth/Verify/RADIUS.pm'],
- documentation => ['docs/makedocs.pl'],
inbound_email => ['email_in.pl'],
jobqueue => ['Bugzilla/Job/*', 'Bugzilla/JobQueue.pm',
'Bugzilla/JobQueue/*', 'jobqueue.pl'],
patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'],
updates => ['Bugzilla/Update.pm'],
- mfa => ['Bugzilla/MFA/*.pm'],
- markdown => ['Bugzilla/Markdown.pm'],
memcached => ['Bugzilla/Memcache.pm'],
- auth_delegation => ['auth.cgi'],
+ mfa => ['Bugzilla/MFA/*.pm'],
);
-sub load_cpan_meta {
- my $dir = bz_locations()->{libpath};
- my @meta_json = map { File::Spec->catfile($dir, $_) } qw( MYMETA.json META.json );
- my ($file) = grep { -f $_ } @meta_json;
-
- if ($file) {
- open my $meta_fh, '<', $file or die "unable to open $file: $!";
- my $str = do { local $/ = undef; scalar <$meta_fh> };
- # detaint
- $str =~ /^(.+)$/s; $str = $1;
- close $meta_fh;
-
- return CPAN::Meta->load_json_string($str);
- }
- else {
- ThrowCodeError('cpan_meta_missing');
+# This implements the REQUIRED_MODULES and OPTIONAL_MODULES stuff
+# described in in Bugzilla::Extension.
+sub _get_extension_requirements {
+ my ($function) = @_;
+
+ my $packages = extension_requirement_packages();
+ my @modules;
+ foreach my $package (@$packages) {
+ if ($package->can($function)) {
+ my $extra_modules = $package->$function;
+ push(@modules, @$extra_modules);
+ }
}
-}
-
-sub check_all_cpan_features {
- my ($meta, $dirs, $output) = @_;
- my %report;
+ return \@modules;
+};
- local $checking_for_indent = 2;
+sub check_requirements {
+ my ($output) = @_;
- print "\nOptional features:\n" if $output;
- my @features = sort { $a->identifier cmp $b->identifier } $meta->features;
- foreach my $feature (@features) {
- next if $feature->identifier eq 'features';
- printf "Feature '%s': %s\n", $feature->identifier, $feature->description if $output;
- my $result = check_cpan_feature($feature, $dirs, $output);
- print "\n" if $output;
+ print "\n", install_string('checking_modules'), "\n" if $output;
+ my $root = ROOT_USER;
+ my $missing = _check_missing(REQUIRED_MODULES, $output);
- $report{$feature->identifier} = {
- description => $feature->description,
- result => $result,
- };
+ print "\n", install_string('checking_dbd'), "\n" if $output;
+ my $have_one_dbd = 0;
+ my $db_modules = DB_MODULE;
+ foreach my $db (keys %$db_modules) {
+ my $dbd = $db_modules->{$db}->{dbd};
+ $have_one_dbd = 1 if have_vers($dbd, $output);
}
- return \%report;
-}
-
-sub check_cpan_feature {
- my ($feature, $dirs, $output) = @_;
+ print "\n", install_string('checking_optional'), "\n" if $output;
+ my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
- return _check_prereqs($feature->prereqs, $dirs, $output);
-}
+ my $missing_apache = _missing_apache_modules(APACHE_MODULES, $output);
-sub check_cpan_requirements {
- my ($meta, $dirs, $output) = @_;
+ # If we're running on Windows, reset the input line terminator so that
+ # console input works properly - loading CGI tends to mess it up
+ $/ = "\015\012" if ON_WINDOWS;
- my $result = _check_prereqs($meta->effective_prereqs, $dirs, $output);
- print colored(install_string('installation_failed'), COLOR_ERROR), "\n" if !$result->{ok} && $output;
- return $result;
+ my $pass = !scalar(@$missing) && $have_one_dbd;
+ return {
+ pass => $pass,
+ one_dbd => $have_one_dbd,
+ missing => $missing,
+ optional => $missing_optional,
+ apache => $missing_apache,
+ any_missing => !$pass || scalar(@$missing_optional),
+ };
}
-sub _check_prereqs {
- my ($prereqs, $dirs, $output) = @_;
- $dirs //= \@INC;
- my $reqs = $prereqs->merged_requirements(['configure', 'runtime'], ['requires']);
- my @found;
- my @missing;
+# A helper for check_requirements
+sub _check_missing {
+ my ($modules, $output) = @_;
- foreach my $module (sort $reqs->required_modules) {
- my $ok = _check_module($reqs, $module, $dirs, $output);
- if ($ok) {
- push @found, $module;
- }
- else {
- push @missing, $module;
+ my @missing;
+ foreach my $module (@$modules) {
+ unless (have_vers($module, $output)) {
+ push(@missing, $module);
}
}
- return { ok => (@missing == 0), found => \@found, missing => \@missing };
+ return \@missing;
}
-sub _check_module {
- my ($reqs, $module, $dirs, $output) = @_;
- my $required_version = $reqs->requirements_for_module($module);
-
- if ($module eq 'perl') {
- my $ok = $reqs->accepts_module($module, $]);
- _checking_for({package => "perl", found => $], wanted => $required_version, ok => $ok}) if $output;
- return $ok;
- } else {
- my $metadata = Module::Metadata->new_from_module($module, inc => $dirs);
- my $version = eval { $metadata->version };
- my $ok = $metadata && $version && $reqs->accepts_module($module, $version || 0);
- _checking_for({package => $module, $version ? ( found => $version ) : (), wanted => $required_version, ok => $ok}) if $output;
-
- return $ok;
+sub _missing_apache_modules {
+ my ($modules, $output) = @_;
+ my $apachectl = _get_apachectl();
+ return [] if !$apachectl;
+ my $command = "$apachectl -t -D DUMP_MODULES";
+ my $cmd_info = `$command 2>&1`;
+ # If apachectl returned a value greater than 0, then there was an
+ # error parsing Apache's configuration, and we can't check modules.
+ my $retval = $?;
+ if ($retval > 0) {
+ print STDERR install_string('apachectl_failed',
+ { command => $command, root => ROOT_USER }), "\n";
+ return [];
+ }
+ my @missing;
+ foreach my $module (keys %$modules) {
+ my $ok = _check_apache_module($module, $modules->{$module},
+ $cmd_info, $output);
+ push(@missing, $module) if !$ok;
}
+ return \@missing;
}
-
sub _get_apachectl {
foreach my $bin_name (APACHE) {
my $bin = bin_loc($bin_name);
@@ -210,10 +559,125 @@ sub _get_apachectl {
return undef;
}
-sub check_webdotbase {
+sub _check_apache_module {
+ my ($module, $config_name, $mod_info, $output) = @_;
+ my $ok;
+ if ($mod_info =~ /^\s+\Q$config_name\E\b/m) {
+ $ok = 1;
+ }
+ if ($output) {
+ _checking_for({ package => $module, ok => $ok });
+ }
+ return $ok;
+}
+
+sub print_module_instructions {
+ my ($check_results, $output) = @_;
+
+ # First we print the long explanatory messages.
+
+ if (scalar @{$check_results->{missing}}) {
+ print install_string('modules_message_required');
+ }
+
+ if (!$check_results->{one_dbd}) {
+ print install_string('modules_message_db');
+ }
+
+ if (my @missing = @{$check_results->{optional}} and $output) {
+ print install_string('modules_message_optional');
+ # Now we have to determine how large the table cols will be.
+ my $longest_name = max(map(length($_->{package}), @missing));
+
+ # The first column header is at least 11 characters long.
+ $longest_name = 11 if $longest_name < 11;
+
+ # The table is TABLE_WIDTH characters long. There are seven mandatory
+ # characters (* and space) in the string. So, we have a total
+ # of TABLE_WIDTH - 7 characters to work with.
+ my $remaining_space = (TABLE_WIDTH - 7) - $longest_name;
+ print '*' x TABLE_WIDTH . "\n";
+ printf "* \%${longest_name}s * %-${remaining_space}s *\n",
+ 'MODULE NAME', 'ENABLES FEATURE(S)';
+ print '*' x TABLE_WIDTH . "\n";
+ foreach my $package (@missing) {
+ printf "* \%${longest_name}s * %-${remaining_space}s *\n",
+ $package->{package},
+ _translate_feature($package->{feature});
+ }
+ }
+
+ if (my @missing = @{ $check_results->{apache} }) {
+ print install_string('modules_message_apache');
+ my $missing_string = join(', ', @missing);
+ my $size = TABLE_WIDTH - 7;
+ printf "* \%-${size}s *\n", $missing_string;
+ my $spaces = TABLE_WIDTH - 2;
+ print "*", (' ' x $spaces), "*\n";
+ }
+
+ my $need_module_instructions =
+ ( (!$output and @{$check_results->{missing}})
+ or ($output and $check_results->{any_missing}) ) ? 1 : 0;
+
+ if ($need_module_instructions or @{ $check_results->{apache} }) {
+ # If any output was required, we want to close the "table"
+ print "*" x TABLE_WIDTH . "\n";
+ }
+
+ # And now we print the actual installation commands.
+
+ if (my @missing = @{$check_results->{optional}} and $output) {
+ print install_string('commands_optional') . "\n\n";
+ foreach my $module (@missing) {
+ my $command = install_command($module);
+ printf "%15s: $command\n", $module->{package};
+ }
+ print "\n";
+ }
+
+ if (!$check_results->{one_dbd}) {
+ print install_string('commands_dbd') . "\n";
+ my %db_modules = %{DB_MODULE()};
+ foreach my $db (keys %db_modules) {
+ my $command = install_command($db_modules{$db}->{dbd});
+ printf "%10s: \%s\n", $db_modules{$db}->{name}, $command;
+ }
+ print "\n";
+ }
+
+ if (my @missing = @{$check_results->{missing}}) {
+ print colored(install_string('commands_required'), COLOR_ERROR), "\n";
+ foreach my $package (@missing) {
+ my $command = install_command($package);
+ print " $command\n";
+ }
+ }
+
+ if ($output && $check_results->{any_missing} && !ON_ACTIVESTATE
+ && !$check_results->{hide_all})
+ {
+ print install_string('install_all', { perl => $^X });
+ }
+ if (!$check_results->{pass}) {
+ print colored(install_string('installation_failed'), COLOR_ERROR),
+ "\n\n";
+ }
+}
+
+sub _translate_feature {
+ my $features = shift;
+ my @strings;
+ foreach my $feature (@$features) {
+ push(@strings, install_string("feature_$feature"));
+ }
+ return join(', ', @strings);
+}
+
+sub check_graphviz {
my ($output) = @_;
- my $webdotbase = Bugzilla->localconfig->{'webdotbase'};
+ my $webdotbase = Bugzilla->params->{'webdotbase'};
return 1 if $webdotbase =~ /^https?:/;
my $return;
@@ -230,9 +694,9 @@ sub check_webdotbase {
my $webdotdir = bz_locations()->{'webdotdir'};
# Check .htaccess allows access to generated images
if (-e "$webdotdir/.htaccess") {
- my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
+ my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
|| die "$webdotdir/.htaccess: " . $!;
- if (!grep(/ \\\.png\$/, $htaccess->getlines)) {
+ if (!grep(/png/, $htaccess->getlines)) {
print STDERR install_string('webdot_bad_htaccess',
{ dir => $webdotdir }), "\n";
}
@@ -242,40 +706,68 @@ sub check_webdotbase {
return $return;
}
-sub check_font_file {
- my ($output) = @_;
-
- my $font_file = Bugzilla->localconfig->{'font_file'};
-
- my $readable;
- $readable = 1 if -r $font_file;
-
- my $ttf;
- $ttf = 1 if $font_file =~ /\.(ttf|otf)$/;
-
- if ($output) {
- _checking_for({ package => 'Font file', ok => $readable && $ttf});
+# This was originally clipped from the libnet Makefile.PL, adapted here for
+# accurate version checking.
+sub have_vers {
+ my ($params, $output) = @_;
+ my $module = $params->{module};
+ my $package = $params->{package};
+ if (!$package) {
+ $package = $module;
+ $package =~ s/::/-/g;
}
-
- if (!$readable) {
- print install_string('bad_font_file', { file => $font_file }), "\n";
+ my $wanted = $params->{version};
+
+ eval "require $module;";
+ # Don't let loading a module change the output-encoding of STDOUT
+ # or STDERR. (CGI.pm tries to set "binmode" on these file handles when
+ # it's loaded, and other modules may do the same in the future.)
+ Bugzilla::Install::Util::set_output_encoding();
+
+ # VERSION is provided by UNIVERSAL::, and can be called even if
+ # the module isn't loaded. We eval'uate ->VERSION because it can die
+ # when the version is not valid (yes, this happens from time to time).
+ # In that case, we use an uglier method to get the version.
+ my $vnum = eval { $module->VERSION };
+ if ($@) {
+ no strict 'refs';
+ $vnum = ${"${module}::VERSION"};
+
+ # If we come here, then the version is not a valid one.
+ # We try to sanitize it.
+ if ($vnum =~ /^((\d+)(\.\d+)*)/) {
+ $vnum = $1;
+ }
}
- elsif (!$ttf) {
- print install_string('bad_font_file_name', { file => $font_file }), "\n";
+ $vnum ||= -1;
+
+ # Must do a string comparison as $vnum may be of the form 5.10.1.
+ my $vok = ($vnum ne '-1' && version->new($vnum) >= version->new($wanted)) ? 1 : 0;
+ my $blacklisted;
+ if ($vok && $params->{blacklist}) {
+ $blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
+ $vok = 0 if $blacklisted;
}
- return $readable && $ttf;
+ if ($output) {
+ _checking_for({
+ package => $package, ok => $vok, wanted => $wanted,
+ found => $vnum, blacklisted => $blacklisted
+ });
+ }
+
+ return $vok ? 1 : 0;
}
sub _checking_for {
my ($params) = @_;
- my ($package, $ok, $wanted, $blacklisted, $found) =
+ my ($package, $ok, $wanted, $blacklisted, $found) =
@$params{qw(package ok wanted blacklisted found)};
my $ok_string = $ok ? install_string('module_ok') : '';
# If we're actually checking versions (like for Perl modules), then
- # we have some rather complex logic to determine what we want to
+ # we have some rather complex logic to determine what we want to
# show. If we're not checking versions (like for GraphViz) we just
# show "ok" or "not found".
if (exists $params->{found}) {
@@ -298,14 +790,30 @@ sub _checking_for {
}
my $black_string = $blacklisted ? install_string('blacklisted') : '';
- my $want_string = $wanted ? "$wanted" : install_string('any');
+ my $want_string = $wanted ? "v$wanted" : install_string('any');
my $str = sprintf "%s %20s %-11s $ok_string $black_string\n",
- ( ' ' x $checking_for_indent ) . install_string('checking_for'),
- $package, "($want_string)";
+ install_string('checking_for'), $package, "($want_string)";
print $ok ? $str : colored($str, COLOR_ERROR);
}
+sub install_command {
+ my $module = shift;
+ my ($command, $package);
+
+ if (ON_ACTIVESTATE) {
+ $command = 'ppm install %s';
+ $package = $module->{package};
+ }
+ else {
+ $command = 'cpanm %s';
+ # Non-Windows installations need to use module names, because
+ # CPAN doesn't understand package names.
+ $package = $module->{module};
+ }
+ return sprintf $command, $package;
+}
+
# This does a reverse mapping for FEATURE_FILES.
sub map_files_to_features {
my %features = FEATURE_FILES;
@@ -321,6 +829,68 @@ sub map_files_to_features {
return \%files;
}
+sub export_cpanfile {
+ my $cpanfile;
+ # Required modules
+ foreach my $module (@{ REQUIRED_MODULES() }) {
+ my $requires = "requires '" . $module->{module} . "'";
+ $requires .= ", '" . $module->{version} . "'" if $module->{version};
+ $requires .= ";\n";
+ $cpanfile .= $requires;
+ }
+ # Recommended modules
+ $cpanfile .= "\n# Optional\n";
+ my %features;
+ foreach my $module (@{ OPTIONAL_MODULES() }) {
+ next if $module->{package} eq 'mod_perl'; # Skip mod_perl since this would be installed by distro
+ if (exists $module->{feature}) {
+ foreach my $feature (@{ $module->{feature} }) {
+ # cpanm requires that each feature only be defined in the cpanfile
+ # once, so we use an intermediate hash to consolidate/de-dupe the
+ # modules associated with each feature.
+ $features{$feature}{$module->{module}} = $module->{version};
+ }
+ }
+ else {
+ my $recommends = "";
+ $recommends .= "recommends '" . $module->{module} . "'";
+ $recommends .= ", '" . $module->{version} . "'" if $module->{version};
+ $recommends .= ";\n";
+ $cpanfile .= $recommends;
+ }
+ }
+ foreach my $feature (sort keys %features) {
+ my $recommends = "";
+ $recommends .= "feature '" . $feature . "' => sub {\n";
+ foreach my $module (sort keys %{ $features{$feature} }) {
+ my $version = $features{$feature}{$module};
+ $recommends .= " recommends '" . $module . "'";
+ $recommends .= ", '$version'" if $version;
+ $recommends .= ";\n";
+ }
+ $recommends .= "};\n";
+ $cpanfile .= $recommends;
+ }
+ # Database modules
+ $cpanfile .= "\n# Database support\n";
+ foreach my $db (keys %{ DB_MODULE() }) {
+ next if !exists DB_MODULE->{$db}->{dbd};
+ my $dbd = DB_MODULE->{$db}->{dbd};
+ my $recommends .= "feature '$db' => sub {\n";
+ $recommends .= " recommends '" . $dbd->{module} . "'";
+ $recommends .= ", '" . $dbd->{version} . "'" if $dbd->{version};
+ $recommends .= ";\n};\n";
+ $cpanfile .= $recommends;
+ }
+
+ # Write out the cpanfile to the document root
+ my $file = bz_locations()->{'libpath'} . '/cpanfile';
+ open(my $fh, '>', $file);
+ print $fh $cpanfile;
+ close $fh;
+ success(install_string('cpanfile_created', { file => $file }));
+}
+
1;
__END__
@@ -340,73 +910,59 @@ perl modules it requires.)
=over
-=item C<FEATURE_FILES>
+=item C<REQUIRED_MODULES>
-A hashref that describes what files should only be compiled if a certain
-feature is enabled. The feature is the key, and the values are arrayrefs
-of file names (which are passed to C<glob>, so shell patterns work).
-
-=back
-
-=head1 SUBROUTINES
-
-=over 4
-
-=item C<check_cpan_requirements>
+An arrayref of hashrefs that describes the perl modules required by
+Bugzilla. The hashes have three keys:
=over
-=item B<Description>
+=item C<package> - The name of the Perl package that you'd find on
+CPAN for this requirement.
-This checks what required perl modules are installed, like
-C<checksetup.pl> does.
+=item C<module> - The name of a module that can be passed to the
+C<install> command in C<CPAN.pm> to install this module.
-=item B<Params>
-
-=over
-
-=item C<$meta> - A C<CPAN::Meta> object.
-
-=item C<$dirs> - the include dirs to search for modules, defaults to @INC.
-
-=item C<$output> - C<true> if you want the function to print out information
-about what it's doing, and the versions of everything installed.
+=item C<version> - The version of this module that we require, or C<0>
+if any version is acceptable.
=back
-=item B<Returns>
+=item C<OPTIONAL_MODULES>
-A hashref containing these values:
+An arrayref of hashrefs that describes the perl modules that add
+additional features to Bugzilla if installed. Its hashes have all
+the fields of L</REQUIRED_MODULES>, plus a C<feature> item--an arrayref
+of strings that describe what features require this module.
-=over
+=item C<FEATURE_FILES>
-=item C<ok> - if all the requirements are met, this is true.
+A hashref that describes what files should only be compiled if a certain
+feature is enabled. The feature is the key, and the values are arrayrefs
+of file names (which are passed to C<glob>, so shell patterns work).
-=item C<found> - an arrayref of found modules
+=back
-=item C<missing> - an arrayref of missing modules
-=back
+=head1 SUBROUTINES
-=back
+=over 4
-=item C<check_cpan_feature>
+=item C<check_requirements>
=over
=item B<Description>
-This checks that the optional Perl modules required for a feature are installed.
+This checks what optional or required perl modules are installed, like
+C<checksetup.pl> does.
=item B<Params>
=over
-=item C<$feature> - A C<CPAN::Meta::Feature> object.
-
-=item C<$dirs> - the include dirs to search for modules, defaults to @INC.
-
-=item C<$output> - C<true> if you want the function to print out information about what it's doing, and the versions of everything installed.
+=item C<$output> - C<true> if you want the function to print out information
+about what it's doing, and the versions of everything installed.
=back
@@ -416,50 +972,28 @@ A hashref containing these values:
=over
-=item C<ok> - if all the requirements are met, this is true.
-
-=item C<found> - an arrayref of found modules
-
-=item C<missing> - an arrayref of missing modules
+=item C<pass> - Whether or not we have all the mandatory requirements.
-=back
+=item C<missing> - An arrayref containing any required modules that
+are not installed or that are not up-to-date. Each item in the array is
+a hashref in the format of items from L</REQUIRED_MODULES>.
-=item C<check_all_cpan_features>
+=item C<optional> - The same as C<missing>, but for optional modules.
-=over
+=item C<apache> - The name of each optional Apache module that is missing.
-=item B<Description>
-
-This checks which optional Perl modules are currently installed which can enable optional features.
-
-=item B<Params>
-
-=over
+=item C<have_one_dbd> - True if at least one C<DBD::> module is installed.
-=item C<$meta> - A C<CPAN::Meta> object.
-
-=item C<$dirs> - the include dirs to search for modules, defaults to @INC.
-
-=item C<$output> - C<true> if you want the function to print out information
-about what it's doing, and the versions of everything installed.
+=item C<any_missing> - True if there are any missing Perl modules, even
+optional modules.
=back
-=item B<Returns>
-
-A hashref keyed on the feature name. The values
-are hashrefs containing C<description> and C<result> keys.
-
-C<description> is the English description of the feature.
-
-C<result> is a hashref in the same format as the return value of C<check_cpan_requirements()>,
-described previously.
-
=back
-=item C<check_webdotbase($output)>
+=item C<check_graphviz($output)>
-Description: Checks if the graphviz binary specified in the
+Description: Checks if the graphviz binary specified in the
C<webdotbase> parameter is a valid binary, or a valid URL.
Params: C<$output> - C<$true> if you want the function to
@@ -467,26 +1001,36 @@ Params: C<$output> - C<$true> if you want the function to
Returns: C<1> if the check was successful, C<0> otherwise.
-=item C<check_font_file($output)>
+=item C<have_vers($module, $output)>
-Description: Checks if the font file specified in the C<font_type> parameter
- is a valid-looking font file.
+ Description: Tells you whether or not you have the appropriate
+ version of the module requested. It also prints
+ out a message to the user explaining the check
+ and the result.
-Params: C<$output> - C<$true> if you want the function to
- print out information about what it's doing.
+ Params: C<$module> - A hashref, in the format of an item from
+ L</REQUIRED_MODULES>.
+ C<$output> - Set to true if you want this function to
+ print information to STDOUT about what it's
+ doing.
-Returns: C<1> if the check was successful, C<0> otherwise.
+ Returns: C<1> if you have the module installed and you have the
+ appropriate version. C<0> otherwise.
-=item C<map_files_to_features>
+=item C<install_command($module)>
-Returns a hashref where file names are the keys and the value is the feature
-that must be enabled in order to compile that file.
+ Description: Prints out the appropriate command to install the
+ module specified, depending on whether you're
+ on Windows or Linux.
-=item C<load_cpan_meta>
+ Params: C<$module> - A hashref, in the format of an item from
+ L</REQUIRED_MODULES>.
-Load MYMETA.json or META.json from the bugzilla directory, and a return a L<CPAN::Meta> object.
+ Returns: nothing
-=back
+=item C<map_files_to_features>
-=back
+Returns a hashref where file names are the keys and the value is the feature
+that must be enabled in order to compile that file.
+=back