1# please insert nothing before this line: -*- mode: cperl; cperl-indent-level: 4; cperl-continued-statement-offset: 4; indent-tabs-mode: nil -*-
2# Licensed to the Apache Software Foundation (ASF) under one or more
3# contributor license agreements.  See the NOTICE file distributed with
4# this work for additional information regarding copyright ownership.
5# The ASF licenses this file to You under the Apache License, Version 2.0
6# (the "License"); you may not use this file except in compliance with
7# the License.  You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17package ModPerl::WrapXS;
18
19use strict;
20use warnings FATAL => 'all';
21
22use constant GvUNIQUE => 0; #$] >= 5.008;
23use Apache::TestTrace;
24use Apache2::Build ();
25use ModPerl::Code ();
26use ModPerl::TypeMap ();
27use ModPerl::MapUtil qw(function_table xs_glue_dirs);
28use File::Path qw(rmtree mkpath);
29use Cwd qw(fastcwd);
30use Data::Dumper;
31use File::Spec::Functions qw(catfile catdir);
32
33our $VERSION = '0.01';
34
35my (@xs_includes) = ('mod_perl.h',
36                    map "modperl_xs_$_.h", qw(sv_convert util typedefs));
37
38my @global_structs = qw(perl_module);
39
40my $build = Apache2::Build->build_config;
41push @global_structs, 'MP_debug_level' unless Apache2::Build::WIN32;
42
43sub new {
44    my $class = shift;
45
46    my $self = bless {
47       typemap   => ModPerl::TypeMap->new,
48       includes  => \@xs_includes,
49       glue_dirs => [xs_glue_dirs()],
50    }, $class;
51
52    $self->typemap->get;
53    $self;
54}
55
56sub typemap  { shift->{typemap} }
57
58sub includes { shift->{includes} }
59
60sub function_list {
61    my $self = shift;
62    my (@list) = @{ function_table() };
63
64    while (my ($name, $val) = each %{ $self->typemap->function_map }) {
65        #entries that do not exist in C::Scan generated tables
66        next unless $name =~ /^DEFINE_/;
67        push @list, $val;
68    }
69
70    return \@list;
71}
72
73sub get_functions {
74    my $self = shift;
75    my $typemap = $self->typemap;
76
77    for my $entry (sort { $a->{name} cmp $b->{name} } @{ $self->function_list() }) {
78        my $func = $typemap->map_function($entry);
79        #print "FAILED to map $entry->{name}\n" unless $func;
80        next unless $func;
81
82        my ($name, $module, $class, $args) =
83          @{ $func } { qw(perl_name module class args) };
84
85        $self->{XS}->{ $module } ||= [];
86
87        #eg ap_fputs()
88        if ($name =~ s/^DEFINE_//) {
89            $func->{name} =~ s/^DEFINE_//;
90
91            if (needs_prefix($func->{name})) {
92                #e.g. DEFINE_add_output_filter
93                $func->{name} = make_prefix($func->{name}, $class);
94            }
95        }
96
97        my $xs_parms = join ', ',
98          map { defined $_->{default} ?
99                  "$_->{name}=$_->{default}" : $_->{name} } @$args;
100
101        (my $parms = $xs_parms) =~ s/=[^,]+//g; #strip defaults
102
103        my $proto = join "\n",
104          (map "    $_->{type} $_->{name}", @$args), "";
105
106        my ($dispatch, $orig_args) =
107          @{ $func } {qw(dispatch orig_args)};
108
109        if ($dispatch =~ /^MPXS_/) {
110            $name =~ s/^mpxs_//;
111            $name =~ s/^$func->{prefix}//;
112            push @{ $self->{newXS}->{ $module } },
113              ["$class\::$name", $dispatch];
114            next;
115        }
116
117        my $passthru = @$args && $args->[0]->{name} eq '...';
118        if ($passthru) {
119            $parms = '...';
120            $proto = '';
121        }
122
123        my $return_type =
124          $name =~ /^DESTROY$/ ? 'void' : $func->{return_type};
125
126        my $attrs = $self->attrs($name);
127
128        my $code = <<EOF;
129$return_type
130$name($xs_parms)
131$proto
132$attrs
133EOF
134
135        if ($dispatch || $orig_args || $func->{thx}) {
136            my $thx = $func->{thx} ? 'aTHX_ ' : "";
137
138            if ($dispatch) {
139                $thx = 'aTHX_ ' if $dispatch =~ /^mpxs_/i;
140            }
141            else {
142                if ($orig_args and @$orig_args == @$args) {
143                    #args were reordered
144                    $parms = join ', ', @$orig_args;
145                }
146
147                $dispatch = $func->{name};
148            }
149
150            if ($passthru) {
151                $thx ||= 'aTHX_ ';
152                $parms = 'items, MARK+1, SP';
153            }
154
155            $thx =~ s/_ $// unless $parms;
156
157            my $retval = $return_type eq 'void' ?
158              ["", ""] : ["RETVAL = ", "OUTPUT:\n    RETVAL\n"];
159
160            my $avoid_warning = "";
161            if (@$args and not $passthru) {
162                $avoid_warning = "    /* avoiding -Wall warnings */\n";
163                $avoid_warning .= join "\n",
164                    (map "    $_->{name} = $_->{name};", @$args), "";
165            }
166            $code .= <<EOF;
167    CODE:
168$avoid_warning
169    $retval->[0]$dispatch($thx$parms);
170
171    $retval->[1]
172EOF
173        }
174
175        $func->{code} = $code;
176        push @{ $self->{XS}->{ $module } }, $func;
177    }
178}
179
180sub get_value {
181    my $e = shift;
182    my $val = 'val';
183
184    if ($e->{class} eq 'PV') {
185        if (my $pool = $e->{pool}) {
186            $pool .= '(obj)';
187            $val = "(SvOK(ST(1)) ?
188                    apr_pstrndup($pool, val, val_len) : NULL)"
189        }
190    }
191
192    return $val;
193}
194
195sub get_structures {
196    my $self = shift;
197    my $typemap = $self->typemap;
198
199    require Apache2::StructureTable;
200    for my $entry (@$Apache2::StructureTable) {
201        my $struct = $typemap->map_structure($entry);
202        next unless $struct;
203
204        my $class = $struct->{class};
205
206        for my $e (@{ $struct->{elts} }) {
207            my ($name, $default, $type, $access_mode) =
208              @{$e}{qw(name default type access_mode)};
209
210            (my $cast = $type) =~ s/:/_/g;
211            my $val = get_value($e);
212
213            my $type_in = $type;
214            my $preinit = "/*nada*/";
215            if ($e->{class} eq 'PV' and $val ne 'val') {
216                $type_in =~ s/char/char_len/;
217                $preinit = "STRLEN val_len;";
218            }
219
220            my $attrs = $self->attrs($name);
221
222            my $code;
223            if ($access_mode eq 'ro') {
224                $code = <<EOF;
225$type
226$name(obj)
227    $class obj
228
229$attrs
230
231    CODE:
232    RETVAL = ($cast) obj->$name;
233
234    OUTPUT:
235    RETVAL
236
237EOF
238            }
239            elsif ($access_mode eq 'rw' or $access_mode eq 'r+w_startup') {
240
241                my $check_runtime = $access_mode eq 'rw'
242                    ? ''
243                    : qq[MP_CROAK_IF_THREADS_STARTED("setting $name");];
244
245                $code = <<EOF;
246$type
247$name(obj, val=$default)
248    $class obj
249    $type_in val
250
251    PREINIT:
252    $preinit
253$attrs
254
255    CODE:
256    RETVAL = ($cast) obj->$name;
257
258    if (items > 1) {
259         $check_runtime
260         obj->$name = ($cast) $val;
261    }
262
263    OUTPUT:
264    RETVAL
265
266EOF
267            }
268            elsif ($access_mode eq 'r+w_startup_dup') {
269
270                my $convert = $cast !~ /\bchar\b/
271                    ? "mp_xs_sv2_$cast"
272                    : "SvPV_nolen";
273
274                $code = <<EOF;
275$type
276$name(obj, val=(SV *)NULL)
277    $class obj
278    SV *val
279
280    PREINIT:
281    $preinit
282$attrs
283
284    CODE:
285    RETVAL = ($cast) obj->$name;
286
287    if (items > 1) {
288         SV *dup = get_sv("_modperl_private::server_rec_$name", TRUE);
289         MP_CROAK_IF_THREADS_STARTED("setting $name");
290         sv_setsv(dup, val);
291         obj->$name = ($cast)$convert(dup);
292    }
293
294    OUTPUT:
295    RETVAL
296
297EOF
298            }
299            elsif ($access_mode eq 'rw_char_undef') {
300                my $pool = $e->{pool}
301                    or die "rw_char_undef accessors need pool";
302                $pool .= '(obj)';
303# XXX: not sure where val=$default is coming from, but for now use
304# hardcoded (SV *)NULL
305                $code = <<EOF;
306$type
307$name(obj, val_sv=(SV *)NULL)
308    $class obj
309    SV *val_sv
310
311    PREINIT:
312$attrs
313
314    CODE:
315    RETVAL = ($cast) obj->$name;
316
317    if (val_sv) {
318        if (SvOK(val_sv)) {
319            STRLEN val_len;
320            char *val = (char *)SvPV(val_sv, val_len);
321            obj->$name = apr_pstrndup($pool, val, val_len);
322        }
323        else {
324            obj->$name = NULL;
325        }
326    }
327
328    OUTPUT:
329    RETVAL
330
331EOF
332            }
333
334            push @{ $self->{XS}->{ $struct->{module} } }, {
335               code  => $code,
336               class => $class,
337               name  => $name,
338            };
339        }
340    }
341}
342
343sub prepare {
344    my $self = shift;
345    $self->{DIR} = 'WrapXS';
346    $self->{XS_DIR} = catdir fastcwd(), 'xs';
347
348    my $verbose = Apache::TestTrace::trace_level() eq 'debug' ? 1 : 0;
349
350    if (-e $self->{DIR}) {
351        rmtree([$self->{DIR}], $verbose, 1);
352    }
353
354    mkpath [$self->{DIR}], $verbose, 0755;
355}
356
357sub class_dirname {
358    my ($self, $class) = @_;
359    my ($base, $sub) = split '::', $class;
360    return "$self->{DIR}/$base" unless $sub; #Apache2 | APR
361    return $sub if $sub eq $self->{DIR}; #WrapXS
362    return "$base/$sub";
363}
364
365sub class_dir {
366    my ($self, $class) = @_;
367
368    my $dirname = $self->class_dirname($class);
369    my $dir = ($dirname =~ m:/: and $dirname !~ m:^$self->{DIR}:) ?
370      catdir($self->{DIR}, $dirname) : $dirname;
371
372    unless (-d $dir) {
373        mkpath [$dir], 0, 0755;
374        debug "mkdir.....$dir";
375    }
376
377    $dir;
378}
379
380sub class_file {
381    my ($self, $class, $file) = @_;
382    catfile $self->class_dir($class), $file;
383}
384
385sub cname {
386    my ($self, $class) = @_;
387    $class =~ s/:/_/g;
388    $class;
389}
390
391sub open_class_file {
392    my ($self, $class, $file) = @_;
393
394    if ($file =~ /^\./) {
395        my $sub = (split '::', $class)[-1];
396        $file = $sub . $file;
397    }
398
399    my $name = $self->class_file($class, $file);
400
401    open my $fh, '>', $name or die "open $name: $!";
402    debug "writing...$name";
403
404    return $fh;
405}
406
407sub module_version {
408    local $_ = shift;
409    require mod_perl2;
410    # XXX: for now APR gets its libapr-0.9 version
411    return /^APR/ ? "0.009000" : "$mod_perl2::VERSION";
412}
413
414sub write_makefilepl {
415    my ($self, $class) = @_;
416
417    my $fh = $self->open_class_file($class, 'Makefile.PL');
418
419    my $includes = $self->includes;
420    my $xs = (split '::', $class)[-1] . '.c';
421    my $deps = {$xs => ""};
422
423    if (my $mod_h = $self->mod_h($class, 1)) {
424        $deps->{$xs} .= " $mod_h";
425    }
426
427    local $Data::Dumper::Terse = 1;
428    $deps = Dumper $deps;
429
430    my $noedit_warning = $self->ModPerl::Code::noedit_warning_hash();
431    require mod_perl2;
432    my $version = module_version($class);
433
434    print $fh <<EOF;
435$noedit_warning
436
437use lib qw(../../../lib); #for Apache2::BuildConfig
438use ModPerl::BuildMM ();
439
440ModPerl::BuildMM::WriteMakefile(
441    'NAME'    => '$class',
442    'VERSION' => '$version',
443    'depend'  => $deps,
444);
445EOF
446
447    close $fh;
448}
449
450sub mod_h {
451    my ($self, $module, $complete) = @_;
452
453    my $dirname = $self->class_dirname($module);
454    my $cname = $self->cname($module);
455    my $mod_h = "$dirname/$cname.h";
456
457    for ($self->{XS_DIR}, @{ $self->{glue_dirs} }) {
458        my $file = "$_/$mod_h";
459        $mod_h = $file if $complete;
460        return $mod_h if -e $file;
461    }
462
463    undef;
464}
465
466sub mod_pm {
467    my ($self, $module, $complete) = @_;
468
469    my $dirname = $self->class_dirname($module);
470    my ($base, $sub) = split '::', $module;
471    my $mod_pm = "$dirname/${sub}_pm";
472
473    for ($self->{XS_DIR}, @{ $self->{glue_dirs} }) {
474        my $file = "$_/$mod_pm";
475        $mod_pm = $file if $complete;
476        return $mod_pm if -e $file;
477    }
478
479    undef;
480}
481
482sub class_c_prefix {
483    my $class = shift;
484    $class =~ s/:/_/g;
485    $class;
486}
487
488sub class_mpxs_prefix {
489    my $class = shift;
490    my $class_prefix = class_c_prefix($class);
491    "mpxs_${class_prefix}_";
492}
493
494sub needs_prefix {
495    my $name = shift;
496    $name !~ /^(ap|apr|mpxs)_/i;
497}
498
499sub make_prefix {
500    my ($name, $class) = @_;
501    my $class_prefix = class_mpxs_prefix($class);
502    return $name if $name =~ /^$class_prefix/;
503    $class_prefix . $name;
504}
505
506sub isa_str {
507    my ($self, $module) = @_;
508    my $str = "";
509
510    if (my $isa = $self->typemap->{function_map}->{isa}->{$module}) {
511        foreach my $sub (sort keys %$isa) {
512            my $base = $isa->{$sub};
513#XXX cannot set isa in the BOOT: section because XSLoader local-ises
514#ISA during bootstrap
515#            $str .= qq{    av_push(get_av("$sub\::ISA", TRUE),
516#                                   newSVpv("$base",0));}
517            $str .= qq{\@$sub\::ISA = '$base';\n}
518        }
519    }
520
521    $str;
522}
523
524sub boot {
525    my ($self, $module) = @_;
526    my $str = "";
527
528    if (my $boot = $self->typemap->{function_map}->{boot}->{$module}) {
529        $str = '    mpxs_' . $self->cname($module) . "_BOOT(aTHX);\n";
530    }
531
532    $str;
533}
534
535my $notshared = join '|', qw(TIEHANDLE); #not sure why yet
536
537sub attrs {
538    my ($self, $name) = @_;
539    my $str = "";
540    return $str if $name =~ /$notshared$/o;
541    $str = "    ATTRS: unique\n" if GvUNIQUE;
542    $str;
543}
544
545sub write_xs {
546    my ($self, $module, $functions) = @_;
547
548    my $fh = $self->open_class_file($module, '.xs');
549    print $fh $self->ModPerl::Code::noedit_warning_c(), "\n";
550    print $fh "\n#define MP_IN_XS\n\n";
551
552    my @includes = @{ $self->includes };
553
554    if (my $mod_h = $self->mod_h($module)) {
555        push @includes, $mod_h;
556    }
557
558    for (@includes) {
559        print $fh qq{\#include "$_"\n\n};
560    }
561
562    my $last_prefix = "";
563
564    for my $func (@$functions) {
565        my $class = $func->{class};
566        my $prefix = $func->{prefix};
567        $last_prefix = $prefix if $prefix;
568
569        if ($func->{name} =~ /^mpxs_/) {
570            #e.g. mpxs_Apache2__RequestRec_
571            my $class_prefix = class_c_prefix($class);
572            if ($func->{name} =~ /$class_prefix/) {
573                $prefix = class_mpxs_prefix($class);
574            }
575        }
576
577        $prefix = $prefix ? "  PREFIX = $prefix" : "";
578        print $fh "MODULE = $module    PACKAGE = $class $prefix\n\n";
579
580        print $fh $func->{code};
581    }
582
583    if (my $destructor = $self->typemap->destructor($last_prefix)) {
584        my $arg = $destructor->{argspec}[0];
585
586        print $fh <<EOF;
587void
588$destructor->{name}($arg)
589    $destructor->{class} $arg
590
591EOF
592    }
593
594    print $fh "MODULE = $module\n";
595    print $fh "PROTOTYPES: disabled\n\n";
596    print $fh "BOOT:\n";
597    print $fh $self->boot($module);
598    print $fh "    items = items; /* -Wall */\n\n";
599
600    if (my $newxs = $self->{newXS}->{$module}) {
601        for my $xs (sort { $a->[0] cmp $b->[0] } @$newxs) {
602            print $fh qq{   cv = newXS("$xs->[0]", $xs->[1], __FILE__);\n};
603            print $fh qq{   GvUNIQUE_on(CvGV(cv));\n} if GvUNIQUE;
604        }
605    }
606
607    if ($module eq 'APR::Pool' && Apache2::Build::PERL_HAS_ITHREADS) {
608        print $fh "    modperl_opt_interp_unselect = APR_RETRIEVE_OPTIONAL_FN(modperl_interp_unselect);\n\n";
609        print $fh "    modperl_opt_thx_interp_get  = APR_RETRIEVE_OPTIONAL_FN(modperl_thx_interp_get);\n\n";
610    }
611
612    close $fh;
613}
614
615sub write_pm {
616    my ($self, $module) = @_;
617
618    my $isa = $self->isa_str($module);
619
620    my $code = "";
621    if (my $mod_pm = $self->mod_pm($module, 1)) {
622        open my $fh, '<', $mod_pm;
623        local $/;
624        $code = <$fh>;
625        close $fh;
626    }
627
628    my $base   = (split '::', $module)[0];
629    unless (-e "lib/$base/XSLoader.pm") {
630        $base = 'Apache2';
631    }
632    my $loader = join '::', $base, 'XSLoader';
633
634    my $fh = $self->open_class_file($module, '.pm');
635    my $noedit_warning = $self->ModPerl::Code::noedit_warning_hash();
636    my $use_apr = ($module =~ /^APR::\w+$/) ? 'use APR ();' : '';
637    my $version = module_version($module);
638
639    print $fh <<EOF;
640$noedit_warning
641
642package $module;
643
644use strict;
645use warnings FATAL => 'all';
646
647$isa
648$use_apr
649use $loader ();
650our \$VERSION = '$version';
651$loader\::load __PACKAGE__;
652
653$code
654
6551;
656__END__
657EOF
658}
659
660my %typemap = (
661    'Apache2::RequestRec' => 'T_APACHEOBJ',
662    'apr_time_t'         => 'T_APR_TIME',
663    'APR::Table'         => 'T_HASHOBJ',
664    'APR::Pool'          => 'T_POOLOBJ',
665    'apr_size_t *'       => 'T_UVPTR',
666);
667
668sub write_typemap {
669    my $self = shift;
670    my $typemap = $self->typemap;
671    my $map = $typemap->get;
672    my %seen;
673
674    my $fh = $self->open_class_file('ModPerl::WrapXS', 'typemap');
675    print $fh $self->ModPerl::Code::noedit_warning_hash(), "\n";
676
677    my %entries = ();
678    my $max_key_len = 0;
679    while (my ($type, $class) = each %$map) {
680        $class ||= $type;
681        next if $seen{$type}++ || $typemap->special($class);
682
683        if ($class =~ /::/) {
684            $entries{$class} = $typemap{$class} || 'T_PTROBJ';
685            $max_key_len = length $class if length $class > $max_key_len;
686        }
687        else {
688            $entries{$type} = $typemap{$type} || "T_$class";
689            $max_key_len = length $type if length $type > $max_key_len;
690        }
691    }
692
693    for (sort keys %entries) {
694        printf $fh "%-${max_key_len}s %s\n", $_, $entries{$_};
695    }
696
697    close $fh;
698}
699
700sub write_typemap_h_file {
701    my ($self, $method) = @_;
702
703    $method = $method . '_code';
704    my ($h, $code) = $self->typemap->$method();
705    my $file = catfile $self->{XS_DIR}, $h;
706
707    open my $fh, '>', $file or die "open $file: $!";
708    print $fh $self->ModPerl::Code::noedit_warning_c(), "\n";
709    print $fh $code;
710    close $fh;
711}
712
713sub write_lookup_method_file {
714    my $self = shift;
715
716    my %map = ();
717    foreach my $module (sort keys %{ $self->{XS} }) {
718        my $functions = $self->{XS}->{$module};
719        my $last_prefix = "";
720        for my $func (@$functions) {
721            my $class = $func->{class};
722            my $prefix = $func->{prefix};
723            $last_prefix = $prefix if $prefix;
724
725            my $name = $func->{perl_name} || $func->{name};
726            $name =~ s/^DEFINE_//;
727
728            if ($name =~ /^mpxs_/) {
729                #e.g. mpxs_Apache2__RequestRec_
730                my $class_prefix = class_c_prefix($class);
731                if ($name =~ /$class_prefix/) {
732                    $prefix = class_mpxs_prefix($class);
733                }
734            }
735            elsif ($name =~ /^ap_sub_req/) {
736                $prefix = 'ap_sub_req_';
737            }
738
739            $name =~ s/^$prefix// if $prefix;
740
741            push @{ $map{$name} }, [$module, $class];
742        }
743
744        # pure XS wrappers don't have the information about the
745        # arguments they receive, since they manipulate the arguments
746        # stack directly. therefore for these methods we can't tell
747        # what are the objects they are invoked on
748        for my $xs (@{ $self->{newXS}->{$module} || []}) {
749            push @{ $map{$1} }, [$module, undef] if $xs->[0] =~ /.+::(.+)/;
750        }
751    }
752
753    local $Data::Dumper::Terse    = 1;
754    local $Data::Dumper::Sortkeys = 1;
755    $Data::Dumper::Terse    = $Data::Dumper::Terse;    # warn
756    $Data::Dumper::Sortkeys = $Data::Dumper::Sortkeys; # warn
757    my $methods = Dumper(\%map);
758    $methods =~ s/\n$//;
759
760    my $package = "ModPerl::MethodLookup";
761    my $file = catfile "lib", "ModPerl", "MethodLookup.pm";
762    debug "creating $file";
763    open my $fh, ">$file" or die "Can't open $file: $!";
764
765    my $noedit_warning = $self->ModPerl::Code::noedit_warning_hash();
766
767    print $fh <<EOF;
768$noedit_warning
769package $package;
770
771use strict;
772use warnings;
773
774my \$methods = $methods;
775
776EOF
777
778    print $fh <<'EOF';
779
780use base qw(Exporter);
781use mod_perl2;
782
783our @EXPORT = qw(print_method print_module print_object);
784our $VERSION = $mod_perl2::VERSION;
785use constant MODULE => 0;
786use constant OBJECT  => 1;
787
788my $modules;
789my $objects;
790
791sub _get_modules {
792    for my $method (sort keys %$methods) {
793        for my $item ( @{ $methods->{$method} }) {
794            push @{ $modules->{$item->[MODULE]} }, [$method, $item->[OBJECT]];
795        }
796    }
797}
798
799sub _get_objects {
800    for my $method (sort keys %$methods) {
801        for my $item ( @{ $methods->{$method} }) {
802            next unless defined $item->[OBJECT];
803            push @{ $objects->{$item->[OBJECT]} }, [$method, $item->[MODULE]];
804        }
805    }
806}
807
808# if there is only one replacement method in 2.0 API we can
809# automatically lookup it, up however if there are more than one
810# (e.g. new()), we need to use a fully qualified value here
811# of course the same if the package is not a mod_perl one.
812#
813# the first field represents the replacement method or undef if none
814# exists, the second field is for extra comments (e.g. when there is
815# no replacement method)
816my $methods_compat = {
817    # Apache2::
818    gensym            => ['Symbol::gensym',
819                          'or use "open my $fh, $file"'],
820    module            => ['Apache2::Module::loaded',
821                          ''],
822    define            => ['exists_config_define',
823                          ''],
824    httpd_conf        => ['add_config',
825                          ''],
826    SERVER_VERSION    => ['get_server_version',
827                          ''],
828    can_stack_handlers=> [undef,
829                          'there is no more need for that method in mp2'],
830
831    # Apache2::RequestRec
832    soft_timeout      => [undef,
833                          'there is no more need for that method in mp2'],
834    hard_timeout      => [undef,
835                          'there is no more need for that method in mp2'],
836    kill_timeout      => [undef,
837                          'there is no more need for that method in mp2'],
838    reset_timeout     => [undef,
839                          'there is no more need for that method in mp2'],
840    cleanup_for_exec  => [undef,
841                          'there is no more need for that method in mp2'],
842    send_http_header  => ['content_type',
843                          ''],
844    header_in         => ['headers_in',
845                          'this method works in mod_perl 1.0 too'],
846    header_out        => ['headers_out',
847                          'this method works in mod_perl 1.0 too'],
848    err_header_out    => ['err_headers_out',
849                          'this method works in mod_perl 1.0 too'],
850    register_cleanup  => ['cleanup_register',
851                          ''],
852    post_connection   => ['cleanup_register',
853                          ''],
854    content           => [undef, # XXX: Apache2::Request::what?
855                          'use CGI.pm or Apache2::Request instead'],
856    clear_rgy_endav   => ['special_list_clear',
857                          ''],
858    stash_rgy_endav   => [undef,
859                          ''],
860    run_rgy_endav     => ['special_list_call',
861                          'this method is no longer needed'],
862    seqno             => [undef,
863                          'internal to mod_perl 1.0'],
864    chdir_file        => [undef, # XXX: to be resolved
865                          'temporary unavailable till the issue with chdir' .
866                          ' in the threaded env is resolved'],
867    log_reason        => ['log_error',
868                          'not in the Apache 2.0 API'],
869    READLINE          => [undef, # XXX: to be resolved
870                          ''],
871    send_fd_length    => [undef,
872                          'not in the Apache 2.0 API'],
873    send_fd           => ['sendfile',
874                          'requires an offset argument'],
875    is_main           => ['main',
876                          'not in the Apache 2.0 API'],
877    cgi_var           => ['subprocess_env',
878                          'subprocess_env can be used with mod_perl 1.0'],
879    cgi_env           => ['subprocess_env',
880                          'subprocess_env can be used with mod_perl 1.0'],
881    each_byterange    => [undef,
882                          'now handled internally by ap_byterange_filter'],
883    set_byterange     => [undef,
884                          'now handled internally by ap_byterange_filter'],
885
886    # Apache::File
887    open              => [undef,
888                          ''],
889    close             => [undef, # XXX: also defined in APR::Socket
890                          ''],
891    tmpfile           => [undef,
892                          'not in the Apache 2.0 API, ' .
893                          'use File::Temp instead'],
894
895    # Apache::Util
896    size_string       => ['format_size',
897                          ''],
898    escape_uri        => ['unescape_path',
899                          ''],
900    escape_url        => ['escape_path',
901                          'and requires a pool object'],
902    unescape_uri      => ['unescape_url',
903                          ''],
904    unescape_url_info => [undef,
905                          'use CGI::Util::unescape() instead'],
906    escape_html       => [undef, # XXX: will be ap_escape_html
907                          'ap_escape_html now requires a pool object'],
908    parsedate         => ['parse_http',
909                          ''],
910    validate_password => ['password_validate',
911                          ''],
912
913    # Apache::Table
914    #new               => ['make',
915    #                      ''], # XXX: there are other 'new' methods
916
917    # Apache::Connection
918    auth_type         => ['ap_auth_type',
919                          'now resides in the request object'],
920};
921
922sub avail_methods_compat {
923    return keys %$methods_compat;
924}
925
926sub avail_methods {
927    return keys %$methods;
928}
929
930sub avail_modules {
931    my %modules = ();
932    for my $method (keys %$methods) {
933        for my $item ( @{ $methods->{$method} }) {
934            $modules{$item->[MODULE]}++;
935        }
936    }
937    return keys %modules;
938}
939
940sub preload_all_modules {
941    _get_modules() unless $modules;
942    eval "require $_" for sort keys %$modules;
943}
944
945sub _print_func {
946    my $func = shift;
947    my @args = @_ ? @_ : @ARGV;
948    no strict 'refs';
949    print( ($func->($_))[0]) for @args;
950}
951
952sub print_module { _print_func('lookup_module', @_) }
953sub print_object { _print_func('lookup_object', @_) }
954
955sub print_method {
956    my @args = @_ ? @_ : @ARGV;
957    while (@args) {
958         my $method = shift @args;
959         my $object = (@args &&
960             (ref($args[0]) || $args[0] =~ /^(Apache2|ModPerl|APR)/))
961             ? shift @args
962             : undef;
963         print( (lookup_method($method, $object))[0]);
964    }
965}
966
967sub sep { return '-' x (shift() + 20) . "\n" }
968
969# what modules contain the passed method.
970# an optional object or a reference to it can be passed to help
971# resolve situations where there is more than one module containing
972# the same method. Inheritance is supported.
973sub lookup_method {
974    my ($method, $object) = @_;
975
976    unless (defined $method) {
977        my $hint = "No 'method' argument was passed\n";
978        return ($hint);
979    }
980
981    # strip the package name for the fully qualified method
982    $method =~ s/.+:://;
983
984    if (exists $methods_compat->{$method}) {
985        my ($replacement, $comment) = @{$methods_compat->{$method}};
986        my $hint = "'$method' is not a part of the mod_perl 2.0 API\n";
987        $comment = length $comment ? " $comment\n" : "";
988
989        # some removed methods have no replacement
990        return $hint . "$comment" unless defined $replacement;
991
992        $hint .= "use '$replacement' instead. $comment";
993
994        # if fully qualified don't look up its container
995        return $hint if $replacement =~ /::/;
996
997        my ($modules_hint, @modules) = lookup_method($replacement, $object);
998        return $hint . $modules_hint;
999    }
1000    elsif (!exists $methods->{$method}) {
1001        my $hint = "Don't know anything about method '$method'\n";
1002        return ($hint);
1003    }
1004
1005    my @items = @{ $methods->{$method} };
1006    if (@items == 1) {
1007        my $module = $items[0]->[MODULE];
1008        my $hint = "To use method '$method' add:\n" . "\tuse $module ();\n";
1009        # we should really check that the method matches the object if
1010        # any was passed, but it may not always work
1011        return ($hint, $module);
1012    }
1013    else {
1014        if (defined $object) {
1015            my $class = ref $object || $object;
1016            for my $item (@items) {
1017                # real class or inheritance
1018                if ($class eq $item->[OBJECT] or
1019                    (ref($object) && $object->isa($item->[OBJECT]))) {
1020                    my $module = $item->[MODULE];
1021                    my $hint = "To use method '$method' add:\n" .
1022                        "\tuse $module ();\n";
1023                    return ($hint, $module);
1024                }
1025            }
1026            # fall-through
1027            local $" = ", ";
1028            my @modules = map $_->[MODULE], @items;
1029            my $hint = "Several modules (@modules) contain method '$method' " .
1030                "but none of them matches class '$class';\n";
1031            return ($hint);
1032
1033        }
1034        else {
1035            my %modules = map { $_->[MODULE] => 1 } @items;
1036            # remove dups if any (e.g. $s->add_input_filter and
1037            # $r->add_input_filter are loaded by the same Apache2::Filter)
1038            my @modules = sort keys %modules;
1039            my $hint;
1040            if (@modules == 1) {
1041                $hint = "To use method '$method' add:\n\tuse $modules[0] ();\n";
1042                return ($hint, $modules[0]);
1043            }
1044            else {
1045                $hint = "There is more than one class with method '$method'\n" .
1046                    "try one of:\n" . join '', map {"\tuse $_ ();\n"} @modules;
1047                return ($hint, @modules);
1048            }
1049        }
1050    }
1051}
1052
1053# what methods are contained in the passed module name
1054sub lookup_module {
1055    my ($module) = shift;
1056
1057    unless (defined $module) {
1058        my $hint = "no 'module' argument was passed\n";
1059        return ($hint);
1060    }
1061
1062    _get_modules() unless $modules;
1063
1064    unless (exists $modules->{$module}) {
1065        my $hint = "don't know anything about module '$module'\n";
1066        return ($hint);
1067    }
1068
1069    my @methods;
1070    my $max_len = 6;
1071    for ( @{ $modules->{$module} } ) {
1072        $max_len = length $_->[0] if length $_->[0] > $max_len;
1073        push @methods, $_->[0];
1074    }
1075
1076    my $format = "%-${max_len}s %s\n";
1077    my $banner = sprintf($format, "Method", "Invoked on object type");
1078    my $hint = join '',
1079        ("\nModule '$module' contains the following XS methods:\n\n",
1080         $banner,  sep(length($banner)),
1081         map( { sprintf $format, $_->[0], $_->[1]||'???'}
1082             @{ $modules->{$module} }),
1083         sep(length($banner)));
1084
1085    return ($hint, @methods);
1086}
1087
1088# what methods can be invoked on the passed object (or its reference)
1089sub lookup_object {
1090    my ($object) = shift;
1091
1092    unless (defined $object) {
1093        my $hint = "no 'object' argument was passed\n";
1094        return ($hint);
1095    }
1096
1097    _get_objects() unless $objects;
1098
1099    # a real object was passed?
1100    $object = ref $object || $object;
1101
1102    unless (exists $objects->{$object}) {
1103        my $hint = "don't know anything about objects of type '$object'\n";
1104        return ($hint);
1105    }
1106
1107    my @methods;
1108    my $max_len = 6;
1109    for ( @{ $objects->{$object} } ) {
1110        $max_len = length $_->[0] if length $_->[0] > $max_len;
1111        push @methods, $_->[0];
1112    }
1113
1114    my $format = "%-${max_len}s %s\n";
1115    my $banner = sprintf($format, "Method", "Module");
1116    my $hint = join '',
1117        ("\nObjects of type '$object' can invoke the following XS methods:\n\n",
1118         $banner, sep(length($banner)),
1119         map({ sprintf $format, $_->[0], $_->[1]} @{ $objects->{$object} }),
1120         sep(length($banner)));
1121
1122    return ($hint, @methods);
1123
1124}
1125
11261;
1127EOF
1128    close $fh;
1129}
1130
1131sub write_module_versions_file {
1132    my $self = shift;
1133
1134    my $file = catfile "lib", "ModPerl", "DummyVersions.pm";
1135    debug "creating $file";
1136    open my $fh, ">$file" or die "Can't open $file: $!";
1137
1138    my $noedit_warning = $self->ModPerl::Code::noedit_warning_hash();
1139    print $fh "$noedit_warning\n";
1140
1141    my @modules = keys %{ $self->{XS} };
1142    push @modules, qw(ModPerl::MethodLookup);
1143
1144    my $len = 0;
1145    for (@modules) {
1146        $len = length $_ if length $_ > $len;
1147    }
1148
1149    require mod_perl2;
1150    $len += length '$::VERSION';
1151    for (sort @modules) {
1152        my $ver = module_version($_);
1153        printf $fh "package %s;\n%-${len}s = %s;\n\n",
1154            $_, '$'.$_."::VERSION", $ver;
1155    }
1156}
1157
1158sub generate {
1159    my $self = shift;
1160
1161    $self->prepare;
1162
1163    for (qw(ModPerl::WrapXS Apache2 APR ModPerl)) {
1164        $self->write_makefilepl($_);
1165    }
1166
1167    $self->write_typemap;
1168
1169    for (qw(typedefs sv_convert)) {
1170        $self->write_typemap_h_file($_);
1171    }
1172
1173    $self->get_functions;
1174    $self->get_structures;
1175    $self->write_export_file('exp') if Apache2::Build::AIX;
1176    $self->write_export_file('def') if Apache2::Build::WIN32;
1177
1178    foreach my $module (sort keys %{ $self->{XS} }) {
1179        my $functions = $self->{XS}->{$module};
1180#        my ($root, $sub) = split '::', $module;
1181#        if (-e "$self->{XS_DIR}/$root/$sub/$sub.xs") {
1182#            $module = join '::', $root, "Wrap$sub";
1183#        }
1184        $self->write_makefilepl($module);
1185        $self->write_xs($module, $functions);
1186        $self->write_pm($module);
1187    }
1188
1189    $self->write_lookup_method_file;
1190    $self->write_module_versions_file;
1191}
1192
1193#three .sym files are generated:
1194#global   - global symbols
1195#ithreads - #ifdef USE_ITHREADS functions
1196#inline   - __inline__ functions
1197#the inline symbols are needed #ifdef MP_DEBUG
1198#since __inline__ will be turned off
1199
1200my %multi_export = map { $_, 1 } qw(exp);
1201
1202sub open_export_files {
1203    my ($self, $name, $ext) = @_;
1204
1205    my $dir = $self->{XS_DIR};
1206    my %handles;
1207    my @types = qw(global inline ithreads);
1208
1209    if ($multi_export{$ext}) {
1210        #write to multiple files
1211        for my $type (@types) {
1212            my $file = "$dir/${name}_$type.$ext";
1213
1214            open my $fh, '>', $file or
1215              die "open $file: $!";
1216
1217            $handles{$type} = $fh;
1218        }
1219    }
1220    else {
1221        #write to one file
1222        my $file = "$dir/$name.$ext";
1223
1224        open my $fh, '>', $file or
1225          die "open $file: $!";
1226
1227        for my $type (@types) {
1228            $handles{$type} = $fh;
1229        }
1230    }
1231
1232    \%handles;
1233}
1234
1235sub func_is_static {
1236    my ($self, $entry) = @_;
1237    if (my $attr = $entry->{attr}) {
1238        return 1 if grep { $_ eq 'static' } @$attr;
1239    }
1240
1241    #C::Scan doesnt always pickup static __inline__
1242    return 1 if $entry->{name} =~ /^mpxs_/o;
1243
1244    return 0;
1245}
1246
1247sub func_is_inline {
1248    my ($self, $entry) = @_;
1249    if (my $attr = $entry->{attr}) {
1250        return 1 if grep { $_ eq '__inline__' } @$attr;
1251    }
1252    return 0;
1253}
1254
1255sub export_file_header_exp {
1256    my $self = shift;
1257    "#!\n";
1258}
1259
1260sub export_file_format_exp {
1261    my ($self, $val) = @_;
1262    "$val\n";
1263}
1264
1265sub export_file_header_def {
1266    my $self = shift;
1267    "LIBRARY\n\nEXPORTS\n\n";
1268}
1269
1270sub export_file_format_def {
1271    my ($self, $val) = @_;
1272    "   $val\n";
1273}
1274
1275my $ithreads_exports = join '|', qw{
1276modperl_cmd_interp_
1277modperl_interp_
1278modperl_list_
1279modperl_tipool_
1280modperl_svptr_table_clone$
1281modperl_mgv_require_module$
1282};
1283
1284sub export_func_handle {
1285    my ($self, $entry, $handles) = @_;
1286
1287    if ($self->func_is_inline($entry)) {
1288        return $handles->{inline};
1289    }
1290    elsif ($entry->{name} =~ /^($ithreads_exports)/) {
1291        return $handles->{ithreads};
1292    }
1293
1294    $handles->{global};
1295}
1296
1297sub write_export_file {
1298    my ($self, $ext) = @_;
1299
1300    my %files = (
1301        modperl => $ModPerl::FunctionTable,
1302        apache2 => $Apache2::FunctionTable,
1303        apr     => $APR::FunctionTable,
1304    );
1305
1306    my $header = \&{"export_file_header_$ext"};
1307    my $format = \&{"export_file_format_$ext"};
1308
1309    foreach my $key (sort keys %files) {
1310        my $table = $files{$key};
1311        my $handles = $self->open_export_files($key, $ext);
1312
1313        my %seen; #only write header once if this is a single file
1314        for my $fh (values %$handles) {
1315            next if $seen{$fh}++;
1316            print $fh $self->$header();
1317        }
1318
1319        # add the symbols which aren't the function table
1320        if ($key eq 'modperl') {
1321            my $fh = $handles->{global};
1322            for my $name (@global_structs) {
1323                print $fh $self->$format($name);
1324            }
1325        }
1326
1327        for my $entry (@$table) {
1328            next if $self->func_is_static($entry);
1329            my $name = $entry->{name};
1330
1331            my $fh = $self->export_func_handle($entry, $handles);
1332
1333            print $fh $self->$format($name);
1334        }
1335
1336        %seen = (); #only close handle once if this is a single file
1337        for my $fh (values %$handles) {
1338            next if $seen{$fh}++;
1339            close $fh;
1340        }
1341    }
1342}
1343
1344sub stats {
1345    my $self = shift;
1346
1347    $self->get_functions;
1348    $self->get_structures;
1349
1350    my %stats;
1351
1352    while (my ($module, $functions) = each %{ $self->{XS} }) {
1353        $stats{$module} += @$functions;
1354        if (my $newxs = $self->{newXS}->{$module}) {
1355            $stats{$module} += @$newxs;
1356        }
1357    }
1358
1359    return \%stats;
1360}
1361
1362sub generate_exports {
1363    my ($self, $fh) = @_;
1364
1365    if (!$build->should_build_apache) {
1366        print $fh <<"EOF";
1367/* This is intentionnaly left blank, only usefull for static build */
1368const void *modperl_ugly_hack = NULL;
1369EOF
1370        return;
1371    }
1372
1373    print $fh <<"EOF";
1374/*
1375 * This is indeed a ugly hack!
1376 * See also src/modules/perl/mod_perl.c for modperl_ugly_hack
1377 * If we don't build such a list of exported API functions, the over-zealous
1378 * linker can and will remove the unused functions completely. In order to
1379 * avoid this, we create this object and modperl_ugly_hack to create a
1380 * dependency between all the exported API and mod_perl.c
1381 */
1382const void *modperl_ugly_hack = NULL;
1383EOF
1384
1385    for my $entry (@$ModPerl::FunctionTable) {
1386        next if $self->func_is_static($entry);
1387        unless (Apache2::Build::PERL_HAS_ITHREADS) {
1388            next if $entry->{name} =~ /^($ithreads_exports)/;
1389        }
1390        ( my $name ) = $entry->{name} =~ /^modperl_(.*)/;
1391        print $fh <<"EOF";
1392#ifndef modperl_$name
1393const void *modperl_hack_$name = (const void *)modperl_$name;
1394#endif
1395
1396EOF
1397    }
1398}
1399
14001;
1401__END__
1402