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