You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucy.apache.org by nw...@apache.org on 2012/04/09 15:41:16 UTC

[lucy-commits] svn commit: r1311229 - in /lucy/trunk: clownfish/perl/lib/Clownfish/CFC/Perl/ clownfish/perl/lib/Clownfish/CFC/Perl/Build.pm perl/buildlib/Lucy/Build.pm

Author: nwellnhof
Date: Mon Apr  9 13:41:15 2012
New Revision: 1311229

URL: http://svn.apache.org/viewvc?rev=1311229&view=rev
Log:
LUCY-215 Break out Clownfish::CFC::Perl::Build

Lucy::Build is now a subclass of Clownfish::CFC::Perl::Build which is a
subclass of Module::Build.

Added:
    lucy/trunk/clownfish/perl/lib/Clownfish/CFC/Perl/
    lucy/trunk/clownfish/perl/lib/Clownfish/CFC/Perl/Build.pm
Modified:
    lucy/trunk/perl/buildlib/Lucy/Build.pm

Added: lucy/trunk/clownfish/perl/lib/Clownfish/CFC/Perl/Build.pm
URL: http://svn.apache.org/viewvc/lucy/trunk/clownfish/perl/lib/Clownfish/CFC/Perl/Build.pm?rev=1311229&view=auto
==============================================================================
--- lucy/trunk/clownfish/perl/lib/Clownfish/CFC/Perl/Build.pm (added)
+++ lucy/trunk/clownfish/perl/lib/Clownfish/CFC/Perl/Build.pm Mon Apr  9 13:41:15 2012
@@ -0,0 +1,392 @@
+# 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.
+
+use strict;
+use warnings;
+
+package Clownfish::CFC::Perl::Build;
+use base qw( Module::Build );
+our $VERSION = '0.01';
+
+use File::Spec::Functions qw( catdir catfile curdir updir abs2rel rel2abs );
+use File::Path qw( mkpath );
+use Config;
+use Carp;
+
+# Add a custom Module::Build hashref property to pass additional build
+# parameters
+if ( $Module::Build::VERSION <= 0.30 ) {
+    __PACKAGE__->add_property( clownfish_params => {} );
+}
+else {
+    # TODO: add sub for property check
+    __PACKAGE__->add_property(
+        'clownfish_params',
+        default => {},
+    );
+}
+
+=for Rationale
+
+When the distribution tarball for the Perl bindings is built, core/, and any
+other needed files/directories are copied into the perl/ directory within the
+main source directory.  Then the distro is built from the contents of the
+perl/ directory, leaving out all the files in ruby/, etc. However, during
+development, the files are accessed from their original locations.
+
+=cut
+
+my $is_distro_not_devel = -e 'core';
+my $base_dir = rel2abs( $is_distro_not_devel ? getcwd() : updir() );
+my $CORE_SOURCE_DIR = catdir( $base_dir, 'core' );
+
+my $AUTOGEN_DIR  = 'autogen';
+my $LIB_DIR      = 'lib';
+my $BUILDLIB_DIR = 'buildlib';
+
+sub new {
+    my $self = shift->SUPER::new( @_ );
+
+    my $extra_ccflags = $self->extra_compiler_flags;
+    if ( $self->config('gccversion') ) {
+        push @$extra_ccflags, qw( -std=gnu99 -D_GNU_SOURCE );
+    }
+    elsif ( $self->config('cc') =~ /^cl\b/ ) {
+        # Compile as C++ under MSVC.
+        push @$extra_ccflags, qw(
+            /TP -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS
+        );
+    }
+    $self->extra_compiler_flags(@$extra_ccflags);
+
+    # TODO: use Charmonizer to determine whether pthreads are userland.
+    if ( $Config{osname} =~ /openbsd/i && $Config{usethreads} ) {
+        my $extra_ldflags = $self->extra_linker_flags;
+        push @$extra_ldflags, '-lpthread';
+        $self->extra_linker_flags(@$extra_ldflags);
+    }
+
+    my $include_dirs = $self->include_dirs;
+    push( @$include_dirs,
+        curdir(), # for ppport.h
+        catfile( $AUTOGEN_DIR, 'include' ),
+    );
+    $self->include_dirs($include_dirs);
+
+    my $autogen_header = $self->clownfish_params('autogen_header');
+    if ( !defined($autogen_header) ) {
+        $self->clownfish_params( autogen_header => <<'END_AUTOGEN' );
+/***********************************************
+
+ !!!! DO NOT EDIT !!!!
+
+ This file was auto-generated by Build.PL.
+
+ ***********************************************/
+
+END_AUTOGEN
+    }
+
+    return $self;
+}
+
+sub _compile_clownfish {
+    my $self = shift;
+
+    require Clownfish::CFC::Model::Hierarchy;
+    require Clownfish::CFC::Binding::Perl;
+    require Clownfish::CFC::Binding::Perl::Class;
+
+    # Compile Clownfish.
+    my $hierarchy = Clownfish::CFC::Model::Hierarchy->new(
+        dest => $AUTOGEN_DIR,
+    );
+    $hierarchy->add_source_dir($CORE_SOURCE_DIR);
+    $hierarchy->build;
+
+    # Process all Binding classes in buildlib.
+    my $pm_filepaths = $self->rscan_dir( $BUILDLIB_DIR, qr/\.pm$/ );
+    for my $pm_filepath (@$pm_filepaths) {
+        next unless $pm_filepath =~ /Binding/;
+        require $pm_filepath;
+        my $package_name = $pm_filepath;
+        $package_name =~ s/buildlib\/(.*)\.pm$/$1/;
+        $package_name =~ s/\//::/g;
+        $package_name->bind_all;
+    }
+
+    my $module_name  = $self->module_name;
+    my @module_parts = split( '::', $module_name );
+    my $parcel       = $module_parts[-1];
+
+    my $binding = Clownfish::CFC::Binding::Perl->new(
+        parcel     => $parcel,
+        hierarchy  => $hierarchy,
+        lib_dir    => $LIB_DIR,
+        boot_class => $module_name,
+        header     => $self->clownfish_params('autogen_header'),
+        footer     => '',
+    );
+
+    return ( $hierarchy, $binding );
+}
+
+sub ACTION_pod {
+    my $self = shift;
+    $self->dispatch("clownfish");
+    $self->_write_pod(@_);
+}
+
+sub _write_pod {
+    my ( $self, $binding ) = @_;
+    if ( !$binding ) {
+        ( undef, $binding ) = $self->_compile_clownfish;
+    }
+    print "Writing POD...\n";
+    my $pod_files = $binding->write_pod;
+    $self->add_to_cleanup($_) for @$pod_files;
+}
+
+sub ACTION_clownfish {
+    my $self = shift;
+
+    $self->add_to_cleanup($AUTOGEN_DIR);
+
+    my @module_dir  = split( '::', $self->module_name );
+    my $class_name  = pop(@module_dir);
+    my $xs_filepath = catfile( $LIB_DIR, @module_dir, "$class_name.xs" );
+
+    my $buildlib_pm_filepaths = $self->rscan_dir( $BUILDLIB_DIR, qr/\.pm$/ );
+    my $cfh_filepaths = $self->rscan_dir( $CORE_SOURCE_DIR, qr/\.cfh$/ );
+
+    # XXX joes thinks this is dubious
+    # Don't bother parsing Clownfish files if everything's up to date.
+    return
+        if $self->up_to_date(
+        [ @$cfh_filepaths, @$buildlib_pm_filepaths ],
+        [ $xs_filepath,    $AUTOGEN_DIR, ]
+        );
+
+    # Write out all autogenerated files.
+    print "Parsing Clownfish files...\n";
+    my ( $hierarchy, $perl_binding ) = $self->_compile_clownfish;
+    require Clownfish::CFC::Binding::Core;
+    my $core_binding = Clownfish::CFC::Binding::Core->new(
+        hierarchy => $hierarchy,
+        header    => $self->clownfish_params('autogen_header'),
+        footer    => '',
+    );
+    print "Writing Clownfish autogenerated files...\n";
+    my $modified = $core_binding->write_all_modified;
+    if ($modified) {
+        unlink('typemap');
+        print "Writing typemap...\n";
+        $self->add_to_cleanup('typemap');
+        $perl_binding->write_xs_typemap;
+    }
+
+    # Rewrite XS if either any .cfh files or relevant .pm files were modified.
+    $modified ||=
+        $self->up_to_date( \@$buildlib_pm_filepaths, $xs_filepath )
+        ? 0
+        : 1;
+
+    if ($modified) {
+        $self->add_to_cleanup($xs_filepath);
+        $perl_binding->write_boot;
+        $perl_binding->write_bindings;
+        $self->_write_pod($perl_binding);
+
+        # Copy .cfh files to blib/arch/Clownfish/_include
+        my $inc_dir = catdir( $self->blib, 'arch', 'Clownfish', '_include' );
+        for my $file (@$cfh_filepaths) {
+            my $rel  = abs2rel( $file, $CORE_SOURCE_DIR );
+            my $dest = catfile( $inc_dir, $rel );
+            $self->copy_if_modified( from => $file, to => $dest, );
+        }
+    }
+
+    # Touch autogenerated files in case the modifications were inconsequential
+    # and didn't trigger a rewrite, so that we won't have to check them again
+    # next pass.
+    if (!$self->up_to_date(
+            [ @$cfh_filepaths, @$buildlib_pm_filepaths ], $xs_filepath
+        )
+        )
+    {
+        utime( time, time, $xs_filepath );    # touch
+    }
+    if (!$self->up_to_date(
+            [ @$cfh_filepaths, @$buildlib_pm_filepaths ], $AUTOGEN_DIR
+        )
+        )
+    {
+        utime( time, time, $AUTOGEN_DIR );    # touch
+    }
+}
+
+# Write ppport.h, which supplies some XS routines not found in older Perls and
+# allows us to use more up-to-date XS API while still supporting Perls back to
+# 5.8.3.
+#
+# The Devel::PPPort docs recommend that we distribute ppport.h rather than
+# require Devel::PPPort itself, but ppport.h isn't compatible with the Apache
+# license.
+sub ACTION_ppport {
+    my $self = shift;
+    if ( !-e 'ppport.h' ) {
+        require Devel::PPPort;
+        $self->add_to_cleanup('ppport.h');
+        Devel::PPPort::WriteFile();
+    }
+}
+
+sub ACTION_compile_custom_xs {
+    my $self = shift;
+
+    $self->dispatch('ppport');
+
+    require ExtUtils::CBuilder;
+    require ExtUtils::ParseXS;
+
+    my $module_name  = $self->module_name;
+    my @module_parts = split( '::', $module_name );
+    my @module_dir   = @module_parts;
+    my $class_name   = pop(@module_dir);
+
+    my $cbuilder = ExtUtils::CBuilder->new( config => $self->config );
+    my $libdir   = catdir( $LIB_DIR, @module_dir );
+    my $archdir  = catdir( $self->blib, 'arch', 'auto', @module_parts );
+    mkpath( $archdir, 0, 0777 ) unless -d $archdir;
+    my @objects;
+
+    # Compile C source files.
+    my $autogen_source_dir = catfile( $AUTOGEN_DIR, 'source' );
+    my $c_files = [];
+    push @$c_files, @{ $self->rscan_dir( $CORE_SOURCE_DIR,    qr/\.c$/ ) };
+    push @$c_files, @{ $self->rscan_dir( $autogen_source_dir, qr/\.c$/ ) };
+    my $c_sources = $self->clownfish_params('extra_c_sources') || [];
+    for my $c_source (@$c_sources) {
+        if ( -d $c_source ) {
+            push @$c_files, @{ $self->rscan_dir( $c_source, qr/\.c$/ ) };
+        }
+        elsif ( $c_source =~ /\.c$/ ) {
+            push @$c_files, $c_source;
+        }
+        else {
+            die("Invalid C source '$c_source'");
+        }
+    }
+    for my $c_file (@$c_files) {
+        my $o_file   = $c_file;
+        my $ccs_file = $c_file;
+        $o_file   =~ s/\.c$/$Config{_o}/ or die "no match";
+        $ccs_file =~ s/\.c$/.ccs/        or die "no match";
+        push @objects, $o_file;
+        next if $self->up_to_date( $c_file, $o_file );
+        $self->add_to_cleanup($o_file);
+        $self->add_to_cleanup($ccs_file);
+        $cbuilder->compile(
+            source               => $c_file,
+            extra_compiler_flags => $self->extra_compiler_flags,
+            include_dirs         => $self->include_dirs,
+            object_file          => $o_file,
+        );
+    }
+
+    # .xs => .c
+    my $xs_filepath         = catfile( $libdir, "$class_name.xs" );
+    my $perl_binding_c_file = catfile( $libdir, "$class_name.c" );
+    $self->add_to_cleanup($perl_binding_c_file);
+    if ( !$self->up_to_date( $xs_filepath, $perl_binding_c_file ) ) {
+        ExtUtils::ParseXS::process_file(
+            filename   => $xs_filepath,
+            prototypes => 0,
+            output     => $perl_binding_c_file,
+        );
+    }
+
+    # .c => .o
+    my $version = $self->dist_version;
+    my $perl_binding_o_file = catfile( $libdir, "$class_name$Config{_o}" );
+    unshift @objects, $perl_binding_o_file;
+    $self->add_to_cleanup($perl_binding_o_file);
+    if ( !$self->up_to_date( $perl_binding_c_file, $perl_binding_o_file ) ) {
+        $cbuilder->compile(
+            source               => $perl_binding_c_file,
+            extra_compiler_flags => $self->extra_compiler_flags,
+            include_dirs         => $self->include_dirs,
+            object_file          => $perl_binding_o_file,
+            # 'defines' is an undocumented parameter to compile(), so we
+            # should officially roll our own variant and generate compiler
+            # flags.  However, that involves writing a bunch of
+            # platform-dependent code, so we'll just take the chance that this
+            # will break.
+            defines => {
+                VERSION    => qq|"$version"|,
+                XS_VERSION => qq|"$version"|,
+            },
+        );
+    }
+
+    # Create .bs bootstrap file, needed by Dynaloader.
+    my $bs_file = catfile( $archdir, "$class_name.bs" );
+    $self->add_to_cleanup($bs_file);
+    if ( !$self->up_to_date( $perl_binding_o_file, $bs_file ) ) {
+        require ExtUtils::Mkbootstrap;
+        ExtUtils::Mkbootstrap::Mkbootstrap($bs_file);
+        if ( !-f $bs_file ) {
+            # Create file in case Mkbootstrap didn't do anything.
+            open( my $fh, '>', $bs_file )
+                or confess "Can't open $bs_file: $!";
+        }
+        utime( (time) x 2, $bs_file );    # touch
+    }
+
+    # Clean up after CBuilder under MSVC.
+    $self->add_to_cleanup('compilet*');
+    $self->add_to_cleanup('*.ccs');
+    $self->add_to_cleanup( catfile( $libdir, "$class_name.ccs" ) );
+    $self->add_to_cleanup( catfile( $libdir, "$class_name.def" ) );
+    $self->add_to_cleanup( catfile( $libdir, "${class_name}_def.old" ) );
+    $self->add_to_cleanup( catfile( $libdir, "$class_name.exp" ) );
+    $self->add_to_cleanup( catfile( $libdir, "$class_name.lib" ) );
+    $self->add_to_cleanup( catfile( $libdir, "$class_name.lds" ) );
+    $self->add_to_cleanup( catfile( $libdir, "$class_name.base" ) );
+
+    # .o => .(a|bundle)
+    my $lib_file = catfile( $archdir, "$class_name.$Config{dlext}" );
+    if ( !$self->up_to_date( [ @objects, $AUTOGEN_DIR ], $lib_file ) ) {
+        $cbuilder->link(
+            module_name        => $module_name,
+            objects            => \@objects,
+            lib_file           => $lib_file,
+            extra_linker_flags => $self->extra_linker_flags,
+        );
+    }
+}
+
+sub ACTION_code {
+    my $self = shift;
+
+    $self->dispatch('clownfish');
+    $self->dispatch('compile_custom_xs');
+
+    $self->SUPER::ACTION_code;
+}
+
+1;
+
+__END__

