# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package ModPerl::WrapXS; use strict; use warnings FATAL => 'all'; use constant GvUNIQUE => 0; #$] >= 5.008; use Apache::TestTrace; use Apache2::Build (); use ModPerl::Code (); use ModPerl::TypeMap (); use ModPerl::MapUtil qw(function_table xs_glue_dirs); use File::Path qw(rmtree mkpath); use Cwd qw(fastcwd); use Data::Dumper; use File::Spec::Functions qw(catfile catdir); our $VERSION = '0.01'; my (@xs_includes) = ('mod_perl.h', map "modperl_xs_$_.h", qw(sv_convert util typedefs)); my @global_structs = qw(perl_module); my $build = Apache2::Build->build_config; push @global_structs, 'MP_debug_level' unless Apache2::Build::WIN32; sub new { my $class = shift; my $self = bless { typemap => ModPerl::TypeMap->new, includes => \@xs_includes, glue_dirs => [xs_glue_dirs()], }, $class; $self->typemap->get; $self; } sub typemap { shift->{typemap} } sub includes { shift->{includes} } sub function_list { my $self = shift; my (@list) = @{ function_table() }; while (my ($name, $val) = each %{ $self->typemap->function_map }) { #entries that do not exist in C::Scan generated tables next unless $name =~ /^DEFINE_/; push @list, $val; } return \@list; } sub get_functions { my $self = shift; my $typemap = $self->typemap; for my $entry (@{ $self->function_list() }) { my $func = $typemap->map_function($entry); #print "FAILED to map $entry->{name}\n" unless $func; next unless $func; my ($name, $module, $class, $args) = @{ $func } { qw(perl_name module class args) }; $self->{XS}->{ $module } ||= []; #eg ap_fputs() if ($name =~ s/^DEFINE_//) { $func->{name} =~ s/^DEFINE_//; if (needs_prefix($func->{name})) { #e.g. DEFINE_add_output_filter $func->{name} = make_prefix($func->{name}, $class); } } my $xs_parms = join ', ', map { defined $_->{default} ? "$_->{name}=$_->{default}" : $_->{name} } @$args; (my $parms = $xs_parms) =~ s/=[^,]+//g; #strip defaults my $proto = join "\n", (map " $_->{type} $_->{name}", @$args), ""; my ($dispatch, $orig_args) = @{ $func } {qw(dispatch orig_args)}; if ($dispatch =~ /^MPXS_/) { $name =~ s/^mpxs_//; $name =~ s/^$func->{prefix}//; push @{ $self->{newXS}->{ $module } }, ["$class\::$name", $dispatch]; next; } my $passthru = @$args && $args->[0]->{name} eq '...'; if ($passthru) { $parms = '...'; $proto = ''; } my $return_type = $name =~ /^DESTROY$/ ? 'void' : $func->{return_type}; my $attrs = $self->attrs($name); my $code = <{thx}) { my $thx = $func->{thx} ? 'aTHX_ ' : ""; if ($dispatch) { $thx = 'aTHX_ ' if $dispatch =~ /^mpxs_/i; } else { if ($orig_args and @$orig_args == @$args) { #args were reordered $parms = join ', ', @$orig_args; } $dispatch = $func->{name}; } if ($passthru) { $thx ||= 'aTHX_ '; $parms = 'items, MARK+1, SP'; } $thx =~ s/_ $// unless $parms; my $retval = $return_type eq 'void' ? ["", ""] : ["RETVAL = ", "OUTPUT:\n RETVAL\n"]; $code .= <[0]$dispatch($thx$parms); $retval->[1] EOF } $func->{code} = $code; push @{ $self->{XS}->{ $module } }, $func; } } sub get_value { my $e = shift; my $val = 'val'; if ($e->{class} eq 'PV') { if (my $pool = $e->{pool}) { $pool .= '(obj)'; $val = "(SvOK(ST(1)) ? apr_pstrndup($pool, val, val_len) : NULL)" } } return $val; } sub get_structures { my $self = shift; my $typemap = $self->typemap; require Apache2::StructureTable; for my $entry (@$Apache2::StructureTable) { my $struct = $typemap->map_structure($entry); next unless $struct; my $class = $struct->{class}; for my $e (@{ $struct->{elts} }) { my ($name, $default, $type, $access_mode) = @{$e}{qw(name default type access_mode)}; (my $cast = $type) =~ s/:/_/g; my $val = get_value($e); my $type_in = $type; my $preinit = "/*nada*/"; if ($e->{class} eq 'PV' and $val ne 'val') { $type_in =~ s/char/char_len/; $preinit = "STRLEN val_len;"; } my $attrs = $self->attrs($name); my $code; if ($access_mode eq 'ro') { $code = <$name; OUTPUT: RETVAL EOF } elsif ($access_mode eq 'rw' or $access_mode eq 'r+w_startup') { my $check_runtime = $access_mode eq 'rw' ? '' : qq[MP_CROAK_IF_THREADS_STARTED("setting $name");]; $code = <$name; if (items > 1) { $check_runtime obj->$name = ($cast) $val; } OUTPUT: RETVAL EOF } elsif ($access_mode eq 'r+w_startup_dup') { my $convert = $cast !~ /\bchar\b/ ? "mp_xs_sv2_$cast" : "SvPV_nolen"; $code = <$name; if (items > 1) { SV *dup = get_sv("_modperl_private::server_rec_$name", TRUE); MP_CROAK_IF_THREADS_STARTED("setting $name"); sv_setsv(dup, val); obj->$name = ($cast)$convert(dup); } OUTPUT: RETVAL EOF } elsif ($access_mode eq 'rw_char_undef') { my $pool = $e->{pool} or die "rw_char_undef accessors need pool"; $pool .= '(obj)'; # XXX: not sure where val=$default is coming from, but for now use # hardcoded Nullsv $code = <$name; if (val_sv) { if (SvOK(val_sv)) { STRLEN val_len; char *val = (char *)SvPV(val_sv, val_len); obj->$name = apr_pstrndup($pool, val, val_len); } else { obj->$name = NULL; } } OUTPUT: RETVAL EOF } push @{ $self->{XS}->{ $struct->{module} } }, { code => $code, class => $class, name => $name, }; } } } sub prepare { my $self = shift; $self->{DIR} = 'WrapXS'; $self->{XS_DIR} = catdir fastcwd(), 'xs'; my $verbose = Apache::TestTrace::trace_level() eq 'debug' ? 1 : 0; if (-e $self->{DIR}) { rmtree([$self->{DIR}], $verbose, 1); } mkpath [$self->{DIR}], $verbose, 0755; } sub class_dirname { my ($self, $class) = @_; my ($base, $sub) = split '::', $class; return "$self->{DIR}/$base" unless $sub; #Apache2 | APR return $sub if $sub eq $self->{DIR}; #WrapXS return "$base/$sub"; } sub class_dir { my ($self, $class) = @_; my $dirname = $self->class_dirname($class); my $dir = ($dirname =~ m:/: and $dirname !~ m:^$self->{DIR}:) ? catdir($self->{DIR}, $dirname) : $dirname; unless (-d $dir) { mkpath [$dir], 0, 0755; debug "mkdir.....$dir"; } $dir; } sub class_file { my ($self, $class, $file) = @_; catfile $self->class_dir($class), $file; } sub cname { my ($self, $class) = @_; $class =~ s/:/_/g; $class; } sub open_class_file { my ($self, $class, $file) = @_; if ($file =~ /^\./) { my $sub = (split '::', $class)[-1]; $file = $sub . $file; } my $name = $self->class_file($class, $file); open my $fh, '>', $name or die "open $name: $!"; debug "writing...$name"; return $fh; } sub module_version { local $_ = shift; require mod_perl2; # XXX: for now APR gets its libapr-0.9 version return /^APR/ ? "0.009000" : "$mod_perl2::VERSION"; } sub write_makefilepl { my ($self, $class) = @_; my $fh = $self->open_class_file($class, 'Makefile.PL'); my $includes = $self->includes; my $xs = (split '::', $class)[-1] . '.c'; my $deps = {$xs => ""}; if (my $mod_h = $self->mod_h($class, 1)) { $deps->{$xs} .= " $mod_h"; } local $Data::Dumper::Terse = 1; $deps = Dumper $deps; my $noedit_warning = $self->ModPerl::Code::noedit_warning_hash(); require mod_perl2; my $version = module_version($class); print $fh < '$class', 'VERSION' => '$version', 'depend' => $deps, ); EOF close $fh; } sub mod_h { my ($self, $module, $complete) = @_; my $dirname = $self->class_dirname($module); my $cname = $self->cname($module); my $mod_h = "$dirname/$cname.h"; for ($self->{XS_DIR}, @{ $self->{glue_dirs} }) { my $file = "$_/$mod_h"; $mod_h = $file if $complete; return $mod_h if -e $file; } undef; } sub mod_pm { my ($self, $module, $complete) = @_; my $dirname = $self->class_dirname($module); my ($base, $sub) = split '::', $module; my $mod_pm = "$dirname/${sub}_pm"; for ($self->{XS_DIR}, @{ $self->{glue_dirs} }) { my $file = "$_/$mod_pm"; $mod_pm = $file if $complete; return $mod_pm if -e $file; } undef; } sub class_c_prefix { my $class = shift; $class =~ s/:/_/g; $class; } sub class_mpxs_prefix { my $class = shift; my $class_prefix = class_c_prefix($class); "mpxs_${class_prefix}_"; } sub needs_prefix { my $name = shift; $name !~ /^(ap|apr|mpxs)_/i; } sub make_prefix { my ($name, $class) = @_; my $class_prefix = class_mpxs_prefix($class); return $name if $name =~ /^$class_prefix/; $class_prefix . $name; } sub isa_str { my ($self, $module) = @_; my $str = ""; if (my $isa = $self->typemap->{function_map}->{isa}->{$module}) { while (my ($sub, $base) = each %$isa) { #XXX cannot set isa in the BOOT: section because XSLoader local-ises #ISA during bootstrap # $str .= qq{ av_push(get_av("$sub\::ISA", TRUE), # newSVpv("$base",0));} $str .= qq{\@$sub\::ISA = '$base';\n} } } $str; } sub boot { my ($self, $module) = @_; my $str = ""; if (my $boot = $self->typemap->{function_map}->{boot}->{$module}) { $str = ' mpxs_' . $self->cname($module) . "_BOOT(aTHX);\n"; } $str; } my $notshared = join '|', qw(TIEHANDLE); #not sure why yet sub attrs { my ($self, $name) = @_; my $str = ""; return $str if $name =~ /$notshared$/o; $str = " ATTRS: unique\n" if GvUNIQUE; $str; } sub write_xs { my ($self, $module, $functions) = @_; my $fh = $self->open_class_file($module, '.xs'); print $fh $self->ModPerl::Code::noedit_warning_c(), "\n"; print $fh "\n#define MP_IN_XS\n\n"; my @includes = @{ $self->includes }; if (my $mod_h = $self->mod_h($module)) { push @includes, $mod_h; } for (@includes) { print $fh qq{\#include "$_"\n\n}; } my $last_prefix = ""; for my $func (@$functions) { my $class = $func->{class}; my $prefix = $func->{prefix}; $last_prefix = $prefix if $prefix; if ($func->{name} =~ /^mpxs_/) { #e.g. mpxs_Apache2__RequestRec_ my $class_prefix = class_c_prefix($class); if ($func->{name} =~ /$class_prefix/) { $prefix = class_mpxs_prefix($class); } } $prefix = $prefix ? " PREFIX = $prefix" : ""; print $fh "MODULE = $module PACKAGE = $class $prefix\n\n"; print $fh $func->{code}; } if (my $destructor = $self->typemap->destructor($last_prefix)) { my $arg = $destructor->{argspec}[0]; print $fh <{name}($arg) $destructor->{class} $arg EOF } print $fh "MODULE = $module\n"; print $fh "PROTOTYPES: disabled\n\n"; print $fh "BOOT:\n"; print $fh $self->boot($module); print $fh " items = items; /* -Wall */\n\n"; if (my $newxs = $self->{newXS}->{$module}) { for my $xs (sort { $a->[0] cmp $b->[0] } @$newxs) { print $fh qq{ cv = newXS("$xs->[0]", $xs->[1], __FILE__);\n}; print $fh qq{ GvUNIQUE_on(CvGV(cv));\n} if GvUNIQUE; } } if ($module eq 'APR::Pool' && Apache2::Build::PERL_HAS_ITHREADS) { print $fh " modperl_opt_interp_unselect = APR_RETRIEVE_OPTIONAL_FN(modperl_interp_unselect);\n\n"; print $fh " modperl_opt_thx_interp_get = APR_RETRIEVE_OPTIONAL_FN(modperl_thx_interp_get);\n\n"; } close $fh; } sub write_pm { my ($self, $module) = @_; my $isa = $self->isa_str($module); my $code = ""; if (my $mod_pm = $self->mod_pm($module, 1)) { open my $fh, '<', $mod_pm; local $/; $code = <$fh>; close $fh; } my $base = (split '::', $module)[0]; unless (-e "lib/$base/XSLoader.pm") { $base = 'Apache2'; } my $loader = join '::', $base, 'XSLoader'; my $fh = $self->open_class_file($module, '.pm'); my $noedit_warning = $self->ModPerl::Code::noedit_warning_hash(); my $use_apr = ($module =~ /^APR::\w+$/) ? 'use APR ();' : ''; my $version = module_version($module); print $fh < 'all'; $isa $use_apr use $loader (); our \$VERSION = '$version'; $loader\::load __PACKAGE__; $code 1; __END__ EOF } my %typemap = ( 'Apache2::RequestRec' => 'T_APACHEOBJ', 'apr_time_t' => 'T_APR_TIME', 'APR::Table' => 'T_HASHOBJ', 'APR::Pool' => 'T_POOLOBJ', 'apr_size_t *' => 'T_UVPTR', ); sub write_typemap { my $self = shift; my $typemap = $self->typemap; my $map = $typemap->get; my %seen; my $fh = $self->open_class_file('ModPerl::WrapXS', 'typemap'); print $fh $self->ModPerl::Code::noedit_warning_hash(), "\n"; my %entries = (); my $max_key_len = 0; while (my ($type, $class) = each %$map) { $class ||= $type; next if $seen{$type}++ || $typemap->special($class); if ($class =~ /::/) { $entries{$class} = $typemap{$class} || 'T_PTROBJ'; $max_key_len = length $class if length $class > $max_key_len; } else { $entries{$type} = $typemap{$type} || "T_$class"; $max_key_len = length $type if length $type > $max_key_len; } } for (sort keys %entries) { printf $fh "%-${max_key_len}s %s\n", $_, $entries{$_}; } close $fh; } sub write_typemap_h_file { my ($self, $method) = @_; $method = $method . '_code'; my ($h, $code) = $self->typemap->$method(); my $file = catfile $self->{XS_DIR}, $h; open my $fh, '>', $file or die "open $file: $!"; print $fh $self->ModPerl::Code::noedit_warning_c(), "\n"; print $fh $code; close $fh; } sub write_lookup_method_file { my $self = shift; my %map = (); while (my ($module, $functions) = each %{ $self->{XS} }) { my $last_prefix = ""; for my $func (@$functions) { my $class = $func->{class}; my $prefix = $func->{prefix}; $last_prefix = $prefix if $prefix; my $name = $func->{perl_name} || $func->{name}; $name =~ s/^DEFINE_//; if ($name =~ /^mpxs_/) { #e.g. mpxs_Apache2__RequestRec_ my $class_prefix = class_c_prefix($class); if ($name =~ /$class_prefix/) { $prefix = class_mpxs_prefix($class); } } elsif ($name =~ /^ap_sub_req/) { $prefix = 'ap_sub_req_'; } $name =~ s/^$prefix// if $prefix; push @{ $map{$name} }, [$module, $class]; } # pure XS wrappers don't have the information about the # arguments they receive, since they manipulate the arguments # stack directly. therefore for these methods we can't tell # what are the objects they are invoked on for my $xs (@{ $self->{newXS}->{$module} || []}) { push @{ $map{$1} }, [$module, undef] if $xs->[0] =~ /.+::(.+)/; } } local $Data::Dumper::Terse = 1; local $Data::Dumper::Sortkeys = 1; $Data::Dumper::Terse = $Data::Dumper::Terse; # warn $Data::Dumper::Sortkeys = $Data::Dumper::Sortkeys; # warn my $methods = Dumper(\%map); $methods =~ s/\n$//; my $package = "ModPerl::MethodLookup"; my $file = catfile "lib", "ModPerl", "MethodLookup.pm"; debug "creating $file"; open my $fh, ">$file" or die "Can't open $file: $!"; my $noedit_warning = $self->ModPerl::Code::noedit_warning_hash(); print $fh < 0; use constant OBJECT => 1; my $modules; my $objects; sub _get_modules { for my $method (sort keys %$methods) { for my $item ( @{ $methods->{$method} }) { push @{ $modules->{$item->[MODULE]} }, [$method, $item->[OBJECT]]; } } } sub _get_objects { for my $method (sort keys %$methods) { for my $item ( @{ $methods->{$method} }) { next unless defined $item->[OBJECT]; push @{ $objects->{$item->[OBJECT]} }, [$method, $item->[MODULE]]; } } } # if there is only one replacement method in 2.0 API we can # automatically lookup it, up however if there are more than one # (e.g. new()), we need to use a fully qualified value here # of course the same if the package is not a mod_perl one. # # the first field represents the replacement method or undef if none # exists, the second field is for extra comments (e.g. when there is # no replacement method) my $methods_compat = { # Apache2:: gensym => ['Symbol::gensym', 'or use "open my $fh, $file"'], module => ['Apache2::Module::loaded', ''], define => ['exists_config_define', ''], httpd_conf => ['add_config', ''], SERVER_VERSION => ['get_server_version', ''], can_stack_handlers=> [undef, 'there is no more need for that method in mp2'], # Apache2::RequestRec soft_timeout => [undef, 'there is no more need for that method in mp2'], hard_timeout => [undef, 'there is no more need for that method in mp2'], kill_timeout => [undef, 'there is no more need for that method in mp2'], reset_timeout => [undef, 'there is no more need for that method in mp2'], cleanup_for_exec => [undef, 'there is no more need for that method in mp2'], send_http_header => ['content_type', ''], header_in => ['headers_in', 'this method works in mod_perl 1.0 too'], header_out => ['headers_out', 'this method works in mod_perl 1.0 too'], err_header_out => ['err_headers_out', 'this method works in mod_perl 1.0 too'], register_cleanup => ['cleanup_register', ''], post_connection => ['cleanup_register', ''], content => [undef, # XXX: Apache2::Request::what? 'use CGI.pm or Apache2::Request instead'], clear_rgy_endav => ['special_list_clear', ''], stash_rgy_endav => [undef, ''], run_rgy_endav => ['special_list_call', 'this method is no longer needed'], seqno => [undef, 'internal to mod_perl 1.0'], chdir_file => [undef, # XXX: to be resolved 'temporary unavailable till the issue with chdir' . ' in the threaded env is resolved'], log_reason => ['log_error', 'not in the Apache 2.0 API'], READLINE => [undef, # XXX: to be resolved ''], send_fd_length => [undef, 'not in the Apache 2.0 API'], send_fd => ['sendfile', 'requires an offset argument'], is_main => ['main', 'not in the Apache 2.0 API'], cgi_var => ['subprocess_env', 'subprocess_env can be used with mod_perl 1.0'], cgi_env => ['subprocess_env', 'subprocess_env can be used with mod_perl 1.0'], each_byterange => [undef, 'now handled internally by ap_byterange_filter'], set_byterange => [undef, 'now handled internally by ap_byterange_filter'], # Apache::File open => [undef, ''], close => [undef, # XXX: also defined in APR::Socket ''], tmpfile => [undef, 'not in the Apache 2.0 API, ' . 'use File::Temp instead'], # Apache::Util size_string => ['format_size', ''], escape_uri => ['unescape_path', ''], escape_url => ['escape_path', 'and requires a pool object'], unescape_uri => ['unescape_url', ''], unescape_url_info => [undef, 'use CGI::Util::unescape() instead'], escape_html => [undef, # XXX: will be ap_escape_html 'ap_escape_html now requires a pool object'], parsedate => ['parse_http', ''], validate_password => ['password_validate', ''], # Apache::Table #new => ['make', # ''], # XXX: there are other 'new' methods # Apache::Connection auth_type => ['ap_auth_type', 'now resides in the request object'], }; sub avail_methods_compat { return keys %$methods_compat; } sub avail_methods { return keys %$methods; } sub avail_modules { my %modules = (); for my $method (keys %$methods) { for my $item ( @{ $methods->{$method} }) { $modules{$item->[MODULE]}++; } } return keys %modules; } sub preload_all_modules { _get_modules() unless $modules; eval "require $_" for keys %$modules; } sub _print_func { my $func = shift; my @args = @_ ? @_ : @ARGV; no strict 'refs'; print( ($func->($_))[0]) for @args; } sub print_module { _print_func('lookup_module', @_) } sub print_object { _print_func('lookup_object', @_) } sub print_method { my @args = @_ ? @_ : @ARGV; while (@args) { my $method = shift @args; my $object = (@args && (ref($args[0]) || $args[0] =~ /^(Apache2|ModPerl|APR)/)) ? shift @args : undef; print( (lookup_method($method, $object))[0]); } } sub sep { return '-' x (shift() + 20) . "\n" } # what modules contain the passed method. # an optional object or a reference to it can be passed to help # resolve situations where there is more than one module containing # the same method. Inheritance is supported. sub lookup_method { my ($method, $object) = @_; unless (defined $method) { my $hint = "No 'method' argument was passed\n"; return ($hint); } # strip the package name for the fully qualified method $method =~ s/.+:://; if (exists $methods_compat->{$method}) { my ($replacement, $comment) = @{$methods_compat->{$method}}; my $hint = "'$method' is not a part of the mod_perl 2.0 API\n"; $comment = length $comment ? " $comment\n" : ""; # some removed methods have no replacement return $hint . "$comment" unless defined $replacement; $hint .= "use '$replacement' instead. $comment"; # if fully qualified don't look up its container return $hint if $replacement =~ /::/; my ($modules_hint, @modules) = lookup_method($replacement, $object); return $hint . $modules_hint; } elsif (!exists $methods->{$method}) { my $hint = "Don't know anything about method '$method'\n"; return ($hint); } my @items = @{ $methods->{$method} }; if (@items == 1) { my $module = $items[0]->[MODULE]; my $hint = "To use method '$method' add:\n" . "\tuse $module ();\n"; # we should really check that the method matches the object if # any was passed, but it may not always work return ($hint, $module); } else { if (defined $object) { my $class = ref $object || $object; for my $item (@items) { # real class or inheritance if ($class eq $item->[OBJECT] or (ref($object) && $object->isa($item->[OBJECT]))) { my $module = $item->[MODULE]; my $hint = "To use method '$method' add:\n" . "\tuse $module ();\n"; return ($hint, $module); } } # fall-through local $" = ", "; my @modules = map $_->[MODULE], @items; my $hint = "Several modules (@modules) contain method '$method' " . "but none of them matches class '$class';\n"; return ($hint); } else { my %modules = map { $_->[MODULE] => 1 } @items; # remove dups if any (e.g. $s->add_input_filter and # $r->add_input_filter are loaded by the same Apache2::Filter) my @modules = keys %modules; my $hint; if (@modules == 1) { $hint = "To use method '$method' add:\n\tuse $modules[0] ();\n"; return ($hint, $modules[0]); } else { $hint = "There is more than one class with method '$method'\n" . "try one of:\n" . join '', map {"\tuse $_ ();\n"} @modules; return ($hint, @modules); } } } } # what methods are contained in the passed module name sub lookup_module { my ($module) = shift; unless (defined $module) { my $hint = "no 'module' argument was passed\n"; return ($hint); } _get_modules() unless $modules; unless (exists $modules->{$module}) { my $hint = "don't know anything about module '$module'\n"; return ($hint); } my @methods; my $max_len = 6; for ( @{ $modules->{$module} } ) { $max_len = length $_->[0] if length $_->[0] > $max_len; push @methods, $_->[0]; } my $format = "%-${max_len}s %s\n"; my $banner = sprintf($format, "Method", "Invoked on object type"); my $hint = join '', ("\nModule '$module' contains the following XS methods:\n\n", $banner, sep(length($banner)), map( { sprintf $format, $_->[0], $_->[1]||'???'} @{ $modules->{$module} }), sep(length($banner))); return ($hint, @methods); } # what methods can be invoked on the passed object (or its reference) sub lookup_object { my ($object) = shift; unless (defined $object) { my $hint = "no 'object' argument was passed\n"; return ($hint); } _get_objects() unless $objects; # a real object was passed? $object = ref $object || $object; unless (exists $objects->{$object}) { my $hint = "don't know anything about objects of type '$object'\n"; return ($hint); } my @methods; my $max_len = 6; for ( @{ $objects->{$object} } ) { $max_len = length $_->[0] if length $_->[0] > $max_len; push @methods, $_->[0]; } my $format = "%-${max_len}s %s\n"; my $banner = sprintf($format, "Method", "Module"); my $hint = join '', ("\nObjects of type '$object' can invoke the following XS methods:\n\n", $banner, sep(length($banner)), map({ sprintf $format, $_->[0], $_->[1]} @{ $objects->{$object} }), sep(length($banner))); return ($hint, @methods); } 1; EOF close $fh; } sub write_module_versions_file { my $self = shift; my $file = catfile "lib", "ModPerl", "DummyVersions.pm"; debug "creating $file"; open my $fh, ">$file" or die "Can't open $file: $!"; my $noedit_warning = $self->ModPerl::Code::noedit_warning_hash(); print $fh "$noedit_warning\n"; my @modules = keys %{ $self->{XS} }; push @modules, qw(ModPerl::MethodLookup); my $len = 0; for (@modules) { $len = length $_ if length $_ > $len; } require mod_perl2; $len += length '$::VERSION'; for (@modules) { my $ver = module_version($_); printf $fh "package %s;\n%-${len}s = %s;\n\n", $_, '$'.$_."::VERSION", $ver; } } sub generate { my $self = shift; $self->prepare; for (qw(ModPerl::WrapXS Apache2 APR ModPerl)) { $self->write_makefilepl($_); } $self->write_typemap; for (qw(typedefs sv_convert)) { $self->write_typemap_h_file($_); } $self->get_functions; $self->get_structures; $self->write_export_file('exp') if Apache2::Build::AIX; $self->write_export_file('def') if Apache2::Build::WIN32; while (my ($module, $functions) = each %{ $self->{XS} }) { # my ($root, $sub) = split '::', $module; # if (-e "$self->{XS_DIR}/$root/$sub/$sub.xs") { # $module = join '::', $root, "Wrap$sub"; # } $self->write_makefilepl($module); $self->write_xs($module, $functions); $self->write_pm($module); } $self->write_lookup_method_file; $self->write_module_versions_file; } #three .sym files are generated: #global - global symbols #ithreads - #ifdef USE_ITHREADS functions #inline - __inline__ functions #the inline symbols are needed #ifdef MP_DEBUG #since __inline__ will be turned off my %multi_export = map { $_, 1 } qw(exp); sub open_export_files { my ($self, $name, $ext) = @_; my $dir = $self->{XS_DIR}; my %handles; my @types = qw(global inline ithreads); if ($multi_export{$ext}) { #write to multiple files for my $type (@types) { my $file = "$dir/${name}_$type.$ext"; open my $fh, '>', $file or die "open $file: $!"; $handles{$type} = $fh; } } else { #write to one file my $file = "$dir/$name.$ext"; open my $fh, '>', $file or die "open $file: $!"; for my $type (@types) { $handles{$type} = $fh; } } \%handles; } sub func_is_static { my ($self, $entry) = @_; if (my $attr = $entry->{attr}) { return 1 if grep { $_ eq 'static' } @$attr; } #C::Scan doesnt always pickup static __inline__ return 1 if $entry->{name} =~ /^mpxs_/o; return 0; } sub func_is_inline { my ($self, $entry) = @_; if (my $attr = $entry->{attr}) { return 1 if grep { $_ eq '__inline__' } @$attr; } return 0; } sub export_file_header_exp { my $self = shift; "#!\n"; } sub export_file_format_exp { my ($self, $val) = @_; "$val\n"; } sub export_file_header_def { my $self = shift; "LIBRARY\n\nEXPORTS\n\n"; } sub export_file_format_def { my ($self, $val) = @_; " $val\n"; } my $ithreads_exports = join '|', qw{ modperl_cmd_interp_ modperl_interp_ modperl_list_ modperl_tipool_ modperl_svptr_table_clone$ modperl_mgv_require_module$ }; sub export_func_handle { my ($self, $entry, $handles) = @_; if ($self->func_is_inline($entry)) { return $handles->{inline}; } elsif ($entry->{name} =~ /^($ithreads_exports)/) { return $handles->{ithreads}; } $handles->{global}; } sub write_export_file { my ($self, $ext) = @_; my %files = ( modperl => $ModPerl::FunctionTable, apache2 => $Apache2::FunctionTable, apr => $APR::FunctionTable, ); my $header = \&{"export_file_header_$ext"}; my $format = \&{"export_file_format_$ext"}; while (my ($key, $table) = each %files) { my $handles = $self->open_export_files($key, $ext); my %seen; #only write header once if this is a single file for my $fh (values %$handles) { next if $seen{$fh}++; print $fh $self->$header(); } # add the symbols which aren't the function table if ($key eq 'modperl') { my $fh = $handles->{global}; for my $name (@global_structs) { print $fh $self->$format($name); } } for my $entry (@$table) { next if $self->func_is_static($entry); my $name = $entry->{name}; my $fh = $self->export_func_handle($entry, $handles); print $fh $self->$format($name); } %seen = (); #only close handle once if this is a single file for my $fh (values %$handles) { next if $seen{$fh}++; close $fh; } } } sub stats { my $self = shift; $self->get_functions; $self->get_structures; my %stats; while (my ($module, $functions) = each %{ $self->{XS} }) { $stats{$module} += @$functions; if (my $newxs = $self->{newXS}->{$module}) { $stats{$module} += @$newxs; } } return \%stats; } sub generate_exports { my ($self, $fh) = @_; if (!$build->should_build_apache) { print $fh <<"EOF"; /* This is intentionnaly left blank, only usefull for static build */ const void *modperl_ugly_hack = NULL; EOF return; } print $fh <<"EOF"; /* * This is indeed a ugly hack! * See also src/modules/perl/mod_perl.c for modperl_ugly_hack * If we don't build such a list of exported API functions, the over-zealous * linker can and will remove the unused functions completely. In order to * avoid this, we create this object and modperl_ugly_hack to create a * dependency between all the exported API and mod_perl.c */ const void *modperl_ugly_hack = NULL; EOF for my $entry (@$ModPerl::FunctionTable) { next if $self->func_is_static($entry); unless (Apache2::Build::PERL_HAS_ITHREADS) { next if $entry->{name} =~ /^($ithreads_exports)/; } ( my $name ) = $entry->{name} =~ /^modperl_(.*)/; print $fh <<"EOF"; #ifndef modperl_$name const void *modperl_hack_$name = (const void *)modperl_$name; #endif EOF } } 1; __END__