use strict; use ModPerl::MM; use 5.005; use Apache::Test5005compat; use Apache::TestMM qw(test clean); use Apache::TestReport (); use Apache::TestSmoke (); use Apache::TestRun (); use Apache::TestConfigPerl (); use Apache::TestSmokePerl (); use Apache::TestReportPerl (); use Config; use File::Find qw(finddepth); use File::Basename; use Apache2::Build; use constant WIN32 => Apache2::Build::WIN32; use Cwd; use ExtUtils::XSBuilder::ParseSource; my $version = "2.XX-dev"; # DUMMY VALUE my $cwd = WIN32 ? Win32::GetLongPathName(cwd) : cwd; $cwd =~ m{^(.+)/glue/perl$} or die "Can't find base directory"; my $base_dir = $1; my $inc_dir = "$base_dir/include"; my $lib_dir = "$base_dir/library"; my $xs_dir = "$base_dir/glue/perl/xsbuilder"; sub slurp($$) { open my $file, $_[1] or die "Can't open $_[1]: $!"; read $file, $_[0], -s $file; } sub cmp_tuples { my ($num_a, $num_b) = @_; while (@$num_a && @$num_b) { my $cmp = shift @$num_a <=> shift @$num_b; return $cmp if $cmp; } return @$num_a <=> @$num_b; } sub autoconf_foo { my ($config, $re_start, $re_end, $re_match) = @_; $$config =~ /^${re_start}APACHE2_INCLUDES${re_end}($re_match)/m or die "Can't find apache include directory"; my $apache_includes = $1; $$config =~ /^${re_start}APR_INCLUDES${re_end}($re_match)/m or die "Can't find apache include directory"; $apache_includes .= " $1"; my $apr_libs =""; $$config =~ m/^${re_start}APREQ_LIBNAME${re_end}($re_match)/m or die "Can't find apreq libname"; ## XXX: 2.60 bug/hack my $apreq_libname = $1; $$config =~ m/^${re_start}PACKAGE_VERSION${re_end}($re_match)/m or die "Can't find package version"; my $version = $1; ## Code around an autoconf 2.60 bug ## http://lists.gnu.org/archive/html/bug-autoconf/2006-06/msg00127.html ## $ grep @PACKAGE_VERSION config.status-2.59 config.status-2.60 ## config.status-2.59:s,@PACKAGE_VERSION@,2.09,;t t ## config.status-2.60:s,@PACKAGE_VERSION@,|#_!!_#|2.09,g foreach ($apache_includes, $apreq_libname, $version) { s/\|#_!!_#\|//g; } return ($apache_includes, $apr_libs, $apreq_libname, $version); } my ($apache_includes, $apache_dir, $apr_libs, $apreq_libname, $perl_lib); if (WIN32) { # XXX May need fixing, Randy! slurp my $config => "$base_dir/configure.ac"; $config =~ /^AC_INIT[^,]+,\s*([^,\s]+)/m or die "Can't find version string"; $version = $1; slurp my $make => "$base_dir/Makefile"; $make =~ /^APACHE=(\S+)/m or die "Cannot find top-level Apache directory"; ($apache_dir = $1) =~ s!\\!/!g; ($apache_includes = "-I$apache_dir" . '/include') =~ s!\\!/!g; ($apr_libs = "-L$apache_dir" . '/lib') =~ s!\\!/!g; $make =~ /^APR_LIB=(\S+)/m or die "Cannot find apr lib"; $apr_libs .= ' -l' . basename($1, '.lib'); $make =~ /^APU_LIB=(\S+)/m or die "Cannot find aprutil lib"; $apr_libs .= ' -l' . basename($1, '.lib'); $apreq_libname = 'apreq2'; $perl_lib = $Config{installsitelib} . '\auto\libaprext'; $perl_lib =~ s{\\}{\\\\}g; } else { slurp my $config => "$base_dir/config.status"; $config =~ /GNU Autoconf (\d+\.\d+)/; my $autoconf_ver = $1; ### XXX: Lord have mercy on us..... if (cmp_tuples([split /\./, $autoconf_ver], [qw(2 61)]) > 0) { ### Autoconf >=2.62 changed the format of the file ### I.E.: S["APACHE2_INCLUDES"]="-I/usr/local/include/apache2" ($apache_includes, $apr_libs, $apreq_libname, $version) = autoconf_foo(\$config, qr/S\[\"/, qr/\"\]=\"/, qr/[^\"]+/); } else { ### I.E.: s,@APACHE2_INCLUDES@,-I/usr/local/include/apache22,;t t ($apache_includes, $apr_libs, $apreq_libname, $version) = autoconf_foo(\$config, qr/s,\@/, qr/\@,/, qr/[^,]+/); } } my $apreq_libs; if (WIN32) { $apreq_libs = qq{-L$base_dir/win32/libs -llib$apreq_libname -lmod_apreq2 -L$perl_lib -llibaprext -L$apache_dir/lib -lmod_perl}; } else { my $apreq2_config = "$base_dir/apreq2-config"; my $bindir = qx{$apreq2_config --bindir}; chomp $bindir; $apreq2_config = "$bindir/apreq2-config" if $ENV{INSTALL}; $apreq_libs = qx{$apreq2_config --link-ld --ldflags --libs}; chomp $apreq_libs; } my $mp2_typemaps = Apache2::Build->new->typemaps; package My::ParseSource; use base qw/ExtUtils::XSBuilder::ParseSource/; use constant WIN32 => ($^O =~ /Win32/i); my @dirs = ("$base_dir/include", "$base_dir/module/apache2"); sub package {'APR::Request'} sub unwanted_includes {[qw/apreq_config.h apreq_private_apache2.h/]} # ParseSource.pm v 0.23 bug: line 214 should read # my @dirs = @{$self->include_dirs}; # for now, we override it here just to work around the bug sub find_includes { my $self = shift; return $self->{includes} if $self->{includes}; require File::Find; my(@dirs) = @{$self->include_dirs}; unless (-d $dirs[0]) { die "could not find include directory"; } # print "Will search @dirs for include files...\n" if ($verbose) ; my @includes; my $unwanted = join '|', @{$self -> unwanted_includes} ; for my $dir (@dirs) { File::Find::finddepth({ wanted => sub { return unless /\.h$/; return if ($unwanted && (/^($unwanted)/o)); my $dir = $File::Find::dir; push @includes, "$dir/$_"; }, follow => not WIN32, }, $dir); } return $self->{includes} = $self -> sort_includes (\@includes) ; } sub include_dirs {\@dirs} package My::WrapXS; use base qw/ExtUtils::XSBuilder::WrapXS/; our $VERSION = $version; use constant WIN32 => ($^O =~ /Win32/i); ################################################## # Finally, we get to the actual script... __PACKAGE__ -> run; my @scripts = (); use File::Spec::Functions qw(catfile); File::Find::finddepth(sub { return unless /(.*?\.pl)\.PL$/; push @scripts, "$File::Find::dir/$1"; }, '.'); Apache::TestMM::filter_args(); Apache::TestMM::generate_script("t/TEST"); Apache::TestSmokePerl->generate_script; Apache::TestReportPerl->generate_script; my %opts = ( NAME => 'libapreq2', DIR => [qw(xs)], clean => { FILES => "xs t/logs t/TEST @scripts" }, realclean => { FILES => "xsbuilder/tables" }, ); ModPerl::MM::WriteMakefile(%opts); # That's the whole script - below is just a bunch of local overrides ################################################## sub get_functions { my $self = shift; $self->{XS}->{"APR::Request::Error"} ||= []; $self->SUPER::get_functions; } sub test_docs { my ($pods, $tests) = @_; require Config; my $bin = $Config::Config{bin}; my $pod2test = catfile $bin, "pod2test"; $pod2test = Apache::TestConfig::which('pod2test') unless -e $pod2test; return "" unless $pod2test and -e $pod2test; return join "", map <, ); my @tests = @docs; s/pod$/t/ for @tests; s/^xsbuilder/xs/ for @tests; my $string = ""; my $test_docs = test_docs(\@docs, \@tests); if ($test_docs) { $string .= $test_docs; $string .= <new]} sub new_typemap {My::TypeMap->new(shift)} sub h_filename_prefix {'apreq_xs_'} sub my_xs_prefix {'apreq_xs_'} sub xs_include_dir { $xs_dir } sub mod_xs { my($self, $module, $complete) = @_; my $dirname = $self->class_dirname($module); my @parts = split '::', $module; my $mod_xs = "$dirname/$parts[-1].xs"; for ($self -> xs_include_dir, @{ $self->{glue_dirs} }) { my $file = "$_/$mod_xs"; $mod_xs = $file if $complete; return $mod_xs if -e $file; } undef; } sub mod_pm { my($self, $module, $complete) = @_; my $dirname = $self->class_dirname($module); my @parts = split '::', $module; my $mod_pm = "$dirname/$parts[-1].pm"; for ($self -> xs_include_dir, @{ $self->{glue_dirs} }) { my $file = "$_/$mod_pm"; $mod_pm = $file if $complete; return $mod_pm if -e $file; } undef; } #inline mod_xs directly, so we can put XS directives there sub write_xs { my($self, $module, $functions) = @_; my $fh = $self->open_class_file($module, '.xs'); print $fh "$self->{noedit_warning_c}\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}; } if (my $mod_xs = $self->mod_xs($module, 1)) { open my $file, $mod_xs or die "can't open $mod_xs: $!"; print $fh $_ while <$file>; print $fh "\n\n"; } my $last_prefix = ""; my $fmap = $self -> typemap -> {function_map} ; my $myprefix = $self -> my_xs_prefix ; for my $func (@$functions) { my $class = $func->{class}; if ($class) { my $prefix = $func->{prefix}; $last_prefix = $prefix if $prefix; if ($func->{name} =~ /^$myprefix/o) { #e.g. mpxs_Apache__RequestRec_ my $class_prefix = $fmap -> class_c_prefix($class); if ($func->{name} =~ /$class_prefix/) { $prefix = $fmap -> class_xs_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 "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 (@$newxs) { print $fh qq{ cv = newXS("$xs->[0]", $xs->[1], __FILE__);\n}; print $fh qq{ GvSHARED_on(CvGV(cv));\n} if ExtUtils::XSBuilder::WrapXS::GvSHARED(); } } close $fh; } sub mod_pod { my($self, $module, $complete) = @_; my $dirname = $self->class_dirname($module); my @parts = split '::', $module; my $mod_pod = "$dirname/$parts[-1].pod"; for ($self -> xs_incsrc_dir, @{ $self->{glue_dirs} }) { my $file = "$_/$mod_pod"; $mod_pod = $file if $complete; print "mod_pod $mod_pod $file $complete\n" ; return $mod_pod if -e $file; } undef; } sub write_docs { my ($self, $module, $functions) = @_; my $podfile = $self->mod_pod($module, 1) or return; my $fh = $self->open_class_file($module, '.pod'); open my $pod, "<", $podfile or die $!; while (<$pod>) { print $fh $_; } } sub pm_text { my($self, $module, $isa, $code) = @_; my $text = <<"EOF"; $self->{noedit_warning_hash} package $module; require DynaLoader ; use strict; use warnings FATAL => 'all'; use vars qw{\$VERSION \@ISA} ; $isa push \@ISA, 'DynaLoader' ; \$VERSION = '$version'; bootstrap $module \$VERSION ; $code 1; __END__ EOF return $text; } sub makefilepl_text { my($self, $class, $deps,$typemap) = @_; my @parts = split (/::/, $class) ; my $mmargspath = '../' x @parts ; $mmargspath .= 'mmargs.pl' ; my $txt = qq{ $self->{noedit_warning_hash} use ModPerl::MM; local \$MMARGS ; if (-f '$mmargspath') { do '$mmargspath' ; die \$\@ if (\$\@) ; } \$MMARGS ||= {} ; ModPerl::MM::WriteMakefile( 'NAME' => '$class', 'VERSION' => '$version', 'TYPEMAPS' => [qw(@$mp2_typemaps $typemap)], 'INC' => "-I$base_dir/glue/perl/xs -I$inc_dir -I$xs_dir $apache_includes", 'LIBS' => "$apreq_libs $apr_libs", } ; $txt .= "'depend' => $deps,\n" if ($deps) ; $txt .= qq{ \%\$MMARGS, ); } ; } # For now, just copy the typemap file in xsbuilder til we # can remove ExtUtils::XSBuilder. sub write_typemap { my $self = shift; my $typemap = $self->typemap; my $map = $typemap->get; my %seen; my $fh = $self->open_class_file('', 'typemap'); print $fh "$self->{noedit_warning_hash}\n"; open my $tfh, "$xs_dir/typemap" or die $!; print $fh $_ while <$tfh>; } package My::TypeMap; use base 'ExtUtils::XSBuilder::TypeMap'; sub null_type { my($self, $type) = @_; my $t = $self->get->{$type}; my $class = $t -> {class} ; if ($class =~ /APREQ_COOKIE_VERSION/) { return 'APREQ_COOKIE_VERSION_DEFAULT'; } else { return $self->SUPER::null_type($type); } } # XXX this needs serious work sub typemap_code { { T_SUBCLASS => { INPUT => <<'EOT', if (SvROK($arg) || !sv_derived_from($arg, \"$Package\")) Perl_croak(aTHX_ \"Usage: argument is not a subclass of $Package\"); $var = SvPV_nolen($arg) EOT }, T_APREQ_COOKIE => { INPUT => '$var = apreq_xs_sv2cookie(aTHX_ $arg)', perl2c => 'apreq_xs_sv2cookie(aTHX_ sv)', OUTPUT => '$arg = apreq_xs_cookie2sv(aTHX_ $var, class, parent);', c2perl => 'apreq_xs_cookie2sv(aTHX_ ptr, class, parent)', }, T_APREQ_PARAM => { INPUT => '$var = apreq_xs_sv2param(aTHX_ $arg)', perl2c => 'apreq_xs_sv2param(aTHX_ sv)', OUTPUT => '$arg = apreq_xs_param2sv(aTHX_ $var, class, parent);', c2perl => 'apreq_xs_param2sv(aTHX_ ptr, class, parent)', }, T_APREQ_HANDLE => { INPUT => '$var = apreq_xs_sv2handle(aTHX_ $arg)', perl2c => 'apreq_xs_sv2handle(aTHX_ sv)', c2perl => 'apreq_xs_handle2sv(aTHX_ ptr, class, parent)', OUTPUT => '$arg = apreq_xs_handle2sv(aTHX_ $var, class, parent);', }, T_APREQ_HANDLE_CGI => { INPUT => '$var = apreq_xs_sv2handle(aTHX_ $arg)', OUTPUT => '$arg = apreq_xs_handle2sv(aTHX_ $var, class, SvRV(ST(1)));' }, T_APREQ_HANDLE_APACHE2 => { INPUT => '$var = apreq_xs_sv2handle(aTHX_ $arg)', OUTPUT => <<'EOT', $arg = apreq_xs_handle2sv(aTHX_ $var, class, SvRV(ST(1))); SvMAGIC(SvRV($arg))->mg_ptr = (void *)r; EOT }, T_APREQ_ERROR => { INPUT => '$var = (HV *)SvRV($arg)', OUTPUT => '$arg = sv_bless(newRV_noinc((SV*)$var), gv_stashpvn(\"${ntype}\", sizeof(\"${ntype}\") - 1, FALSE);' }, T_HASHOBJ => { INPUT => <<'EOT', # '$var = modperl_hash_tied_object(aTHX_ \"${ntype}\", $arg)' if (sv_derived_from($arg, \"${ntype}\")) { if (SVt_PVHV == SvTYPE(SvRV($arg))) { SV *hv = SvRV($arg); MAGIC *mg; if (SvMAGICAL(hv)) { if ((mg = mg_find(hv, PERL_MAGIC_tied))) { $var = (void *)MgObjIV(mg); } else { Perl_warn(aTHX_ \"Not a tied hash: (magic=%c)\", mg); $var = NULL; } } else { Perl_warn(aTHX_ \"SV is not tied\"); $var = NULL; } } else { $var = (void *)SvObjIV($arg); } } else { Perl_croak(aTHX_ \"argument is not a blessed reference \" \"(expecting an %s derived object)\", \"${ntype}\"); } EOT OUTPUT => <<'EOT', # '$arg = modperl_hash_tie(aTHX_ \"${ntype}\", $arg, $var);' { SV *hv = (SV*)newHV(); SV *rsv = $arg; sv_setref_pv(rsv, \"${ntype}\", $var); sv_magic(hv, rsv, PERL_MAGIC_tied, Nullch, 0); $arg = SvREFCNT_inc(sv_bless(sv_2mortal(newRV_noinc(hv)), gv_stashpv(\"${ntype}\", TRUE))); } EOT }, } }