Modified: lucy/trunk/perl/buildlib/Lucy/Build.pm
URL: http://svn.apache.org/viewvc/lucy/trunk/perl/buildlib/Lucy/Build.pm?rev=1311229&r1=1311228&r2=1311229&view=diff
==============================================================================
--- lucy/trunk/perl/buildlib/Lucy/Build.pm (original)
+++ lucy/trunk/perl/buildlib/Lucy/Build.pm Mon Apr  9 13:41:15 2012
@@ -22,34 +22,27 @@ use lib 'clownfish/perl/blib/arch';
 use lib 'clownfish/perl/blib/lib';
 
 package Lucy::Build;
-use base qw( Module::Build );
+
+# We want to subclass Clownfish::CFC::Perl::Build, but Clownfish might not be
+# built yet. So we look in 'clownfish/perl/lib' directly and cleanup @INC
+# afterwards.
+use lib '../clownfish/perl/lib';
+use lib 'clownfish/perl/lib';
+use base qw( Clownfish::CFC::Perl::Build );
+no lib '../clownfish/perl/lib';
+no lib 'clownfish/perl/lib';
+
 our $VERSION = 0.003000;
 $VERSION = eval $VERSION;
 
-use File::Spec::Functions
-    qw( catdir catfile splitpath updir no_upwards abs2rel rel2abs );
-use File::Path qw( mkpath rmtree );
-use File::Copy qw( copy move );
-use File::Find qw( find );
+use File::Spec::Functions qw( catdir catfile updir rel2abs );
+use File::Path qw( rmtree );
+use File::Copy qw( move );
 use Config;
 use Env qw( @PATH );
