Requirements.pm 22.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
# -*- 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.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
16
#                 Marc Schumann <wurblzap@gmail.com>
17 18 19 20 21 22 23 24 25 26 27

package Bugzilla::Install::Requirements;

# NOTE: This package MUST NOT "use" any Bugzilla modules other than
# Bugzilla::Constants, anywhere. We may "use" standard perl modules.
#
# Subroutines may "require" and "import" from modules, but they
# MUST NOT "use."

use strict;

28
use Bugzilla::Constants;
29
use Bugzilla::Install::Util qw(vers_cmp install_string);
30
use List::Util qw(max);
31
use Safe;
32
use Term::ANSIColor;
33

34 35 36
use base qw(Exporter);
our @EXPORT = qw(
    REQUIRED_MODULES
37
    OPTIONAL_MODULES
38
    FEATURE_FILES
39

40
    check_requirements
41
    check_graphviz
42 43
    have_vers
    install_command
44
    map_files_to_features
45 46
);

47 48 49
# This is how many *'s are in the top of each "box" message printed
# by checksetup.pl.
use constant TABLE_WIDTH => 71;
50

51 52 53
# The below two constants are subroutines so that they can implement
# a hook. Other than that they are actually constants.

54 55 56 57
# "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.
58 59 60 61
#
# "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.
62
sub REQUIRED_MODULES {
63
    my $perl_ver = sprintf('%vd', $^V);
64
    my @modules = (
65
    {
66
        package => 'CGI.pm',
67
        module  => 'CGI',
68 69
        # Perl 5.10 requires CGI 3.33 due to a taint issue when
        # uploading attachments, see bug 416382.
70
        # Require CGI 3.21 for -httponly support, see bug 368502.
71 72 73
        version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '3.21',
        # CGI::Carp in 3.46 and 3.47 breaks Template Toolkit
        blacklist => ['^3\.46$', '^3\.47$'],
74
    },
75 76 77 78 79
    {
        package => 'Digest-SHA',
        module  => 'Digest::SHA',
        version => 0
    },
80
    {
81 82
        package => 'TimeDate',
        module  => 'Date::Format',
83 84
        version => '2.21'
    },
85 86 87 88 89 90
    # 0.28 fixed some important bugs in DateTime.
    {
        package => 'DateTime',
        module  => 'DateTime',
        version => '0.28'
    },
91 92 93 94 95 96 97 98 99
    # 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'
    },
100 101 102 103 104
    {
        package => 'DBI',
        module  => 'DBI',
        version => '1.41'
    },
105 106
    # 2.22 fixes various problems related to UTF8 strings in hash keys,
    # as well as line endings on Windows.
107
    {
108 109
        package => 'Template-Toolkit',
        module  => 'Template',
110
        version => '2.22'
111
    },
112 113 114
    {
        package => 'Email-Send',
        module  => 'Email::Send',
115 116
        version => ON_WINDOWS ? '2.16' : '2.00',
        blacklist => ['^2\.196$']
117 118
    },
    {
119 120 121 122
        package => 'Email-MIME',
        module  => 'Email::MIME',
        version => '1.861'
    },
123 124 125 126 127 128
    {
        package => 'Email-MIME-Encodings',
        module  => 'Email::MIME::Encodings',
        # Fixes bug 486206
        version => '1.313',
    },
129
    {
130 131
        package => 'Email-MIME-Modifier',
        module  => 'Email::MIME::Modifier',
132
        version => '1.442'
133
    },
134 135 136 137 138
    {
        package => 'URI',
        module  => 'URI',
        version => 0
    },
139 140 141 142 143 144
    );

    my $all_modules = _get_extension_requirements(
        'REQUIRED_MODULES', \@modules);
    return $all_modules;
};
145

