fix ticketing system error on bootstrap of new install
[freeside.git] / rt / sbin / rt-test-dependencies.in
1 #!@PERL@
2 # BEGIN BPS TAGGED BLOCK {{{
3 #
4 # COPYRIGHT:
5 #
6 # This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
7 #                                          <sales@bestpractical.com>
8 #
9 # (Except where explicitly superseded by other copyright notices)
10 #
11 #
12 # LICENSE:
13 #
14 # This work is made available to you under the terms of Version 2 of
15 # the GNU General Public License. A copy of that license should have
16 # been provided with this software, but in any event can be snarfed
17 # from www.gnu.org.
18 #
19 # This work is distributed in the hope that it will be useful, but
20 # WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22 # General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301 or visit their web page on the internet at
28 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
29 #
30 #
31 # CONTRIBUTION SUBMISSION POLICY:
32 #
33 # (The following paragraph is not intended to limit the rights granted
34 # to you to modify and distribute this software under the terms of
35 # the GNU General Public License and is only of importance to you if
36 # you choose to contribute your changes and enhancements to the
37 # community by submitting them to Best Practical Solutions, LLC.)
38 #
39 # By intentionally submitting any modifications, corrections or
40 # derivatives to this work, or any other work intended for use with
41 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
42 # you are the copyright holder for those contributions and you grant
43 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
44 # royalty-free, perpetual, license to use, copy, create derivative
45 # works based on those contributions, and sublicense and distribute
46 # those contributions and any derivatives thereof.
47 #
48 # END BPS TAGGED BLOCK }}}
49 #
50 # This is just a basic script that checks to make sure that all
51 # the modules needed by RT before you can install it.
52 #
53
54 use strict;
55 use warnings;
56 no warnings qw(numeric redefine);
57 use Getopt::Long;
58 use Cwd qw(abs_path);
59 my %args;
60 my %deps;
61 my @orig_argv = @ARGV;
62 # Save our path because installers or tests can change cwd
63 my $script_path = abs_path($0);
64
65 GetOptions(
66     \%args,                               'v|verbose',
67     'install!',
68     'with-MYSQL', 'with-PG', 'with-SQLITE', 'with-ORACLE',
69     'with-FASTCGI', 'with-MODPERL1', 'with-MODPERL2', 'with-STANDALONE',
70
71     'with-DEVELOPER',
72
73     'with-GPG',
74     'with-ICAL',
75     'with-GRAPHVIZ',
76     'with-GD',
77     'with-DASHBOARDS',
78     'with-USERLOGO',
79     'with-HTML-DOC',
80
81     'list-deps',
82     'siteinstall!',
83     'help|h',
84 );
85
86 if ( $args{help} ) {
87     require Pod::Usage;
88     Pod::Usage::pod2usage( { verbose => 2 } );
89     exit;
90 }
91
92 # Set up defaults
93 my %default = (
94     'with-MASON' => 1,
95     'with-PSGI' => 0,
96     'with-CORE' => 1,
97     'with-CLI' => 1,
98     'with-MAILGATE' => 1, 
99     'with-DEVELOPER' => @RT_DEVELOPER@,
100     'with-GPG' => @RT_GPG_DEPS@,
101     'with-SMIME' => @RT_SMIME_DEPS@,
102     'with-ICAL' => 1,
103     'with-SMTP' => 1,
104     'with-GRAPHVIZ' => @RT_GRAPHVIZ@,
105     'with-GD' => @RT_GD@,
106     'with-DASHBOARDS' => 1,
107     'with-USERLOGO' => 1,
108     'with-HTML-DOC' => @RT_DEVELOPER@,
109 );
110 $args{$_} = $default{$_} foreach grep !exists $args{$_}, keys %default;
111
112 {
113   my $section;
114   my %always_show_sections = (
115     perl => 1,
116     users => 1,
117   );
118
119   sub section {
120     my $s = shift;
121     $section = $s;
122     print "$s:\n" unless $args{'list-deps'};
123   }
124
125   sub print_found {
126     my $msg = shift;
127     my $test = shift;
128     my $extra = shift;
129
130     unless ( $args{'list-deps'} ) {
131         if ( $args{'v'} or not $test or $always_show_sections{$section} ) {
132             print "\t$msg ...";
133             print $test ? "found" : "MISSING";
134             print "\n";
135         }
136
137         print "\t\t$extra\n" if defined $extra;
138     }
139   }
140 }
141
142 sub conclude {
143     my %missing_by_type = @_;
144
145     unless ( $args{'list-deps'} ) {
146         unless ( keys %missing_by_type ) {
147             print "\nAll dependencies have been found.\n";
148             return;
149         }
150
151         print "\nSOME DEPENDENCIES WERE MISSING.\n";
152
153         for my $type ( keys %missing_by_type ) {
154             my $missing = $missing_by_type{$type};
155
156             print "$type missing dependencies:\n";
157             for my $name ( keys %$missing ) {
158                 my $module  = $missing->{$name};
159                 my $version = $module->{version};
160                 my $error = $module->{error};
161                 print_found( $name . ( $version && !$error ? " >= $version" : "" ),
162                     0, $module->{error} );
163             }
164         }
165
166         print "\nPerl library path for @PERL@:\n";
167         print "    $_\n" for @INC;
168
169         exit 1;
170     }
171 }
172
173 sub text_to_hash {
174     my %hash;
175     for my $line ( split /\n/, $_[0] ) {
176         my($key, $value) = $line =~ /(\S+)\s*(\S*)/;
177         $value ||= '';
178         $hash{$key} = $value;
179     }
180
181     return %hash;
182 }
183 sub set_dep {
184     my ($name, $module, $version) = @_;
185     my %list = @{$deps{$name}};
186     $list{$module} = ($version || '');
187     $deps{$name} = [ %list ];
188 }
189
190 $deps{'CORE'} = [ text_to_hash( << '.') ];
191 Apache::Session 1.53
192 CGI 3.38
193 CGI::Cookie 1.20
194 CGI::Emulate::PSGI
195 CGI::PSGI 0.12
196 Class::Accessor::Fast
197 Crypt::Eksblowfish
198 CSS::Squish 0.06
199 Data::GUID
200 Date::Extract 0.02
201 Date::Manip
202 DateTime 0.44
203 DateTime::Format::Natural 0.67
204 DateTime::Locale 0.40
205 DBI 1.37
206 DBIx::SearchBuilder 1.65
207 Devel::GlobalDestruction
208 Devel::StackTrace 1.19
209 Digest::base
210 Digest::MD5 2.27
211 Digest::SHA
212 Email::Address 1.897
213 Email::Address::List 0.02
214 Encode
215 Errno
216 File::Glob
217 File::ShareDir
218 File::Spec 0.8
219 File::Temp 0.19
220 HTML::Entities
221 HTML::FormatText::WithLinks 0.14
222 HTML::FormatText::WithLinks::AndTables
223 HTML::Mason 1.43
224 HTML::Mason::PSGIHandler 0.52
225 HTML::Quoted
226 HTML::RewriteAttributes 0.05
227 HTML::Scrubber 0.08
228 HTTP::Message 6.0
229 IPC::Run3
230 JSON
231 LWP::Simple
232 List::MoreUtils
233 Locale::Maketext 1.06
234 Locale::Maketext::Fuzzy 0.11
235 Locale::Maketext::Lexicon 0.32
236 Log::Dispatch 2.30
237 Mail::Header 2.12
238 Mail::Mailer 1.57
239 MIME::Entity 5.504
240 Module::Refresh 0.03
241 Module::Versions::Report 1.05
242 Encode 2.64
243 Net::CIDR
244 Plack 1.0002
245 Regexp::Common
246 Regexp::Common::net::CIDR
247 Regexp::IPv6
248 Role::Basic 0.12
249 Scalar::Util
250 Storable 2.08
251 Symbol::Global::Name 0.04
252 Sys::Syslog 0.16
253 Text::Password::Pronounceable
254 Text::Quoted 2.07
255 Text::Template 1.44
256 Text::WikiFormat 0.76
257 Text::Wrapper
258 Time::HiRes
259 Time::ParseDate
260 Tree::Simple 1.04
261 UNIVERSAL::require
262 XML::RSS 1.05
263 .
264 set_dep( CORE => 'Symbol::Global::Name' => 0.05 ) if $] >= 5.019003;
265 set_dep( CORE => CGI => 4.00 )                    if $] > 5.019003;
266
267 $deps{'MAILGATE'} = [ text_to_hash( << '.') ];
268 Crypt::SSLeay
269 Getopt::Long
270 LWP::Protocol::https
271 LWP::UserAgent 6.0
272 Net::SSL
273 Pod::Usage
274 .
275
276 $deps{'CLI'} = [ text_to_hash( << '.') ];
277 Getopt::Long 2.24
278 HTTP::Request::Common
279 LWP
280 Term::ReadKey
281 Term::ReadLine
282 Text::ParseWords
283 .
284
285 $deps{'DEVELOPER'} = [ text_to_hash( << '.') ];
286 Email::Abstract
287 File::Find
288 File::Which
289 Locale::PO
290 Log::Dispatch::Perl
291 Mojo::DOM
292 Plack::Middleware::Test::StashWarnings 0.08
293 Set::Tiny
294 String::ShellQuote 0 # needed for gnupg-incoming.t
295 Test::Builder 0.90 # needed for is_passing
296 Test::Deep 0 # needed for shredder tests
297 Test::Email
298 Test::Expect 0.31
299 Test::LongString
300 Test::MockTime
301 Test::NoWarnings
302 Test::Pod
303 Test::Warn
304 Test::WWW::Mechanize 1.30
305 Test::WWW::Mechanize::PSGI
306 WWW::Mechanize 1.52
307 XML::Simple
308 .
309
310 $deps{'FASTCGI'} = [ text_to_hash( << '.') ];
311 FCGI 0.74
312 FCGI::ProcManager
313 .
314
315 $deps{'MODPERL1'} = [ text_to_hash( << '.') ];
316 Apache::Request
317 .
318
319 $deps{'MYSQL'} = [ text_to_hash( << '.') ];
320 DBD::mysql 2.1018
321 .
322
323 $deps{'ORACLE'} = [ text_to_hash( << '.') ];
324 DBD::Oracle
325 .
326
327 $deps{'PG'} = [ text_to_hash( << '.') ];
328 DBIx::SearchBuilder 1.66
329 DBD::Pg 1.43
330 .
331
332 $deps{'SQLITE'} = [ text_to_hash( << '.') ];
333 DBD::SQLite 1.00
334 .
335
336 $deps{'GPG'} = [ text_to_hash( << '.') ];
337 File::Which
338 GnuPG::Interface
339 PerlIO::eol
340 .
341
342 $deps{'SMIME'} = [ text_to_hash( << '.') ];
343 Crypt::X509
344 File::Which
345 String::ShellQuote
346 .
347
348 $deps{'ICAL'} = [ text_to_hash( << '.') ];
349 Data::ICal
350 .
351
352 $deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
353 MIME::Types
354 URI 1.59
355 URI::QueryParam
356 .
357
358 $deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
359 GraphViz
360 IPC::Run 0.90
361 .
362
363 $deps{'GD'} = [ text_to_hash( << '.') ];
364 GD
365 GD::Graph 1.47
366 GD::Text
367 .
368
369 $deps{'USERLOGO'} = [ text_to_hash( << '.') ];
370 Convert::Color
371 .
372
373 $deps{'HTML-DOC'} = [ text_to_hash( <<'.') ];
374 HTML::Entities
375 Pod::Simple 3.24
376 .
377
378 my %AVOID = (
379     'DBD::Oracle' => [qw(1.23)],
380     'Devel::StackTrace' => [qw(1.28 1.29)],
381     'DateTime::Locale' => [qw(1.00 1.01)]
382 );
383
384 if ($args{'download'}) {
385     download_mods();
386 }
387
388
389 check_perl_version();
390
391 check_users();
392
393 my %Missing_By_Type = ();
394 foreach my $type (sort grep $args{$_}, keys %args) {
395     next unless ($type =~ /^with-(.*?)$/) and $deps{$1};
396
397     $type = $1;
398     section("$type dependencies");
399
400     my @missing;
401     my @deps = @{ $deps{$type} };
402
403     my %missing = test_deps(@deps);
404
405     if ( $args{'install'} ) {
406         for my $module (keys %missing) {
407             resolve_dep($module, $missing{$module}{version});
408             my $m = $module . '.pm';
409             $m =~ s!::!/!g;
410             if ( delete $INC{$m} ) {
411                 my $symtab = $module . '::';
412                 no strict 'refs';
413                 for my $symbol ( keys %{$symtab} ) {
414                     next if substr( $symbol, -2, 2 ) eq '::';
415                     delete $symtab->{$symbol};
416                 }
417             }
418             delete $missing{$module}
419                 if test_dep($module, $missing{$module}{version}, $AVOID{$module});
420         }
421     }
422
423     $Missing_By_Type{$type} = \%missing if keys %missing;
424 }
425
426 if ( $args{'install'} && keys %Missing_By_Type ) {
427     exec($script_path, @orig_argv, '--no-install');
428 }
429 else {
430     conclude(%Missing_By_Type);
431 }
432
433 sub test_deps {
434     my @deps = @_;
435
436     my %missing;
437     while(@deps) {
438         my $module = shift @deps;
439         my $version = shift @deps;
440         my($test, $error) = test_dep($module, $version, $AVOID{$module});
441         my $msg = $module . ($version && !$error ? " >= $version" : '');
442         print_found($msg, $test, $error);
443
444         $missing{$module} = { version => $version, error => $error } unless $test;
445     }
446
447     return %missing;
448 }
449
450 sub test_dep {
451     my $module = shift;
452     my $version = shift;
453     my $avoid = shift;
454
455     if ( $args{'list-deps'} ) {
456         print $module, ': ', $version || 0, "\n"; 
457     }
458     else {
459         no warnings 'deprecated';
460         eval "{ local \$ENV{__WARN__}; use $module $version () }";
461         if ( my $error = $@ ) {
462             return 0 unless wantarray;
463
464             $error =~ s/\n(.*)$//s;
465             $error =~ s/at \(eval \d+\) line \d+\.$//;
466             undef $error if $error =~ /this is only/;
467
468             my $path = $module;
469             $path =~ s{::}{/}g;
470             undef $error if defined $error and $error =~ /^Can't locate $path\.pm in \@INC/;
471
472             return ( 0, $error );
473         }
474         
475         if ( $avoid ) {
476             my $version = $module->VERSION;
477             if ( grep $version eq $_, @$avoid ) {
478                 return 0 unless wantarray;
479                 return (0, "It's known that there are problems with RT and version '$version' of '$module' module. If it's the latest available version of the module then you have to downgrade manually.");
480             }
481         }
482
483         return 1;
484     }
485 }
486
487 sub resolve_dep {
488     my $module = shift;
489     my $version = shift;
490
491     unless (defined $args{siteinstall}) {
492         require Config;
493         my %uniq;
494         my @order = grep {($_ eq $Config::Config{sitelibexp}
495                         or $_ eq $Config::Config{privlibexp})
496                         and not $uniq{$_}++} @INC;
497         if ($] < 5.011 and @order == 2
498                 and $order[0] eq $Config::Config{sitelibexp}
499                 and $order[1] eq $Config::Config{privlibexp}) {
500
501             print "\n";
502             print "Patched perl, with site_perl before core in \@INC, detected.\n";
503             print "Installing dual-life modules into site_perl so they are not\n";
504             print "later overridden by the distribution's package.\n";
505
506             $args{siteinstall} = 1;
507         } else {
508             $args{siteinstall} = 0;
509         }
510     }
511
512     print "\nInstall module $module\n";
513
514     my $ext = $ENV{'RT_FIX_DEPS_CMD'} || $ENV{'PERL_PREFER_CPAN_CLIENT'};
515     unless( $ext ) {
516         my $configured = 1;
517         {
518             local @INC = @INC;
519             if ( $ENV{'HOME'} ) {
520                 unshift @INC, "$ENV{'HOME'}/.cpan";
521             }
522             $configured = eval { require CPAN::MyConfig } || eval { require CPAN::Config };
523         }
524         unless ( $configured ) {
525             print <<END;
526 You haven't configured the CPAN shell yet.
527 Please run `@PERL@ -MCPAN -e shell` to configure it.
528 END
529             exit(1);
530         }
531
532         my $installdirs = $CPAN::Config->{makepl_arg} ||= "";
533         $installdirs =~ s/(\bINSTALLDIRS=\S+|$)/ INSTALLDIRS=site/
534             if $args{siteinstall};
535         local $CPAN::Config->{makepl_arg} = $installdirs;
536
537         my $rv = eval { require CPAN; CPAN::Shell->install($module) };
538         return $rv unless $@;
539
540         print <<END;
541 Failed to load module CPAN.
542
543 -------- Error ---------
544 $@
545 ------------------------
546
547 When we tried to start installing RT's perl dependencies, 
548 we were unable to load the CPAN client. This module is usually distributed
549 with Perl. This usually indicates that your vendor has shipped an unconfigured
550 or incorrectly configured CPAN client.
551 The error above may (or may not) give you a hint about what went wrong
552
553 You have several choices about how to install dependencies in 
554 this situatation:
555
556 1) use a different tool to install dependencies by running setting the following
557    shell environment variable and rerunning this tool:
558     RT_FIX_DEPS_CMD='@PERL@ -MCPAN -e"install %s"'
559 2) Attempt to configure CPAN by running:
560    `@PERL@ -MCPAN -e shell` program from shell.
561    If this fails, you may have to manually upgrade CPAN (see below)
562 3) Try to update the CPAN client. Download it from:
563    http://search.cpan.org/dist/CPAN and try again
564 4) Install each dependency manually by downloading them one by one from
565    http://search.cpan.org
566
567 END
568         exit(1);
569     }
570
571     if( $ext =~ /\%s/) {
572         $ext =~ s/\%s/$module/g; # sprintf( $ext, $module );
573     } else {
574         $ext .= " $module";
575     }
576     print "\t\tcommand: '$ext'\n";
577     return scalar `$ext 1>&2`;
578 }
579
580 sub check_perl_version {
581   section("perl");
582   eval {require 5.010_001};
583   if ($@) {
584     print_found("5.10.1", 0, sprintf("RT requires Perl v5.10.1 or newer. Your current Perl is v%vd", $^V));
585     exit(1);
586   } else {
587     print_found( sprintf(">=5.10.1(%vd)", $^V), 1 );
588   }
589 }
590
591 sub check_users {
592   section("users");
593   print_found("rt group (@RTGROUP@)",      defined getgrnam("@RTGROUP@"));
594   print_found("bin owner (@BIN_OWNER@)",   defined getpwnam("@BIN_OWNER@"));
595   print_found("libs owner (@LIBS_OWNER@)", defined getpwnam("@LIBS_OWNER@"));
596   print_found("libs group (@LIBS_GROUP@)", defined getgrnam("@LIBS_GROUP@"));
597   print_found("web owner (@WEB_USER@)",    defined getpwnam("@WEB_USER@"));
598   print_found("web group (@WEB_GROUP@)",   defined getgrnam("@WEB_GROUP@"));
599 }
600
601 1;
602
603 __END__
604
605 =head1 NAME
606
607 rt-test-dependencies - test rt's dependencies
608
609 =head1 SYNOPSIS
610
611     rt-test-dependencies
612     rt-test-dependencies --install
613     rt-test-dependencies --with-mysql --with-fastcgi
614
615 =head1 DESCRIPTION
616
617 by default, C<rt-test-dependencies> determines whether you have installed all
618 the perl modules RT needs to run.
619
620 the "RT_FIX_DEPS_CMD" environment variable, if set, will be used instead of
621 the standard CPAN shell by --install to install any required modules.  it will
622 be called with the module name, or, if "RT_FIX_DEPS_CMD" contains a "%s", will
623 replace the "%s" with the module name before calling the program.
624
625 =head1 OPTIONS
626
627 =over
628
629 =item install
630
631     install missing modules
632
633 =item verbose
634
635 list the status of all dependencies, rather than just the missing ones.
636
637 -v is equal to --verbose
638
639 =item specify dependencies
640
641 =over
642
643 =item --with-mysql
644
645 database interface for mysql
646
647 =item --with-pg
648
649 database interface for postgresql
650
651 =item --with-oracle
652
653 database interface for oracle
654
655 =item --with-sqlite
656
657 database interface and driver for sqlite (unsupported)
658
659 =item --with-fastcgi
660
661 libraries needed to support the fastcgi handler
662
663 =item --with-modperl1
664
665 libraries needed to support the modperl 1 handler
666
667 =item --with-modperl2
668
669 libraries needed to support the modperl 2 handler
670
671 =item --with-developer
672
673 tools needed for RT development
674
675 =back
676
677 =back
678