-use Fcntl;
 use Carp;
 use Cwd qw( getcwd );
 
-# Add a custom Module::Build hashref property to pass additional build
-# parameters
-if ( $Module::Build::VERSION <= 0.30 ) {
-    __PACKAGE__->add_property( clownfish_params => {} );
-}
-else {
-    # TODO: add sub for property check
-    __PACKAGE__->add_property(
-        'clownfish_params',
-        default => {},
-    );
-}
-
 BEGIN { unshift @PATH, rel2abs( getcwd() ) }
 
 =for Rationale
@@ -83,10 +76,8 @@ my $UTF8PROC_C = catfile( $UTF8PROC_SRC_
 my $CORE_SOURCE_DIR = catdir( $base_dir, 'core' );
 my $CLOWNFISH_DIR = catdir( $base_dir, 'clownfish', 'perl' );
 my $CLOWNFISH_BUILD  = catfile( $CLOWNFISH_DIR, 'Build' );
-my $AUTOGEN_DIR      = 'autogen';
 my $XS_SOURCE_DIR    = 'xs';
 my $LIB_DIR          = 'lib';
-my $BUILDLIB_DIR     = 'buildlib';
 
 sub new {
     my $self = shift->SUPER::new( recursive_test_files => 1, @_ );
@@ -99,7 +90,6 @@ sub new {
 
     my $extra_ccflags = $self->extra_compiler_flags;
     if ( $self->config('gccversion') ) {
-        push @$extra_ccflags, qw( -std=gnu99 -D_GNU_SOURCE );
         if ( $Config{osname} =~ /openbsd/i && !$Config{usethreads} ) {
             push @$extra_ccflags, '-DLUCY_NOTHREADS';
         }
@@ -112,25 +102,10 @@ sub new {
             );
         }
     }
-    elsif ( $self->config('cc') =~ /^cl\b/ ) {
-        # Compile as C++ under MSVC.
-        push @$extra_ccflags, qw(
-            /TP -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS
-        );
-    }
     $self->extra_compiler_flags(@$extra_ccflags);
 
-    # TODO: use Charmonizer to determine whether pthreads are userland.
-    if ( $Config{osname} =~ /openbsd/i && $Config{usethreads} ) {
-        my $extra_ldflags = $self->extra_linker_flags;
-        push @$extra_ldflags, '-lpthread';
-        $self->extra_linker_flags(@$extra_ldflags);
-    }
-
     my $include_dirs = $self->include_dirs;
     push( @$include_dirs,
-        getcwd(),
-        catfile( $AUTOGEN_DIR, 'include' ),
         $CORE_SOURCE_DIR,
         $XS_SOURCE_DIR,
         $SNOWSTEM_INC_DIR,
@@ -238,63 +213,6 @@ sub ACTION_lemon {
     );
 }
 
-sub _compile_clownfish {
-    my $self = shift;
-
-    require Clownfish::CFC::Model::Hierarchy;
-    require Clownfish::CFC::Binding::Perl;
-    require Clownfish::CFC::Binding::Perl::Class;
-
-    # Compile Clownfish.
-    my $hierarchy = Clownfish::CFC::Model::Hierarchy->new(
-        dest   => $AUTOGEN_DIR,
-    );
-    $hierarchy->add_source_dir($CORE_SOURCE_DIR);
-    $hierarchy->build;
-
-    # Process all __BINDING__ blocks.
-    my $pm_filepaths = $self->rscan_dir( $BUILDLIB_DIR, qr/\.pm$/ );
-    for my $pm_filepath (@$pm_filepaths) {
-        next unless $pm_filepath =~ /Binding/;
-        require $pm_filepath;
-        my $package_name = $pm_filepath;
-        $package_name =~ s/buildlib\/(.*)\.pm$/$1/;
-        $package_name =~ s/\//::/g;
-        $package_name->bind_all;
-    }
-
-    my $module_name  = $self->module_name;
-    my @module_parts = split( '::', $module_name );
-    my $parcel       = $module_parts[-1];
-
-    my $binding = Clownfish::CFC::Binding::Perl->new(
-        parcel     => $parcel,
-        hierarchy  => $hierarchy,
-        lib_dir    => $LIB_DIR,
-        boot_class => $module_name,
-        header     => $self->clownfish_params('autogen_header'),
-        footer     => '',
-    );
-
-    return ( $hierarchy, $binding );
-}
-
-sub ACTION_pod {
-    my $self = shift;
-    $self->dispatch("cfc");
-    $self->_write_pod(@_);
-}
-
-sub _write_pod {
-    my ( $self, $binding ) = @_;
-    if ( !$binding ) {
-        ( undef, $binding ) = $self->_compile_clownfish;
-    }
-    print "Writing POD...\n";
-    my $pod_files = $binding->write_pod;
-    $self->add_to_cleanup($_) for @$pod_files;
-}
-
 sub ACTION_cfc {
     my $self    = shift;
     my $old_dir = getcwd();
@@ -314,95 +232,7 @@ sub ACTION_clownfish {
     $self->dispatch('charmonizer_tests');
     $self->dispatch('cfc');
 
-    $self->add_to_cleanup($AUTOGEN_DIR);
-
-    my @module_dir  = split( '::', $self->module_name );
-    my $class_name  = pop(@module_dir);
-    my $xs_filepath = catfile( $LIB_DIR, @module_dir, "$class_name.xs" );
-
-    my $buildlib_pm_filepaths = $self->rscan_dir( $BUILDLIB_DIR, qr/\.pm$/ );
-    my $cfh_filepaths = $self->rscan_dir( $CORE_SOURCE_DIR, qr/\.cfh$/ );
-
-    # XXX joes thinks this is dubious
-    # Don't bother parsing Clownfish files if everything's up to date.
-    return
-        if $self->up_to_date(
-        [ @$cfh_filepaths, @$buildlib_pm_filepaths ],
-        [ $xs_filepath,    $AUTOGEN_DIR, ]
-        );
-
-    # Write out all autogenerated files.
-    print "Parsing Clownfish files...\n";
-    my ( $hierarchy, $perl_binding ) = $self->_compile_clownfish;
-    require Clownfish::CFC::Binding::Core;
-    my $core_binding = Clownfish::CFC::Binding::Core->new(
-        hierarchy => $hierarchy,
-        header    => $self->clownfish_params('autogen_header'),
-        footer    => '',
-    );
-    print "Writing Clownfish autogenerated files...\n";
-    my $modified = $core_binding->write_all_modified;
-    if ($modified) {
-        unlink('typemap');
-        print "Writing typemap...\n";
-        $self->add_to_cleanup('typemap');
-        $perl_binding->write_xs_typemap;
-    }
-
-    # Rewrite XS if either any .cfh files or relevant .pm files were modified.
-    $modified ||=
-        $self->up_to_date( \@$buildlib_pm_filepaths, $xs_filepath )
-        ? 0
-        : 1;
-
-    if ($modified) {
-        $self->add_to_cleanup($xs_filepath);
-        $perl_binding->write_boot;
-        $perl_binding->write_bindings;
-        $self->_write_pod($perl_binding);
-
-        # Copy .cfh files to blib/arch/Clownfish/_include
-        my $inc_dir = catdir( $self->blib, 'arch', 'Clownfish', '_include' );
-        for my $file (@$cfh_filepaths) {
-            my $rel  = abs2rel( $file, $CORE_SOURCE_DIR );
-            my $dest = catfile( $inc_dir, $rel );
-            $self->copy_if_modified( from => $file, to => $dest, );
-        }
-    }
-
-    # Touch autogenerated files in case the modifications were inconsequential
-    # and didn't trigger a rewrite, so that we won't have to check them again
-    # next pass.
-    if (!$self->up_to_date(
-            [ @$cfh_filepaths, @$buildlib_pm_filepaths ], $xs_filepath
-        )
-        )
-    {
-        utime( time, time, $xs_filepath );    # touch
-    }
-    if (!$self->up_to_date(
-            [ @$cfh_filepaths, @$buildlib_pm_filepaths ], $AUTOGEN_DIR
-        )
-        )
-    {
-        utime( time, time, $AUTOGEN_DIR );    # touch
-    }
-}
-
-# Write ppport.h, which supplies some XS routines not found in older Perls and
-# allows us to use more up-to-date XS API while still supporting Perls back to
-# 5.8.3.
-#
-# The Devel::PPPort docs recommend that we distribute ppport.h rather than
-# require Devel::PPPort itself, but ppport.h isn't compatible with the Apache
-# license.
-sub ACTION_ppport {
-    my $self = shift;
-    if ( !-e 'ppport.h' ) {
-        require Devel::PPPort;
-        $self->add_to_cleanup('ppport.h');
-        Devel::PPPort::WriteFile();
-    }
+    $self->SUPER::ACTION_clownfish;
 }
 
 sub ACTION_suppressions {
@@ -539,136 +369,9 @@ sub ACTION_parsers {
 sub ACTION_compile_custom_xs {
     my $self = shift;
 
-    $self->dispatch('ppport');
     $self->dispatch('parsers');
 
-    require ExtUtils::CBuilder;
-    require ExtUtils::ParseXS;
-
-    my $module_name  = $self->module_name;
-    my @module_parts = split( '::', $module_name );
-    my @module_dir   = @module_parts;
-    my $class_name   = pop(@module_dir);
-
-    my $cbuilder = ExtUtils::CBuilder->new( config => $self->config );
-    my $libdir   = catdir( $LIB_DIR, @module_dir );
-    my $archdir  = catdir( $self->blib, 'arch', 'auto', @module_parts );
-    mkpath( $archdir, 0, 0777 ) unless -d $archdir;
-    my @objects;
-
-    # Compile C source files.
-    my $autogen_source_dir = catfile( $AUTOGEN_DIR, 'source' );
-    my $c_files = [];
-    push @$c_files, @{ $self->rscan_dir( $CORE_SOURCE_DIR,    qr/\.c$/ ) };
-    push @$c_files, @{ $self->rscan_dir( $autogen_source_dir, qr/\.c$/ ) };
-    my $c_sources = $self->clownfish_params('extra_c_sources') || [];
-    for my $c_source (@$c_sources) {
-        if ( -d $c_source ) {
-            push @$c_files, @{ $self->rscan_dir( $c_source, qr/\.c$/ ) };
-        }
-        elsif ( $c_source =~ /\.c$/ ) {
-            push @$c_files, $c_source;
-        }
-        else {
-            die("Invalid C source '$c_source'");
-        }
-    }
-    for my $c_file (@$c_files) {
-        my $o_file   = $c_file;
-        my $ccs_file = $c_file;
-        $o_file   =~ s/\.c$/$Config{_o}/ or die "no match";
-        $ccs_file =~ s/\.c$/.ccs/        or die "no match";
-        push @objects, $o_file;
-        next if $self->up_to_date( $c_file, $o_file );
-        $self->add_to_cleanup($o_file);
-        $self->add_to_cleanup($ccs_file);
-        $cbuilder->compile(
-            source               => $c_file,
-            extra_compiler_flags => $self->extra_compiler_flags,
-            include_dirs         => $self->include_dirs,
-            object_file          => $o_file,
-        );
-    }
-
-    # .xs => .c
-    my $xs_filepath         = catfile( $libdir, "$class_name.xs" );
-    my $perl_binding_c_file = catfile( $libdir, "$class_name.c" );
-    $self->add_to_cleanup($perl_binding_c_file);
-    if ( !$self->up_to_date( $xs_filepath, $perl_binding_c_file ) ) {
-        ExtUtils::ParseXS::process_file(
-            filename   => $xs_filepath,
-            prototypes => 0,
-            output     => $perl_binding_c_file,
-        );
-    }
-
-    # .c => .o
-    my $version = $self->dist_version;
-    my $perl_binding_o_file = catfile( $libdir, "$class_name$Config{_o}" );
-    unshift @objects, $perl_binding_o_file;
-    $self->add_to_cleanup($perl_binding_o_file);
-    if ( !$self->up_to_date( $perl_binding_c_file, $perl_binding_o_file ) ) {
-        $cbuilder->compile(
-            source               => $perl_binding_c_file,
-            extra_compiler_flags => $self->extra_compiler_flags,
-            include_dirs         => $self->include_dirs,
-            object_file          => $perl_binding_o_file,
-            # 'defines' is an undocumented parameter to compile(), so we
-            # should officially roll our own variant and generate compiler
-            # flags.  However, that involves writing a bunch of
-            # platform-dependent code, so we'll just take the chance that this
-            # will break.
-            defines => {
-                VERSION    => qq|"$version"|,
-                XS_VERSION => qq|"$version"|,
-            },
-        );
-    }
-
-    # Create .bs bootstrap file, needed by Dynaloader.
-    my $bs_file = catfile( $archdir, "$class_name.bs" );
-    $self->add_to_cleanup($bs_file);
-    if ( !$self->up_to_date( $perl_binding_o_file, $bs_file ) ) {
-        require ExtUtils::Mkbootstrap;
-        ExtUtils::Mkbootstrap::Mkbootstrap($bs_file);
-        if ( !-f $bs_file ) {
-            # Create file in case Mkbootstrap didn't do anything.
-            open( my $fh, '>', $bs_file )
-                or confess "Can't open $bs_file: $!";
-        }
-        utime( (time) x 2, $bs_file );    # touch
-    }
-
-    # Clean up after CBuilder under MSVC.
-    $self->add_to_cleanup('compilet*');
-    $self->add_to_cleanup('*.ccs');
-    $self->add_to_cleanup( catfile( $libdir, "$class_name.ccs" ) );
-    $self->add_to_cleanup( catfile( $libdir, "$class_name.def" ) );
-    $self->add_to_cleanup( catfile( $libdir, "${class_name}_def.old" ) );
-    $self->add_to_cleanup( catfile( $libdir, "$class_name.exp" ) );
-    $self->add_to_cleanup( catfile( $libdir, "$class_name.lib" ) );
-    $self->add_to_cleanup( catfile( $libdir, "$class_name.lds" ) );
-    $self->add_to_cleanup( catfile( $libdir, "$class_name.base" ) );
-
-    # .o => .(a|bundle)
-    my $lib_file = catfile( $archdir, "$class_name.$Config{dlext}" );
-    if ( !$self->up_to_date( [ @objects, $AUTOGEN_DIR ], $lib_file ) ) {
-        $cbuilder->link(
-            module_name        => $module_name,
-            objects            => \@objects,
-            lib_file           => $lib_file,
-            extra_linker_flags => $self->extra_linker_flags,
-        );
-    }
-}
-
-sub ACTION_code {
-    my $self = shift;
-
-    $self->dispatch('clownfish');
-    $self->dispatch('compile_custom_xs');
-
-    $self->SUPER::ACTION_code;
+    $self->SUPER::ACTION_compile_custom_xs;
 }
 
 sub autogen_header {