Over the last few years, it has been a presumption that when I work on a project in Perl, I will use the standard Perl tools– ExtUtils::MakeMaker and, later, Module::Build –for managing the Perl library code I write.

But yesterday, for the first time, I looked at extending Module::Build to do more than just the stock actions. And you know what, it was easy.

Now the specific issue I was running up against was that I needed to insure that the database I was running my tests against was installed and clean. I had been using a Makefile, but that was a hack–for instance, I wasn't actually checking the presence of the database or anything, I was looking for a file I wrote when I created the database. I probably could have made make check for the actual database, but it's imperative, rather than procedural, style makes this kind of ugly.

Also, I wanted a clean instance of this database before I did a test run of the conversion utility (this is all work on a heavily revised AnteSpam, and we're moving from keeping config info in ldap to putting it in a replicated PostgreSQL database). And I wanted a clean instance of this database before I ran the PostgreSQL Autodoc tool to generate a nice diagram and DocBook documentation of the structure.

Oh, and I got so frigging tired of SQL's spectacular verbosity ( badly exacerbated by the fact that I was commenting on most of the structures so the information would show up in the DocBook documentation) that I wrote a simple preprocessor–so I had to make sure that was run if necessary before creating the database.

Oh, and I wanted to build the documentation automatically. And I kept forgetting to run the Build script with the environment set properly for the database, so I wanted that to be handled easily.

So, I made a file, Build.pm, which sits right alongside Build.PL, and subclasses Module::Build. And to that file I added a function (admittedly very simplistic, and, as a result, somewhat overenthusiastic) to drop and recreate the database:

sub create_db {
    my $self = shift;

    # Get the database name
    my $database = $self->args ("database");

    # Drop the database if it already exists
    $self->do_system (qq{dropdb $database}) if ($self->do_system (qq{psql -l | egrep -q $database}));

    # Create the database
    $self->do_system (qq{createdb $database});

    # Make sure the schema is up-to-date
    $self->dispatch ("ddlpp");

    # Load up the schema and initial data
    $self->do_system (qq{psql -q -f antespam.sql});
};

There are several cool things here. First, you can look at, at run-time, arguments that were given to the script when it was created. So you can do:

perl Build.PL database=foo

and when you actually invoke the resulting build script, the bits you write can look for a database argument, and use what was set initially. The rest of it should be fairly obvious–yes, I'm just shelling out to psql rather than doing it all in DBI–except for the dispatch call. You see, you can add additional actions to your script. In this case, I added an action called ddlpp (for DDL pre-processor) to build the sql from my data definition file. It's short, just:

sub ACTION_ddlpp {
    my $self = shift;
    $self->do_system (qq{ddlpp antespam.dp antespam.sql}) unless ($self->up_to_date ("antespam.dp", "antespam.sql"));
};

You'll notice, though, that it will only run ddlpp again if the .dp file is newer than the .sql file. That's cool.

Anyway, I also overrode the standard test action, to make sure the database is created:

sub ACTION_test {
    my $self = shift;

    # Set up database access
    local $ENV{PGDATABASE} = $self->args ("database");
    local $ENV{PGHOST} = $self->args ("host");
    local $ENV{PGPASSWORD} = $self->args ("password");
    local $ENV{PGUSER} = $self->args ("user");

    # Make sure the database is created
    $self->create_db;

    # Run tests as normal
    $self->SUPER::ACTION_test (@_);
}

All this does is set the appropriate environment variables for psql to pick up, creates the database, and then runs the normal test action that it inherited from Module::Build. The convert action is similar, except it shells out to the convert script.

Etc., etc. I'm not holding this up as any paragon of implementation–in fact, it's exposed some shortcuts I've taken that I ought not be taking, so I'm gonna have to clean those up eventually, and I should be able to add automatic .dp to .sql conversion and so forth–but for an hour or two of poking around, I've made some not-inconsequential extensions to the build system, giving me a much cleaner, more integrated process.