fix rt-session-viewer mucking up upgrades
[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-2009 Best Practical Solutions, LLC
7 #                                          <jesse@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 no warnings qw(numeric redefine);
56 use Getopt::Long;
57 my %args;
58 my %deps;
59 GetOptions(
60     \%args,                               'v|verbose',
61     'install',                            'with-MYSQL',
62     'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE',
63     'with-ORACLE',                        'with-FASTCGI', 'with-FASTCGI-SERVER',
64     'with-SPEEDYCGI',                     'with-MODPERL1',
65     'with-MODPERL2',                      'with-DEV',
66     'with-STANDALONE',
67
68     'with-GPG',
69     'with-ICAL',
70     'with-SMTP',
71     'with-GRAPHVIZ',
72     'with-GD',
73     'with-DASHBOARDS',
74
75     'download=s',
76     'repository=s',
77     'list-deps'
78 );
79
80 unless (keys %args) {
81     help();
82     exit(1);
83 }
84
85 # Set up defaults
86 my %default = (
87     'with-MASON' => 1,
88     'with-CORE' => 1,
89     'with-CLI' => 1,
90     'with-MAILGATE' => 1, 
91     'with-DEV' => @RT_DEVEL_MODE@, 
92     'with-STANDALONE' => 1,
93     'with-GPG' => @RT_GPG@,
94     'with-ICAL' => 1,
95     'with-SMTP' => 1,
96     'with-GRAPHVIZ' => @RT_GRAPHVIZ@,
97     'with-GD' => @RT_GD@,
98     'with-DASHBOARDS' => 1
99 );
100 $args{$_} = $default{$_} foreach grep !exists $args{$_}, keys %default;
101
102 {
103   my $section;
104   my %always_show_sections = (
105     perl => 1,
106     users => 1,
107   );
108
109   sub section {
110     my $s = shift;
111     $section = $s;
112     print "$s:\n" unless $args{'list-deps'};
113   }
114
115   sub print_found {
116     my $msg = shift;
117     my $test = shift;
118     my $extra = shift;
119
120     unless ( $args{'list-deps'} ) {
121         if ( $args{'v'} or not $test or $always_show_sections{$section} ) {
122             print "\t$msg ...";
123             print $test ? "found" : "MISSING";
124             print "\n";
125         }
126
127         print "\t\t$extra\n" if defined $extra;
128     }
129   }
130 }
131
132 sub conclude {
133     my %missing_by_type = @_;
134
135     unless ( $args{'list-deps'} ) {
136         unless ( keys %missing_by_type ) {
137             print "\nAll dependencies have been found.\n";
138             return;
139         }
140
141         print "\nSOME DEPENDENCIES WERE MISSING.\n";
142
143         for my $type ( keys %missing_by_type ) {
144             my $missing = $missing_by_type{$type};
145
146             print "$type missing dependencies:\n";
147             for my $name ( keys %$missing ) {
148                 my $module  = $missing->{$name};
149                 my $version = $module->{version};
150                 my $error = $module->{error};
151                 print_found( $name . ( $version && !$error ? " >= $version" : "" ),
152                     0, $module->{error} );
153             }
154         }
155         exit 1;
156     }
157 }
158
159
160 sub help {
161
162     print <<'.';
163
164 By default, testdeps determine whether you have 
165 installed all the perl modules RT needs to run.
166
167     --install           Install missing modules
168
169 The following switches will tell the tool to check for specific dependencies
170
171     --with-mysql        Database interface for MySQL
172     --with-postgresql   Database interface for PostgreSQL 
173     --with-oracle       Database interface for Oracle
174     --with-sqlite       Database interface and driver for SQLite (unsupported)
175
176     --with-standalone     Libraries needed to support the standalone simple pure perl server
177     --with-fastcgi-server Libraries needed to support the external fastcgi server
178     --with-fastcgi        Libraries needed to support the fastcgi handler
179     --with-speedycgi      Libraries needed to support the speedycgi handler
180     --with-modperl1       Libraries needed to support the modperl 1 handler
181     --with-modperl2       Libraries needed to support the modperl 2 handler
182
183     --with-dev          Tools needed for RT development
184
185 You can also specify -v or --verbose to list the status of all dependencies,
186 rather than just the missing ones.
187
188 The "RT_FIX_DEPS_CMD" environment variable, if set, will be used
189 instead of the standard CPAN shell by --install to install any
190 required modules.  It will be called with the module name, or, if
191 "RT_FIX_DEPS_CMD" contains a "%s", will replace the "%s" with the
192 module name before calling the program.
193 .
194 }
195
196
197 sub text_to_hash {
198     my %hash;
199     for my $line ( split /\n/, $_[0] ) {
200         my($key, $value) = $line =~ /(\S+)\s*(\S*)/;
201         $value ||= '';
202         $hash{$key} = $value;
203     }
204
205     return %hash;
206 }
207
208 $deps{'CORE'} = [ text_to_hash( << '.') ];
209 Digest::base
210 Digest::MD5 2.27
211 DBI 1.37
212 Class::ReturnValue 0.40
213 DBIx::SearchBuilder 1.54
214 Text::Template 1.44
215 File::ShareDir
216 File::Spec 0.8
217 HTML::Entities 
218 HTML::Scrubber 0.08
219 Log::Dispatch 2.0
220 Sys::Syslog 0.16
221 Locale::Maketext 1.06
222 Locale::Maketext::Lexicon 0.32
223 Locale::Maketext::Fuzzy
224 MIME::Entity 5.425
225 Mail::Mailer 1.57
226 Email::Address
227 Text::Wrapper 
228 Time::ParseDate
229 Time::HiRes 
230 File::Temp 0.18
231 Text::Quoted 2.02
232 Tree::Simple 1.04
233 UNIVERSAL::require
234 Regexp::Common
235 Scalar::Util
236 Module::Versions::Report 1.05
237 Cache::Simple::TimedExpiry
238 Calendar::Simple
239 Encode 2.21
240 CSS::Squish 0.06
241 File::Glob
242 Devel::StackTrace 1.19
243 .
244
245 $deps{'MASON'} = [ text_to_hash( << '.') ];
246 HTML::Mason 1.36
247 Errno
248 Digest::MD5 2.27
249 CGI::Cookie 1.20
250 Storable 2.08
251 Apache::Session 1.53
252 XML::RSS 1.05
253 Text::WikiFormat 0.76
254 CSS::Squish 0.06
255 Devel::StackTrace 1.19
256 .
257
258 $deps{'STANDALONE'} = [ text_to_hash( << '.') ];
259 HTTP::Server::Simple 0.34
260 HTTP::Server::Simple::Mason 0.09
261 Net::Server
262 .
263
264 $deps{'MAILGATE'} = [ text_to_hash( << '.') ];
265 HTML::TreeBuilder
266 HTML::FormatText
267 Getopt::Long
268 LWP::UserAgent
269 Pod::Usage
270 .
271
272 $deps{'CLI'} = [ text_to_hash( << '.') ];
273 Getopt::Long 2.24
274 LWP
275 HTTP::Request::Common
276 Text::ParseWords
277 Term::ReadLine
278 Term::ReadKey
279 .
280
281 $deps{'DEV'} = [ text_to_hash( << '.') ];
282 HTML::Form
283 HTML::TokeParser
284 WWW::Mechanize
285 Test::WWW::Mechanize 1.04
286 Module::Refresh 0.03
287 Test::Expect 0.31
288 XML::Simple
289 File::Find
290 Test::Deep 0 # needed for shredder tests
291 String::ShellQuote 0 # needed for gnupg-incoming.t
292 Test::HTTP::Server::Simple 0.09
293 Test::HTTP::Server::Simple::StashWarnings 0.02
294 Log::Dispatch::Perl
295 Test::Warn
296 Test::Builder 0.77 # needed to fix TODO test
297 IPC::Run3
298 Test::MockTime
299 HTTP::Server::Simple::Mason 0.13
300 Log::Dispatch::Perl
301 .
302
303 $deps{'FASTCGI'} = [ text_to_hash( << '.') ];
304 CGI 3.38
305 FCGI
306 CGI::Fast 
307 .
308
309 $deps{'FASTCGI-SERVER'} = [ text_to_hash( << '.') ];
310 CGI 3.38
311 CGI::Fast
312 FCGI::ProcManager
313 File::Basename
314 File::Spec
315 Getopt::Long
316 Pod::Usage
317 .
318
319 $deps{'SPEEDYCGI'} = [ text_to_hash( << '.') ];
320 CGI 3.38
321 CGI::SpeedyCGI
322 .
323
324
325 $deps{'MODPERL1'} = [ text_to_hash( << '.') ];
326 CGI 3.38
327 Apache::Request
328 Apache::DBI 0.92
329 .
330
331 $deps{'MODPERL2'} = [ text_to_hash( << '.') ];
332 CGI 3.38
333 Apache::DBI
334 HTML::Mason 1.36
335 .
336
337 $deps{'MYSQL'} = [ text_to_hash( << '.') ];
338 DBD::mysql 2.1018
339 .
340
341 $deps{'ORACLE'} = [ text_to_hash( << '.') ];
342 DBD::Oracle
343 .
344
345 $deps{'POSTGRESQL'} = [ text_to_hash( << '.') ];
346 DBD::Pg 1.43
347 .
348
349 $deps{'SQLITE'} = [ text_to_hash( << '.') ];
350 DBD::SQLite 1.00
351 .
352
353 $deps{'GPG'} = [ text_to_hash( << '.') ];
354 GnuPG::Interface
355 PerlIO::eol
356 .
357
358 $deps{'ICAL'} = [ text_to_hash( << '.') ];
359 Data::ICal
360 .
361
362 $deps{'SMTP'} = [ text_to_hash( << '.') ];
363 Net::SMTP
364 .
365
366 $deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
367 HTML::RewriteAttributes 0.02
368 MIME::Types
369 .
370
371 $deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
372 GraphViz
373 IPC::Run
374 IPC::Run::SafeHandles
375 .
376
377 $deps{'GD'} = [ text_to_hash( << '.') ];
378 GD
379 GD::Graph
380 GD::Text
381 .
382
383 my %AVOID = (
384     'DBD::Oracle' => [qw(1.23)],
385 );
386
387 if ($args{'download'}) {
388     download_mods();
389 }
390
391
392 check_perl_version();
393
394 check_users();
395
396 my %Missing_By_Type = ();
397 foreach my $type (sort grep $args{$_}, keys %args) {
398     next unless ($type =~ /^with-(.*?)$/);
399
400     $type = $1;
401     section("$type dependencies");
402
403     my @missing;
404     my @deps = @{ $deps{$type} };
405
406     my %missing = test_deps(@deps);
407
408     if ( $args{'install'} ) {
409         for my $module (keys %missing) {
410             resolve_dep($module, $missing{$module}{version});
411             delete $missing{$module}
412                 if test_dep($module, $missing{$module}{version}, $AVOID{$module});
413         }
414     }
415
416     $Missing_By_Type{$type} = \%missing if keys %missing;
417 }
418
419 conclude(%Missing_By_Type);
420
421 sub test_deps {
422     my @deps = @_;
423
424     my %missing;
425     while(@deps) {
426         my $module = shift @deps;
427         my $version = shift @deps;
428         my($test, $error) = test_dep($module, $version, $AVOID{$module});
429         my $msg = $module . ($version && !$error ? " >= $version" : '');
430         print_found($msg, $test, $error);
431
432         $missing{$module} = { version => $version, error => $error } unless $test;
433     }
434
435     return %missing;
436 }
437
438 sub test_dep {
439     my $module = shift;
440     my $version = shift;
441     my $avoid = shift;
442
443     if ( $args{'list-deps'} ) {
444         print $module, ': ', $version || 0, "\n"; 
445     }
446     else {
447         eval "use $module $version ()";
448         if ( my $error = $@ ) {
449             return 0 unless wantarray;
450
451             $error =~ s/\n(.*)$//s;
452             $error =~ s/at \(eval \d+\) line \d+\.$//;
453             undef $error if $error =~ /this is only/;
454
455             return ( 0, $error );
456         }
457         
458         if ( $avoid ) {
459             my $version = $module->VERSION;
460             if ( grep $version eq $_, @$avoid ) {
461                 return 0 unless wantarray;
462                 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.");
463             }
464         }
465
466         return 1;
467     }
468 }
469
470 sub resolve_dep {
471     my $module = shift;
472     my $version = shift;
473
474     print "\nInstall module $module\n";
475
476     my $ext = $ENV{'RT_FIX_DEPS_CMD'};
477     unless( $ext ) {
478         my $configured = 1;
479         {
480             local @INC = @INC;
481             if ( $ENV{'HOME'} ) {
482                 unshift @INC, "$ENV{'HOME'}/.cpan";
483             }
484             $configured = eval { require CPAN::MyConfig } || eval { require CPAN::Config };
485         }
486         unless ( $configured ) {
487             print <<END;
488 You haven't configured the CPAN shell yet.
489 Please run `@PERL@ -MCPAN -e shell` to configure it.
490 END
491             exit(1);
492         }
493         my $rv = eval { require CPAN; CPAN::Shell->install($module) };
494         return $rv unless $@;
495
496         print <<END;
497 Failed to load module CPAN.
498
499 -------- Error ---------
500 $@
501 ------------------------
502
503 When we tried to start installing RT's perl dependencies, 
504 we were unable to load the CPAN client. This module is usually distributed
505 with Perl. This usually indicates that your vendor has shipped an unconfigured
506 or incorrectly configured CPAN client.
507 The error above may (or may not) give you a hint about what went wrong
508
509 You have several choices about how to install dependencies in 
510 this situatation:
511
512 1) use a different tool to install dependencies by running setting the following
513    shell environment variable and rerunning this tool:
514     RT_FIX_DEPS_CMD='@PERL@ -MCPAN -e"install %s"'
515 2) Attempt to configure CPAN by running:
516    `@PERL@ -MCPAN -e shell` program from shell.
517    If this fails, you may have to manually upgrade CPAN (see below)
518 3) Try to update the CPAN client. Download it from:
519    http://search.cpan.org/dist/CPAN and try again
520 4) Install each dependency manually by downloading them one by one from
521    http://search.cpan.org
522
523 END
524         exit(1);
525     }
526
527     if( $ext =~ /\%s/) {
528         $ext =~ s/\%s/$module/g; # sprintf( $ext, $module );
529     } else {
530         $ext .= " $module";
531     }
532     print "\t\tcommand: '$ext'\n";
533     return scalar `$ext 1>&2`;
534 }
535
536 sub download_mods {
537     my %modules;
538     use CPAN;
539     
540     foreach my $key (keys %deps) {
541         my @deps = (@{$deps{$key}});
542         while (@deps) {
543             my $mod = shift @deps;
544             my $ver = shift @deps;
545             next if ($mod =~ /^(DBD-|Apache-Request)/);
546             $modules{$mod} = $ver;
547         }
548     }
549     my @mods = keys %modules;
550     CPAN::get();
551     my $moddir = $args{'download'};
552     foreach my $mod (@mods) {
553         $CPAN::Config->{'build_dir'} = $moddir;
554         CPAN::get($mod);
555     }
556
557     opendir(DIR, $moddir);
558     while ( my $dir = readdir(DIR)) {
559         print "Dir is $dir\n";
560         next if ( $dir =~ /^\.\.?$/);
561
562         # Skip things we've previously tagged
563         my $out = `svn ls $args{'repository'}/tags/$dir`;
564         next if ($out);
565
566         if ($dir =~ /^(.*)-(.*?)$/) {
567             `svn_load_dirs -no_user_input -t tags/$dir -v $args{'repository'} dists/$1 $moddir/$dir`;
568             `rm -rf $moddir/$dir`;
569
570         }
571
572     }
573     closedir(DIR);
574     exit;
575 }
576
577 sub check_perl_version {
578   section("perl");
579   eval {require 5.008003};
580   if ($@) {
581     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.");
582     exit(1);
583   } else {
584     print_found( sprintf(">=5.8.3(%vd)", $^V), 1 );
585   }
586 }
587
588 sub check_users {
589   section("users");
590   print_found("rt group (@RTGROUP@)",      defined getgrnam("@RTGROUP@"));
591   print_found("bin owner (@BIN_OWNER@)",   defined getpwnam("@BIN_OWNER@"));
592   print_found("libs owner (@LIBS_OWNER@)", defined getpwnam("@LIBS_OWNER@"));
593   print_found("libs group (@LIBS_GROUP@)", defined getgrnam("@LIBS_GROUP@"));
594   print_found("web owner (@WEB_USER@)",    defined getpwnam("@WEB_USER@"));
595   print_found("web group (@WEB_GROUP@)",   defined getgrnam("@WEB_GROUP@"));
596 }
597
598
599
600 1;