home *** CD-ROM | disk | FTP | other *** search
- # Copyright 2001-2004 The Apache Software Foundation
- #
- # Licensed 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 Apache::ParseSource;
-
- use strict;
- use warnings FATAL => 'all';
-
- use Apache::Build ();
- use Config;
- use File::Basename;
- use File::Spec::Functions qw(catdir);
-
- our $VERSION = '0.02';
-
- sub new {
- my $class = shift;
-
- my $self = bless {
- config => Apache::Build->build_config,
- @_,
- }, $class;
-
- my $prefixes = join '|', @{ $self->{prefixes} || [qw(ap_ apr_)] };
- $self->{prefix_re} = qr{^($prefixes)};
-
- $Apache::Build::APXS ||= $self->{apxs};
-
- $self;
- }
-
- sub config {
- shift->{config};
- }
-
- sub parse {
- my $self = shift;
-
- $self->{scan_filename} = $self->generate_cscan_file;
-
- $self->{c} = $self->scan;
- }
-
- sub DESTROY {
- my $self = shift;
- unlink $self->{scan_filename}
- }
-
- {
- package Apache::ParseSource::Scan;
-
- our @ISA = qw(ModPerl::CScan);
-
- sub get {
- local $SIG{__DIE__} = \&Carp::confess;
- shift->SUPER::get(@_);
- }
- }
-
- my @c_scan_defines = (
- 'CORE_PRIVATE', #so we get all of apache
- 'MP_SOURCE_SCAN', #so we can avoid some c-scan barfing
- '_NETINET_TCP_H', #c-scan chokes on netinet/tcp.h
- # 'APR_OPTIONAL_H', #c-scan chokes on apr_optional.h
- 'apr_table_do_callback_fn_t=void', #c-scan chokes on function pointers
- );
-
-
- # some types c-scan failing to resolve
- push @c_scan_defines, map { "$_=void" }
- qw(PPADDR_t PerlExitListEntry modperl_tipool_vtbl_t);
-
- sub scan {
- require ModPerl::CScan;
- ModPerl::CScan->VERSION(0.75);
- require Carp;
-
- my $self = shift;
-
- my $c = ModPerl::CScan->new(filename => $self->{scan_filename});
-
- my $includes = $self->includes;
-
- # where to find perl headers, but we don't want to parse them otherwise
- my $perl_core_path = catdir $Config{installarchlib}, "CORE";
- push @$includes, $perl_core_path;
-
- $c->set(includeDirs => $includes);
-
- my @defines = @c_scan_defines;
-
- unless ($Config{useithreads} and $Config{useithreads} eq 'define') {
- #fake -DITHREADS so function tables are the same for
- #vanilla and ithread perls, that is,
- #make sure THX and friends are always expanded
- push @defines, 'MP_SOURCE_SCAN_NEED_ITHREADS';
- }
-
- $c->set(Defines => join ' ', map "-D$_", @defines);
-
- bless $c, 'Apache::ParseSource::Scan';
- }
-
- sub include_dirs {
- my $self = shift;
- ($self->config->apxs('-q' => 'INCLUDEDIR'),
- $self->config->mp_include_dir);
- }
-
- sub includes { shift->config->includes }
-
- sub find_includes {
- my $self = shift;
-
- return $self->{includes} if $self->{includes};
-
- require File::Find;
-
- my @includes = ();
- # don't pick preinstalled mod_perl headers if any, but pick the rest
- {
- my @dirs = $self->include_dirs;
- die "could not find include directory (build the project first)"
- unless -d $dirs[0];
-
- my $unwanted = join '|', qw(ap_listen internal version
- apr_optional mod_include mod_cgi
- mod_proxy mod_ssl ssl_ apr_anylock
- apr_rmm ap_config mod_log_config
- mod_perl modperl_ apreq);
- $unwanted = qr|^$unwanted|;
- my $wanted = '';
-
- push @includes, find_includes_wanted($wanted, $unwanted, @dirs);
- }
-
- # now add the live mod_perl headers (to make sure that we always
- # work against the latest source)
- {
- my @dirs = map { catdir $self->config->{cwd}, $_ }
- catdir(qw(src modules perl)), 'xs';
-
- my $unwanted = '';
- my $wanted = join '|', qw(mod_perl modperl_);
- $wanted = qr|^$wanted|;
-
- push @includes, find_includes_wanted($wanted, $unwanted, @dirs);
- }
-
- # now reorg the header files list, so the fragile scan won't choke
- my @apr = ();
- my @mp = ();
- my @rest = ();
- for (@includes) {
- if (/mod_perl.h$/) {
- # mod_perl.h needs to be included before other mod_perl
- # headers
- unshift @mp, $_;
- }
- elsif (/modperl_\w+.h$/) {
- push @mp, $_;
- }
- elsif (/apr_\w+\.h$/ ) {
- # apr headers need to be included first
- push @apr, $_;
- }
- else {
- push @rest, $_;
- }
- }
- @includes = (@apr, @rest, @mp);
-
- return $self->{includes} = \@includes;
- }
-
- sub find_includes_wanted {
- my($wanted, $unwanted, @dirs) = @_;
- my @includes = ();
- for my $dir (@dirs) {
- File::Find::finddepth({
- wanted => sub {
- return unless /\.h$/;
-
- if ($wanted) {
- return unless /$wanted/;
- }
- else {
- return if /$unwanted/;
- }
-
- my $dir = $File::Find::dir;
- push @includes, "$dir/$_";
- },
- (Apache::Build::WIN32 ? '' : follow => 1),
- }, $dir);
- }
- return @includes;
- }
-
- sub generate_cscan_file {
- my $self = shift;
-
- my $includes = $self->find_includes;
-
- my $filename = '.apache_includes';
- open my $fh, '>', $filename or die "can't open $filename: $!";
-
- for my $path (@$includes) {
- my $filename = basename $path;
- print $fh qq(\#include "$path"\n);
- }
-
- close $fh;
-
- return $filename;
- }
-
- my $filemode = join '|',
- qw{READ WRITE CREATE APPEND TRUNCATE BINARY EXCL BUFFERED DELONCLOSE};
-
- my %defines_wanted = (
- Apache => {
- common => [qw{OK DECLINED DONE}],
- config => [qw{DECLINE_CMD}],
- context => [qw(NOT_IN_ GLOBAL_ONLY)],
- http => [qw{HTTP_}],
- log => [qw(APLOG_)],
- methods => [qw{M_ METHODS}],
- mpmq => [qw{AP_MPMQ_}],
- options => [qw{OPT_}],
- override => [qw{OR_ ACCESS_CONF RSRC_CONF}],
- platform => [qw{CRLF CR LF}],
- remotehost => [qw{REMOTE_}],
- satisfy => [qw{SATISFY_}],
- types => [qw{DIR_MAGIC_TYPE}],
- },
- APR => {
- common => [qw{APR_SUCCESS}],
- error => [qw{APR_E}],
- filemode => ["APR_($filemode)"],
- filepath => [qw{APR_FILEPATH_}],
- fileperms => [qw{APR_\w(READ|WRITE|EXECUTE)}],
- finfo => [qw{APR_FINFO_}],
- flock => [qw{APR_FLOCK_}],
- hook => [qw{APR_HOOK_}],
- limit => [qw{APR_LIMIT}],
- poll => [qw{APR_POLL}],
- socket => [qw{APR_SO_}],
- status => [qw{APR_TIMEUP}],
- table => [qw{APR_OVERLAP_TABLES_}],
- uri => [qw{APR_URI_}],
- },
- ModPerl => {
- common => [qw{MODPERL_RC_}],
- }
- );
-
- my %defines_wanted_re;
- while (my($class, $groups) = each %defines_wanted) {
- while (my($group, $wanted) = each %$groups) {
- my $pat = join '|', @$wanted;
- $defines_wanted_re{$class}->{$group} = $pat; #qr{^($pat)};
- }
- }
-
- my %enums_wanted = (
- Apache => { map { $_, 1 } qw(cmd_how input_mode filter_type conn_keepalive) },
- APR => { map { $_, 1 } qw(apr_shutdown_how apr_read_type apr_lockmech apr_filetype) },
- );
-
- my $defines_unwanted = join '|', qw{
- HTTP_VERSION APR_EOL_STR APLOG_MARK APLOG_NOERRNO APR_SO_TIMEOUT
- };
-
- sub get_constants {
- my($self) = @_;
-
- my $includes = $self->find_includes;
- my(%constants, %seen);
-
- for my $file (@$includes) {
- open my $fh, $file or die "open $file: $!";
- while (<$fh>) {
- if (s/^\#define\s+(\w+)\s+.*/$1/) {
- chomp;
- next if /_H$/;
- next if $seen{$_}++;
- $self->handle_constant(\%constants);
- }
- elsif (m/enum[^\{]+\{/) {
- $self->handle_enum($fh, \%constants);
- }
- }
- close $fh;
- }
-
- #maintain a few handy shortcuts from 1.xx
- #aliases are defined in ModPerl::Code
- push @{ $constants{'Apache'}->{common} },
- qw(NOT_FOUND FORBIDDEN AUTH_REQUIRED SERVER_ERROR REDIRECT);
-
- return \%constants;
- }
-
- sub handle_constant {
- my($self, $constants) = @_;
- my $keys = keys %defines_wanted_re; #XXX broken bleedperl ?
-
- return if /^($defines_unwanted)/o;
-
- while (my($class, $groups) = each %defines_wanted_re) {
- my $keys = keys %$groups; #XXX broken bleedperl ?
-
- while (my($group, $re) = each %$groups) {
- next unless /^($re)/;
- push @{ $constants->{$class}->{$group} }, $_;
- return;
- }
- }
- }
-
- sub handle_enum {
- my($self, $fh, $constants) = @_;
-
- my($name, $e) = $self->parse_enum($fh);
- return unless $name;
-
- $name =~ s/^ap_//;
- $name =~ s/_(e|t)$//;
-
- my $class;
- for (keys %enums_wanted) {
- next unless $enums_wanted{$_}->{$name};
- $class = $_;
- }
-
- return unless $class;
- $name =~ s/^apr_//;
-
- push @{ $constants->{$class}->{$name} }, @$e if $e;
- }
-
- #this should win an award for worlds lamest parser
- sub parse_enum {
- my($self, $fh) = @_;
- my $code = $_;
- my @e;
-
- unless ($code =~ /;\s*$/) {
- local $_;
- while (<$fh>) {
- $code .= $_;
- last if /;\s*$/;
- }
- }
-
- my $name;
- if ($code =~ s/^\s*enum\s+(\w*)\s*//) {
- $name = $1;
- }
- elsif ($code =~ s/^\s*typedef\s+enum\s+//) {
- $code =~ s/\s*(\w+)\s*;\s*$//;
- $name = $1;
- }
-
- $code =~ s:/\*.*?\*/::sg;
- $code =~ s/\s*=\s*\w+//g;
- $code =~ s/^[^\{]*\{//s;
- $code =~ s/\}[^;]*;?//s;
- $code =~ s/^\s*\n//gm;
-
- while ($code =~ /\b(\w+)\b,?/g) {
- push @e, $1;
- }
-
- return ($name, \@e);
- }
-
- sub wanted_functions { shift->{prefix_re} }
- sub wanted_structures { shift->{prefix_re} }
-
- sub get_functions {
- my $self = shift;
-
- my $key = 'parsed_fdecls';
- return $self->{$key} if $self->{$key};
-
- my $c = $self->{c};
-
- my $fdecls = $c->get($key);
-
- my %seen;
- my $wanted = $self->wanted_functions;
-
- my @functions;
-
- for my $entry (@$fdecls) {
- my($rtype, $name, $args) = @$entry;
- next unless $name =~ $wanted;
- next if $seen{$name}++;
- my @attr;
-
- for (qw(static __inline__)) {
- if ($rtype =~ s/^($_)\s+//) {
- push @attr, $1;
- }
- }
-
- #XXX: working around ModPerl::CScan confusion here
- #macro defines ap_run_error_log causes
- #cpp filename:linenumber to be included as part of the type
- for (@$args) {
- next unless $_->[0];
- $_->[0] =~ s/^\#.*?\"\s+//;
- $_->[0] =~ s/^register //;
- }
-
- my $func = {
- name => $name,
- return_type => $rtype,
- args => [map {
- { type => $_->[0], name => $_->[1] }
- } @$args],
- };
-
- $func->{attr} = \@attr if @attr;
-
- push @functions, $func;
- }
-
- # sort the functions by the 'name' attribute to ensure a
- # consistent output on different systems.
- $self->{$key} = [sort { $a->{name} cmp $b->{name} } @functions];
- }
-
- sub get_structs {
- my $self = shift;
-
- my $key = 'typedef_structs';
- return $self->{$key} if $self->{$key};
-
- my $c = $self->{c};
-
- my $typedef_structs = $c->get($key);
-
- my %seen;
- my $wanted = $self->wanted_structures;
- my $other = join '|', qw(_rec module
- piped_log uri_t htaccess_result
- cmd_parms cmd_func cmd_how);
-
- my @structures;
- my $sx = qr(^struct\s+);
-
- while (my($type, $elts) = each %$typedef_structs) {
- next unless $type =~ $wanted or $type =~ /($other)$/o;
-
- $type =~ s/$sx//;
-
- next if $seen{$type}++;
-
- my $struct = {
- type => $type,
- elts => [map {
- my $type = $_->[0];
- $type =~ s/$sx//;
- $type .= $_->[1] if $_->[1];
- $type =~ s/:\d+$//; #unsigned:1
- { type => $type, name => $_->[2] }
- } @$elts],
- };
-
- push @structures, $struct;
- }
-
- # sort the structs by the 'type' attribute to ensure a consistent
- # output on different systems.
- $self->{$key} = [sort { $a->{type} cmp $b->{type} } @structures];
- }
-
- sub write_functions_pm {
- my $self = shift;
- my $file = shift || 'FunctionTable.pm';
- my $name = shift || 'Apache::FunctionTable';
-
- $self->write_pm($file, $name, $self->get_functions);
- }
-
- sub write_structs_pm {
- my $self = shift;
- my $file = shift || 'StructureTable.pm';
- my $name = shift || 'Apache::StructureTable';
-
- $self->write_pm($file, $name, $self->get_structs);
- }
-
- sub write_constants_pm {
- my $self = shift;
- my $file = shift || 'ConstantsTable.pm';
- my $name = shift || 'Apache::ConstantsTable';
-
- $self->write_pm($file, $name, $self->get_constants);
- }
-
- sub write_pm {
- my($self, $file, $name, $data) = @_;
-
- require Data::Dumper;
- local $Data::Dumper::Indent = 1;
-
- my($subdir) = (split '::', $name)[0];
-
- my $tdir = 'xs/tables/current';
- if (-d "$tdir/$subdir") {
- $file = "$tdir/$subdir/$file";
- }
-
- # sort the hashes (including nested ones) for a consistent dump
- canonsort(\$data);
-
- my $dump = Data::Dumper->new([$data],
- [$name])->Dump;
-
- my $package = ref($self) || $self;
- my $version = $self->VERSION;
- my $date = scalar localtime;
-
- my $new_content = << "EOF";
- package $name;
-
- # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- # ! WARNING: generated by $package/$version
- # ! $date
- # ! do NOT edit, any changes will be lost !
- # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
- $dump
-
- 1;
- EOF
-
- my $old_content = '';
- if (-e $file) {
- open my $pm, '<', $file or die "open $file: $!";
- local $/ = undef; # slurp the file
- $old_content = <$pm>;
- close $pm;
- }
-
- my $overwrite = 1;
- if ($old_content) {
- # strip the date line, which will never be the same before
- # comparing
- my $table_header = qr{^\#\s!.*};
- (my $old = $old_content) =~ s/$table_header//mg;
- (my $new = $new_content) =~ s/$table_header//mg;
- $overwrite = 0 if $old eq $new;
- }
-
- if ($overwrite) {
- open my $pm, '>', $file or die "open $file: $!";
- print $pm $new_content;
- close $pm;
- }
-
- }
-
- # canonsort(\$data);
- # sort nested hashes in the data structure.
- # the data structure itself gets modified
-
- sub canonsort {
- my $ref = shift;
- my $type = ref $$ref;
-
- return unless $type;
-
- require Tie::IxHash;
-
- my $data = $$ref;
-
- if ($type eq 'ARRAY') {
- for (@$data) {
- canonsort(\$_);
- }
- }
- elsif ($type eq 'HASH') {
- for (keys %$data) {
- canonsort(\$data->{$_});
- }
-
- tie my %ixhash, 'Tie::IxHash';
-
- # reverse sort so we get the order of:
- # return_type, name, args { type, name } for functions
- # type, elts { type, name } for structures
-
- for (sort { $b cmp $a } keys %$data) {
- $ixhash{$_} = $data->{$_};
- }
-
- $$ref = \%ixhash;
- }
- }
-
- 1;
- __END__
-