added RDBMS optional dependency groups for use by DBIC users + tests
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Optional / Dependencies.pm
1 package DBIx::Class::Optional::Dependencies;
2
3 use warnings;
4 use strict;
5
6 use Carp;
7
8 # NO EXTERNAL NON-5.8.1 CORE DEPENDENCIES EVER (e.g. C::A::G)
9 # This module is to be loaded by Makefile.PM on a pristine system
10
11 # POD is generated automatically by calling _gen_pod from the
12 # Makefile.PL in $AUTHOR mode
13
14 my $json_any = {
15   'JSON::Any'                     => '1.22',
16 };
17
18 my $moose_basic = {
19   'Moose'                         => '0.98',
20   'MooseX::Types'                 => '0.21',
21 };
22
23 my $replicated = {
24   %$moose_basic,
25   'Hash::Merge'                   => '0.12',
26 };
27
28 my $admin_basic = {
29   %$moose_basic,
30   %$json_any,
31   'MooseX::Types::Path::Class'    => '0.05',
32   'MooseX::Types::JSON'           => '0.02',
33   'namespace::autoclean'          => '0.09',
34 };
35
36 my $datetime_basic = {
37   'DateTime'                      => '0.55',
38   'DateTime::Format::Strptime'    => '1.2',
39 };
40
41 my $id_shortener = {
42   'Math::BigInt'                  => '1.89',
43   'Math::Base36'                  => '0.07',
44 };
45
46 my $rdbms_sqlite = {
47   'DBD::SQLite'                   => '0',
48 };
49 my $rdbms_pg = {
50   'DBD::Pg'                       => '0',
51 };
52 my $rdbms_mssql_odbc = {
53   'DBD::ODBC'                     => '0',
54 };
55 my $rdbms_mssql_sybase = {
56   'DBD::Sybase'                   => '0',
57 };
58 my $rdbms_mysql = {
59   'DBD::mysql'                    => '0',
60 };
61 my $rdbms_oracle = {
62   'DBD::Oracle'                   => '0',
63   %$id_shortener,
64 };
65 my $rdbms_ase = {
66   'DBD::Sybase'                   => '0',
67 };
68 my $rdbms_db2 = {
69   'DBD::DB2'                      => '0',
70 };
71
72 my $reqs = {
73   dist => {
74     #'Module::Install::Pod::Inherit' => '0.01',
75   },
76
77   replicated => {
78     req => $replicated,
79     pod => {
80       title => 'Storage::Replicated',
81       desc => 'Modules required for L<DBIx::Class::Storage::DBI::Replicated>',
82     },
83   },
84
85   test_replicated => {
86     req => {
87       %$replicated,
88       'Test::Moose'               => '0',
89     },
90   },
91
92
93   admin => {
94     req => {
95       %$admin_basic,
96     },
97     pod => {
98       title => 'DBIx::Class::Admin',
99       desc => 'Modules required for the DBIx::Class administrative library',
100     },
101   },
102
103   admin_script => {
104     req => {
105       %$moose_basic,
106       %$admin_basic,
107       'Getopt::Long::Descriptive' => '0.081',
108       'Text::CSV'                 => '1.16',
109     },
110     pod => {
111       title => 'dbicadmin',
112       desc => 'Modules required for the CLI DBIx::Class interface dbicadmin',
113     },
114   },
115
116   deploy => {
117     req => {
118       'SQL::Translator'           => '0.11006',
119     },
120     pod => {
121       title => 'Storage::DBI::deploy()',
122       desc => 'Modules required for L<DBIx::Class::Storage::DBI/deploy> and L<DBIx::Class::Storage::DBI/deployment_statements>',
123     },
124   },
125
126   id_shortener => {
127     req => $id_shortener,
128   },
129
130   test_pod => {
131     req => {
132       'Test::Pod'                 => '1.41',
133     },
134   },
135
136   test_podcoverage => {
137     req => {
138       'Test::Pod::Coverage'       => '1.08',
139       'Pod::Coverage'             => '0.20',
140     },
141   },
142
143   test_notabs => {
144     req => {
145       'Test::NoTabs'              => '0.9',
146     },
147   },
148
149   test_eol => {
150     req => {
151       'Test::EOL'                 => '0.6',
152     },
153   },
154
155   test_prettydebug => {
156     req => $json_any,
157   },
158
159   test_leaks => {
160     req => {
161       'Test::Memory::Cycle'       => '0',
162       'Devel::Cycle'              => '1.10',
163     },
164   },
165
166   test_dt => {
167     req => $datetime_basic,
168   },
169
170   test_dt_sqlite => {
171     req => {
172       %$datetime_basic,
173       # t/36datetime.t
174       # t/60core.t
175       'DateTime::Format::SQLite'  => '0',
176     },
177   },
178
179   test_dt_mysql => {
180     req => {
181       %$datetime_basic,
182       # t/inflate/datetime_mysql.t
183       # (doesn't need Mysql itself)
184       'DateTime::Format::MySQL'   => '0',
185     },
186   },
187
188   test_dt_pg => {
189     req => {
190       %$datetime_basic,
191       # t/inflate/datetime_pg.t
192       # (doesn't need PG itself)
193       'DateTime::Format::Pg'      => '0.16004',
194     },
195   },
196
197   test_cdbicompat => {
198     req => {
199       'DBIx::ContextualFetch'     => '0',
200       'Class::DBI::Plugin::DeepAbstractSearch' => '0',
201       'Class::Trigger'            => '0',
202       'Time::Piece::MySQL'        => '0',
203       'Clone'                     => '0',
204       'Date::Simple'              => '3.03',
205     },
206   },
207
208   # this is just for completeness as SQLite
209   # is a core dep of DBIC for testing
210   rdbms_sqlite => {
211     req => {
212       %$rdbms_sqlite,
213     },
214     pod => {
215       title => 'SQLite support',
216       desc => 'Modules required to connect to SQLite',
217     },
218   },
219
220   rdbms_pg => {
221     req => {
222       %$rdbms_pg,
223     },
224     pod => {
225       title => 'PostgreSQL support',
226       desc => 'Modules required to connect to PostgreSQL',
227     },
228   },
229
230   rdbms_mssql_odbc => {
231     req => {
232       %$rdbms_mssql_odbc,
233     },
234     pod => {
235       title => 'MSSQL support via DBD::ODBC',
236       desc => 'Modules required to connect to MSSQL via DBD::ODBC',
237     },
238   },
239
240   rdbms_mssql_sybase => {
241     req => {
242       %$rdbms_mssql_sybase,
243     },
244     pod => {
245       title => 'MSSQL support via DBD::Sybase',
246       desc => 'Modules required to connect to MSSQL support via DBD::Sybase',
247     },
248   },
249
250   rdbms_mysql => {
251     req => {
252       %$rdbms_mysql,
253     },
254     pod => {
255       title => 'MySQL support',
256       desc => 'Modules required to connect to MySQL',
257     },
258   },
259
260   rdbms_oracle => {
261     req => {
262       %$rdbms_oracle,
263     },
264     pod => {
265       title => 'Oracle support',
266       desc => 'Modules required to connect to Oracle',
267     },
268   },
269
270   rdbms_ase => {
271     req => {
272       %$rdbms_ase,
273     },
274     pod => {
275       title => 'Sybase ASE support',
276       desc => 'Modules required to connect to Sybase ASE',
277     },
278   },
279
280   rdbms_db2 => {
281     req => {
282       %$rdbms_db2,
283     },
284     pod => {
285       title => 'DB2 support',
286       desc => 'Modules required to connect to DB2',
287     },
288   },
289
290 # the order does matter because the rdbms support group might require
291 # a different version that the test group
292   test_rdbms_pg => {
293     req => {
294       $ENV{DBICTEST_PG_DSN}
295         ? (
296           %$rdbms_pg,
297           'Sys::SigAction'        => '0',
298           'DBD::Pg'               => '2.009002',
299         ) : ()
300     },
301   },
302
303   test_rdbms_mssql_odbc => {
304     req => {
305       $ENV{DBICTEST_MSSQL_ODBC_DSN}
306         ? (
307           %$rdbms_mssql_odbc,
308         ) : ()
309     },
310   },
311
312   test_rdbms_mssql_sybase => {
313     req => {
314       $ENV{DBICTEST_MSSQL_DSN}
315         ? (
316           %$rdbms_mssql_sybase,
317         ) : ()
318     },
319   },
320
321   test_rdbms_mysql => {
322     req => {
323       $ENV{DBICTEST_MYSQL_DSN}
324         ? (
325           %$rdbms_mysql,
326         ) : ()
327     },
328   },
329
330   test_rdbms_oracle => {
331     req => {
332       $ENV{DBICTEST_ORA_DSN}
333         ? (
334           %$rdbms_oracle,
335           'DateTime::Format::Oracle' => '0',
336           'DBD::Oracle'              => '1.24',
337         ) : ()
338     },
339   },
340
341   test_rdbms_ase => {
342     req => {
343       $ENV{DBICTEST_SYBASE_DSN}
344         ? (
345           %$rdbms_ase,
346           'DateTime::Format::Sybase' => '0',
347         ) : ()
348     },
349   },
350
351   test_rdbms_db2 => {
352     req => {
353       $ENV{DBICTEST_DB2_DSN}
354         ? (
355           %$rdbms_db2,
356         ) : ()
357     },
358   },
359
360   test_memcached => {
361     req => {
362       $ENV{DBICTEST_MEMCACHED}
363         ? (
364           'Cache::Memcached' => 0,
365         ) : ()
366     },
367   },
368
369 };
370
371
372 sub req_list_for {
373   my ($class, $group) = @_;
374
375   croak "req_list_for() expects a requirement group name"
376     unless $group;
377
378   my $deps = $reqs->{$group}{req}
379     or croak "Requirement group '$group' does not exist";
380
381   return { %$deps };
382 }
383
384
385 our %req_availability_cache;
386 sub req_ok_for {
387   my ($class, $group) = @_;
388
389   croak "req_ok_for() expects a requirement group name"
390     unless $group;
391
392   return $class->_check_deps($group)->{status};
393 }
394
395 sub req_missing_for {
396   my ($class, $group) = @_;
397
398   croak "req_missing_for() expects a requirement group name"
399     unless $group;
400
401   return $class->_check_deps($group)->{missing};
402 }
403
404 sub req_errorlist_for {
405   my ($class, $group) = @_;
406
407   croak "req_errorlist_for() expects a requirement group name"
408     unless $group;
409
410   return $class->_check_deps($group)->{errorlist};
411 }
412
413 sub _check_deps {
414   my ($class, $group) = @_;
415
416   return $req_availability_cache{$group} ||= do {
417
418     my $deps = $class->req_list_for ($group);
419
420     my %errors;
421     for my $mod (keys %$deps) {
422       my $req_line = "require $mod;";
423       if (my $ver = $deps->{$mod}) {
424         $req_line .= "$mod->VERSION($ver);";
425       }
426
427       eval $req_line;
428
429       $errors{$mod} = $@ if $@;
430     }
431
432     my $res;
433
434     if (keys %errors) {
435       my $missing = join (', ', map { $deps->{$_} ? "$_ >= $deps->{$_}" : $_ } (sort keys %errors) );
436       $missing .= " (see $class for details)" if $reqs->{$group}{pod};
437       $res = {
438         status => 0,
439         errorlist => \%errors,
440         missing => $missing,
441       };
442     }
443     else {
444       $res = {
445         status => 1,
446         errorlist => {},
447         missing => '',
448       };
449     }
450
451     $res;
452   };
453 }
454
455 sub req_group_list {
456   return { map { $_ => { %{ $reqs->{$_}{req} || {} } } } (keys %$reqs) };
457 }
458
459 # This is to be called by the author only (automatically in Makefile.PL)
460 sub _gen_pod {
461   my ($class, $distver) = @_;
462
463   my $modfn = __PACKAGE__ . '.pm';
464   $modfn =~ s/\:\:/\//g;
465
466   my $podfn = __FILE__;
467   $podfn =~ s/\.pm$/\.pod/;
468
469   $distver ||=
470     eval { require DBIx::Class; DBIx::Class->VERSION; }
471       ||
472     die
473 "\n\n---------------------------------------------------------------------\n" .
474 'Unable to load core DBIx::Class module to determine current version, '.
475 'possibly due to missing dependencies. Author-mode autodocumentation ' .
476 "halted\n\n" . $@ .
477 "\n\n---------------------------------------------------------------------\n"
478   ;
479
480   my $sqltver = $class->req_list_for ('deploy')->{'SQL::Translator'}
481     or die "Hrmm? No sqlt dep?";
482
483   my @chunks = (
484     <<'EOC',
485 #########################################################################
486 #####################  A U T O G E N E R A T E D ########################
487 #########################################################################
488 #
489 # The contents of this POD file are auto-generated.  Any changes you make
490 # will be lost. If you need to change the generated text edit _gen_pod()
491 # at the end of $modfn
492 #
493 EOC
494     '=head1 NAME',
495     "$class - Optional module dependency specifications (for module authors)",
496     '=head1 SYNOPSIS',
497     <<"EOS",
498 Somewhere in your build-file (e.g. L<Module::Install>'s Makefile.PL):
499
500   ...
501
502   configure_requires 'DBIx::Class' => '$distver';
503
504   require $class;
505
506   my \$deploy_deps = $class->req_list_for('deploy');
507
508   for (keys %\$deploy_deps) {
509     requires \$_ => \$deploy_deps->{\$_};
510   }
511
512   ...
513
514 Note that there are some caveats regarding C<configure_requires()>, more info
515 can be found at L<Module::Install/configure_requires>
516 EOS
517     '=head1 DESCRIPTION',
518     <<'EOD',
519 Some of the less-frequently used features of L<DBIx::Class> have external
520 module dependencies on their own. In order not to burden the average user
521 with modules he will never use, these optional dependencies are not included
522 in the base Makefile.PL. Instead an exception with a descriptive message is
523 thrown when a specific feature is missing one or several modules required for
524 its operation. This module is the central holding place for  the current list
525 of such dependencies, for DBIx::Class core authors, and DBIx::Class extension
526 authors alike.
527 EOD
528     '=head1 CURRENT REQUIREMENT GROUPS',
529     <<'EOD',
530 Dependencies are organized in C<groups> and each group can list one or more
531 required modules, with an optional minimum version (or 0 for any version).
532 The group name can be used in the
533 EOD
534   );
535
536   for my $group (sort keys %$reqs) {
537     my $p = $reqs->{$group}{pod}
538       or next;
539
540     my $modlist = $reqs->{$group}{req}
541       or next;
542
543     next unless keys %$modlist;
544
545     push @chunks, (
546       "=head2 $p->{title}",
547       "$p->{desc}",
548       '=over',
549       ( map { "=item * $_" . ($modlist->{$_} ? " >= $modlist->{$_}" : '') } (sort keys %$modlist) ),
550       '=back',
551       "Requirement group: B<$group>",
552     );
553   }
554
555   push @chunks, (
556     '=head1 METHODS',
557     '=head2 req_group_list',
558     '=over',
559     '=item Arguments: none',
560     '=item Returns: \%list_of_requirement_groups',
561     '=back',
562     <<'EOD',
563 This method should be used by DBIx::Class packagers, to get a hashref of all
564 dependencies keyed by dependency group. Each key (group name) can be supplied
565 to one of the group-specific methods below.
566 EOD
567
568     '=head2 req_list_for',
569     '=over',
570     '=item Arguments: $group_name',
571     '=item Returns: \%list_of_module_version_pairs',
572     '=back',
573     <<'EOD',
574 This method should be used by DBIx::Class extension authors, to determine the
575 version of modules a specific feature requires in the B<current> version of
576 DBIx::Class. See the L</SYNOPSIS> for a real-world
577 example.
578 EOD
579
580     '=head2 req_ok_for',
581     '=over',
582     '=item Arguments: $group_name',
583     '=item Returns: 1|0',
584     '=back',
585     <<'EOD',
586 Returns true or false depending on whether all modules required by
587 C<$group_name> are present on the system and loadable.
588 EOD
589
590     '=head2 req_missing_for',
591     '=over',
592     '=item Arguments: $group_name',
593     '=item Returns: $error_message_string',
594     '=back',
595     <<"EOD",
596 Returns a single line string suitable for inclusion in larger error messages.
597 This method would normally be used by DBIx::Class core-module author, to
598 indicate to the user that he needs to install specific modules before he will
599 be able to use a specific feature.
600
601 For example if some of the requirements for C<deploy> are not available,
602 the returned string could look like:
603
604  SQL::Translator >= $sqltver (see $class for details)
605
606 The author is expected to prepend the necessary text to this message before
607 returning the actual error seen by the user.
608 EOD
609
610     '=head2 req_errorlist_for',
611     '=over',
612     '=item Arguments: $group_name',
613     '=item Returns: \%list_of_loaderrors_per_module',
614     '=back',
615     <<'EOD',
616 Returns a hashref containing the actual errors that occured while attempting
617 to load each module in the requirement group.
618 EOD
619     '=head1 AUTHOR',
620     'See L<DBIx::Class/CONTRIBUTORS>.',
621     '=head1 LICENSE',
622     'You may distribute this code under the same terms as Perl itself',
623   );
624
625   open (my $fh, '>', $podfn) or croak "Unable to write to $podfn: $!";
626   print $fh join ("\n\n", @chunks);
627   close ($fh);
628 }
629
630 1;