146 147
sub OPTIONAL_MODULES {
    my @modules = (
148
    {
149 150 151
        package => 'GD',
        module  => 'GD',
        version => '1.20',
152
        feature => [qw(graphical_reports new_charts old_charts)],
153
    },
154 155
    {
        package => 'Chart',
156
        module  => 'Chart::Lines',
157
        version => '1.0',
158
        feature => [qw(new_charts old_charts)],
159
    },
160
    {
161
        package => 'Template-GD',
162 163
        # 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.
164 165
        module  => 'Template::Plugin::GD::Image',
        version => 0,
166
        feature => ['graphical_reports'],
167
    },
168
    {
169 170
        package => 'GDTextUtil',
        module  => 'GD::Text',
171
        version => 0,
172
        feature => ['graphical_reports'],
173
    },
174 175 176
    {
        package => 'GDGraph',
        module  => 'GD::Graph',
177
        version => 0,
178
        feature => ['graphical_reports'],
179 180
    },
    {
181 182 183
        package => 'XML-Twig',
        module  => 'XML::Twig',
        version => 0,
184
        feature => ['moving', 'updates'],
185
    },
186 187 188 189 190
    {
        package => 'MIME-tools',
        # MIME::Parser is packaged as MIME::Tools on ActiveState Perl
        module  => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
        version => '5.406',
191
        feature => ['moving'],
192
    },
193
    {
194 195 196
        package => 'libwww-perl',
        module  => 'LWP::UserAgent',
        version => 0,
197
        feature => ['updates'],
198 199
    },
    {
200 201 202
        package => 'PatchReader',
        module  => 'PatchReader',
        version => '0.9.4',
203
        feature => ['patch_viewer'],
204 205
    },
    {
206 207 208
        package => 'perl-ldap',
        module  => 'Net::LDAP',
        version => 0,
209
        feature => ['auth_ldap'],
210
    },
211
    {
212
        package => 'Authen-SASL',
213 214
        module  => 'Authen::SASL',
        version => 0,
215
        feature => ['smtp_auth'],
216
    },
217 218 219 220
    {
        package => 'RadiusPerl',
        module  => 'Authen::Radius',
        version => 0,
221
        feature => ['auth_radius'],
222
    },
223
    {
224 225
        package => 'SOAP-Lite',
        module  => 'SOAP::Lite',
226 227 228
        # 0.710.04 is required for correct UTF-8 handling, but .04 and .05 are
        # affected by bug 468009.
        version => '0.710.06',
229
        feature => ['xmlrpc'],
230
    },
231 232 233 234
    {
        package => 'JSON-RPC',
        module  => 'JSON::RPC',
        version => 0,
235
        feature => ['jsonrpc'],
236
    },
237 238 239 240 241 242
    {
        package => 'Test-Taint',
        module  => 'Test::Taint',
        version => 0,
        feature => ['jsonrpc', 'xmlrpc'],
    },
243
    {
244 245 246 247
        # We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
        package => 'HTML-Parser',
        module  => 'HTML::Parser',
        version => '3.40',
248
        feature => ['html_desc'],
249 250
    },
    {
251 252 253
        package => 'HTML-Scrubber',
        module  => 'HTML::Scrubber',
        version => 0,
254
        feature => ['html_desc'],
255
    },
256 257 258 259 260 261

    # Inbound Email
    {
        package => 'Email-MIME-Attachment-Stripper',
        module  => 'Email::MIME::Attachment::Stripper',
        version => 0,
262
        feature => ['inbound_email'],
263 264 265 266 267
    },
    {
        package => 'Email-Reply',
        module  => 'Email::Reply',
        version => 0,
268
        feature => ['inbound_email'],
269 270
    },

271 272 273 274 275
    # Mail Queueing
    {
        package => 'TheSchwartz',
        module  => 'TheSchwartz',
        version => 0,
276
        feature => ['jobqueue'],
277 278 279 280 281
    },
    {
        package => 'Daemon-Generic',
        module  => 'Daemon::Generic',
        version => 0,
282
        feature => ['jobqueue'],
283 284
    },

285
    # mod_perl
286
    {
287 288 289
        package => 'mod_perl',
        module  => 'mod_perl2',
        version => '1.999022',
290
        feature => ['mod_perl'],
291
    },
292 293
    );

294 295 296 297 298
    my $all_modules = _get_extension_requirements(
        'OPTIONAL_MODULES', \@modules);
    return $all_modules;
};

