| ExtUtils-XSBuilder documentation | Contained in the ExtUtils-XSBuilder distribution. |
ExtUtils::XSBuilder::WrapXS - create perl XS wrappers for C functions
For more information, see ExtUtils::XSBuilder
Returns an array ref of new ParseSource objects for all source files that should be used to generate XS files
Returns a new typemap object
Returns a new podtemplate object
Returns a list of XS include files.
Default: use all include files that ParseSource::find_includes returns, but
strip path info
Returns a list of additional XS glue directories to seach for maps in.
Returns a directory which serves as a base for other directories.
Default: '.'
Returns the directory to search for map files in
Default: <xs_base_dir/xsbuilder/maps>
Returns the directory to search for files to include into the source. For
example, <xs_incsrc_dir/Apache/DAV/Resource/Resource_pm> will be included into
the Apache::DAV::Resource module.
Default: <xs_base_dir/xsbuilder>
Returns a directory to search for include files for pm and XS
Default: <xs_base_dir/xsinclude>
Returns the directory to write generated XS and header files in
Default: <xs_base_dir/xs>
Returns text for Makefile.PL
Defines a prefix for generated header files
Default: 'xs_'
Defines a prefix used for all XS functions
Default: 'xs_'
Defines a prefix used for all conversion functions/macros.
Default: my_xs_prefix
Returns true if the passed name should be prefixed
Returns the text of a .pm file, or undef if no .pm file should be
written.
Default: Create a .pm file which bootstraps the XS code
Called for each structure element that is written to the map file by checkmaps. Allows the user to change the element name, for example adding a different perl name.
Default: returns the element unmodified
Called for each function that is written to the map file by checkmaps. Allows the user to change the function name, for example adding a different perl name.
Default: returns the element unmodified
| ExtUtils-XSBuilder documentation | Contained in the ExtUtils-XSBuilder distribution. |
package ExtUtils::XSBuilder::WrapXS; use strict; use warnings FATAL => 'all'; use constant GvSHARED => 0; #$^V gt v5.7.0; use File::Spec ; use ExtUtils::XSBuilder::TypeMap (); use ExtUtils::XSBuilder::MapUtil qw(function_table structure_table callback_table); use ExtUtils::XSBuilder::PODTemplate ; use File::Path qw(rmtree mkpath); use Cwd qw(fastcwd); use Data::Dumper; use Carp qw(confess) ; our $VERSION = '0.03'; my %warnings; my $verbose = 0 ;
# ============================================================================ sub new { my $class = shift; my $self = bless { }, $class; $self -> {glue_dirs} = [$self -> xs_glue_dirs()] ; $self -> {typemap} = $self -> new_typemap ; $self -> {parsesource} = $self -> new_parsesource ; $self -> {xs_includes} = $self -> xs_includes ; $self -> {callbackno} = 1 ; for (qw(c hash)) { my $w = "noedit_warning_$_"; my $method = $w ; $self->{$w} = $self->$method(); } $self->typemap->get; $self; } # ============================================================================ sub classname { my $self = shift || __PACKAGE__; ref($self) || $self; } # ============================================================================ sub calls_trace { my $frame = 1; my $trace = ''; while (1) { my($package, $filename, $line) = caller($frame); last unless $filename; $trace .= "$frame. $filename:$line\n"; $frame++; } return $trace; } # ============================================================================ sub noedit_warning_c { my $class = classname(shift); my $warning = \$warnings{C}->{$class}; return $$warning if $$warning; my $v = join '/', $class, $class->VERSION; my $trace = calls_trace(); $trace =~ s/^/ * /mg; $$warning = <<EOF; /* * *********** WARNING ************** * This file generated by $v * Any changes made here will be lost * *********************************** $trace */ EOF } # ============================================================================ #this is named hash after the `#' character #rather than named perl, since #comments are used #non-Perl files, e.g. Makefile, typemap, etc. sub noedit_warning_hash { my $class = classname(shift); my $warning = \$warnings{hash}->{$class}; return $$warning if $$warning; ($$warning = noedit_warning_c($class)) =~ s/^/\# /mg; $$warning; } # ============================================================================
sub new_parsesource { [ ExtUtils::XSBuilder::ParseSource->new ] } # ============================================================================
sub new_typemap { ExtUtils::XSBuilder::TypeMap->new (shift) } # ============================================================================
sub new_podtemplate { ExtUtils::XSBuilder::PODTemplate->new } # ============================================================================
sub xs_includes { my $self = shift ; my $parsesource = $self -> parsesource_objects ; my @includes ; my @paths ; foreach my $src (@$parsesource) { push @includes, @{ $src -> find_includes } ; push @paths, @{ $src -> include_paths } ; } foreach (@paths) { s#(\\|/)$## ; s#\\#/# ; } foreach (@includes) { s#\\#/# ; } # strip include paths foreach my $file (@includes) { foreach my $path (@paths) { if ($file =~ /^\Q$path\E(\/|\\)(.*?)$/i) { $file = $2 ; last ; } } } my %includes = map { $_ => 1 } @includes ; my $fixup1 = $self -> h_filename_prefix . 'preperl.h' ; my $fixup2 = $self -> h_filename_prefix . 'postperl.h' ; return [ keys %includes, -f $self -> xs_include_dir . '/'. $fixup1?$fixup1:(), 'EXTERN.h', 'perl.h', 'XSUB.h', -f $self -> xs_include_dir . '/'. $fixup2?$fixup2:(), $self -> h_filename_prefix . 'sv_convert.h', $self -> h_filename_prefix . 'typedefs.h', ] ; } # ============================================================================
sub xs_glue_dirs { () ; } # ============================================================================
sub xs_base_dir { '.' } ; # ============================================================================
sub xs_map_dir { File::Spec -> catdir ($_[0] -> xs_base_dir, 'xsbuilder', 'maps') } ; # ============================================================================
sub xs_incsrc_dir { File::Spec -> catdir ($_[0] -> xs_base_dir, 'xsbuilder') ; } ; # ============================================================================
sub xs_include_dir { File::Spec -> catdir ($_[0] -> xs_base_dir, 'xsinclude') ; } ; # ============================================================================
sub xs_target_dir { File::Spec -> catdir ($_[0] -> xs_base_dir, 'xs') ; } # ============================================================================ sub typemap { shift->{typemap} } # ============================================================================ sub includes { shift->{xs_includes} || [] } # ============================================================================ sub parsesource_objects { shift->{parsesource} } # ============================================================================ sub function_list { my $self = shift; my(@list) = @{ function_table($self) }; 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 callback_list { my $self = shift; my(@list) = @{ callback_table($self) }; while (my($name, $val) = each %{ $self->typemap->callback_map }) { #entries that do not exist in C::Scan generated tables next unless $name =~ /^DEFINE_/; push @list, $val; } return \@list; } # ============================================================================ sub get_callback_function { my ($self, $func, $struct, $elt) = @_ ; my $myprefix = $self -> my_xs_prefix ; my $n ; $elt -> {callbackno} = $n = $self -> {callbackno}++ ; my $structelt = $elt -> {name} ; my $class = $struct -> {class} ; my $cclass = $self -> cname($class) ; my($name, $args, $retargs, $return_type, $orig_args, $userdataarg) = @{ $func } { qw(perl_name args retargs return_type orig_args userdataarg) }; $struct -> {staticcnt} ||= 4 ; my $staticcnt = $struct -> {staticcnt} ; #print "get_callback_function: ", Data::Dumper -> Dump([$func]), "\n" ; my $code = "\n/* --- $class -> $structelt --- */\n\n" ; my $cbname = "${myprefix}cb_${cclass}__$structelt" ; my %retargs = map { $_->{name} => $_ } @$retargs ; my %args = map { $_->{name} => $_ } @$args ; my @args = map { my $name = /^(?:\*|&)(.*?)$/?$1:$_ ; ($args{$name}{rtype} || $retargs{$name}{rtype}) . (/^&/?" * $name":" $name") } @$orig_args ; $return_type = $self -> cname($return_type) ; my $return_class = $self -> typemap -> map_class ($return_type) || $return_type; if ($return_class =~ / /) { print "ERROR: return class '$return_class' contains spaces" ; } my $desttype = 'CV' ; if ($structelt) { $desttype = 'SV' ; } my $numret = $return_type eq 'void'?0:1 ; $numret += @$retargs ; my $callflags = $numret == 0?'G_VOID':$numret == 1?'G_SCALAR':'G_ARRAY' ; $code .= qq[ static $return_type $cbname (] . join (',', "$desttype * __cbdest", @args) . qq[) { ] ; $code .= " $return_type __retval ;\n" if ($return_type && $return_type ne 'void') ; $code .= " SV * __retsv ;\n" if ($numret) ; $code .= qq[ int __cnt ; dSP ; ENTER ; SAVETMPS ; PUSHMARK(SP) ; ]; if ($structelt) { $code .= " PUSHs(__cbdest) ;\n" ; } foreach (@$orig_args) { my $type = /^(?:\*|\&)(.*?)$/?$1:$_ ; my $name = /^\*(.*?)$/?"&$1":$_ ; next if ($retargs{$type}{class}) ; if (!$args{$type}{class} && !$args{$type}{type}) { print "WARNING: unknown type for argument '$name' in struct member '$structelt'\n" ; print Dumper ($args) ; next ; } my $class = $args{$type}{class} || $args{$type}{type} ; if ($class =~/\s/) { print "WARNING: type '$class' for argument '$name' in struct member '$structelt' contains spaces\n" ; print Dumper ($args) ; next ; } $code .= ' PUSHs(' . $self -> convert_2obj ($class, $name) . ") ;\n" ; } $code .= qq[ PUTBACK ; ] ; if ($structelt) { $code .= " __cnt = perl_call_method(\"cb_$structelt\", $callflags) ;\n" ; } else { $code .= " __cnt = perl_call_sv(__cbdest, $callflags) ;\n" ; } $code .= qq[ if (__cnt != $numret) croak (\"$cbname expected $numret return values\") ; ] if ($numret > 0) ; $code .= qq[ SPAGAIN ; ] ; if ($return_type && $return_type ne 'void') { $code .= " __retsv = POPs;\n" ; $code .= ' __retval = ' . $self -> convert_sv2 ($return_type, $return_class, '__retsv') . ";\n" } foreach (@$retargs) { $code .= " __retsv = POPs;\n" ; $code .= " *$_->{name} = " . $self -> convert_sv2 ($_->{rtype}, $_->{class}, '__retsv') . ";\n" ; } $code .= qq[ PUTBACK ; FREETMPS ; LEAVE ; ] ; $code .= " return __retval ;\n" if ($return_type && $return_type ne 'void') ; $code .= qq[ } ] ; if (!$userdataarg) { $staticcnt ||= 4 ; for (my $i = 0 ; $i < $staticcnt; $i++) { $code .= qq[ static $return_type ${cbname}_obj$i (] . join (',', @args) . qq[) { ] . ($return_type eq 'void'?'':'return') . qq[ ${cbname} (] . join (',', "${myprefix}${cclass}_obj[$i]", map { /^(?:\*|\&)?(.*?)$/ } @$orig_args) . qq[) ; } ] ; } $code .= "typedef $return_type (*t${cbname}_func)(" . join (',', @args) . qq") ;\n" ; $code .= "static t${cbname}_func ${myprefix}${cbname}_func [$staticcnt] = {\n " . join (",\n ", map { "${cbname}_obj$_" } (0..$staticcnt-1)) . "\n } ;\n\n\n" ; } unshift @{ $self->{XS}->{ $func->{module} } }, { code => $code, class => '', name => $name, }; } # ============================================================================ sub get_function { my ($self, $func) = @_ ; my $myprefix = $self -> my_xs_prefix ; my($name, $module, $class, $args, $retargs) = @{ $func } { qw(perl_name module class args retargs) }; my %retargs = map { $_->{name} => $_ } @$retargs ; print "get_function: ", Data::Dumper -> Dump([$func]), "\n" if ($verbose); #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 ; if ($func -> {dispatch_argspec}) { $parms = $func -> {dispatch_argspec} ; } else { ($parms = join (',', $xs_parms, map { "\&$_->{name}" } @$retargs)) =~ s/=[^,]+//g; #strip defaults } my $proto = join "\n", (map " $_->{type} $_->{name}", @$args) ; my $return_type = $name =~ /^DESTROY$/ ? 'void' : $func->{return_type}; my $retdecl = @$retargs?(join "\n", (map { my $type = $self -> cname($_->{class}) ; $type =~ s/\*$//; ' ' . $type . " $_->{name};"} @$retargs), #' ' . $self -> cname($return_type) . ' RETVAL', ''):''; my($dispatch, $orig_args) = @{ $func } {qw(dispatch orig_args)}; if ($dispatch =~ /^$myprefix/io) { $name =~ s/^$myprefix//; $name =~ s/^$func->{prefix}//; push @{ $self->{newXS}->{ $module } }, ["$class\::$name", $dispatch]; return; } my $passthru = @$args && $args->[0]->{name} eq '...'; if ($passthru) { $parms = '...'; $proto = ''; } my $attrs = $self->attrs($name); my $code = <<EOF; $return_type $name($xs_parms) EOF $code .= "$proto\n" if ($proto) ; $code .= "$attrs\n" if ($attrs) ; $code .= "PREINIT:\n$retdecl" if ($retdecl) ; if ($dispatch || $orig_args) { my $thx = ""; if ($dispatch) { $thx = 'aTHX_ ' if $dispatch =~ /^$myprefix/i; if ($orig_args && !$func -> {dispatch_argspec}) { $parms = join ', ', map { $retargs{$_}?"&$_":$_} @$orig_args; } } else { ### ??? gr ### if ($orig_args and @$orig_args == @$args) { if ($orig_args && @$orig_args) { #args were reordered $parms = join ', ', map { $retargs{$_}?"&$_":$_} @$orig_args; } $dispatch = $func->{name}; } if ($passthru) { $thx ||= 'aTHX_ '; $parms = 'items, MARK+1, SP'; } my $retval = $return_type eq 'void' ? ["", ""] : ["RETVAL = ", "OUTPUT:\n RETVAL\n"]; my $retnum = $retdecl?scalar(@$retargs) + ($return_type eq 'void' ?0:1):0 ; $code .= $retdecl?"PPCODE:":"CODE:" ; $code .= "\n $retval->[0]$dispatch($thx$parms);\n" ; if ($retdecl) { my $retclass = $self -> typemap -> map_class ($return_type) || $return_type ; if ($retclass =~ / /) { print "ERROR: return class '$retclass' contains spaces" ; } $code .= " XSprePUSH;\n" ; $code .= " EXTEND(SP, $retnum) ;\n" ; $code .= ' PUSHs(' . $self -> convert_2obj ($retclass, 'RETVAL') . ") ;\n" ; foreach (@$retargs) { if ($_->{class} =~ / /) { print "ERROR: $_->{class} contains spaces; retargs = ", Dumper ($_) ; } $code .= ' PUSHs(' . $self -> convert_2obj ($_->{class}, $_->{name}) . ") ;\n" ; } } else { $code .= "$retval->[1]\n" ; } } $code .= "\n" ; $func->{code} = $code; push @{ $self->{XS}->{ $module } }, $func; } # ============================================================================ sub get_functions { my $self = shift; my $typemap = $self->typemap; my %seen ; for my $entry (@{ $self->function_list() }) { #print "get_func ", Dumper ($entry) ; my $func = $typemap->map_function($entry); #print "FAILED to map $entry->{name}\n" unless $func; next unless $func; print "WARNING: Duplicate function: $entry->{name}\n" if ($seen{$entry->{name}}++) ; $self -> get_function ($func) ; } } # ============================================================================ sub get_value { my $e = shift; my $val = 'val'; if ($e->{class} eq 'PV') { if (my $pool = $e->{pool}) { $pool .= '(obj)'; $val = "((ST(1) == &PL_sv_undef) ? NULL : apr_pstrndup($pool, val, val_len))" } } return $val; } # ============================================================================ sub get_structure_callback_init { my ($self, $class, $struct) = @_ ; my $cclass = $self -> cname($class) ; my $myprefix = $self -> my_xs_prefix ; my $staticcnt = $struct -> {staticcnt} ; my $cnv = $self -> convert_sv2 ($cclass, $class, 'obj') ; my $code = qq[ void init_callbacks (obj, val=NULL) SV * obj SV * val PREINIT: int n = -1 ; int i ; $cclass cobj = $cnv ; SV * ref ; SV * perl_obj ; CODE: if (items > 1) obj = val ; perl_obj = SvRV(obj) ; ref = newRV_noinc(perl_obj) ; for (i=0;i < $staticcnt;i++) { if ($myprefix${cclass}_obj[i] == ref) { n = i ; break ; } } if (n < 0) for (i=0;i < $staticcnt;i++) { if ($myprefix${cclass}_obj[i] == NULL) { n = i ; break ; } } if (n < 0) croak ("Limit for concurrent object callbacks reached for $class. Limit is $staticcnt") ; $myprefix${cclass}_obj[n] = ref ; ] ; foreach my $e (@{ $struct->{elts} }) { if ($e -> {callback}) { my $cbname = "${myprefix}cb_${cclass}__$e->{name}" ; $code .= " cobj -> $e->{name} = ${myprefix}${cbname}_func[n] ;\n" ; } } $code .= qq[ ] ; my $ccode = "static SV * ${myprefix}${cclass}_obj[$staticcnt] ;\n\n" ; push @{ $self->{XS}->{ $struct->{module} } }, { code => $code, class => $class, name => 'init_callbacks', }; unshift @{ $self->{XS}->{ $struct->{module} } }, { code => $ccode, class => '', name => 'init_callbacks', }; } # ============================================================================ sub get_structure_new { my ($self, $class, $struct) = @_ ; my $cclass = $self -> cname($class) ; my $cnvprefix = $self -> my_cnv_prefix ; my $alloc = $struct -> {alloc} || 'malloc(sizeof(*cobj))' ; my $code = qq[ SV * new (class,initializer=NULL) char * class SV * initializer PREINIT: SV * svobj ; $cclass cobj ; SV * tmpsv ; CODE: ${cnvprefix}${cclass}_create_obj(cobj,svobj,RETVAL,$alloc) ; if (initializer) { if (!SvROK(initializer) || !(tmpsv = SvRV(initializer))) croak ("initializer for ${class}::new is not a reference") ; if (SvTYPE(tmpsv) == SVt_PVHV || SvTYPE(tmpsv) == SVt_PVMG) ${cclass}_new_init (aTHX_ cobj, tmpsv, 0) ; else if (SvTYPE(tmpsv) == SVt_PVAV) { int i ; SvGROW(svobj, sizeof (*cobj) * av_len((AV *)tmpsv)) ; for (i = 0; i <= av_len((AV *)tmpsv); i++) { SV * * itemrv = av_fetch((AV *)tmpsv, i, 0) ; SV * item ; if (!itemrv || !*itemrv || !SvROK(*itemrv) || !(item = SvRV(*itemrv))) croak ("array element of initializer for ${class}::new is not a reference") ; ${cclass}_new_init (aTHX_ &cobj[i], item, 1) ; } } else { croak ("initializer for ${class}::new is not a hash/array/object reference") ; } } OUTPUT: RETVAL ] ; my $c_code = qq[ void ${cclass}_new_init (pTHX_ $cclass obj, SV * item, int overwrite) { SV * * tmpsv ; if (SvTYPE(item) == SVt_PVMG) memcpy (obj, (void *)SvIVX(item), sizeof (*obj)) ; else if (SvTYPE(item) == SVt_PVHV) { ] ; foreach my $e (@{ $struct->{elts} }) { if ($e -> {name} =~ /^(.*?)\[(.*?)\]$/) { my $strncpy = $2 ; my $name = $1 ; my $perl_name ; ($perl_name = $e -> {perl_name}) =~ s/\[.*?\]$// ; $c_code .= " if ((tmpsv = hv_fetch((HV *)item, \"$perl_name\", sizeof(\"$perl_name\") - 1, 0)) || overwrite) {\n" ; $c_code .= " STRLEN l = 0;\n" ; $c_code .= " if (tmpsv) {\n" ; $c_code .= " char * s = SvPV(*tmpsv,l) ;\n" ; $c_code .= " if (l > ($strncpy)-1) l = ($strncpy) - 1 ;\n" ; $c_code .= " strncpy(obj->$name, s, l) ;\n" ; $c_code .= " }\n" ; $c_code .= " obj->$name\[l] = '\\0';\n" ; $c_code .= " }\n" ; } elsif (($e -> {class} !~ /::/) || ($e -> {rtype} =~ /\*$/)) { $c_code .= " if ((tmpsv = hv_fetch((HV *)item, \"$e->{perl_name}\", sizeof(\"$e->{perl_name}\") - 1, 0)) || overwrite) {\n" ; if ($e -> {malloc}) { my $type = $e->{rtype} ; my $dest = "obj -> $e->{name}" ; my $src = 'tmpobj' ; my $expr = eval ('"' . $e -> {malloc} . '"') ; print $@ if ($@) ; $c_code .= " $type tmpobj = (" . $self -> convert_sv2 ($e->{rtype}, $e->{class}, '(tmpsv && *tmpsv?*tmpsv:&PL_sv_undef)') . ");\n" ; $c_code .= " if (tmpobj)\n" ; $c_code .= " $expr;\n" ; $c_code .= " else\n" ; $c_code .= " $dest = NULL ;\n" ; } else { $c_code .= ' ' . "obj -> $e->{name} = " . $self -> convert_sv2 ($e->{rtype}, $e->{class}, '(tmpsv && *tmpsv?*tmpsv:&PL_sv_undef)') . " ;\n" ; } $c_code .= " }\n" ; } } $c_code .= qq[ ; } else croak ("initializer for ${class}::new is not a hash or object reference") ; } ; ] ; push @{ $self->{XS}->{ $struct->{module} } }, { code => $code, class => $class, name => 'new', }; unshift @{ $self->{XS}->{ $struct->{module} } }, { code => $c_code, class => '', name => 'new', }; } # ============================================================================ sub get_structure_destroy { my ($self, $class, $struct) = @_ ; my $cclass = $self -> cname($class) ; my $cnvprefix = $self -> my_cnv_prefix ; my $code = qq[ void DESTROY (obj) $class obj CODE: ${cclass}_destroy (aTHX_ obj) ; ] ; my $numfree = 0 ; my $c_code = qq[ void ${cclass}_destroy (pTHX_ $cclass obj) { ]; foreach my $e (@{ $struct->{elts} }) { if (($e -> {class} !~ /::/) || ($e -> {rtype} =~ /\*$/)) { if ($e -> {free}) { my $src = "obj -> $e->{name}" ; my $type = $e->{rtype} ; my $expr = eval ('"' . $e -> {free} . '"') ; print $@ if ($@) ; $c_code .= " if (obj -> $e->{name})\n" ; $c_code .= ' ' . $expr . ";\n" ; $numfree++ ; } } } $c_code .= "\n};\n\n" ; if ($numfree) { push @{ $self->{XS}->{ $struct->{module} } }, { code => $code, class => $class, name => 'destroy', }; unshift @{ $self->{XS}->{ $struct->{module} } }, { code => $c_code, class => '', name => 'destroy', }; } } # ============================================================================ sub get_structures { my $self = shift; my $typemap = $self->typemap; my $has_callbacks = 0 ; for my $entry (@{ structure_table($self) }) { print 'struct ', $entry->{type} || '???', "...\n" ; my $struct = $typemap->map_structure($entry); print Data::Dumper -> Dump ([$entry, $struct], ['Table Entry', 'Mapfile Entry']) if ($verbose) ; if (!$struct) { print "WARNING: Struture '$entry->{type}' not found in map file\n" ; next ; } my $class = $struct->{class}; $has_callbacks = 0 ; for my $e (@{ $struct->{elts} }) { my($name, $default, $type, $perl_name ) = @{$e}{qw(name default type perl_name)}; print " $name...\n" ; if ($e -> {callback}) { #print "callback < ", Dumper ($e) , "\n" ; $self -> get_function ($e -> {func}) ; $self -> get_callback_function ($e -> {func}, $struct, $e) ; $has_callbacks++ ; } else { (my $cast = $type) =~ s/:/_/g; my $val = get_value($e); my $type_in = $type; my $preinit = "/*nada*/"; my $address = '' ; my $rdonly = 0 ; my $strncpy ; if ($e->{class} eq 'PV' and $val ne 'val') { $type_in =~ s/char/char_len/; $preinit = "STRLEN val_len;"; } elsif (($e->{class} =~ /::/) && ($e -> {rtype} !~ /\*\s*$/)) { # an inlined struct is read only $rdonly = 1 ; $address = '&' ; } elsif ($name =~ /^(.*?)\[(.*?)\]$/) { $strncpy = $2 ; $name = $1 ; $perl_name =~ s/\[.*?\]$// ; $type = 'char *' ; $type_in = 'char *' ; $cast = 'char *' ; } my $attrs = $self->attrs($name); my $code = <<EOF; $type $perl_name(obj, val=$default) $class obj $type_in val PREINIT: $preinit $attrs CODE: RETVAL = ($cast) $address obj->$name; EOF if ($rdonly) { $code .= <<EOF if (items > 1) { croak (\"$name is read only\") ; } EOF } else { $code .= "\n if (items > 1) {\n" ; if ($e -> {malloc}) { my $dest = "obj->$name" ; my $src = $val ; my $type = $cast ; my $expr = eval ('"' . $e -> {malloc} . '"') ; print $@ if ($@) ; $code .= ' ' . $expr . ";\n" ; } elsif ($strncpy) { $code .= " strncpy(obj->$name, ($cast) $val, ($strncpy) - 1) ;\n" ; $code .= " obj->$name\[($strncpy)-1] = '\\0';\n" ; } else { $code .= " obj->$name = ($cast) $val;\n" ; } $code .= " }\n" ; } $code .= <<EOF; OUTPUT: RETVAL EOF push @{ $self->{XS}->{ $struct->{module} } }, { code => $code, class => $class, name => $name, perl_name => $e -> {perl_name}, comment => $e -> {comment}, struct_member => $e, }; } } $self -> get_structure_new($class, $struct) if ($struct->{has_new}) ; $self -> get_structure_destroy($class, $struct) if ($struct->{has_new}) ; $self -> get_structure_callback_init ($class, $struct) if ($has_callbacks); } } # ============================================================================ sub prepare { my $self = shift; $self->{DIR} = $self -> xs_target_dir; $self->{XS_DIR} = $self -> xs_target_dir ; if (-e $self->{DIR}) { rmtree([$self->{DIR}], 1, 1); } mkpath [$self->{DIR}], 1, 0755; } # ============================================================================ sub class_dirname { my($self, $class) = @_; # my($base, $sub) = split '::', $class; # return "$self->{DIR}/$base" unless $sub; #Apache | APR # return $sub if $sub eq $self->{DIR}; #WrapXS # return "$base/$sub"; $class =~ s/::/\//g ; return $class ; } # ============================================================================ sub class_dir { my($self, $class) = @_; my $dirname = $self->class_dirname($class); #my $dir = ($dirname =~ m:/: and $dirname !~ m:^$self->{DIR}:) ? # join('/', $self->{DIR}, $dirname) : $dirname; my $dir = join('/', $self->{DIR}, $dirname) ; mkpath [$dir], 1, 0755 unless -d $dir; $dir; } # ============================================================================ sub class_file { my($self, $class, $file) = @_; join '/', $self->class_dir($class), $file; } # ============================================================================ sub cname { my($self, $class) = @_; confess ('ERROR: class is undefined in cname') if (!defined ($class)) ; $class =~ s/::$// ; $class =~ s/:/_/g; $class; } # ============================================================================ sub convert_2obj { my($self, $class, $name) = @_; $self -> my_cnv_prefix . $self -> cname($class) . "_2obj($name)" ; } # ============================================================================ sub convert_sv2 { my($self, $rtype, $class, $name) = @_; $class =~ s/^const\s+// ; $class =~ s/char\s*\*/PV/ ; $class =~ s/SV\s*\*/SV/ ; return "($rtype)" . $self -> my_cnv_prefix . 'sv2_' . $self -> cname($class) . "($name)" ; } # ============================================================================ 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: $!"; print "writing...$name\n"; return $fh; } # ============================================================================
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 ExtUtils::MakeMaker (); local \$MMARGS ; if (-f '$mmargspath') { do '$mmargspath' ; die \$\@ if (\$\@) ; } \$MMARGS ||= {} ; ExtUtils::MakeMaker::WriteMakefile( 'NAME' => '$class', 'VERSION' => '0.01', 'TYPEMAPS' => ['$typemap'], } ; $txt .= "'depend' => $deps,\n" if ($deps) ; $txt .= qq{ \%\$MMARGS, ); } ; } # ============================================================================ sub write_makefilepl { my($self, $class) = @_; $self -> {makefilepls}{$class} = 1 ; my $fh = $self->open_class_file($class, 'Makefile.PL'); my $includes = $self->includes; my @parts = split '::', $class ; my $xs = @parts?$parts[-1] . '.c':'' ; my $deps = {$xs => ""}; if (my $mod_h = $self->mod_h($class, 1)) { my $abs = File::Spec -> rel2abs ($mod_h) ; my $rel = File::Spec -> abs2rel ($abs, $self -> class_dir ($class)) ; $deps->{$xs} .= " $rel"; } local $Data::Dumper::Terse = 1; $deps = Dumper $deps; $deps = undef if (!$class) ; $class ||= 'WrapXS' ; print $fh $self -> makefilepl_text ($class, $deps, ('../' x @parts) . 'typemap') ; close $fh; } # ============================================================================ sub write_missing_makefilepls { my($self, $class) = @_; my %classes = ('' => 1) ; foreach (keys %{$self -> {makefilepls}}) { my @parts = split (/::/, $_) ; my $i ; for ($i = 0; $i < @parts; $i++) { $classes{join('::', @parts[0..$i])} = 1 ; } } foreach my $class (keys %classes) { next if ($self -> {makefilepls}{$class}) ; $self -> write_makefilepl ($class) ; } } # ============================================================================ 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_include_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 @parts = split '::', $module; my $mod_pm = "$dirname/$parts[-1]_pm"; for ($self -> xs_incsrc_dir, @{ $self->{glue_dirs} }) { my $file = "$_/$mod_pm"; $mod_pm = $file if $complete; print "mod_pm $mod_pm $file $complete\n" ; return $mod_pm if -e $file; } undef; } # ============================================================================
sub h_filename_prefix { 'xs_' } # ============================================================================
sub my_xs_prefix { 'xs_' } # ============================================================================
sub my_cnv_prefix { $_[0] -> my_xs_prefix } # ============================================================================
sub needs_prefix { return 0 if (!$_[1]) ; my $pf = $_[0] -> my_xs_prefix ; return $_[1] !~ /^$pf/i; } # ============================================================================ 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 = ' ' . $self -> my_xs_prefix . $self->cname($module) . "_BOOT(aTHXo);\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: shared\n" if GvSHARED; $str; } # ============================================================================ 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}; } 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 <<EOF; void $destructor->{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 GvSHARED; } } close $fh; } # ============================================================================
sub pm_text { my($self, $module, $isa, $code) = @_; return <<EOF; $self->{noedit_warning_hash} package $module; require DynaLoader ; use strict ; use vars qw{\$VERSION \@ISA} ; $isa push \@ISA, 'DynaLoader' ; \$VERSION = '0.01'; bootstrap $module \$VERSION ; $code 1; __END__ EOF } # ============================================================================ 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]; my $loader = join '::', $base, 'XSLoader'; my $text = $self -> pm_text ($module, $isa, $code) ; return if (!$text) ; my $fh = $self->open_class_file($module, '.pm'); print $fh $text ; } # ============================================================================ 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"; while (my($type, $t) = each %$map) { my $class = $t -> {class} ; $class ||= $type; next if $seen{$type}++ || $typemap->special($class); my $typemap = $t -> {typemapid} ; if ($class =~ /::/) { next if $seen{$class}++ ; $class =~ s/::$// ; print $fh "$class\t$typemap\n"; } else { print $fh "$type\t$typemap\n"; } } my $cnvprefix = $self -> my_cnv_prefix ; my $typemap_code = $typemap -> typemap_code ($cnvprefix); foreach my $dir ('INPUT', 'OUTPUT') { print $fh "\n$dir\n" ; while (my($type, $code) = each %{$typemap_code}) { print $fh "$type\n$code->{$dir}\n\n" if ($code->{$dir}) ; } } close $fh; } # ============================================================================ sub write_typemap_h_file { my($self, $method) = @_; $method = $method . '_code'; my($h, $code) = $self->typemap->$method(); my $file = join '/', $self->{XS_DIR}, $h; open my $fh, '>', $file or die "open $file: $!"; print $fh "$self->{noedit_warning_c}\n"; print $fh $code; close $fh; } # ============================================================================ sub _pod_gen_siglet { my $class = shift || '' ; return '\%' if $class eq 'HV'; return '\@' if $class eq 'AV'; return '$'; } # ============================================================================ # Determine if the name is that of a function or an object sub _pod_is_function { my $class = shift || ''; #print "_pod_is_function($class)\n"; my %func_class = ( SV => 1, IV => 1, NV => 1, PV => 1, UV => 1, PTR => 1, ); exists $func_class{$class}; } # ============================================================================ sub generate_pod { my $self = shift ; my $fh = shift; my $pdd = shift; my $templ = $self -> new_podtemplate ; my $since = $templ -> since_default ; print $fh $templ -> gen_pod_head ($pdd->{module}) ; my $detail = $pdd->{functions_detailed}; unless ( ref($detail) eq 'ARRAY') { warn "No functions listed in pdd structure for $pdd->{module}"; return; } foreach my $f (@$detail) { # Generate the function or method name my $method = $f->{perl_name}; $method = $1 if ($f->{prefix} && ($method =~ /^$f->{prefix}(.*?)$/)) ; $method = $1 if ($f->{class_xs_prefix} && ($method =~ /^(?:DEFINE_)?$f->{class_xs_prefix}(.*?)$/)) ; if (!$method) { warn "Cannot determinate method name for '$f->{name}'" ; next ; } my $comment = $f->{comment_parsed}; my $commenttext = ($comment->{func_desc} || '') . "\n\n" . ($comment->{doxygen_remark} || '') ; my $member = $f -> {struct_member}; if ($member) { print $fh $templ -> gen_pod_struct_member ($f->{class}, '$obj', $f->{struct_member}->{class}, $f->{perl_name}, $commenttext, $since) ; } else { my $args = $f->{args}; if ($args && @$args) { my @param_nm = map { $_ -> {name} } @$args ; # Parameter names my $obj_nm; my $obj_sym; my $offset = 0; my $first_param = $f->{args}[0]; unless (_pod_is_function($first_param->{class})) { $obj_nm = $param_nm[0]; # Object Name $obj_sym = &_pod_gen_siglet($first_param->{class}). $obj_nm; $offset++; } my $retclass ; my $retcomment = $comment -> {doxygen_return} || '' ; if ($f -> {return_type} && $f -> {return_type} ne 'void') { my $rettype = $self -> typemap->get->{$f -> {return_type}} ; $retclass = $rettype?$rettype->{class}:$f -> {return_type}; } my @param; my $i = 0 ; for my $param_nm (@param_nm) { my $arg = $args->[$i++]; push @param, { class => $arg->{class}, name => &_pod_gen_siglet($arg->{class}) . $param_nm, comment => ($comment->{doxygen_param_desc}{$param_nm} || '') } ; } print $fh $templ -> gen_pod_func ($obj_sym, $obj_sym, $method, \@param, $retclass, $retcomment, $commenttext, $since) ; } } } } # ============================================================================ # pdd = PERL Data Dumper sub write_docs { my($self, $module, $functions) = @_; my $fh = $self->open_class_file($module, '.pdd'); print $fh "$self->{noedit_warning_hash}\n"; # Includes my @includes = @{ $self->includes }; if (my $mod_h = $self->mod_h($module)) { push @includes, $mod_h; } my $last_prefix = ""; my $fmap = $self->typemap->{function_map} ; my $myprefix = $self->my_xs_prefix ; # Finding doxygen- and other data inside the comments # This code only knows the syntax for @ingroup, @param, @remark, # @return and @warning. At the moment all other doxygen commands # are treated as multiple-occurance, no-parameter commands. # Note: Nor does @deffunc exist in the doxygen specification, # neither does @remark (but @remarks), @tip and @see. So we treat # @remark like @remarks, but we don't do any speacial treating for # @deffunc. Ideas or suggestions anyone? # --Axel Beckert foreach my $details (@$functions) { #print "Comment: ", $details->{name} || '?', ': ', $details->{comment} || '-', "\n" ; #print "----> ", Dumper ($details) ;# if (!$details->{comment}) ; if (defined $details->{comment} and my $comment = $details->{comment}) { $details->{comment_parsed} = {}; # Source file if ($comment =~ s/^\s*(\S*\.c)\s+\*\n//s) { $details->{comment_parsed}{source_file} = $1; } # Initialize several fields $details->{comment_parsed}{func_desc} = ""; my $doxygen = 0; # flag indicating that we already have # seen doxygen fields in this comment my $type = 0; # name of doxygen field my $pre = 0; # if we should recognize leading # spaces. Example see apr_table_overlap # Setting some regexps my $ordinary_line = qr/^\s*?\*(\s*(.*?))\s*$/; my $pre_begin = qr(<PRE>)i; my $pre_end = qr(</PRE>)i; # Parse the rest of the comment line by line, because # doxygen fields can appear more than once foreach my $line (split /\n/, $comment) { # Yesss! This looks like doxygen data. if ($line =~ /^\s*\*\s+[\\@](\w+)\s+(.*)\s*$/) { $type = $doxygen = $1; my $info = $2; # setting the recognizing of leading spaces $pre = ($info =~ $pre_begin ? 1 : $pre); $pre = ($info =~ $pre_end ? 0 : $pre); # Already had a doxygen element of this type for this func. if (defined $details->{comment_parsed}{"doxygen_$type"}) { push(@{ $details->{comment_parsed}{"doxygen_$type"} }, $info); } # Hey, hadn't seen this doxygen type in this function yet! else { $details->{comment_parsed}{"doxygen_$type"} = [ $info ]; } } # Further line belonging to doxygen field of the last line elsif ($doxygen) { # An empty line ends a doxygen paragraph if ($line =~ /^\s*$/) { $doxygen = 0; next; } # Those two situations should never appear. But we # better double check those things. croak("There already was a doxygen comment, but it didn't set an type.\nStrange things happen") unless defined $details->{comment_parsed}{"doxygen_$type"}; croak("This ($line) maybe an syntactic incorrect doxygen line.\nStrange things happen") unless $line =~ $ordinary_line; my $info = $2; $info = $1 if $pre; # setting the recognizing of leading spaces $pre = ($info =~ $pre_begin ? 1 : $pre); $pre = ($info =~ $pre_end ? 0 : $pre); $info =~ s(^\s+</PRE>)(</PRE>)i; # Ok, get me the last line of documentation. my $lastline = pop @{ $details->{comment_parsed}{"doxygen_$type"} }; # Concatenate that line and the actual line with a newline $info = "$lastline\n$info"; # Strip empty lines at the end and beginning # unless there was a <PRE> before. unless ($pre) { $info =~ s/[\n\s]+$//s; $info =~ s/^[\n\s]+//s; } # Push the back into the array push(@{ $details->{comment_parsed}{"doxygen_$type"} }, $info); } # Booooh! Just an ordinary comment elsif ($line =~ $ordinary_line) { my $info = $2; $info = $1 if $pre; # setting the recognizing of leading spaces $pre = ($info =~ $pre_begin ? 1 : $pre); $pre = ($info =~ $pre_end ? 0 : $pre); $info =~ s(^\s+(</PRE>))($1)i; # Only add if not an empty line at the beginning $details->{comment_parsed}{func_desc} .= "$info\n" unless ($info =~ /^\s*$/ and $details->{comment_parsed}{func_desc} eq ""); } else { if (defined $details->{comment_parsed}{unidentified}) { push(@{ $details->{comment_parsed}{unidentified} }, $line); } else { $details->{comment_parsed}{unidentified} = [ $line ]; } } } # Unnecessary linebreaks at the end of the function description $details->{comment_parsed}{func_desc} =~ s/[\n\s]+$//s if defined $details->{comment_parsed}{func_desc}; if (defined $details->{comment_parsed}{doxygen_param}) { # Remove the description from the doxygen_param and # move into an hash. A sole hash doesn't work, because # it usually screws up the parameter order my %param; my @param; foreach (@{ $details->{comment_parsed}{doxygen_param} }) { my ($var, $desc) = split(" ",$_,2); $param{$var} = $desc; push(@param, $var); } $details->{comment_parsed}{doxygen_param} = [ @param ]; $details->{comment_parsed}{doxygen_param_desc} = { %param }; } if (defined $details->{comment_parsed}{doxygen_defgroup}) { # Change doxygen_defgroup from array to hash my %defgroup; foreach (@{ $details->{comment_parsed}{doxygen_defgroup} }) { my ($var, $desc) = split(" ",$_,2); $defgroup{$var} = $desc; } $details->{comment_parsed}{doxygen_defgroup} = { %defgroup }; } if (defined $details->{comment_parsed}{doxygen_ingroup}) { # There should be a list of all parameters my @ingroup = (); foreach (@{ $details->{comment_parsed}{doxygen_ingroup} }) { push(@ingroup, split()); } $details->{comment_parsed}{doxygen_ingroup} = [ @ingroup ]; } foreach (qw(return warning remark)) { if (defined $details->{comment_parsed}{"doxygen_$_"}) { # Multiple adjacent @$_ should be concatenated, so # we can make an scalar out of it. Although we # actually still disregard the case, that there # are several non-adjacent @$_s. $details->{comment_parsed}{"doxygen_$_"} = join("\n", @{ $details->{comment_parsed}{"doxygen_$_"} }); } } # Dump the output for debugging purposes # print STDERR "### $details->{perl_name}:\n". # Dumper $details->{comment_parsed}; # print STDERR "### Original Comment:\n". # Dumper $details->{comment}; } # Some more per function information, used in the XS files my $class = $details->{class}; if ($class) { my $prefix = $details->{prefix}; $last_prefix = $prefix if $prefix; if ($details->{name} =~ /^$myprefix/o) { #e.g. mpxs_Apache__RequestRec_ my $class_prefix = $fmap -> class_c_prefix($class); if ($details->{name} =~ /$class_prefix/) { $details->{class_xs_prefix} = $fmap->class_xs_prefix($class); } $details->{class_c_prefix} = $class_prefix; } } } # Some more information, used in the XS files my $destructor = $self->typemap->destructor($last_prefix); my $boot = $self->boot($module); if ($boot) { chomp($boot); $boot =~ s/(\s+$|^\s+)//; } my $newxs = $self->{newXS}->{$module}; # Finally do the PDD Dump my $pdd = { module => $module, functions => [ map $$_{perl_name}, @$functions ], functions_detailed => [ @$functions ], includes => [ @includes ], my_xs_prefix => $myprefix, destructor => $destructor, boot => $boot, newXS => $newxs }; print $fh Dumper $pdd; close $fh; $fh = $self->open_class_file($module, '.pod'); $self -> generate_pod($fh, $pdd); close $fh; } # ============================================================================ sub generate { my $self = shift; $self->prepare; # now done by write_missing_makefilepls #for (qw(ModPerl::WrapXS Apache APR)) { # $self->write_makefilepl($_); #} $self->write_typemap; for (qw(typedefs sv_convert)) { $self->write_typemap_h_file($_); } $self->get_functions; $self->get_structures; 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"; # } if (!$module) { print "WARNING: empty module\n" ; next ; } print "mod $module\n" ; $self->write_makefilepl($module); $self->write_xs($module, $functions); $self->write_pm($module); $self->write_docs($module, $functions); } $self -> write_missing_makefilepls ; } # ============================================================================ 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 mapline_elem { return $_[1] } ; # ============================================================================
sub mapline_func { return $_[1] } ; # ============================================================================ sub checkmaps { my $self = shift; my $prefix = shift; $self = $self -> new if (!ref $self) ; my $result = $self -> {typemap} -> checkmaps ; $self -> {typemap} -> writemaps ($result, $prefix) if ($prefix) ; return $result ; } # ============================================================================ sub run { my $class = shift ; my $xs = $class -> new; $xs->generate; } 1; __END__