1use strict;
2
3use 5.005;
4
5use Data::Dumper;
6use File::Spec;
7
8use lib 'inc';
9
10use Alzabo::Build;
11
12use Getopt::Long qw( :config pass_through );
13
14my %opts;
15GetOptions( 'dist'   => \$opts{dist},
16            'root:s' => \$opts{root},
17            'pg!'    => \$opts{pg},
18            'mysql!' => \$opts{mysql},
19            'automated' => \$opts{automated},
20            'help'   => \$opts{help},
21          );
22$opts{automated} = 1;
23
24if ( $opts{help} )
25{
26    print <<'EOF';
27
28  perl Build.PL [--automated] [--pg] [--mysql]
29
30This script accepts several options:
31
32  --automated         Run without prompts
33
34  --pg                Include prereqs for PostgreSQL support
35
36  --mysql             Include prereqs for MySQL support
37
38  --root              Root dir for storing Alzabos schemas
39
40  --help              What you are reading
41
42EOF
43
44    exit;
45}
46
47{
48    my ( $config, $prereqs, $tests );
49    unless ( $opts{dist} )
50    {
51        ( $config, $prereqs, $tests ) = config();
52        write_config_module($config);
53    }
54    else
55    {
56        $prereqs = dist_prereqs();
57        $config = {};
58    }
59
60    my $build =
61        Alzabo::Build->new( module_name => 'Alzabo',
62                            license => 'perl',
63                            %$prereqs,
64                            sign => 1,
65                          );
66
67    $build->create_build_script;
68
69    $build->notes( test_config => $tests );
70
71    $build->add_to_cleanup( File::Spec->catdir( 't', 'schemas' ),
72                            File::Spec->catfile( 'lib', 'Alzabo', 'Config.pm' ),
73                          );
74}
75
76sub config
77{
78    # try to see if there is an existing Alzabo installation
79    eval { require Alzabo; };
80    eval { require Alzabo::Config; };
81
82    if ( ! $@ &&
83         %Alzabo::Config::CONFIG &&
84         defined Alzabo::Config::root_dir() &&
85         length Alzabo::Config::root_dir() &&
86         -d Alzabo::Config::root_dir() &&
87         Alzabo::Config::available_schemas() && $Alzabo::VERSION < 0.55 )
88    {
89        print <<'EOF';
90
91You appear to have schemas created with an older version of Alzabo.
92If you want to continue to use these, you may need to run the
93convert.pl script in the eg/ directory _before_ installing this
94version of Alzabo.
95
96For newer versions, starting with the transition from 0.64 to 0.65,
97Alzabo automatically converts schemas as needed.
98
99EOF
100
101        exit unless Module::Build->y_n( '  Continue?', 'no' );
102    }
103
104    my %config;
105
106    $config{root_dir} = root_dir();
107
108    my ( $prereqs, $tests ) = features();
109
110    my $test_config = test_config($tests);
111
112    return \%config, $prereqs, $test_config;
113}
114
115sub root_dir
116{
117    my $root_dir =
118        ( $opts{root}
119          ? $opts{root}
120          : %Alzabo::Config::CONFIG
121          ? Alzabo::Config::root_dir()
122          : find_possible_root()
123        );
124
125    return $root_dir if $opts{automated};
126
127    print <<'EOF';
128
129Please select a root directory for Alzabo (schema files will be stored
130under this root.
131EOF
132
133    return Module::Build->prompt( '  Alzabo root?', $root_dir );
134}
135
136sub find_possible_root
137{
138    my @dirs;
139
140    if ( $^O =~ /win/i )
141    {
142	# A bit too thorough?
143	foreach ('C'..'Z')
144	{
145	    unshift @dirs, "$_:\\Program Files";
146	}
147    }
148    else
149    {
150	@dirs = qw( /var/lib /usr/local );
151    }
152
153    unshift @dirs, '/opt' if $^O =~ /solaris/i;
154
155    foreach (@dirs)
156    {
157	$_ .= '/alzabo';
158
159	return $_ if -e $_;
160    }
161
162    return '';
163}
164
165sub features
166{
167    # These are always needed
168    my %prereqs = default_prereqs();
169
170    my ( %tests );
171
172    # extra prereqs for certain features
173    my %features =
174        ( mysql => { phrase   => 'to use the MySQL driver',
175                     requires => { 'DBD::mysql' => 2.1017 },
176                     test     => 'mysql',
177                   },
178
179          pg    => { phrase     => 'to use the PostgreSQL driver',
180                     requires   => { 'DBD::Pg' => 1.13,
181                                     'Text::Balanced' => 0,
182                                     'Digest::MD5' => 0,
183                                   },
184                     test   => 'pg',
185                   },
186        );
187
188    if ( $opts{automated} )
189    {
190        for my $k ( grep { $opts{$_} } keys %features )
191        {
192            _add_to_prereqs( \%prereqs, $features{$k} );
193            $tests{$k} = 1;
194        }
195
196        return \%prereqs, \%tests;
197    }
198
199    print <<'EOF';
200
201The following questions pertain to optional features of Alzabo.  These
202questions help the installer determine what additional system checks
203to perform.
204
205EOF
206
207    foreach my $feature ( map { $features{$_} } sort keys %features )
208    {
209	print "\n";
210
211	my $has = 1;
212        my $mods = '';
213        foreach my $type ( qw( requires recommends ) )
214        {
215            if ( $feature->{$type} )
216            {
217                my $text = "$type";
218                while ( my ( $mod, $ver ) = each %{ $feature->{$type} } )
219                {
220                    $text .= " $mod";
221                    $text .= " ($ver)" if $ver;
222
223                    $has = 0
224                        unless Module::Build->check_installed_version( $mod, $ver );
225                }
226
227                $mods .= ' and ' if $mods;
228                $mods .= $text;
229            }
230        }
231
232	print "\u$feature->{phrase} $mods.\n";
233
234	my $wanted =
235            Module::Build->y_n( "  Do you want $feature->{phrase}?", $has ? 'yes' : 'no' );
236
237	if ($wanted)
238	{
239            _add_to_prereqs( \%prereqs, $feature );
240
241	    $tests{ $feature->{test} } = 1 if exists $feature->{test};
242	}
243    }
244
245    return \%prereqs, \%tests;
246}
247
248sub _add_to_prereqs
249{
250    my $prereqs = shift;
251    my $feature = shift;
252
253    foreach my $type ( grep { $feature->{$_} } qw( requires recommends ) )
254    {
255        $prereqs->{$type} = { %{ $prereqs->{$type} },
256                              %{ $feature->{$type} },
257                            };
258    }
259}
260
261sub default_prereqs
262{
263    return
264        ( requires =>
265          { 'Class::Factory::Util' => 1.3,
266            'DBI' => minimum_dbi_version(),
267            'Digest::MD5' => 0,
268            'Exception::Class' => 0.97,
269            'Params::Validate' => 0.58,
270            'Scalar::Util' => 1.01,
271            'Storable' => 0.7,
272            'Test::Simple' => 0.47,
273            'Test::Harness' => 1.26,
274            'Tie::IxHash' => 0,
275            'Time::HiRes' => 0,
276            perl => 5.006,
277          },
278          recommends => {},
279          build_requires => { 'Pod::Man' => 1.14 },
280        );
281}
282
283sub dist_prereqs
284{
285    my %prereqs = default_prereqs();
286
287    $prereqs{requires}{DBI} = 1.21;
288
289    $prereqs{recommends}{'DBD::mysql'} = 2.1017;
290    $prereqs{recommends}{'DBD::Pg'} = 1.13;
291
292    return \%prereqs;
293}
294
295sub minimum_dbi_version
296{
297    if ( eval { require DBI } && $DBI::VERSION == 1.24 )
298    {
299        warn <<'EOF';
300You appear to have DBI version 1.24 installed.  This version has a bug
301which causes major problems with Alzabo.  Please upgrade or downgrade.
302EOF
303        return 1.25;
304    }
305
306    return 1.21;
307}
308
309sub write_config_module
310{
311    my $config = shift;
312
313    # config items that the config module cares about
314    my @keys = qw( root_dir );
315
316    my $file = File::Spec->catfile( 'inc', 'Alzabo', 'Config.pm.tmpl' );
317    local *MOD;
318    open MOD, "<$file"
319	or die "can't open $file: $!\n";
320    my $mod = join '', <MOD>;
321    close MOD
322	or die "can't close $file: $!\n";
323
324    my $c = "(\n";
325    foreach my $k (@keys)
326    {
327	my $val;
328	if ( length $config->{$k} )
329	{
330	    $val = "'$config->{$k}'";
331	}
332	else
333	{
334	    $val = "undef";
335	}
336
337	$c .= "'$k' => $val,\n";
338    }
339    $c .= ")";
340
341    $mod =~ s/"'CONFIG'"/$c/;
342
343    my $config_pm = File::Spec->catfile( 'lib', 'Alzabo', 'Config.pm' );
344    open MOD, '>', $config_pm
345	or die "can't write to $config_pm: $!\n";
346    print MOD $mod
347	or die "can't write to $config_pm: $!\n";
348    close MOD
349	or die "can't close $config_pm: $!\n";
350}
351
352sub test_config
353{
354    my $tests = shift;
355
356    return if $opts{automated};
357
358    my @config;
359
360    my %names = ( mysql => 'Mysql',
361		  pg => 'Postgres',
362		  oracle => 'Oracle' );
363
364    foreach my $t ( sort keys %$tests )
365    {
366	my $name = $names{$t};
367
368	print <<'EOF';
369
370The information from the following questions are used solely for
371testing the pieces of Alzabo that require a real database for proper
372testing.
373EOF
374
375	my $do = Module::Build->prompt( "  Do tests with $name RDBMS?", 'yes' );
376	next unless $do =~ /^y/i;
377
378	print <<"EOF";
379
380Please provide a username that can be used to connect to the $name
381RDBMS?  This user must have the ability to create a new
382database/schema.
383EOF
384
385	my $user = Module::Build->prompt( '  Username?' );
386	my $password;
387	if ($user)
388	{
389	    $password = Module::Build->prompt( "  Password for $user?" );
390	}
391
392	print <<"EOF";
393
394What host is the $name RDBMS located on.  Press enter to skip this if
395the database server is located on the localhost or can be determined
396in another way (for example, Oracle can use TNS to find the database).
397EOF
398
399	my $host = Module::Build->prompt( '  Host?' );
400
401	print <<"EOF";
402
403What port is the $name RDBMS located on.  Press enter to skip this.
404EOF
405
406	my $port = Module::Build->prompt( '  Port?' );
407
408	print <<'EOF';
409
410Please provide a database name that can be used for testing.  A
411database/schema with this name will be created and dropped during the
412testing process.
413EOF
414
415	my $db_name = Module::Build->prompt( '  Database name?', "test_alzabo_$t" );
416
417        push @config,
418        { rdbms    => $t,
419          user     => $user,
420          password => $password,
421          host     => $host,
422          port     => $port,
423          schema_name => $db_name,
424        };
425    }
426
427    return \@config;
428}
429