299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
# 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'],
    moving        => ['importxml.pl'],
    auth_ldap     => ['Bugzilla/Auth/Verify/LDAP.pm'],
    auth_radius   => ['Bugzilla/Auth/Verify/RADIUS.pm'],
    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'],
);

315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
# This implements the install-requirements hook described in Bugzilla::Hook.
sub _get_extension_requirements {
    my ($function, $base_modules) = @_;
    my @all_modules;
    # get a list of all extensions
    my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
    foreach my $extension (@extensions) {
        my $file = "$extension/code/install-requirements.pl";
        if (-e $file) {
            my $safe = new Safe;
            # This is a very liberal Safe.
            $safe->permit(qw(:browse require entereval caller));
            $safe->rdo($file);
            if ($@) {
                warn $@;
                next;
            }
            my $modules = eval { &{$safe->varglob($function)}($base_modules) };
            next unless $modules;
            push(@all_modules, @$modules);
        }
    }

    unshift(@all_modules, @$base_modules);
    return \@all_modules;
};
341

342 343 344
sub check_requirements {
    my ($output) = @_;

345
    print "\n", install_string('checking_modules'), "\n" if $output;
346
    my $root = ROOT_USER;
347
    my $missing = _check_missing(REQUIRED_MODULES, $output);
348

349
    print "\n", install_string('checking_dbd'), "\n" if $output;
350 351 352
    my $have_one_dbd = 0;
    my $db_modules = DB_MODULE;
    foreach my $db (keys %$db_modules) {
353 354
        my $dbd = $db_modules->{$db}->{dbd};
        $have_one_dbd = 1 if have_vers($dbd, $output);
355 356
    }

357
    print "\n", install_string('checking_optional'), "\n" if $output;
358
    my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
359

360 361 362 363
    # 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;

364
    my $pass = !scalar(@$missing) && $have_one_dbd;
365 366 367
    return {
        pass     => $pass,
        one_dbd  => $have_one_dbd,
368 369 370
        missing  => $missing,
        optional => $missing_optional,
        any_missing => !$pass || scalar(@$missing_optional),
371 372
    };
}
373

374 375 376
# A helper for check_requirements
sub _check_missing {
    my ($modules, $output) = @_;
377

378
    my @missing;
379 380
    foreach my $module (@$modules) {
        unless (have_vers($module, $output)) {
381
            push(@missing, $module);
382
        }
383
    }
384

385
    return \@missing;
386
}
387

388 389 390 391 392 393 394 395 396 397 398
# Returns the build ID of ActivePerl. If several versions of
# ActivePerl are installed, it won't be able to know which one
# you are currently running. But that's our best guess.
sub _get_activestate_build_id {
    eval 'use Win32::TieRegistry';
    return 0 if $@;
    my $key = Win32::TieRegistry->new('LMachine\Software\ActiveState\ActivePerl')
      or return 0;
    return $key->GetValue("CurrentVersion");
}

399 400
sub print_module_instructions {
    my ($check_results, $output) = @_;
401

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
    # 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",
430 431
                   $package->{package}, 
                   _translate_feature($package->{feature});
432 433 434 435
        }
    }

    # We only print the PPM repository note if we have to.
436
    if ((!$output && @{$check_results->{missing}})
437 438 439
        || ($output && $check_results->{any_missing}))
    {
        if (ON_WINDOWS) {
440 441 442 443 444 445 446 447
            my $perl_ver = sprintf('%vd', $^V);
            
            # URL when running Perl 5.8.x.
            my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
            # Packages for Perl 5.10 are not compatible with Perl 5.8.
            if (vers_cmp($perl_ver, '5.10') > -1) {
                $url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
            }
448 449
            print colored(install_string('ppm_repo_add', 
                                 { theory_url => $url_to_theory58S }), 'red');
450
            # ActivePerls older than revision 819 require an additional command.
451
            if (_get_activestate_build_id() < 819) {
452
                print install_string('ppm_repo_up');
453
            }
454
        }
455 456 457

        # If any output was required, we want to close the "table"
        print "*" x TABLE_WIDTH . "\n";
458 459
    }

