blob: 9f5cb08e6f0e5aa92d7c31cd289a8d9bf372ceda [file] [log] [blame]
# please insert nothing before this line: -*- mode: cperl; cperl-indent-level: 4; cperl-continued-statement-offset: 4; indent-tabs-mode: nil -*-
# 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 (sort { $a->{name} cmp $b->{name} } @{ $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 = <<EOF;
$return_type
$name($xs_parms)
$proto
$attrs
EOF
if ($dispatch || $orig_args || $func->{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"];
my $avoid_warning = "";
if (@$args and not $passthru) {
$avoid_warning = " /* avoiding -Wall warnings */\n";
$avoid_warning .= join "\n",
(map " $_->{name} = $_->{name};", @$args), "";
}
$code .= <<EOF;
CODE:
$avoid_warning
$retval->[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 = <<EOF;
$type
$name(obj)
$class obj
$attrs
CODE:
RETVAL = ($cast) obj->$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 = <<EOF;
$type
$name(obj, val=$default)
$class obj
$type_in val
PREINIT:
$preinit
$attrs
CODE:
RETVAL = ($cast) obj->$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 = <<EOF;
$type
$name(obj, val=(SV *)NULL)
$class obj
SV *val
PREINIT:
$preinit
$attrs
CODE:
RETVAL = ($cast) obj->$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 (SV *)NULL
$code = <<EOF;
$type
$name(obj, val_sv=(SV *)NULL)
$class obj
SV *val_sv
PREINIT:
$attrs
CODE:
RETVAL = ($cast) obj->$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 <<EOF;
$noedit_warning
use lib qw(../../../lib); #for Apache2::BuildConfig
use ModPerl::BuildMM ();
ModPerl::BuildMM::WriteMakefile(
'NAME' => '$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}) {
foreach my $sub (sort keys %$isa) {
my $base = $isa->{$sub};
#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 <<EOF;
void
$destructor->{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 <<EOF;
$noedit_warning
package $module;
use strict;
use warnings FATAL => '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 = ();
foreach my $module (sort keys %{ $self->{XS} }) {
my $functions = $self->{XS}->{$module};
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 <<EOF;
$noedit_warning
package $package;
use strict;
use warnings;
my \$methods = $methods;
EOF
print $fh <<'EOF';
use base qw(Exporter);
use mod_perl2;
our @EXPORT = qw(print_method print_module print_object);
our $VERSION = $mod_perl2::VERSION;
use constant MODULE => 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 sort 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 = sort 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 (sort @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;
foreach my $module (sort keys %{ $self->{XS} }) {
my $functions = $self->{XS}->{$module};
# 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"};
foreach my $key (sort keys %files) {
my $table = $files{$key};
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__