blob: f2f6eb0342d812393688d2211ae37d35a44da5ab [file] [log] [blame]
package Apache::ExtUtils;
use strict;
use Exporter ();
use IO::File ();
use File::Copy ();
use File::Basename qw(basename);
$Apache::ExtUtils::VERSION = '1.04';
my @config_export = qw(%Config ldopts ccopts);
@Apache::ExtUtils::EXPORT = qw(command_table);
@Apache::ExtUtils::EXPORT_OK = (qw(pm), @config_export);
my $errsv = "";
sub import {
my $class = shift;
my $config_export = join '|', @config_export;
for my $symbol (@_) {
#perl -Mlib=lib -MApache::ExtUtils=%Config -e 'print $Config{cc}'
if ($symbol =~ /$config_export/o) {
require Config;
*Apache::ExtUtils::Config = \%Config::Config;
Config_pm_fixup();
require ExtUtils::Embed;
}
}
local $Exporter::ExportLevel = 1;
$class->Exporter::import(@_);
}
*ldopts = \&ExtUtils::Embed::ldopts;
*ccopts = \&ExtUtils::Embed::ccopts;
sub Config_pm_fixup {
eval { require Apache::MyConfig; };
my %config_fixups = (
ld => sub { s/(.*)/basename $1/e },
ccdlflags => sub { s/-R\s+/-R/; },
ccflags => sub {
s/-D_GNU_SOURCE//;
unless ($Apache::MyConfig::Setup{PERL_USELARGEFILES}) {
s/-D_LARGEFILE_SOURCE\s+-D_FILE_OFFSET_BITS=\d+//;
}
},
);
while (my($key, $sub) = each %config_fixups) {
local $_ = $Config::Config{$key};
$sub->();
(tied %Config::Config)->{$key} = $_;
}
}
sub command_table {
my($class, $cmds);
if(@_ == 2) {
($class, $cmds) = @_;
}
else {
$cmds = shift;
$class = caller;
}
(my $file = $class) =~ s,.*::,,;
eval {
require "$file.pm"; #so we can see prototypes
};
if ($@) {
unless ($@ =~ /Can.t locate /) {
$errsv = $@;
}
require ExtUtils::testlib;
ExtUtils::testlib->import;
require lib;
my $lib = "lib";#hmm, lib->import + -w == Unquoted string "lib" ...
$lib->import('./lib');
eval { require $class };
if ($@ and $@ !~ /Can.t locate /) {
$errsv ||= $@;
}
}
unless (-e "$file.xs.orig") {
File::Copy::cp("$file.xs", "$file.xs.orig");
}
my $fh = IO::File->new(">$file.xs") or die $!;
my $xs = __PACKAGE__->xs_cmd_table($class, $cmds);
print $fh $xs;
close $fh;
}
#the first two `$$' are for the parms object and per-directory object
my $proto_perl2c = {
'$$$$$' => "TAKE3",
'$$$$' => "TAKE2",
'$$$' => "TAKE1",
'$$' => "NO_ARGS",
'' => "NO_ARGS",
'$$$;$' => "TAKE12",
'$$$$;$' => "TAKE23",
'$$$;$$' => "TAKE123",
'$$@' => "ITERATE",
'$$@;@' => "ITERATE2",
'$$$;*' => "RAW_ARGS",
};
my $proto_c2perl = {
map { $proto_perl2c->{$_}, $_ } keys %$proto_perl2c
};
sub proto_perl2c { $proto_perl2c }
sub proto_c2perl { $proto_c2perl }
sub cmd_info {
my($name, $subname, $info, $args_how) = @_;
return <<EOF;
static mod_perl_cmd_info cmd_info_$name = {
"$subname", "$info",
};
EOF
}
sub xs_cmd_table {
my($self, $class, $cmds) = @_;
(my $modname = $class) =~ s/::/__/g;
(my $pmname = $class) =~ s,::,/,g;
$pmname .= '.pm';
my $cmdtab = "";
my $infos = "";
for my $cmd (@$cmds) {
my($name, $sub, $cmd_data, $req_override, $args_how, $proto, $desc);
my $hash;
if(ref($cmd) eq "ARRAY") {
($name,$desc) = @$cmd;
}
elsif(ref($cmd) eq "HASH") {
$name = $cmd->{name};
$sub = $cmd->{func} || $cmd->{name};
$sub = join '::', $class, $sub unless defined &$sub;
$cmd_data = $cmd->{cmd_data};
$req_override = $cmd->{req_override};
$desc = $cmd->{errmsg};
$args_how = $cmd->{args_how};
}
else {
$name = $cmd;
}
$name ||= $sub;
my $realname = $name;
if ($name =~ s/[\<\>]//g && !$cmd->{func}) {
if($name =~ s:^/::) {
$name .= "_END";
}
$sub = join '::', $class, $name;
}
$sub ||= join '::', $class, $name;
$req_override ||= "OR_ALL";
my $meth = $class->can($name) if $name;
if(not $args_how and ($meth || defined(&$sub))) {
if(defined($proto = prototype($meth || \&{$sub}))) {
#extra $ is for config data
$args_how = $proto_perl2c->{$proto};
}
else {
$args_how ||= "TAKE123";
}
}
$desc ||= "1-3 value(s) for $name";
unless ($args_how) {
$errsv ||= $@;
die "Can't determine prototype for `$sub': $errsv";
}
(my $cname = $name) =~ s/\W/_/g;
$infos .= cmd_info($cname, $sub, $cmd_data, $args_how);
$cmdtab .= <<EOF;
{ "$realname", perl_cmd_perl_$args_how,
(void*)&cmd_info_$cname,
$req_override, $args_how, "$desc" },
EOF
}
my $dir_merger = $class->can('DIR_MERGE') ?
"perl_perl_merge_dir_config" : "NULL";
my $dir_create = $class->can('DIR_CREATE') ?
"perl_perl_create_dir_config" : "NULL";
my $server_merger = $class->can('SERVER_MERGE') ?
"perl_perl_merge_srv_config" : "NULL";
my $server_create = $class->can('SERVER_CREATE') ?
"perl_perl_create_srv_config" : "NULL";
return <<EOF;
#include "modules/perl/mod_perl.h"
static mod_perl_perl_dir_config *newPerlConfig(pool *p)
{
mod_perl_perl_dir_config *cld =
(mod_perl_perl_dir_config *)
palloc(p, sizeof (mod_perl_perl_dir_config));
cld->obj = Nullsv;
cld->pclass = "$class";
register_cleanup(p, cld, perl_perl_cmd_cleanup, null_cleanup);
return cld;
}
static void *create_dir_config_sv (pool *p, char *dirname)
{
return newPerlConfig(p);
}
static void *create_srv_config_sv (pool *p, server_rec *s)
{
return newPerlConfig(p);
}
static void stash_mod_pointer (char *class, void *ptr)
{
SV *sv = newSV(0);
sv_setref_pv(sv, NULL, (void*)ptr);
hv_store(perl_get_hv("Apache::XS_ModuleConfig",TRUE),
class, strlen(class), sv, FALSE);
}
$infos
static command_rec mod_cmds[] = {
$cmdtab
{ NULL }
};
module MODULE_VAR_EXPORT XS_${modname} = {
STANDARD_MODULE_STUFF,
NULL, /* module initializer */
create_dir_config_sv, /* per-directory config creator */
$dir_merger, /* dir config merger */
create_srv_config_sv, /* server config creator */
$server_merger, /* server config merger */
mod_cmds, /* command table */
NULL, /* [7] list of handlers */
NULL, /* [2] filename-to-URI translation */
NULL, /* [5] check/validate user_id */
NULL, /* [6] check user_id is valid *here* */
NULL, /* [4] check access by host address */
NULL, /* [7] MIME type checker/setter */
NULL, /* [8] fixups */
NULL, /* [10] logger */
NULL, /* [3] header parser */
NULL, /* process initializer */
NULL, /* process exit/cleanup */
NULL, /* [1] post read_request handling */
};
#define this_module "$pmname"
static void remove_module_cleanup(void *data)
{
if (find_linked_module("$class")) {
/* need to remove the module so module index is reset */
remove_module(&XS_${modname});
}
if (data) {
/* make sure BOOT section is re-run on restarts */
(void)hv_delete(GvHV(incgv), this_module,
strlen(this_module), G_DISCARD);
if (dowarn) {
/* avoid subroutine redefined warnings */
perl_clear_symtab(gv_stashpv("$class", FALSE));
}
}
}
MODULE = $class PACKAGE = $class
PROTOTYPES: DISABLE
BOOT:
XS_${modname}.name = "$class";
add_module(&XS_${modname});
stash_mod_pointer("$class", &XS_${modname});
register_cleanup(perl_get_startup_pool(), (void *)1,
remove_module_cleanup, null_cleanup);
void
END()
CODE:
remove_module_cleanup(NULL);
EOF
}
#perl -MApache::ExtUtils=pm -e pm -- Apache::Foo
sub pm {
my($class) = @_ ? @_ : @ARGV;
(my $name = $class) =~ s/.*::(\w+)$/$1/;
write_pm($class, $name);
write_makepl($class, $name);
}
sub outfh {
my($file) = @_;
my $fh = local *FH;
if (-e $file) {
die "$file exists";
}
open $fh, ">$file" or die "open $file: $!";
print STDERR "writing $file\n";
return $fh;
}
sub write_pm {
my($class, $name) = @_;
my $fh = outfh("$name.pm");
print $fh <<EOF;
package $class;
use strict;
use Apache::ModuleConfig ();
use DynaLoader ();
if(\$ENV{MOD_PERL}) {
no strict;
\$VERSION = '1.00';
\@ISA = qw(DynaLoader);
__PACKAGE__->bootstrap(\$VERSION);
}
sub DirectiveName (\$\$\$) {
my(\$cfg, \$parms, \$arg) = \@_;
my \$scfg = Apache::ModuleConfig->get(\$parms->server);
}
1;
__END__
EOF
close $fh or die $!;
}
sub write_makepl {
my($class, $name) = @_;
my $fh = outfh("Makefile.PL");
print $fh <<EOF;
package $class;
use ExtUtils::MakeMaker;
use Apache::ExtUtils qw(command_table);
use Apache::src ();
my \@directives = (
{
name => 'DirectiveName',
errmsg => 'the syntax error message',
args_how => 'TAKE1',
req_override => 'OR_ALL',
}
);
command_table(\\\@directives);
WriteMakefile(
'NAME' => __PACKAGE__,
'VERSION_FROM' => '$name.pm',
'INC' => Apache::src->new->inc,
);
EOF
close $fh or die $!;
}
1;
__END__
=head1 NAME
Apache::ExtUtils - Utils for Apache:C/Perl glue
=head1 SYNOPSIS
use Apache::ExtUtils ();
=head1 DESCRIPTION
Under constuction, all here subject to change.
=head1 AUTHOR
Doug MacEachern