460 461 462 463 464 465 466
    # 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};
467 468 469 470
        }
        print "\n";
    }

471
    if (!$check_results->{one_dbd}) {
472
        print install_string('commands_dbd') . "\n";
473 474
        my %db_modules = %{DB_MODULE()};
        foreach my $db (keys %db_modules) {
475
            my $command = install_command($db_modules{$db}->{dbd});
476
            printf "%10s: \%s\n", $db_modules{$db}->{name}, $command;
477 478 479 480
        }
        print "\n";
    }

481
    if (my @missing = @{$check_results->{missing}}) {
482
        print colored(install_string('commands_required'), 'red') . "\n";
483
        foreach my $package (@missing) {
484 485
            my $command = install_command($package);
            print "    $command\n";
486 487
        }
    }
488

489 490 491
    if ($output && $check_results->{any_missing} && !ON_WINDOWS
        && !$check_results->{hide_all}) 
    {
492 493
        print install_string('install_all', { perl => $^X });
    }
494 495 496
    if (!$check_results->{pass}) {
        print colored(install_string('installation_failed'), 'red') . "\n\n";
    }
497 498
}

499 500 501 502 503 504 505 506 507
sub _translate_feature {
    my $features = shift;
    my @strings;
    foreach my $feature (@$features) {
        push(@strings, install_string("feature_$feature"));
    }
    return join(', ', @strings);
}

508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
sub check_graphviz {
    my ($output) = @_;

    return 1 if (Bugzilla->params->{'webdotbase'} =~ /^https?:/);

    printf("Checking for %15s %-9s ", "GraphViz", "(any)") if $output;

    my $return = 0;
    if(-x Bugzilla->params->{'webdotbase'}) {
        print "ok: found\n" if $output;
        $return = 1;
    } else {
        print "not a valid executable: " . Bugzilla->params->{'webdotbase'} . "\n";
    }

    my $webdotdir = bz_locations()->{'webdotdir'};
    # Check .htaccess allows access to generated images
    if (-e "$webdotdir/.htaccess") {
        my $htaccess = new IO::File("$webdotdir/.htaccess", 'r') 
            || die "$webdotdir/.htaccess: " . $!;
        if (!grep(/png/, $htaccess->getlines)) {
            print "Dependency graph images are not accessible.\n";
            print "delete $webdotdir/.htaccess and re-run checksetup.pl to fix.\n";
        }
        $htaccess->close;
    }

    return $return;
}

538
# This was originally clipped from the libnet Makefile.PL, adapted here to
539
# use the below vers_cmp routine for accurate version checking.
540
sub have_vers {
541 542 543 544 545 546 547 548 549 550
    my ($params, $output) = @_;
    my $module  = $params->{module};
    my $package = $params->{package};
    if (!$package) {
        $package = $module;
        $package =~ s/::/-/g;
    }
    my $wanted  = $params->{version};

    eval "require $module;";
551

552
    # VERSION is provided by UNIVERSAL::
553
    my $vnum = eval { $module->VERSION } || -1;
554 555 556 557

    # CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
    # That breaks the standard version tests, so we need to manually correct
    # the version
558
    if ($module eq 'CGI' && $vnum =~ /(2\.7\d)(\d+)/) {
559 560 561
        $vnum = $1 . "." . $2;
    }

562
    my $vstr;
563
    if ($vnum eq "-1") { # string compare just in case it's non-numeric
564
        $vstr = install_string('module_not_found');
565 566
    }
    elsif (vers_cmp($vnum,"0") > -1) {
567
        $vstr = install_string('module_found', { ver => $vnum });
568 569
    }
    else {
570
        $vstr = install_string('module_unknown_version');
571 572 573
    }

    my $vok = (vers_cmp($vnum,$wanted) > -1);
574 575 576 577 578 579
    my $blacklisted;
    if ($vok && $params->{blacklist}) {
        $blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
        $vok = 0 if $blacklisted;
    }

580 581 582 583 584
    if ($output) {
        my $ok           = $vok ? install_string('module_ok') : '';
        my $black_string = $blacklisted ? install_string('blacklisted') : '';
        my $want_string  = $wanted ? "v$wanted" : install_string('any');

585
        $ok = "$ok:" if $ok;
586 587 588
        my $str = sprintf "%s %19s %-9s $ok $vstr $black_string\n",
                    install_string('checking_for'), $package, "($want_string)";
        print $vok ? $str : colored($str, 'red');
589 590
    }
    
591 592 593 594 595
    return $vok ? 1 : 0;
}

