Compiling a SWIG wrapper using Module::Build

Perhaps I have just failed to grok the Module::Build and ExtUtils::CBuilder man pages, but I had a hard time understanding how to compile the Ifeffit wrapper using these tools. This is a shame. In almost every way, M::B is insanely better than ExtUtils::MakeMaker. However, for this particular chore, EU::MM is trivial and I had to jump through some hoops to make my build work with M::B.

A word of background. Ifeffit is Matt Newville's crufty, old, but mostly wonderful XAS data oprocessing and analysis library. It is written in Fortran. We use SWIG to build wrappers for perl and python, allowing us to mostly compensate for Ifeffit's cruft.

To start, here is the Makefile.PL I have used for years to compile the wrapper:

   1 use ExtUtils::MakeMaker;
   2 
   3 ## there were about 20 lines of perl here which figure out the value of the $compile_flags scalar
   4 
   5 WriteMakefile
   6 (
   7  'NAME'         => 'Ifeffit',
   8  'VERSION_FROM' => 'lib/Ifeffit.pm',
   9  'PREREQ_PM'    => {
  10                     'version'             => '0',
  11                    },
  12  'INSTALLDIRS'  => 'site',
  13  'OBJECT'       => 'ifeffit_wrap.o',
  14  'LIBS'         => [$compile_flags],
  15 );

That's it! EU::MM was smart enough to figure out to rename the shared object library to Ifeffit.so and to move it into blib/arch/auto/Ifeffit/ to stage it for installation. A quick make install and we were done.

How is this done with M::B? There is a brief mention of using ExtUtils::CBuilder in the Module::Build::Authoring document, but that doesn't really explain in any depth or give an example. The Module::Build::API pod is similarly terse. Now, it is important to mention that the authors of M::B have written a fine document and a fine tool. It just lacks a clear explanation of how to compile something like my SWIG wrapper.

After some searching around CPAN, I finally found some inspiration from some of Alberto Simões' packages. Following his example, I first make a separate file from Build.PL called (in my case) DemeterBuilder.pm. I used that to subclass Module::Build and to define a series of M::B actions for handling the compilation. Doing this with a separate file is not essential, but it made a clean separation between the features I added to the subclass and the rest of the (otherwise normal) Build.PL.

My Build.PL file starts with the following:

   1 use Module::Build;
   2 use DemeterBuilder; # subclass of Module::Build defining some Demeter specific installation instructions
   3 
   4 my $build = DemeterBuilder
   5   -> new(
   6          module_name        => 'Demeter',
   7          license            => 'gpl',
   8          dist_author        => 'Bruce Ravel <bravel AT bnl DOT gov>',
   9          needs_compiler     => 1,
  10 
  11          ## and so on...
  12         )

The business part of DemeterBuilder.pm is shown here. It does three things:

  1. It overrides the build action, wrapping the compilation and staging of the SWIG wrapper around M::B's normal build action.

  2. The compile_ifeffit_wrapper action uses ExtUtils::CBuilder as explained in its pod.

  3. The post_build action moves the shared object to the staging area under blib/.

(Since writing this, I have added a lot of functionality to DemeterBuilder.pm, but it still does what's described here.)

   1 package DemeterBuilder;
   2 #
   3 # subclass of Module::Build defining some Demeter specific installation instructions
   4 #
   5 
   6 use base 'Module::Build';
   7 
   8 use warnings;
   9 use strict;
  10 use Carp;
  11 use File::Copy;
  12 use File::Path qw(mkpath);
  13 use File::Spec;
  14 
  15 sub ACTION_build {
  16   my $self = shift;
  17   $self->dispatch("compile_ifeffit_wrapper");
  18   $self->SUPER::ACTION_build;
  19   $self->dispatch("post_build");
  20 }
  21 
  22 sub ACTION_compile_ifeffit_wrapper {
  23   my $self = shift;
  24 
  25   my ($compile_flags);
  26   ## several uninteresting lines used to figure out some compilation flags have been removed
  27 
  28   my $cbuilder = $self->cbuilder;
  29   my $obj_file = $cbuilder->compile(source => 'src/ifeffit_wrap.c');
  30   my $lib_file = $cbuilder->link(objects => $obj_file, extra_linker_flags=>$compile_flags, lib_file=>'src/Ifeffit.so');
  31 };
  32 
  33 sub ACTION_post_build {
  34   my $self = shift;
  35   $self->copy_if_modified( from    => File::Spec->catfile('src','Ifeffit.so'),
  36                            to_dir  => File::Spec->catdir('blib','arch','auto','Ifeffit'),
  37                            flatten => 1);
  38   $self->copy_if_modified( from    => File::Spec->catfile('src','Ifeffit.bs'),
  39                            to_dir  => File::Spec->catdir('blib','arch','auto','Ifeffit'),
  40                            flatten => 1);
  41 };

All this happens automagically with EU::MM. I worry that my solution will prove fragile when I move to get things working on the other platforms. Hopefully, CBuilder and copy_if_modified know how to the right thing....

Demeter/SwigModuleBuild (last edited 2011-03-25 13:37:01 by BruceRavel)