1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4#
5# This Source Code Form is "Incompatible With Secondary Licenses", as
6# defined by the Mozilla Public License, v. 2.0.
7
8package Bugzilla::Install::Requirements;
9
10# NOTE: This package MUST NOT "use" any Bugzilla modules other than
11# Bugzilla::Constants, anywhere. We may "use" standard perl modules.
12#
13# Subroutines may "require" and "import" from modules, but they
14# MUST NOT "use."
15
16use strict;
17use version;
18
19use Bugzilla::Constants;
20use Bugzilla::Install::Util qw(vers_cmp install_string bin_loc
21                               extension_requirement_packages);
22use List::Util qw(max);
23use Term::ANSIColor;
24
25# Return::Value 1.666002 pollutes the error log with warnings about this
26# deprecated module. We have to set NO_CLUCK = 1 before loading Email::Send
27# in have_vers() to disable these warnings.
28BEGIN {
29    $Return::Value::NO_CLUCK = 1;
30}
31
32use base qw(Exporter);
33our @EXPORT = qw(
34    REQUIRED_MODULES
35    OPTIONAL_MODULES
36    FEATURE_FILES
37
38    check_requirements
39    check_graphviz
40    have_vers
41    install_command
42    map_files_to_features
43);
44
45# This is how many *'s are in the top of each "box" message printed
46# by checksetup.pl.
47use constant TABLE_WIDTH => 71;
48
49# Optional Apache modules that have no Perl component to them.
50# If these are installed, Bugzilla has additional functionality.
51#
52# The keys are the names of the modules, the values are what the module
53# is called in the output of "apachectl -t -D DUMP_MODULES".
54use constant APACHE_MODULES => {
55    mod_headers => 'headers_module',
56    mod_env     => 'env_module',
57    mod_expires => 'expires_module',
58};
59
60# These are all of the binaries that we could possibly use that can
61# give us info about which Apache modules are installed.
62# If we can't use "apachectl", the "httpd" binary itself takes the same
63# parameters. Note that on Debian and Gentoo, there is an "apache2ctl",
64# but it takes different parameters on each of those two distros, so we
65# don't use apache2ctl.
66use constant APACHE => qw(apachectl httpd apache2 apache);
67
68# If we don't find any of the above binaries in the normal PATH,
69# these are extra places we look.
70use constant APACHE_PATH => [qw(
71    /usr/sbin
72    /usr/local/sbin
73    /usr/libexec
74    /usr/local/libexec
75)];
76
77# The below two constants are subroutines so that they can implement
78# a hook. Other than that they are actually constants.
79
80# "package" is the perl package we're checking for. "module" is the name
81# of the actual module we load with "require" to see if the package is
82# installed or not. "version" is the version we need, or 0 if we'll accept
83# any version.
84#
85# "blacklist" is an arrayref of regular expressions that describe versions that
86# are 'blacklisted'--that is, even if the version is high enough, Bugzilla
87# will refuse to say that it's OK to run with that version.
88sub REQUIRED_MODULES {
89    my $perl_ver = sprintf('%vd', $^V);
90    my @modules = (
91    {
92        package => 'CGI.pm',
93        module  => 'CGI',
94        # 3.51 fixes a security problem that affects Bugzilla.
95        # (bug 591165)
96        version => '3.51',
97    },
98    {
99        package => 'Digest-SHA',
100        module  => 'Digest::SHA',
101        version => 0
102    },
103    # 0.23 fixes incorrect handling of 1/2 & 3/4 timezones.
104    {
105        package => 'TimeDate',
106        module  => 'Date::Format',
107        version => '2.23'
108    },
109    # 0.28 fixed some important bugs in DateTime.
110    {
111        package => 'DateTime',
112        module  => 'DateTime',
113        version => '0.28'
114    },
115    # 0.79 is required to work on Windows Vista and Windows Server 2008.
116    # As correctly detecting the flavor of Windows is not easy,
117    # we require this version for all Windows installations.
118    # 0.71 fixes a major bug affecting all platforms.
119    {
120        package => 'DateTime-TimeZone',
121        module  => 'DateTime::TimeZone',
122        version => ON_WINDOWS ? '0.79' : '0.71'
123    },
124    # 1.54 is required for Perl 5.10+. It also makes DBD::Oracle happy.
125    {
126        package => 'DBI',
127        module  => 'DBI',
128        version => (vers_cmp($perl_ver, '5.13.3') > -1) ? '1.614' : '1.54'
129    },
130    # 2.22 fixes various problems related to UTF8 strings in hash keys,
131    # as well as line endings on Windows.
132    {
133        package => 'Template-Toolkit',
134        module  => 'Template',
135        version => '2.22'
136    },
137    # 2.04 implement the "Test" method (to write to data/mailer.testfile).
138    {
139        package => 'Email-Send',
140        module  => 'Email::Send',
141        version => ON_WINDOWS ? '2.16' : '2.04',
142        blacklist => ['^2\.196$']
143    },
144    {
145        package => 'Email-MIME',
146        module  => 'Email::MIME',
147        # This fixes a memory leak in walk_parts that affected jobqueue.pl.
148        version => '1.904'
149    },
150    {
151        package => 'URI',
152        module  => 'URI',
153        # This version properly handles a semicolon as the delimiter
154        # in a URL query string.
155        version => '1.37',
156    },
157    # 0.32 fixes several memory leaks in the XS version of some functions.
158    {
159        package => 'List-MoreUtils',
160        module  => 'List::MoreUtils',
161        version => 0.32,
162    },
163    {
164        package => 'Math-Random-ISAAC',
165        module  => 'Math::Random::ISAAC',
166        version => '1.0.1',
167    },
168    );
169
170    if (ON_WINDOWS) {
171        push(@modules,
172        {
173            package => 'Win32',
174            module  => 'Win32',
175            # 0.35 fixes a memory leak in GetOSVersion, which we use.
176            version => 0.35,
177        },
178        {
179            package => 'Win32-API',
180            module  => 'Win32::API',
181            # 0.55 fixes a bug with char* that might affect Bugzilla::RNG.
182            version => '0.55',
183        },
184        {
185            package => 'DateTime-TimeZone-Local-Win32',
186            module  => 'DateTime::TimeZone::Local::Win32',
187            # We require DateTime::TimeZone 0.79, so this version must match.
188            version => '0.79',
189        }
190        );
191    }
192
193    my $extra_modules = _get_extension_requirements('REQUIRED_MODULES');
194    push(@modules, @$extra_modules);
195    return \@modules;
196};
197
198sub OPTIONAL_MODULES {
199    my $perl_ver = sprintf('%vd', $^V);
200    my @modules = (
201    {
202        package => 'GD',
203        module  => 'GD',
204        version => '1.20',
205        feature => [qw(graphical_reports new_charts old_charts)],
206    },
207    {
208        package => 'Chart',
209        module  => 'Chart::Lines',
210        # Versions below 2.1 cannot be detected accurately.
211        # There is no 2.1.0 release (it was 2.1), but .0 is required to fix
212        # https://rt.cpan.org/Public/Bug/Display.html?id=28218.
213        version => '2.1.0',
214        feature => [qw(new_charts old_charts)],
215    },
216    {
217        package => 'Template-GD',
218        # This module tells us whether or not Template-GD is installed
219        # on Template-Toolkits after 2.14, and still works with 2.14 and lower.
220        module  => 'Template::Plugin::GD::Image',
221        version => 0,
222        feature => ['graphical_reports'],
223    },
224    {
225        package => 'GDTextUtil',
226        module  => 'GD::Text',
227        version => 0,
228        feature => ['graphical_reports'],
229    },
230    {
231        package => 'GDGraph',
232        module  => 'GD::Graph',
233        version => 0,
234        feature => ['graphical_reports'],
235    },
236    {
237        package => 'MIME-tools',
238        # MIME::Parser is packaged as MIME::Tools on ActiveState Perl
239        module  => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
240        version => '5.406',
241        feature => ['moving'],
242    },
243    {
244        package => 'libwww-perl',
245        module  => 'LWP::UserAgent',
246        version => 0,
247        feature => ['updates'],
248    },
249    {
250        package => 'XML-Twig',
251        module  => 'XML::Twig',
252        version => 0,
253        feature => ['moving', 'updates'],
254    },
255    {
256        package => 'PatchReader',
257        module  => 'PatchReader',
258        # 0.9.6 fixes two notable bugs and significantly improves the UX.
259        version => '0.9.6',
260        feature => ['patch_viewer'],
261    },
262    {
263        package => 'perl-ldap',
264        module  => 'Net::LDAP',
265        version => 0,
266        feature => ['auth_ldap'],
267    },
268    {
269        package => 'Authen-SASL',
270        module  => 'Authen::SASL',
271        version => 0,
272        feature => ['smtp_auth'],
273    },
274    {
275        package => 'Net-SMTP-SSL',
276        module  => 'Net::SMTP::SSL',
277        version => 1.01,
278        feature => ['smtp_ssl'],
279    },
280    {
281        package => 'RadiusPerl',
282        module  => 'Authen::Radius',
283        version => 0,
284        feature => ['auth_radius'],
285    },
286    # XXX - Once we require XMLRPC::Lite 0.717 or higher, we can
287    # remove SOAP::Lite from the list.
288    {
289        package => 'SOAP-Lite',
290        module  => 'SOAP::Lite',
291        # Fixes various bugs, including 542931 and 552353 + stops
292        # throwing warnings with Perl 5.12.
293        version => '0.712',
294        feature => ['xmlrpc'],
295    },
296    # Since SOAP::Lite 1.0, XMLRPC::Lite is no longer included
297    # and so it must be checked separately.
298    {
299        package => 'XMLRPC-Lite',
300        module  => 'XMLRPC::Lite',
301        version => '0.712',
302        feature => ['xmlrpc'],
303    },
304    {
305        package => 'JSON-RPC',
306        module  => 'JSON::RPC',
307        version => 0,
308        feature => ['jsonrpc'],
309    },
310    {
311        package => 'JSON-XS',
312        module  => 'JSON::XS',
313        # 2.0 is the first version that will work with JSON::RPC.
314        version => '2.0',
315        feature => ['jsonrpc_faster'],
316    },
317    {
318        package => 'Test-Taint',
319        module  => 'Test::Taint',
320        version => 0,
321        feature => ['jsonrpc', 'xmlrpc'],
322    },
323    {
324        # We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
325        package => 'HTML-Parser',
326        module  => 'HTML::Parser',
327        version => (vers_cmp($perl_ver, '5.13.3') > -1) ? '3.67' : '3.40',
328        feature => ['html_desc'],
329    },
330    {
331        package => 'HTML-Scrubber',
332        module  => 'HTML::Scrubber',
333        version => 0,
334        feature => ['html_desc'],
335    },
336    {
337        # we need version 2.21 of Encode for mime_name
338        package => 'Encode',
339        module  => 'Encode',
340        version => 2.21,
341        feature => ['detect_charset'],
342    },
343    {
344        package => 'Encode-Detect',
345        module  => 'Encode::Detect',
346        version => 0,
347        feature => ['detect_charset'],
348    },
349
350    # Inbound Email
351    {
352        package => 'Email-Reply',
353        module  => 'Email::Reply',
354        version => 0,
355        feature => ['inbound_email'],
356    },
357    {
358        package => 'HTML-FormatText-WithLinks',
359        module  => 'HTML::FormatText::WithLinks',
360        # We need 0.13 to set the "bold" marker to "*".
361        version => '0.13',
362        feature => ['inbound_email'],
363    },
364
365    # Mail Queueing
366    {
367        package => 'TheSchwartz',
368        module  => 'TheSchwartz',
369        # 1.07 supports the prioritization of jobs.
370        version => 1.07,
371        feature => ['jobqueue'],
372    },
373    {
374        package => 'Daemon-Generic',
375        module  => 'Daemon::Generic',
376        version => 0,
377        feature => ['jobqueue'],
378    },
379    {
380        package => 'File-Slurp',
381        module  => 'File::Slurp',
382        version => '9999.13',
383        feature => ['jobqueue'],
384    },
385
386    # mod_perl
387    {
388        package => 'mod_perl',
389        module  => 'mod_perl2',
390        version => '1.999022',
391        feature => ['mod_perl'],
392    },
393    {
394        package => 'Apache-SizeLimit',
395        module  => 'Apache2::SizeLimit',
396        # 0.96 properly determines process size on Linux.
397        version => '0.96',
398        feature => ['mod_perl'],
399    },
400
401    # typesniffer
402    {
403        package => 'File-MimeInfo',
404        module  => 'File::MimeInfo::Magic',
405        version => '0',
406        feature => ['typesniffer'],
407    },
408    {
409        package => 'IO-stringy',
410        module  => 'IO::Scalar',
411        version => '0',
412        feature => ['typesniffer'],
413    },
414    );
415
416    my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
417    push(@modules, @$extra_modules);
418    return \@modules;
419};
420
421# This maps features to the files that require that feature in order
422# to compile. It is used by t/001compile.t and mod_perl.pl.
423use constant FEATURE_FILES => (
424    jsonrpc       => ['Bugzilla/WebService/Server/JSONRPC.pm', 'jsonrpc.cgi'],
425    xmlrpc        => ['Bugzilla/WebService/Server/XMLRPC.pm', 'xmlrpc.cgi',
426                      'Bugzilla/WebService.pm', 'Bugzilla/WebService/*.pm'],
427    moving        => ['importxml.pl'],
428    auth_ldap     => ['Bugzilla/Auth/Verify/LDAP.pm'],
429    auth_radius   => ['Bugzilla/Auth/Verify/RADIUS.pm'],
430    inbound_email => ['email_in.pl'],
431    jobqueue      => ['Bugzilla/Job/*', 'Bugzilla/JobQueue.pm',
432                      'Bugzilla/JobQueue/*', 'jobqueue.pl'],
433    patch_viewer  => ['Bugzilla/Attachment/PatchReader.pm'],
434    updates       => ['Bugzilla/Update.pm'],
435);
436
437# This implements the REQUIRED_MODULES and OPTIONAL_MODULES stuff
438# described in in Bugzilla::Extension.
439sub _get_extension_requirements {
440    my ($function) = @_;
441
442    my $packages = extension_requirement_packages();
443    my @modules;
444    foreach my $package (@$packages) {
445        if ($package->can($function)) {
446            my $extra_modules = $package->$function;
447            push(@modules, @$extra_modules);
448        }
449    }
450    return \@modules;
451};
452
453sub check_requirements {
454    my ($output) = @_;
455
456    print "\n", install_string('checking_modules'), "\n" if $output;
457    my $root = ROOT_USER;
458    my $missing = _check_missing(REQUIRED_MODULES, $output);
459
460    print "\n", install_string('checking_dbd'), "\n" if $output;
461    my $have_one_dbd = 0;
462    my $db_modules = DB_MODULE;
463    foreach my $db (keys %$db_modules) {
464        my $dbd = $db_modules->{$db}->{dbd};
465        $have_one_dbd = 1 if have_vers($dbd, $output);
466    }
467
468    print "\n", install_string('checking_optional'), "\n" if $output;
469    my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
470
471    my $missing_apache = _missing_apache_modules(APACHE_MODULES, $output);
472
473    # If we're running on Windows, reset the input line terminator so that
474    # console input works properly - loading CGI tends to mess it up
475    $/ = "\015\012" if ON_WINDOWS;
476
477    my $pass = !scalar(@$missing) && $have_one_dbd;
478    return {
479        pass     => $pass,
480        one_dbd  => $have_one_dbd,
481        missing  => $missing,
482        optional => $missing_optional,
483        apache   => $missing_apache,
484        any_missing => !$pass || scalar(@$missing_optional),
485    };
486}
487
488# A helper for check_requirements
489sub _check_missing {
490    my ($modules, $output) = @_;
491
492    my @missing;
493    foreach my $module (@$modules) {
494        unless (have_vers($module, $output)) {
495            push(@missing, $module);
496        }
497    }
498
499    return \@missing;
500}
501
502sub _missing_apache_modules {
503    my ($modules, $output) = @_;
504    my $apachectl = _get_apachectl();
505    return [] if !$apachectl;
506    my $command = "$apachectl -t -D DUMP_MODULES";
507    my $cmd_info = `$command 2>&1`;
508    # If apachectl returned a value greater than 0, then there was an
509    # error parsing Apache's configuration, and we can't check modules.
510    my $retval = $?;
511    if ($retval > 0) {
512        print STDERR install_string('apachectl_failed',
513            { command => $command, root => ROOT_USER }), "\n";
514        return [];
515    }
516    my @missing;
517    foreach my $module (keys %$modules) {
518        my $ok = _check_apache_module($module, $modules->{$module},
519                                      $cmd_info, $output);
520        push(@missing, $module) if !$ok;
521    }
522    return \@missing;
523}
524
525sub _get_apachectl {
526    foreach my $bin_name (APACHE) {
527        my $bin = bin_loc($bin_name);
528        return $bin if $bin;
529    }
530    # Try again with a possibly different path.
531    foreach my $bin_name (APACHE) {
532        my $bin = bin_loc($bin_name, APACHE_PATH);
533        return $bin if $bin;
534    }
535    return undef;
536}
537
538sub _check_apache_module {
539    my ($module, $config_name, $mod_info, $output) = @_;
540    my $ok;
541    if ($mod_info =~ /^\s+\Q$config_name\E\b/m) {
542        $ok = 1;
543    }
544    if ($output) {
545        _checking_for({ package => $module, ok => $ok });
546    }
547    return $ok;
548}
549
550sub print_module_instructions {
551    my ($check_results, $output) = @_;
552
553    # First we print the long explanatory messages.
554
555    if (scalar @{$check_results->{missing}}) {
556        print install_string('modules_message_required');
557    }
558
559    if (!$check_results->{one_dbd}) {
560        print install_string('modules_message_db');
561    }
562
563    if (my @missing = @{$check_results->{optional}} and $output) {
564        print install_string('modules_message_optional');
565        # Now we have to determine how large the table cols will be.
566        my $longest_name = max(map(length($_->{package}), @missing));
567
568        # The first column header is at least 11 characters long.
569        $longest_name = 11 if $longest_name < 11;
570
571        # The table is TABLE_WIDTH characters long. There are seven mandatory
572        # characters (* and space) in the string. So, we have a total
573        # of TABLE_WIDTH - 7 characters to work with.
574        my $remaining_space = (TABLE_WIDTH - 7) - $longest_name;
575        print '*' x TABLE_WIDTH . "\n";
576        printf "* \%${longest_name}s * %-${remaining_space}s *\n",
577               'MODULE NAME', 'ENABLES FEATURE(S)';
578        print '*' x TABLE_WIDTH . "\n";
579        foreach my $package (@missing) {
580            printf "* \%${longest_name}s * %-${remaining_space}s *\n",
581                   $package->{package},
582                   _translate_feature($package->{feature});
583        }
584    }
585
586    if (my @missing = @{ $check_results->{apache} }) {
587        print install_string('modules_message_apache');
588        my $missing_string = join(', ', @missing);
589        my $size = TABLE_WIDTH - 7;
590        printf "*    \%-${size}s *\n", $missing_string;
591        my $spaces = TABLE_WIDTH - 2;
592        print "*", (' ' x $spaces), "*\n";
593    }
594
595    my $need_module_instructions =
596        ( (!$output and @{$check_results->{missing}})
597          or ($output and $check_results->{any_missing}) ) ? 1 : 0;
598
599    if ($need_module_instructions or @{ $check_results->{apache} }) {
600        # If any output was required, we want to close the "table"
601        print "*" x TABLE_WIDTH . "\n";
602    }
603
604    # And now we print the actual installation commands.
605
606    if (my @missing = @{$check_results->{optional}} and $output) {
607        print install_string('commands_optional') . "\n\n";
608        foreach my $module (@missing) {
609            my $command = install_command($module);
610            printf "%15s: $command\n", $module->{package};
611        }
612        print "\n";
613    }
614
615    if (!$check_results->{one_dbd}) {
616        print install_string('commands_dbd') . "\n";
617        my %db_modules = %{DB_MODULE()};
618        foreach my $db (keys %db_modules) {
619            my $command = install_command($db_modules{$db}->{dbd});
620            printf "%10s: \%s\n", $db_modules{$db}->{name}, $command;
621        }
622        print "\n";
623    }
624
625    if (my @missing = @{$check_results->{missing}}) {
626        print colored(install_string('commands_required'), COLOR_ERROR), "\n";
627        foreach my $package (@missing) {
628            my $command = install_command($package);
629            print "    $command\n";
630        }
631    }
632
633    if ($output && $check_results->{any_missing} && !ON_ACTIVESTATE
634        && !$check_results->{hide_all})
635    {
636        print install_string('install_all', { perl => $^X });
637    }
638    if (!$check_results->{pass}) {
639        print colored(install_string('installation_failed'), COLOR_ERROR),
640              "\n\n";
641    }
642}
643
644sub _translate_feature {
645    my $features = shift;
646    my @strings;
647    foreach my $feature (@$features) {
648        push(@strings, install_string("feature_$feature"));
649    }
650    return join(', ', @strings);
651}
652
653sub check_graphviz {
654    my ($output) = @_;
655
656    my $webdotbase = Bugzilla->params->{'webdotbase'};
657    return 1 if $webdotbase =~ /^https?:/;
658
659    my $return;
660    $return = 1 if -x $webdotbase;
661
662    if ($output) {
663        _checking_for({ package => 'GraphViz', ok => $return });
664    }
665
666    if (!$return) {
667        print install_string('bad_executable', { bin => $webdotbase }), "\n";
668    }
669
670    my $webdotdir = bz_locations()->{'webdotdir'};
671    # Check .htaccess allows access to generated images
672    if (-e "$webdotdir/.htaccess") {
673        my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
674            || die "$webdotdir/.htaccess: " . $!;
675        if (!grep(/png/, $htaccess->getlines)) {
676            print STDERR install_string('webdot_bad_htaccess',
677                                        { dir => $webdotdir }), "\n";
678        }
679        $htaccess->close;
680    }
681
682    return $return;
683}
684
685# This was originally clipped from the libnet Makefile.PL, adapted here for
686# accurate version checking.
687sub have_vers {
688    my ($params, $output) = @_;
689    my $module  = $params->{module};
690    my $package = $params->{package};
691    if (!$package) {
692        $package = $module;
693        $package =~ s/::/-/g;
694    }
695    my $wanted  = $params->{version};
696
697    eval "require $module;";
698    # Don't let loading a module change the output-encoding of STDOUT
699    # or STDERR. (CGI.pm tries to set "binmode" on these file handles when
700    # it's loaded, and other modules may do the same in the future.)
701    Bugzilla::Install::Util::set_output_encoding();
702
703    # VERSION is provided by UNIVERSAL::, and can be called even if
704    # the module isn't loaded. We eval'uate ->VERSION because it can die
705    # when the version is not valid (yes, this happens from time to time).
706    # In that case, we use an uglier method to get the version.
707    my $vnum = eval { $module->VERSION };
708    if ($@) {
709        no strict 'refs';
710        $vnum = ${"${module}::VERSION"};
711
712        # If we come here, then the version is not a valid one.
713        # We try to sanitize it.
714        if ($vnum =~ /^((\d+)(\.\d+)*)/) {
715            $vnum = $1;
716        }
717    }
718    $vnum ||= -1;
719
720    # Must do a string comparison as $vnum may be of the form 5.10.1.
721    my $vok = ($vnum ne '-1' && version->new($vnum) >= version->new($wanted)) ? 1 : 0;
722    my $blacklisted;
723    if ($vok && $params->{blacklist}) {
724        $blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
725        $vok = 0 if $blacklisted;
726    }
727
728    if ($output) {
729        _checking_for({
730            package => $package, ok => $vok, wanted => $wanted,
731            found   => $vnum, blacklisted => $blacklisted
732        });
733    }
734
735    return $vok ? 1 : 0;
736}
737
738sub _checking_for {
739    my ($params) = @_;
740    my ($package, $ok, $wanted, $blacklisted, $found) =
741        @$params{qw(package ok wanted blacklisted found)};
742
743    my $ok_string = $ok ? install_string('module_ok') : '';
744
745    # If we're actually checking versions (like for Perl modules), then
746    # we have some rather complex logic to determine what we want to
747    # show. If we're not checking versions (like for GraphViz) we just
748    # show "ok" or "not found".
749    if (exists $params->{found}) {
750        my $found_string;
751        # We do a string compare in case it's non-numeric. We make sure
752        # it's not a version object as negative versions are forbidden.
753        if ($found && !ref($found) && $found eq '-1') {
754            $found_string = install_string('module_not_found');
755        }
756        elsif ($found) {
757            $found_string = install_string('module_found', { ver => $found });
758        }
759        else {
760            $found_string = install_string('module_unknown_version');
761        }
762        $ok_string = $ok ? "$ok_string: $found_string" : $found_string;
763    }
764    elsif (!$ok) {
765        $ok_string = install_string('module_not_found');
766    }
767
768    my $black_string = $blacklisted ? install_string('blacklisted') : '';
769    my $want_string  = $wanted ? "v$wanted" : install_string('any');
770
771    my $str = sprintf "%s %20s %-11s $ok_string $black_string\n",
772                install_string('checking_for'), $package, "($want_string)";
773    print $ok ? $str : colored($str, COLOR_ERROR);
774}
775
776sub install_command {
777    my $module = shift;
778    my ($command, $package);
779
780    if (ON_ACTIVESTATE) {
781        $command = 'ppm install %s';
782        $package = $module->{package};
783    }
784    else {
785        $command = "$^X install-module.pl \%s";
786        # Non-Windows installations need to use module names, because
787        # CPAN doesn't understand package names.
788        $package = $module->{module};
789    }
790    return sprintf $command, $package;
791}
792
793# This does a reverse mapping for FEATURE_FILES.
794sub map_files_to_features {
795    my %features = FEATURE_FILES;
796    my %files;
797    foreach my $feature (keys %features) {
798        my @my_files = @{ $features{$feature} };
799        foreach my $pattern (@my_files) {
800            foreach my $file (glob $pattern) {
801                $files{$file} = $feature;
802            }
803        }
804    }
805    return \%files;
806}
807
8081;
809
810__END__
811
812=head1 NAME
813
814Bugzilla::Install::Requirements - Functions and variables dealing
815  with Bugzilla's perl-module requirements.
816
817=head1 DESCRIPTION
818
819This module is used primarily by C<checksetup.pl> to determine whether
820or not all of Bugzilla's prerequisites are installed. (That is, all the
821perl modules it requires.)
822
823=head1 CONSTANTS
824
825=over
826
827=item C<REQUIRED_MODULES>
828
829An arrayref of hashrefs that describes the perl modules required by
830Bugzilla. The hashes have three keys:
831
832=over
833
834=item C<package> - The name of the Perl package that you'd find on
835CPAN for this requirement.
836
837=item C<module> - The name of a module that can be passed to the
838C<install> command in C<CPAN.pm> to install this module.
839
840=item C<version> - The version of this module that we require, or C<0>
841if any version is acceptable.
842
843=back
844
845=item C<OPTIONAL_MODULES>
846
847An arrayref of hashrefs that describes the perl modules that add
848additional features to Bugzilla if installed. Its hashes have all
849the fields of L</REQUIRED_MODULES>, plus a C<feature> item--an arrayref
850of strings that describe what features require this module.
851
852=item C<FEATURE_FILES>
853
854A hashref that describes what files should only be compiled if a certain
855feature is enabled. The feature is the key, and the values are arrayrefs
856of file names (which are passed to C<glob>, so shell patterns work).
857
858=back
859
860
861=head1 SUBROUTINES
862
863=over 4
864
865=item C<check_requirements>
866
867=over
868
869=item B<Description>
870
871This checks what optional or required perl modules are installed, like
872C<checksetup.pl> does.
873
874=item B<Params>
875
876=over
877
878=item C<$output> - C<true> if you want the function to print out information
879about what it's doing, and the versions of everything installed.
880
881=back
882
883=item B<Returns>
884
885A hashref containing these values:
886
887=over
888
889=item C<pass> - Whether or not we have all the mandatory requirements.
890
891=item C<missing> - An arrayref containing any required modules that
892are not installed or that are not up-to-date. Each item in the array is
893a hashref in the format of items from L</REQUIRED_MODULES>.
894
895=item C<optional> - The same as C<missing>, but for optional modules.
896
897=item C<apache> - The name of each optional Apache module that is missing.
898
899=item C<have_one_dbd> - True if at least one C<DBD::> module is installed.
900
901=item C<any_missing> - True if there are any missing Perl modules, even
902optional modules.
903
904=back
905
906=back
907
908=item C<check_graphviz($output)>
909
910Description: Checks if the graphviz binary specified in the
911  C<webdotbase> parameter is a valid binary, or a valid URL.
912
913Params:      C<$output> - C<$true> if you want the function to
914                 print out information about what it's doing.
915
916Returns:     C<1> if the check was successful, C<0> otherwise.
917
918=item C<have_vers($module, $output)>
919
920 Description: Tells you whether or not you have the appropriate
921              version of the module requested. It also prints
922              out a message to the user explaining the check
923              and the result.
924
925 Params:      C<$module> - A hashref, in the format of an item from
926                           L</REQUIRED_MODULES>.
927              C<$output> - Set to true if you want this function to
928                           print information to STDOUT about what it's
929                           doing.
930
931 Returns:   C<1> if you have the module installed and you have the
932            appropriate version. C<0> otherwise.
933
934=item C<install_command($module)>
935
936 Description: Prints out the appropriate command to install the
937              module specified, depending on whether you're
938              on Windows or Linux.
939
940 Params:      C<$module> - A hashref, in the format of an item from
941                           L</REQUIRED_MODULES>.
942
943 Returns:     nothing
944
945=item C<map_files_to_features>
946
947Returns a hashref where file names are the keys and the value is the feature
948that must be enabled in order to compile that file.
949
950=back
951