sub install_command {
    my $module = shift;
596 597
    my ($command, $package);

598
    if (ON_WINDOWS) {
599 600 601 602
        $command = 'ppm install %s';
        $package = $module->{package};
    }
    else {
603
        $command = "$^X install-module.pl \%s";
604 605 606
        # Non-Windows installations need to use module names, because
        # CPAN doesn't understand package names.
        $package = $module->{module};
607
    }
608
    return sprintf $command, $package;
609 610
}

611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
# This does a reverse mapping for FEATURE_FILES.
sub map_files_to_features {
    my %features = FEATURE_FILES;
    my %files;
    foreach my $feature (keys %features) {
        my @my_files = @{ $features{$feature} };
        foreach my $pattern (@my_files) {
            foreach my $file (glob $pattern) {
                $files{$file} = $feature;
            }
        }
    }
    return \%files;
}

626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
1;

__END__

=head1 NAME

Bugzilla::Install::Requirements - Functions and variables dealing
  with Bugzilla's perl-module requirements.

=head1 DESCRIPTION

This module is used primarily by C<checksetup.pl> to determine whether
or not all of Bugzilla's prerequisites are installed. (That is, all the
perl modules it requires.)

=head1 CONSTANTS

643
=over
644 645 646 647

=item C<REQUIRED_MODULES>

An arrayref of hashrefs that describes the perl modules required by 
648 649 650 651 652 653 654 655 656 657 658 659
Bugzilla. The hashes have three keys: 

=over

=item C<package> - The name of the Perl package that you'd find on
CPAN for this requirement. 

=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 C<version> - The version of this module that we require, or C<0>
if any version is acceptable.
660 661 662

=back

663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678
=item C<OPTIONAL_MODULES>

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.

=item C<FEATURE_FILES>

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


679 680 681 682
=head1 SUBROUTINES

=over 4

683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
=item C<check_requirements>

=over

=item B<Description>

This checks what optional or required perl modules are installed, like
C<checksetup.pl> does.

=item B<Params>

=over

=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

=item B<Returns>

A hashref containing these values:

=over

=item C<pass> - Whether or not we have all the mandatory requirements.

=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<optional> - The same as C<missing>, but for optional modules.

=item C<have_one_dbd> - True if at least one C<DBD::> module is installed.

=item C<any_missing> - True if there are any missing modules, even optional
modules.

=back

=back
723

724 725 726 727 728 729 730 731 732 733
=item C<check_graphviz($output)>

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
                 print out information about what it's doing.

Returns:     C<1> if the check was successful, C<0> otherwise.

734
=item C<have_vers($module, $output)>
735 736 737 738 739 740

 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.

741 742
 Params:      C<$module> - A hashref, in the format of an item from 
                           L</REQUIRED_MODULES>.
743 744 745
              C<$output> - Set to true if you want this function to
                           print information to STDOUT about what it's
                           doing.
746 747 748 749 750 751 752 753 754 755

 Returns:   C<1> if you have the module installed and you have the
            appropriate version. C<0> otherwise.

=item C<install_command($module)>

 Description: Prints out the appropriate command to install the
              module specified, depending on whether you're
              on Windows or Linux.

756 757
 Params:      C<$module> - A hashref, in the format of an item from
                           L</REQUIRED_MODULES>.
758 759 760

 Returns:     nothing

761 762 763 764 765
=item C<map_files_to_features>

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.

766
=back