6fa29761e82cfcc5c98c8eea5d616a2820bc6c7d
[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-2015 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!',                           'with-MYSQL',
68     'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE',
69     'with-ORACLE',                        'with-FASTCGI',
70     'with-MODPERL1',                      'with-MODPERL2',
71     'with-STANDALONE',
72
73     'with-DEV',
74
75     'with-GPG',
76     'with-ICAL',
77     'with-SMTP',
78     'with-GRAPHVIZ',
79     'with-GD',
80     'with-DASHBOARDS',
81     'with-USERLOGO',
82     'with-SSL-MAILGATE',
83     'with-HTML-DOC',
84
85     'download=s',
86     'repository=s',
87     'list-deps',
88     'help|h',
89 );
90
91 if ( $args{help} ) {
92     require Pod::Usage;
93     Pod::Usage::pod2usage( { verbose => 2 } );
94     exit;
95 }
96
97 # Set up defaults
98 my %default = (
99     'with-MASON' => 1,
100     'with-PSGI' => 0,
101     'with-CORE' => 1,
102     'with-CLI' => 1,
103     'with-MAILGATE' => 1, 
104     'with-DEVELOPER' => @RT_DEVELOPER@,
105     'with-GPG' => @RT_GPG_DEPS@,
106     'with-SMIME' => @RT_SMIME_DEPS@,
107     'with-ICAL' => 1,
108     'with-SMTP' => 1,
109     'with-GRAPHVIZ' => @RT_GRAPHVIZ@,
110     'with-GD' => @RT_GD@,
111     'with-DASHBOARDS' => 1,
112     'with-USERLOGO' => 1,
113     'with-HTML-DOC' => @RT_DEVELOPER@,
114 );
115 $args{$_} = $default{$_} foreach grep !exists $args{$_}, keys %default;
116
117 {
118   my $section;
119   my %always_show_sections = (
120     perl => 1,
121     users => 1,
122   );
123
124   sub section {
125     my $s = shift;
126     $section = $s;
127     print "$s:\n" unless $args{'list-deps'};
128   }
129
130   sub print_found {
131     my $msg = shift;
132     my $test = shift;
133     my $extra = shift;
134
135     unless ( $args{'list-deps'} ) {
136         if ( $args{'v'} or not $test or $always_show_sections{$section} ) {
137             print "\t$msg ...";
138             print $test ? "found" : "MISSING";
139             print "\n";
140         }
141
142         print "\t\t$extra\n" if defined $extra;
143     }
144   }
145 }
146
147 sub conclude {
148     my %missing_by_type = @_;
149
150     unless ( $args{'list-deps'} ) {
151         unless ( keys %missing_by_type ) {
152             print "\nAll dependencies have been found.\n";
153             return;
154         }
155
156         print "\nSOME DEPENDENCIES WERE MISSING.\n";
157
158         for my $type ( keys %missing_by_type ) {
159             my $missing = $missing_by_type{$type};
160
161             print "$type missing dependencies:\n";
162             for my $name ( keys %$missing ) {
163                 my $module  = $missing->{$name};
164                 my $version = $module->{version};
165                 my $error = $module->{error};
166                 print_found( $name . ( $version && !$error ? " >= $version" : "" ),
167                     0, $module->{error} );
168             }
169         }
170         exit 1;
171     }
172 }
173
174 sub text_to_hash {
175     my %hash;
176     for my $line ( split /\n/, $_[0] ) {
177         my($key, $value) = $line =~ /(\S+)\s*(\S*)/;
178         $value ||= '';
179         $hash{$key} = $value;
180     }
181
182     return %hash;
183 }
184 sub set_dep {
185     my ($name, $module, $version) = @_;
186     my %list = @{$deps{$name}};
187     $list{$module} = ($version || '');
188     $deps{$name} = [ %list ];
189 }
190
191 $deps{'CORE'} = [ text_to_hash( << '.') ];
192 Class::Accessor 0.34
193 DateTime 0.44
194 DateTime::Locale 0.40
195 Digest::base
196 Digest::MD5 2.27
197 Digest::SHA
198 DBI 1.37
199 Class::ReturnValue 0.40
200 DBIx::SearchBuilder 1.59
201 Text::Template 1.44
202 File::ShareDir
203 File::Spec 0.8
204 HTML::Quoted
205 HTML::Scrubber 0.08
206 HTML::TreeBuilder
207 HTML::FormatText
208 Log::Dispatch 2.23
209 Sys::Syslog 0.16
210 Locale::Maketext 1.06
211 Locale::Maketext::Lexicon 0.32
212 Locale::Maketext::Fuzzy
213 MIME::Entity 5.425
214 Mail::Mailer 1.57
215 Email::Address
216 Text::Wrapper 
217 Time::ParseDate
218 Time::HiRes 
219 File::Temp 0.19
220 Text::Quoted 2.02
221 Tree::Simple 1.04
222 UNIVERSAL::require
223 Regexp::Common
224 Scalar::Util
225 Module::Versions::Report 1.05
226 Cache::Simple::TimedExpiry
227 Encode 2.39
228 CSS::Squish 0.06
229 File::Glob
230 Devel::StackTrace 1.19
231 Text::Password::Pronounceable
232 Devel::GlobalDestruction
233 List::MoreUtils
234 Net::CIDR
235 Regexp::Common::net::CIDR
236 Regexp::IPv6
237 .
238
239 $deps{'MASON'} = [ text_to_hash( << '.') ];
240 HTML::Mason 1.43
241 Errno
242 Digest::MD5 2.27
243 CGI::Cookie 1.20
244 Storable 2.08
245 Apache::Session 1.53
246 XML::RSS 1.05
247 Text::WikiFormat 0.76
248 CSS::Squish 0.06
249 Devel::StackTrace 1.19
250 JSON
251 IPC::Run3
252 .
253
254 $deps{'PSGI'} = [ text_to_hash( << '.') ];
255 CGI 3.38
256 CGI::PSGI 0.12
257 HTML::Mason::PSGIHandler 0.52
258 Plack 0.9971
259 Plack::Handler::Starlet
260 CGI::Emulate::PSGI
261 .
262 set_dep( PSGI => CGI => 4.00 ) if $] > 5.019003;
263
264
265 $deps{'MAILGATE'} = [ text_to_hash( << '.') ];
266 Getopt::Long
267 LWP::UserAgent
268 Pod::Usage
269 .
270
271 $deps{'SSL-MAILGATE'} = [ text_to_hash( << '.') ];
272 Crypt::SSLeay
273 Net::SSL
274 LWP::UserAgent 6.0
275 LWP::Protocol::https
276 .
277
278 $deps{'CLI'} = [ text_to_hash( << '.') ];
279 Getopt::Long 2.24
280 LWP
281 HTTP::Request::Common
282 Text::ParseWords
283 Term::ReadLine
284 Term::ReadKey
285 .
286
287 $deps{'DEV'} = [ text_to_hash( << '.') ];
288 Email::Abstract
289 Test::Email
290 HTML::Form
291 HTML::TokeParser
292 WWW::Mechanize 1.52
293 Test::WWW::Mechanize 1.30
294 Module::Refresh 0.03
295 Test::Expect 0.31
296 XML::Simple
297 File::Find
298 Test::Deep 0 # needed for shredder tests
299 String::ShellQuote 0 # needed for gnupg-incoming.t
300 Log::Dispatch::Perl
301 Test::Warn
302 Test::Builder 0.90 # needed for is_passing
303 Test::MockTime
304 Log::Dispatch::Perl
305 Test::WWW::Mechanize::PSGI
306 Plack::Middleware::Test::StashWarnings 0.08
307 Test::LongString
308 Test::NoWarnings
309 Locale::PO
310 .
311
312 $deps{'FASTCGI'} = [ text_to_hash( << '.') ];
313 FCGI 0.74
314 FCGI::ProcManager
315 .
316
317 $deps{'MODPERL1'} = [ text_to_hash( << '.') ];
318 Apache::Request
319 Apache::DBI 0.92
320 .
321
322 $deps{'MODPERL2'} = [ text_to_hash( << '.') ];
323 Apache::DBI
324 HTML::Mason 1.36
325 .
326
327 $deps{'MYSQL'} = [ text_to_hash( << '.') ];
328 DBD::mysql 2.1018
329 .
330
331 $deps{'ORACLE'} = [ text_to_hash( << '.') ];
332 DBD::Oracle
333 .
334
335 $deps{'POSTGRESQL'} = [ text_to_hash( << '.') ];
336 DBIx::SearchBuilder 1.66
337 DBD::Pg 1.43
338 .
339
340 $deps{'SQLITE'} = [ text_to_hash( << '.') ];
341 DBD::SQLite 1.00
342 .
343
344 $deps{'GPG'} = [ text_to_hash( << '.') ];
345 GnuPG::Interface
346 PerlIO::eol
347 .
348
349 $deps{'ICAL'} = [ text_to_hash( << '.') ];
350 Data::ICal
351 .
352
353 $deps{'SMTP'} = [ text_to_hash( << '.') ];
354 Net::SMTP
355 .
356
357 $deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
358 HTML::RewriteAttributes 0.05
359 MIME::Types
360 URI 1.59
361 .
362
363 $deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
364 GraphViz
365 IPC::Run 0.90
366 .
367
368 $deps{'GD'} = [ text_to_hash( << '.') ];
369 GD
370 GD::Graph
371 GD::Text
372 .
373
374 $deps{'USERLOGO'} = [ text_to_hash( << '.') ];
375 Convert::Color
376 .
377
378 $deps{'HTML-DOC'} = [ text_to_hash( <<'.') ];
379 Pod::Simple 3.24
380 HTML::Entities
381 .
382
383 my %AVOID = (
384     'DBD::Oracle' => [qw(1.23)],
385     'Email::Address' => [qw(1.893 1.894)],
386     'Devel::StackTrace' => [qw(1.28 1.29)],
387 );
388
389 if ($args{'download'}) {
390     download_mods();
391 }
392
393
394 check_perl_version();
395
396 check_users();
397
398 my %Missing_By_Type = ();
399 foreach my $type (sort grep $args{$_}, keys %args) {
400     next unless ($type =~ /^with-(.*?)$/) and $deps{$1};
401
402     $type = $1;
403     section("$type dependencies");
404
405     my @missing;
406     my @deps = @{ $deps{$type} };
407
408     my %missing = test_deps(@deps);
409
410     if ( $args{'install'} ) {
411         for my $module (keys %missing) {
412             resolve_dep($module, $missing{$module}{version});
413             my $m = $module . '.pm';
414             $m =~ s!::!/!g;
415             if ( delete $INC{$m} ) {
416                 my $symtab = $module . '::';
417                 no strict 'refs';
418                 for my $symbol ( keys %{$symtab} ) {
419                     next if substr( $symbol, -2, 2 ) eq '::';
420                     delete $symtab->{$symbol};
421                 }
422             }
423             delete $missing{$module}
424                 if test_dep($module, $missing{$module}{version}, $AVOID{$module});
425         }
426     }
427
428     $Missing_By_Type{$type} = \%missing if keys %missing;
429 }
430
431 if ( $args{'install'} && keys %Missing_By_Type ) {
432     exec($script_path, @orig_argv, '--no-install');
433 }
434 else {
435     conclude(%Missing_By_Type);
436 }
437
438 sub test_deps {
439     my @deps = @_;
440
441     my %missing;
442     while(@deps) {
443         my $module = shift @deps;
444         my $version = shift @deps;
445         my($test, $error) = test_dep($module, $version, $AVOID{$module});
446         my $msg = $module . ($version && !$error ? " >= $version" : '');
447         print_found($msg, $test, $error);
448
449         $missing{$module} = { version => $version, error => $error } unless $test;
450     }
451
452     return %missing;
453 }
454
455 sub test_dep {
456     my $module = shift;
457     my $version = shift;
458     my $avoid = shift;
459
460     if ( $args{'list-deps'} ) {
461         print $module, ': ', $version || 0, "\n"; 
462     }
463     else {
464         no warnings 'deprecated';
465         eval "use $module $version ()";
466         if ( my $error = $@ ) {
467             return 0 unless wantarray;
468
469             $error =~ s/\n(.*)$//s;
470             $error =~ s/at \(eval \d+\) line \d+\.$//;
471             undef $error if $error =~ /this is only/;
472
473             return ( 0, $error );
474         }
475         
476         if ( $avoid ) {
477             my $version = $module->VERSION;
478             if ( grep $version eq $_, @$avoid ) {
479                 return 0 unless wantarray;
480                 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.");
481             }
482         }
483
484         return 1;
485     }
486 }
487
488 sub resolve_dep {
489     my $module = shift;
490     my $version = shift;
491
492     print "\nInstall module $module\n";
493
494     my $ext = $ENV{'RT_FIX_DEPS_CMD'} || $ENV{'PERL_PREFER_CPAN_CLIENT'};
495     unless( $ext ) {
496         my $configured = 1;
497         {
498             local @INC = @INC;
499             if ( $ENV{'HOME'} ) {
500                 unshift @INC, "$ENV{'HOME'}/.cpan";
501             }
502             $configured = eval { require CPAN::MyConfig } || eval { require CPAN::Config };
503         }
504         unless ( $configured ) {
505             print <<END;
506 You haven't configured the CPAN shell yet.
507 Please run `@PERL@ -MCPAN -e shell` to configure it.
508 END
509             exit(1);
510         }
511         my $rv = eval { require CPAN; CPAN::Shell->install($module) };
512         return $rv unless $@;
513
514         print <<END;
515 Failed to load module CPAN.
516
517 -------- Error ---------
518 $@
519 ------------------------
520
521 When we tried to start installing RT's perl dependencies, 
522 we were unable to load the CPAN client. This module is usually distributed
523 with Perl. This usually indicates that your vendor has shipped an unconfigured
524 or incorrectly configured CPAN client.
525 The error above may (or may not) give you a hint about what went wrong
526
527 You have several choices about how to install dependencies in 
528 this situatation:
529
530 1) use a different tool to install dependencies by running setting the following
531    shell environment variable and rerunning this tool:
532     RT_FIX_DEPS_CMD='@PERL@ -MCPAN -e"install %s"'
533 2) Attempt to configure CPAN by running:
534    `@PERL@ -MCPAN -e shell` program from shell.
535    If this fails, you may have to manually upgrade CPAN (see below)
536 3) Try to update the CPAN client. Download it from:
537    http://search.cpan.org/dist/CPAN and try again
538 4) Install each dependency manually by downloading them one by one from
539    http://search.cpan.org
540
541 END
542         exit(1);
543     }
544
545     if( $ext =~ /\%s/) {
546         $ext =~ s/\%s/$module/g; # sprintf( $ext, $module );
547     } else {
548         $ext .= " $module";
549     }
550     print "\t\tcommand: '$ext'\n";
551     return scalar `$ext 1>&2`;
552 }
553
554 sub download_mods {
555     my %modules;
556     use CPAN;
557     
558     foreach my $key (keys %deps) {
559         my @deps = (@{$deps{$key}});
560         while (@deps) {
561             my $mod = shift @deps;
562             my $ver = shift @deps;
563             next if ($mod =~ /^(DBD-|Apache-Request)/);
564             $modules{$mod} = $ver;
565         }
566     }
567     my @mods = keys %modules;
568     CPAN::get();
569     my $moddir = $args{'download'};
570     foreach my $mod (@mods) {
571         $CPAN::Config->{'build_dir'} = $moddir;
572         CPAN::get($mod);
573     }
574
575     opendir(DIR, $moddir);
576     while ( my $dir = readdir(DIR)) {
577         print "Dir is $dir\n";
578         next if ( $dir =~ /^\.\.?$/);
579
580         # Skip things we've previously tagged
581         my $out = `svn ls $args{'repository'}/tags/$dir`;
582         next if ($out);
583
584         if ($dir =~ /^(.*)-(.*?)$/) {
585             `svn_load_dirs -no_user_input -t tags/$dir -v $args{'repository'} dists/$1 $moddir/$dir`;
586             `rm -rf $moddir/$dir`;
587
588         }
589
590     }
591     closedir(DIR);
592     exit;
593 }
594
595 sub check_perl_version {
596   section("perl");
597   eval {require 5.008003};
598   if ($@) {
599     print_found("5.8.3", 0,"RT is known to be non-functional on versions of perl older than 5.8.3. Please upgrade to 5.8.3 or newer.");
600     exit(1);
601   } else {
602     print_found( sprintf(">=5.8.3(%vd)", $^V), 1 );
603   }
604 }
605
606 sub check_users {
607   section("users");
608   print_found("rt group (@RTGROUP@)",      defined getgrnam("@RTGROUP@"));
609   print_found("bin owner (@BIN_OWNER@)",   defined getpwnam("@BIN_OWNER@"));
610   print_found("libs owner (@LIBS_OWNER@)", defined getpwnam("@LIBS_OWNER@"));
611   print_found("libs group (@LIBS_GROUP@)", defined getgrnam("@LIBS_GROUP@"));
612   print_found("web owner (@WEB_USER@)",    defined getpwnam("@WEB_USER@"));
613   print_found("web group (@WEB_GROUP@)",   defined getgrnam("@WEB_GROUP@"));
614 }
615
616 1;
617
618 __END__
619
620 =head1 NAME
621
622 rt-test-dependencies - test rt's dependencies
623
624 =head1 SYNOPSIS
625
626     rt-test-dependencies
627     rt-test-dependencies --install
628     rt-test-dependencies --with-mysql --with-fastcgi
629
630 =head1 DESCRIPTION
631
632 by default, C<rt-test-dependencies> determines whether you have installed all
633 the perl modules RT needs to run.
634
635 the "RT_FIX_DEPS_CMD" environment variable, if set, will be used instead of
636 the standard CPAN shell by --install to install any required modules.  it will
637 be called with the module name, or, if "RT_FIX_DEPS_CMD" contains a "%s", will
638 replace the "%s" with the module name before calling the program.
639
640 =head1 OPTIONS
641
642 =over
643
644 =item install
645
646     install missing modules
647
648 =item verbose
649
650 list the status of all dependencies, rather than just the missing ones.
651
652 -v is equal to --verbose
653
654 =item specify dependencies
655
656 =over
657
658 =item --with-mysql
659
660     database interface for mysql
661
662 =item --with-postgresql
663
664     database interface for postgresql 
665
666 =item with-oracle       
667     
668     database interface for oracle
669
670 =item with-sqlite 
671
672     database interface and driver for sqlite (unsupported)
673
674 =item with-fastcgi 
675
676     libraries needed to support the fastcgi handler
677
678 =item with-modperl1
679
680     libraries needed to support the modperl 1 handler
681
682 =item with-modperl2
683
684     libraries needed to support the modperl 2 handler
685
686 =item with-dev
687
688     tools needed for RT development
689
690 =back
691
692 =back
693