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