Clarify optdep API signatures to be explicitly single-argument
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Optional / Dependencies.pm
1 package DBIx::Class::Optional::Dependencies;
2
3 ### This may look crazy, but it in fact tangibly ( by 50(!)% ) shortens
4 #   the skip-test time when everything requested is unavailable
5 use if $ENV{RELEASE_TESTING} => 'warnings';
6 use if $ENV{RELEASE_TESTING} => 'strict';
7
8 sub croak {
9   require Carp;
10   Carp::croak(@_);
11 };
12 ###
13
14 # NO EXTERNAL NON-5.8.1 CORE DEPENDENCIES EVER (e.g. C::A::G)
15 # This module is to be loaded by Makefile.PM on a pristine system
16
17 # POD is generated automatically by calling _gen_pod from the
18 # Makefile.PL in $AUTHOR mode
19
20 # NOTE: the rationale for 2 JSON::Any versions is that
21 # we need the newer only to work around JSON::XS, which
22 # itself is an optional dep
23 my $min_json_any = {
24   'JSON::Any'                     => '1.23',
25 };
26 my $test_and_dist_json_any = {
27   'JSON::Any'                     => '1.31',
28 };
29
30 my $moose_basic = {
31   'Moose'                         => '0.98',
32   'MooseX::Types'                 => '0.21',
33   'MooseX::Types::LoadableClass'  => '0.011',
34 };
35
36 my $dbic_reqs = {
37   replicated => {
38     req => $moose_basic,
39     pod => {
40       title => 'Storage::Replicated',
41       desc => 'Modules required for L<DBIx::Class::Storage::DBI::Replicated>',
42     },
43   },
44
45   test_replicated => {
46     include => 'replicated',
47     req => {
48       'Test::Moose' => '0',
49     },
50   },
51
52   admin => {
53     req => {
54       %$moose_basic,
55       %$min_json_any,
56       'MooseX::Types::Path::Class' => '0.05',
57       'MooseX::Types::JSON' => '0.02',
58     },
59     pod => {
60       title => 'DBIx::Class::Admin',
61       desc => 'Modules required for the DBIx::Class administrative library',
62     },
63   },
64
65   admin_script => {
66     include => 'admin',
67     req => {
68       'Getopt::Long::Descriptive' => '0.081',
69       'Text::CSV' => '1.16',
70     },
71     pod => {
72       title => 'dbicadmin',
73       desc => 'Modules required for the CLI DBIx::Class interface dbicadmin',
74     },
75   },
76
77   deploy => {
78     req => {
79       'SQL::Translator'           => '0.11018',
80     },
81     pod => {
82       title => 'Storage::DBI::deploy()',
83       desc => 'Modules required for L<DBIx::Class::Storage::DBI/deployment_statements> and L<DBIx::Class::Schema/deploy>',
84     },
85   },
86
87   id_shortener => {
88     req => {
89       'Math::BigInt' => '1.80',
90       'Math::Base36' => '0.07',
91     },
92   },
93
94   test_component_accessor => {
95     req => {
96       'Class::Unload'             => '0.07',
97     },
98   },
99
100   test_pod => {
101     req => {
102       'Test::Pod'                 => '1.42',
103     },
104     release_testing_mandatory => 1,
105   },
106
107   test_podcoverage => {
108     req => {
109       'Test::Pod::Coverage'       => '1.08',
110       'Pod::Coverage'             => '0.20',
111     },
112     release_testing_mandatory => 1,
113   },
114
115   test_whitespace => {
116     req => {
117       'Test::EOL'                 => '1.0',
118       'Test::NoTabs'              => '0.9',
119     },
120     release_testing_mandatory => 1,
121   },
122
123   test_strictures => {
124     req => {
125       'Test::Strict'              => '0.20',
126     },
127     release_testing_mandatory => 1,
128   },
129
130   test_prettydebug => {
131     req => $min_json_any,
132   },
133
134   test_admin_script => {
135     include => 'admin_script',
136     req => {
137       %$test_and_dist_json_any,
138       'JSON' => 0,
139       'JSON::PP' => 0,
140       'Cpanel::JSON::XS' => 0,
141       'JSON::XS' => 0,
142       $^O eq 'MSWin32'
143         # for t/admin/10script.t
144         ? ('Win32::ShellQuote' => 0)
145         # DWIW does not compile (./configure even) on win32
146         : ('JSON::DWIW' => 0 )
147       ,
148     }
149   },
150
151   test_leaks_heavy => {
152     req => {
153       'Class::MethodCache' => '0.02',
154       'PadWalker' => '1.06',
155     },
156   },
157
158   test_dt => {
159     req => {
160       'DateTime'                    => '0.55',
161       'DateTime::Format::Strptime'  => '1.2',
162     },
163   },
164
165   test_dt_sqlite => {
166     include => 'test_dt',
167     req => {
168       # t/36datetime.t
169       # t/60core.t
170       'DateTime::Format::SQLite'  => '0',
171     },
172   },
173
174   test_dt_mysql => {
175     include => 'test_dt',
176     req => {
177       # t/inflate/datetime_mysql.t
178       # (doesn't need Mysql itself)
179       'DateTime::Format::MySQL'   => '0',
180     },
181   },
182
183   test_dt_pg => {
184     include => 'test_dt',
185     req => {
186       # t/inflate/datetime_pg.t
187       # (doesn't need PG itself)
188       'DateTime::Format::Pg'      => '0.16004',
189     },
190   },
191
192   test_cdbicompat => {
193     include => 'test_dt',
194     req => {
195       'Class::DBI::Plugin::DeepAbstractSearch' => '0',
196       'Time::Piece::MySQL'        => '0',
197       'Date::Simple'              => '3.03',
198     },
199   },
200
201   rdbms_generic_odbc => {
202     req => {
203       'DBD::ODBC' => 0,
204     }
205   },
206
207   rdbms_generic_ado => {
208     req => {
209       'DBD::ADO' => 0,
210     }
211   },
212
213   # this is just for completeness as SQLite
214   # is a core dep of DBIC for testing
215   rdbms_sqlite => {
216     req => {
217       'DBD::SQLite' => 0,
218     },
219     pod => {
220       title => 'SQLite support',
221       desc => 'Modules required to connect to SQLite',
222     },
223   },
224
225   rdbms_pg => {
226     req => {
227       # when changing this list make sure to adjust xt/optional_deps.t
228       'DBD::Pg' => 0,
229     },
230     pod => {
231       title => 'PostgreSQL support',
232       desc => 'Modules required to connect to PostgreSQL',
233     },
234   },
235
236   rdbms_mssql_odbc => {
237     include => 'rdbms_generic_odbc',
238     pod => {
239       title => 'MSSQL support via DBD::ODBC',
240       desc => 'Modules required to connect to MSSQL via DBD::ODBC',
241     },
242   },
243
244   rdbms_mssql_sybase => {
245     req => {
246       'DBD::Sybase' => 0,
247     },
248     pod => {
249       title => 'MSSQL support via DBD::Sybase',
250       desc => 'Modules required to connect to MSSQL via DBD::Sybase',
251     },
252   },
253
254   rdbms_mssql_ado => {
255     include => 'rdbms_generic_ado',
256     pod => {
257       title => 'MSSQL support via DBD::ADO (Windows only)',
258       desc => 'Modules required to connect to MSSQL via DBD::ADO. This particular DBD is available on Windows only',
259     },
260   },
261
262   rdbms_msaccess_odbc => {
263     include => 'rdbms_generic_odbc',
264     pod => {
265       title => 'MS Access support via DBD::ODBC',
266       desc => 'Modules required to connect to MS Access via DBD::ODBC',
267     },
268   },
269
270   rdbms_msaccess_ado => {
271     include => 'rdbms_generic_ado',
272     pod => {
273       title => 'MS Access support via DBD::ADO (Windows only)',
274       desc => 'Modules required to connect to MS Access via DBD::ADO. This particular DBD is available on Windows only',
275     },
276   },
277
278   rdbms_mysql => {
279     req => {
280       'DBD::mysql' => 0,
281     },
282     pod => {
283       title => 'MySQL support',
284       desc => 'Modules required to connect to MySQL',
285     },
286   },
287
288   rdbms_oracle => {
289     include => 'id_shortener',
290     req => {
291       'DBD::Oracle' => 0,
292     },
293     pod => {
294       title => 'Oracle support',
295       desc => 'Modules required to connect to Oracle',
296     },
297   },
298
299   rdbms_ase => {
300     req => {
301       'DBD::Sybase' => 0,
302     },
303     pod => {
304       title => 'Sybase ASE support',
305       desc => 'Modules required to connect to Sybase ASE',
306     },
307   },
308
309   rdbms_db2 => {
310     req => {
311       'DBD::DB2' => 0,
312     },
313     pod => {
314       title => 'DB2 support',
315       desc => 'Modules required to connect to DB2',
316     },
317   },
318
319   rdbms_db2_400 => {
320     include => 'rdbms_generic_odbc',
321     pod => {
322       title => 'DB2 on AS/400 support',
323       desc => 'Modules required to connect to DB2 on AS/400',
324     },
325   },
326
327   rdbms_informix => {
328     req => {
329       'DBD::Informix' => 0,
330     },
331     pod => {
332       title => 'Informix support',
333       desc => 'Modules required to connect to Informix',
334     },
335   },
336
337   rdbms_sqlanywhere => {
338     req => {
339       'DBD::SQLAnywhere' => 0,
340     },
341     pod => {
342       title => 'SQLAnywhere support',
343       desc => 'Modules required to connect to SQLAnywhere',
344     },
345   },
346
347   rdbms_sqlanywhere_odbc => {
348     include => 'rdbms_generic_odbc',
349     pod => {
350       title => 'SQLAnywhere support via DBD::ODBC',
351       desc => 'Modules required to connect to SQLAnywhere via DBD::ODBC',
352     },
353   },
354
355   rdbms_firebird => {
356     req => {
357       'DBD::Firebird' => 0,
358     },
359     pod => {
360       title => 'Firebird support',
361       desc => 'Modules required to connect to Firebird',
362     },
363   },
364
365   rdbms_firebird_interbase => {
366     req => {
367       'DBD::InterBase' => 0,
368     },
369     pod => {
370       title => 'Firebird support via DBD::InterBase',
371       desc => 'Modules required to connect to Firebird via DBD::InterBase',
372     },
373   },
374
375   rdbms_firebird_odbc => {
376     include => 'rdbms_generic_odbc',
377     pod => {
378       title => 'Firebird support via DBD::ODBC',
379       desc => 'Modules required to connect to Firebird via DBD::ODBC',
380     },
381   },
382
383   test_rdbms_pg => {
384     include => 'rdbms_pg',
385     env => [
386       DBICTEST_PG_DSN => 1,
387       DBICTEST_PG_USER => 0,
388       DBICTEST_PG_PASS => 0,
389     ],
390     req => {
391       # the order does matter because the rdbms support group might require
392       # a different version that the test group
393       #
394       # when changing this list make sure to adjust xt/optional_deps.t
395       'DBD::Pg' => '2.009002',  # specific version to test bytea
396     },
397   },
398
399   test_rdbms_mssql_odbc => {
400     include => 'rdbms_mssql_odbc',
401     env => [
402       DBICTEST_MSSQL_ODBC_DSN => 1,
403       DBICTEST_MSSQL_ODBC_USER => 0,
404       DBICTEST_MSSQL_ODBC_PASS => 0,
405     ],
406   },
407
408   test_rdbms_mssql_ado => {
409     include => 'rdbms_mssql_ado',
410     env => [
411       DBICTEST_MSSQL_ADO_DSN => 1,
412       DBICTEST_MSSQL_ADO_USER => 0,
413       DBICTEST_MSSQL_ADO_PASS => 0,
414     ],
415   },
416
417   test_rdbms_mssql_sybase => {
418     include => 'rdbms_mssql_sybase',
419     env => [
420       DBICTEST_MSSQL_DSN => 1,
421       DBICTEST_MSSQL_USER => 0,
422       DBICTEST_MSSQL_PASS => 0,
423     ],
424   },
425
426   test_rdbms_msaccess_odbc => {
427     include => 'rdbms_msaccess_odbc',
428     env => [
429       DBICTEST_MSACCESS_ODBC_DSN => 1,
430       DBICTEST_MSACCESS_ODBC_USER => 0,
431       DBICTEST_MSACCESS_ODBC_PASS => 0,
432     ],
433     req => {
434       'Data::GUID' => '0',
435     },
436   },
437
438   test_rdbms_msaccess_ado => {
439     include => 'rdbms_msaccess_ado',
440     env => [
441       DBICTEST_MSACCESS_ADO_DSN => 1,
442       DBICTEST_MSACCESS_ADO_USER => 0,
443       DBICTEST_MSACCESS_ADO_PASS => 0,
444     ],
445     req => {
446       'Data::GUID' => 0,
447     },
448   },
449
450   test_rdbms_mysql => {
451     include => 'rdbms_mysql',
452     env => [
453       DBICTEST_MYSQL_DSN => 1,
454       DBICTEST_MYSQL_USER => 0,
455       DBICTEST_MYSQL_PASS => 0,
456     ],
457   },
458
459   test_rdbms_oracle => {
460     include => 'rdbms_oracle',
461     env => [
462       DBICTEST_ORA_DSN => 1,
463       DBICTEST_ORA_USER => 0,
464       DBICTEST_ORA_PASS => 0,
465     ],
466     req => {
467       'DateTime::Format::Oracle' => '0',
468       'DBD::Oracle'              => '1.24',
469     },
470   },
471
472   test_rdbms_ase => {
473     include => 'rdbms_ase',
474     env => [
475       DBICTEST_SYBASE_DSN => 1,
476       DBICTEST_SYBASE_USER => 0,
477       DBICTEST_SYBASE_PASS => 0,
478     ],
479   },
480
481   test_rdbms_db2 => {
482     include => 'rdbms_db2',
483     env => [
484       DBICTEST_DB2_DSN => 1,
485       DBICTEST_DB2_USER => 0,
486       DBICTEST_DB2_PASS => 0,
487     ],
488   },
489
490   test_rdbms_db2_400 => {
491     include => 'rdbms_db2_400',
492     env => [
493       DBICTEST_DB2_400_DSN => 1,
494       DBICTEST_DB2_400_USER => 0,
495       DBICTEST_DB2_400_PASS => 0,
496     ],
497   },
498
499   test_rdbms_informix => {
500     include => 'rdbms_informix',
501     env => [
502       DBICTEST_INFORMIX_DSN => 1,
503       DBICTEST_INFORMIX_USER => 0,
504       DBICTEST_INFORMIX_PASS => 0,
505     ],
506   },
507
508   test_rdbms_sqlanywhere => {
509     include => 'rdbms_sqlanywhere',
510     env => [
511       DBICTEST_SQLANYWHERE_DSN => 1,
512       DBICTEST_SQLANYWHERE_USER => 0,
513       DBICTEST_SQLANYWHERE_PASS => 0,
514     ],
515   },
516
517   test_rdbms_sqlanywhere_odbc => {
518     include => 'rdbms_sqlanywhere_odbc',
519     env => [
520       DBICTEST_SQLANYWHERE_ODBC_DSN => 1,
521       DBICTEST_SQLANYWHERE_ODBC_USER => 0,
522       DBICTEST_SQLANYWHERE_ODBC_PASS => 0,
523     ],
524   },
525
526   test_rdbms_firebird => {
527     include => 'rdbms_firebird',
528     env => [
529       DBICTEST_FIREBIRD_DSN => 1,
530       DBICTEST_FIREBIRD_USER => 0,
531       DBICTEST_FIREBIRD_PASS => 0,
532     ],
533   },
534
535   test_rdbms_firebird_interbase => {
536     include => 'rdbms_firebird_interbase',
537     env => [
538       DBICTEST_FIREBIRD_INTERBASE_DSN => 1,
539       DBICTEST_FIREBIRD_INTERBASE_USER => 0,
540       DBICTEST_FIREBIRD_INTERBASE_PASS => 0,
541     ],
542   },
543
544   test_rdbms_firebird_odbc => {
545     include => 'rdbms_firebird_odbc',
546     env => [
547       DBICTEST_FIREBIRD_ODBC_DSN => 1,
548       DBICTEST_FIREBIRD_ODBC_USER => 0,
549       DBICTEST_FIREBIRD_ODBC_PASS => 0,
550     ],
551   },
552
553   test_memcached => {
554     env => [
555       DBICTEST_MEMCACHED => 1,
556     ],
557     req => {
558       'Cache::Memcached' => 0,
559     },
560   },
561
562   dist_dir => {
563     req => {
564       %$test_and_dist_json_any,
565       'ExtUtils::MakeMaker' => '6.64',
566       'Pod::Inherit'        => '0.91',
567     },
568   },
569
570   dist_upload => {
571     req => {
572       'CPAN::Uploader' => '0.103001',
573     },
574   },
575 };
576
577
578
579 ### Public API
580
581 sub import {
582   my $class = shift;
583
584   if (@_) {
585
586     my $action = shift;
587
588     if ($action eq '-die_without') {
589       my $err;
590       {
591         local $@;
592         eval { $class->die_unless_req_ok_for(\@_); 1 }
593           or $err = $@;
594       }
595       die "\n$err\n" if $err;
596     }
597     elsif ($action eq '-list_missing') {
598       print $class->modreq_missing_for(\@_);
599       print "\n";
600       exit 0;
601     }
602     elsif ($action eq '-skip_all_without') {
603
604       # sanity check - make sure ->current_test is 0 and no plan has been declared
605       do {
606         local $@;
607         defined eval {
608           Test::Builder->new->current_test
609             or
610           Test::Builder->new->has_plan
611         };
612       } and croak("Unable to invoke -skip_all_without after testing has started");
613
614       if ( my $missing = $class->req_missing_for(\@_) ) {
615
616         die ("\nMandatory requirements not satisfied during release-testing: $missing\n\n")
617           if $ENV{RELEASE_TESTING} and $class->_groups_to_reqs(\@_)->{release_testing_mandatory};
618
619         print "1..0 # SKIP requirements not satisfied: $missing\n";
620         exit 0;
621       }
622     }
623     elsif ($action =~ /^-/) {
624       croak "Unknown import-time action '$action'";
625     }
626     else {
627       croak "$class is not an exporter, unable to import '$action'";
628     }
629   }
630
631   1;
632 }
633
634 sub unimport {
635   croak( __PACKAGE__ . " does not implement unimport" );
636 }
637
638 # OO for (mistakenly considered) ease of extensibility, not due to any need to
639 # carry state of any sort. This API is currently used outside, so leave as-is.
640 # FIXME - make sure to not propagate this further if module is extracted as a
641 # standalone library - keep the stupidity to a DBIC-secific shim!
642 #
643 sub req_list_for {
644   shift->_groups_to_reqs(shift)->{effective_modreqs};
645 }
646
647 sub modreq_list_for {
648   shift->_groups_to_reqs(shift)->{modreqs};
649 }
650
651 sub req_group_list {
652   +{ map
653     { $_ => $_[0]->_groups_to_reqs($_) }
654     keys %$dbic_reqs
655   }
656 }
657
658 sub req_errorlist_for { shift->modreq_errorlist_for(shift) }  # deprecated
659 sub modreq_errorlist_for {
660   my ($self, $groups) = @_;
661   $self->_errorlist_for_modreqs( $self->_groups_to_reqs($groups)->{modreqs} );
662 }
663
664 sub req_ok_for {
665   shift->req_missing_for(shift) ? 0 : 1;
666 }
667
668 sub req_missing_for {
669   my ($self, $groups) = @_;
670
671   my $reqs = $self->_groups_to_reqs($groups);
672   my $mods_missing = $self->modreq_missing_for($groups);
673
674   return '' if
675     ! $mods_missing
676       and
677     ! $reqs->{missing_envvars}
678   ;
679
680   my @res = $mods_missing || ();
681
682   push @res, 'the following group(s) of environment variables: ' . join ' and ', map
683     { __envvar_group_desc($_) }
684     @{$reqs->{missing_envvars}}
685   if $reqs->{missing_envvars};
686
687   return (
688     ( join ' as well as ', @res )
689       .
690     ( $reqs->{modreqs_fully_documented} ? " (see @{[ ref $self || $self ]} documentation for details)" : '' ),
691   );
692 }
693
694 sub modreq_missing_for {
695   my ($self, $groups) = @_;
696
697   my $reqs = $self->_groups_to_reqs($groups);
698   my $modreq_errors = $self->_errorlist_for_modreqs($reqs->{modreqs})
699     or return '';
700
701   join ' ', map
702     { $reqs->{modreqs}{$_} ? qq("$_~>=$reqs->{modreqs}{$_}") : $_ }
703     sort { lc($a) cmp lc($b) } keys %$modreq_errors
704   ;
705 }
706
707 sub die_unless_req_ok_for {
708   if (my $err = shift->req_missing_for(shift) ) {
709     die "Unable to continue due to missing requirements: $err\n";
710   }
711 }
712
713
714
715 ### Private functions
716
717 # potentially shorten group desc
718 sub __envvar_group_desc {
719   my @envs = @{$_[0]};
720
721   my (@res, $last_prefix);
722   while (my $ev = shift @envs) {
723     my ($pref, $sep, $suff) = split / ([\_\-]) (?= [^\_\-]+ \z )/x, $ev;
724
725     if ( defined $sep and ($last_prefix||'') eq $pref ) {
726         push @res, "...${sep}${suff}"
727     }
728     else {
729       push @res, $ev;
730     }
731
732     $last_prefix = $pref if $sep;
733   }
734
735   join '/', @res;
736 }
737
738
739
740 ### Private OO API
741 our %req_unavailability_cache;
742
743 # this method is just a lister and envvar/metadata checker - it does not try to load anything
744 my $processed_groups = {};
745 sub _groups_to_reqs {
746   my ($self, $groups) = @_;
747
748   $groups = [ $groups || () ]
749     unless ref $groups eq 'ARRAY';
750
751   croak "@{[ (caller(1))[3] ]}() expects a requirement group name or arrayref of group names"
752     unless @$groups;
753
754   my $ret = {
755     modreqs => {},
756     modreqs_fully_documented => 1,
757   };
758
759   for my $group ( grep { ! $processed_groups->{$_} } @$groups ) {
760
761     $group =~ /\A [A-Za-z][0-9A-Z_a-z]* \z/x
762       or croak "Invalid requirement group name '$group': only ascii alphanumerics and _ are allowed";
763
764     croak "Requirement group '$group' is not defined" unless defined $dbic_reqs->{$group};
765
766     my $group_reqs = $dbic_reqs->{$group}{req};
767
768     # sanity-check
769     for (keys %$group_reqs) {
770
771       $_ =~ /\A [A-Z_a-z][0-9A-Z_a-z]* (?:::[0-9A-Z_a-z]+)* \z /x
772         or croak "Requirement '$_' in group '$group' is not a valid module name";
773
774       # !!!DO NOT CHANGE!!!
775       # remember - version.pm may not be available on the system
776       croak "Requirement '$_' in group '$group' specifies an invalid version '$group_reqs->{$_}' (only plain non-underscored floating point decimals are supported)"
777         if ( ($group_reqs->{$_}||0) !~ / \A [0-9]+ (?: \. [0-9]+ )? \z /x );
778     }
779
780     # check if we have all required envvars if such names are defined
781     my ($some_envs_required, $some_envs_missing);
782     if (my @e = @{$dbic_reqs->{$group}{env} || [] }) {
783
784       croak "Unexpected 'env' attribute under group '$group' (only allowed in test_* groups)"
785         unless $group =~ /^test_/;
786
787       croak "Unexpected *odd* list in 'env' under group '$group'"
788         if @e % 2;
789
790       my @group_envnames_list;
791
792       # deconstruct the whole thing
793       while (@e) {
794         push @group_envnames_list, my $envname = shift @e;
795
796         # env required or not
797         next unless shift @e;
798
799         $some_envs_required ||= 1;
800
801         $some_envs_missing ||= (
802           ! defined $ENV{$envname}
803             or
804           ! length $ENV{$envname}
805         );
806       }
807
808       croak "None of the envvars in group '$group' declared as required, making the requirement moot"
809         unless $some_envs_required;
810
811       push @{$ret->{missing_envvars}}, \@group_envnames_list if $some_envs_missing;
812     }
813
814     # get the reqs for includes if any
815     my $inc_reqs;
816     if (my $incs = $dbic_reqs->{$group}{include}) {
817       $incs = [ $incs ] unless ref $incs eq 'ARRAY';
818
819       croak "Malformed 'include' for group '$group': must be another existing group name or arrayref of existing group names"
820         unless @$incs;
821
822       local $processed_groups->{$group} = 1;
823
824       my $subreqs = $self->_groups_to_reqs($incs);
825
826       croak "Includes with variable effective_modreqs not yet supported"
827         if $subreqs->{effective_modreqs_differ};
828
829       $inc_reqs = $subreqs->{modreqs};
830
831     }
832
833     # assemble into the final ret
834     for my $type (
835       'modreqs',
836       $some_envs_missing ? () : 'effective_modreqs'
837     ) {
838       for my $req_bag ($group_reqs, $inc_reqs||()) {
839         for my $mod (keys %$req_bag) {
840
841           $ret->{$type}{$mod} = $req_bag->{$mod}||0 if (
842
843             ! exists $ret->{$type}{$mod}
844               or
845             # we sanitized the version to be numeric above - we can just -gt it
846             ($req_bag->{$mod}||0) > $ret->{$type}{$mod}
847
848           );
849         }
850       }
851     }
852
853     $ret->{effective_modreqs_differ} ||= !!$some_envs_missing;
854
855     $ret->{modreqs_fully_documented} &&= !!$dbic_reqs->{$group}{pod};
856
857     $ret->{release_testing_mandatory} ||= !!$dbic_reqs->{$group}{release_testing_mandatory};
858   }
859
860   return $ret;
861 }
862
863
864 # this method tries to load specified modreqs and returns a hashref of
865 # module/loaderror pairs for anything that failed
866 sub _errorlist_for_modreqs {
867   # args supposedly already went through _groups_to_reqs and are therefore sanitized
868   # safe to eval at will
869   my ($self, $reqs) = @_;
870
871   my $ret;
872
873   for my $m ( keys %$reqs ) {
874     my $v = $reqs->{$m};
875
876     if (! exists $req_unavailability_cache{$m}{$v} ) {
877       local $@;
878       eval( "require $m;" . ( $v ? "$m->VERSION(q($v))" : '' ) );
879       $req_unavailability_cache{$m}{$v} = $@;
880     }
881
882     $ret->{$m} = $req_unavailability_cache{$m}{$v}
883       if $req_unavailability_cache{$m}{$v};
884   }
885
886   $ret;
887 }
888
889
890 # This is to be called by the author only (automatically in Makefile.PL)
891 sub _gen_pod {
892   my ($class, $distver, $pod_dir) = @_;
893
894   die "No POD root dir supplied" unless $pod_dir;
895
896   $distver ||=
897     eval { require DBIx::Class; DBIx::Class->VERSION; }
898       ||
899     die
900 "\n\n---------------------------------------------------------------------\n" .
901 'Unable to load core DBIx::Class module to determine current version, '.
902 'possibly due to missing dependencies. Author-mode autodocumentation ' .
903 "halted\n\n" . $@ .
904 "\n\n---------------------------------------------------------------------\n"
905   ;
906
907   # do not ask for a recent version, use 1.x API calls
908   # this *may* execute on a smoker with old perl or whatnot
909   require File::Path;
910
911   (my $modfn = __PACKAGE__ . '.pm') =~ s|::|/|g;
912
913   (my $podfn = "$pod_dir/$modfn") =~ s/\.pm$/\.pod/;
914   (my $dir = $podfn) =~ s|/[^/]+$||;
915
916   File::Path::mkpath([$dir]);
917
918   my $sqltver = $class->req_list_for('deploy')->{'SQL::Translator'}
919     or die "Hrmm? No sqlt dep?";
920
921
922   my @chunks;
923
924 #@@
925 #@@ HEADER
926 #@@
927   push @chunks, <<"EOC";
928 #########################################################################
929 #####################  A U T O G E N E R A T E D ########################
930 #########################################################################
931 #
932 # The contents of this POD file are auto-generated.  Any changes you make
933 # will be lost. If you need to change the generated text edit _gen_pod()
934 # at the end of $modfn
935 #
936
937 =head1 NAME
938
939 $class - Optional module dependency specifications (for module authors)
940 EOC
941
942
943 #@@
944 #@@ SYNOPSIS HEADING
945 #@@
946   push @chunks, <<"EOC";
947 =head1 SYNOPSIS
948
949 Somewhere in your build-file (e.g. L<ExtUtils::MakeMaker>'s F<Makefile.PL>):
950
951   ...
952
953   \$EUMM_ARGS{CONFIGURE_REQUIRES} = {
954     \%{ \$EUMM_ARGS{CONFIGURE_REQUIRES} || {} },
955     'DBIx::Class' => '$distver',
956   };
957
958   ...
959
960   my %DBIC_DEPLOY_DEPS = %{ eval {
961     require $class;
962     $class->req_list_for('deploy');
963   } || {} };
964
965   \$EUMM_ARGS{PREREQ_PM} = {
966     \%DBIC_DEPLOY_DEPS,
967     \%{ \$EUMM_ARGS{PREREQ_PM} || {} },
968   };
969
970   ...
971
972   ExtUtils::MakeMaker::WriteMakefile(\%EUMM_ARGS);
973
974 B<Note>: The C<eval> protection within the example is due to support for
975 requirements during L<the C<configure> build phase|CPAN::Meta::Spec/Phases>
976 not being available on a sufficient portion of production installations of
977 Perl. Robust support for such dependency requirements is available in the
978 L<CPAN> installer only since version C<1.94_56> first made available for
979 production with perl version C<5.12>. It is the belief of the current
980 maintainer that support for requirements during the C<configure> build phase
981 will not be sufficiently ubiquitous until the B<year 2020> at the earliest,
982 hence the extra care demonstrated above. It should also be noted that some
983 3rd party installers (e.g. L<cpanminus|App::cpanminus>) do the right thing
984 with configure requirements independent from the versions of perl and CPAN
985 available.
986 EOC
987
988
989 #@@
990 #@@ DESCRIPTION HEADING
991 #@@
992   push @chunks, <<'EOC';
993 =head1 DESCRIPTION
994
995 Some of the less-frequently used features of L<DBIx::Class> have external
996 module dependencies on their own. In order not to burden the average user
997 with modules they will never use, these optional dependencies are not included
998 in the base Makefile.PL. Instead an exception with a descriptive message is
999 thrown when a specific feature can't find one or several modules required for
1000 its operation. This module is the central holding place for the current list
1001 of such dependencies, for DBIx::Class core authors, and DBIx::Class extension
1002 authors alike.
1003
1004 Dependencies are organized in L<groups|/CURRENT REQUIREMENT GROUPS> where each
1005 group can list one or more required modules, with an optional minimum version
1006 (or 0 for any version). In addition groups prefixed with C<test_> can specify
1007 a set of environment variables, some (or all) of which are marked as required
1008 for the group to be considered by L</req_list_for>
1009
1010 Each group name (or a combination thereof) can be used in the
1011 L<public methods|/METHODS> as described below.
1012 EOC
1013
1014
1015 #@@
1016 #@@ REQUIREMENT GROUPLIST HEADING
1017 #@@
1018   push @chunks, '=head1 CURRENT REQUIREMENT GROUPS';
1019
1020   for my $group (sort keys %$dbic_reqs) {
1021     my $p = $dbic_reqs->{$group}{pod}
1022       or next;
1023
1024     my $modlist = $class->modreq_list_for($group);
1025
1026     next unless keys %$modlist;
1027
1028     push @chunks, (
1029       "=head2 $p->{title}",
1030       "$p->{desc}",
1031       '=over',
1032       ( map { "=item * $_" . ($modlist->{$_} ? " >= $modlist->{$_}" : '') } (sort keys %$modlist) ),
1033       '=back',
1034       "Requirement group: B<$group>",
1035     );
1036   }
1037
1038
1039 #@@
1040 #@@ API DOCUMENTATION HEADING
1041 #@@
1042   push @chunks, <<'EOC';
1043
1044 =head1 IMPORT-LIKE ACTIONS
1045
1046 Even though this module is not an L<Exporter>, it recognizes several C<actions>
1047 supplied to its C<import> method.
1048
1049 =head2 -skip_all_without
1050
1051 =over
1052
1053 =item Arguments: @group_names
1054
1055 =back
1056
1057 A convenience wrapper for use during testing:
1058 EOC
1059
1060   push @chunks, " use $class -skip_all_without => qw(admin test_rdbms_mysql);";
1061
1062   push @chunks, 'Roughly equivalent to the following code:';
1063
1064   push @chunks, sprintf <<'EOS', ($class) x 2;
1065
1066  BEGIN {
1067    require %s;
1068    if ( my $missing = %s->req_missing_for(\@group_names_) ) {
1069      print "1..0 # SKIP requirements not satisfied: $missing\n";
1070      exit 0;
1071    }
1072  }
1073 EOS
1074
1075   push @chunks, <<'EOC';
1076
1077 It also takes into account the C<RELEASE_TESTING> environment variable and
1078 behaves like L</-die_without> for any requirement groups marked as
1079 C<release_testing_mandatory>.
1080
1081 =head2 -die_without
1082
1083 =over
1084
1085 =item Arguments: @group_names
1086
1087 =back
1088
1089 A convenience wrapper around L</die_unless_req_ok_for>:
1090 EOC
1091
1092   push @chunks, " use $class -die_without => qw(deploy admin);";
1093
1094   push @chunks, <<'EOC';
1095
1096 =head2 -list_missing
1097
1098 =over
1099
1100 =item Arguments: @group_names
1101
1102 =back
1103
1104 A convenience wrapper around L</modreq_missing_for>:
1105
1106  perl -Ilib -MDBIx::Class::Optional::Dependencies=-list_missing,deploy,admin | cpanm
1107
1108 =head1 METHODS
1109
1110 =head2 req_group_list
1111
1112 =over
1113
1114 =item Arguments: none
1115
1116 =item Return Value: \%list_of_requirement_groups
1117
1118 =back
1119
1120 This method should be used by DBIx::Class packagers, to get a hashref of all
1121 dependencies B<keyed> by dependency group. Each key (group name), or a combination
1122 thereof (as an arrayref) can be supplied to the methods below.
1123 The B<values> of the returned hash are currently a set of options B<without a
1124 well defined structure>. If you have use for any of the contents - contact the
1125 maintainers, instead of treating this as public (left alone stable) API.
1126
1127 =head2 req_list_for
1128
1129 =over
1130
1131 =item Arguments: $group_name | \@group_names
1132
1133 =item Return Value: \%set_of_module_version_pairs
1134
1135 =back
1136
1137 This method should be used by DBIx::Class extension authors, to determine the
1138 version of modules a specific set of features requires for this version of
1139 DBIx::Class (regardless of their availability on the system).
1140 See the L</SYNOPSIS> for a real-world example.
1141
1142 When handling C<test_*> groups this method behaves B<differently> from
1143 L</modreq_list_for> below (and is the only such inconsistency among the
1144 C<req_*> methods). If a particular group declares as requirements some
1145 C<environment variables> and these requirements are not satisfied (the envvars
1146 are unset) - then the C<module requirements> of this group are not included in
1147 the returned list.
1148
1149 =head2 modreq_list_for
1150
1151 =over
1152
1153 =item Arguments: $group_name | \@group_names
1154
1155 =item Return Value: \%set_of_module_version_pairs
1156
1157 =back
1158
1159 Same as L</req_list_for> but does not take into consideration any
1160 C<environment variable requirements> - returns just the list of required
1161 modules.
1162
1163 =head2 req_ok_for
1164
1165 =over
1166
1167 =item Arguments: $group_name | \@group_names
1168
1169 =item Return Value: 1|0
1170
1171 =back
1172
1173 Returns true or false depending on whether all modules/envvars required by
1174 the group(s) are loadable/set on the system.
1175
1176 =head2 req_missing_for
1177
1178 =over
1179
1180 =item Arguments: $group_name | \@group_names
1181
1182 =item Return Value: $error_message_string
1183
1184 =back
1185
1186 Returns a single-line string suitable for inclusion in larger error messages.
1187 This method would normally be used by DBIx::Class core features, to indicate to
1188 the user that they need to install specific modules and/or set specific
1189 environment variables before being able to use a specific feature set.
1190
1191 For example if some of the requirements for C<deploy> are not available,
1192 the returned string could look like:
1193 EOC
1194
1195   push @chunks, qq{ "SQL::Translator~>=$sqltver" (see $class documentation for details)};
1196
1197   push @chunks, <<'EOC';
1198 The author is expected to prepend the necessary text to this message before
1199 returning the actual error seen by the user. See also L</modreq_missing_for>
1200
1201 =head2 modreq_missing_for
1202
1203 =over
1204
1205 =item Arguments: $group_name | \@group_names
1206
1207 =item Return Value: $error_message_string
1208
1209 =back
1210
1211 Same as L</req_missing_for> except that the error string is guaranteed to be
1212 either empty, or contain a set of module requirement specifications suitable
1213 for piping to e.g. L<cpanminus|App::cpanminus>. The method explicitly does not
1214 attempt to validate the state of required environment variables (if any).
1215
1216 For instance if some of the requirements for C<deploy> are not available,
1217 the returned string could look like:
1218 EOC
1219
1220   push @chunks, qq{ "SQL::Translator~>=$sqltver"};
1221
1222   push @chunks, <<'EOC';
1223
1224 See also L</-list_missing>.
1225
1226 =head2 die_unless_req_ok_for
1227
1228 =over
1229
1230 =item Arguments: $group_name | \@group_names
1231
1232 =back
1233
1234 Checks if L</req_ok_for> passes for the supplied group(s), and
1235 in case of failure throws an exception including the information
1236 from L</req_missing_for>. See also L</-die_without>.
1237
1238 =head2 modreq_errorlist_for
1239
1240 =over
1241
1242 =item Arguments: $group_name | \@group_names
1243
1244 =item Return Value: \%set_of_loaderrors_per_module
1245
1246 =back
1247
1248 Returns a hashref containing the actual errors that occurred while attempting
1249 to load each module in the requirement group(s).
1250
1251 =head2 req_errorlist_for
1252
1253 Deprecated method name, equivalent (via proxy) to L</modreq_errorlist_for>.
1254
1255 EOC
1256
1257 #@@
1258 #@@ FOOTER
1259 #@@
1260   push @chunks, <<'EOC';
1261 =head1 FURTHER QUESTIONS?
1262
1263 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
1264
1265 =head1 COPYRIGHT AND LICENSE
1266
1267 This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
1268 by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
1269 redistribute it and/or modify it under the same terms as the
1270 L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
1271 EOC
1272
1273   eval {
1274     open (my $fh, '>', $podfn) or die;
1275     print $fh join ("\n\n", @chunks) or die;
1276     print $fh "\n" or die;
1277     close ($fh) or die;
1278   } or croak( "Unable to write $podfn: " . ( $! || $@ || 'unknown error') );
1279 }
1280
1281 1;