Relocatable Code in Bermuda

Ovid on 2008-02-14T11:51:01

The problem: Bermuda needs to be relocatable. This means that a lot of the auto-generated code needs to not require that Bermuda be installed. My Bermuda::Writer package needs to supply a base serialization class, but I need to test this class. To do that, I had Bermuda::Serialize::Base both in its own file, but also embedded in my Bermuda writer as a string.

That duplication is bad, but someone needs control over their serialization base class name and it shouldn't force them to use 'Bermuda', so here's what I did:

sub _base_class_code {
    my ( $class, $package ) = @_;

    my $writer = $INC{'Bermuda/Writer.pm'};
    my @path   = splitdir($writer);
    pop @path;    # discard Writer.pm
    my $base = catfile( @path, 'Serialize', 'Base.pm' );

    open my $fh, '<', $base or die "Cannot open ($base) for reading: $!";
    my $base_class_code = do { local $/; <$fh> };
    close $fh or die "Cannot close ($base): $!";
    $base_class_code =~ s/Bermuda::Serialize::Base/$package/gs;
    return $base_class_code;
}

This makes me feel really icky, but I've removed some duplication and now this code can be relocated.


Unix blinders?

Aristotle on 2008-02-14T12:48:08

Are you sure you want splitdir/catfile and not splitpath/catpath?

Re:Unix blinders?

Ovid on 2008-02-14T13:04:11

To be honest, I use those so seldom, I never really paid attention to the difference.

Making both of those changes results in a lot of uninit value warnings from catpath, even though the tests pass :/

sub catpath {
    my ($self,$volume,$directory,$file) = @_;

    # this gives me uninit warnings
    if ( $directory ne ''                &&
         $file ne ''                     &&
         substr( $directory, -1 ) ne '/' &&
         substr( $file, 0, 1 ) ne '/'
    ) {
        $directory .= "/$file" ;
    }
    else {

        # as does this ...
        $directory .= $file ;
    }

    return $directory ;
}

Re:Unix blinders?

Aristotle on 2008-02-15T01:46:35

Huh, what are you passing to it?

Re:Unix blinders?

Ovid on 2008-02-16T09:20:09

Er, um, really wish I had read the documentation rather than doing a quick %s/one/fortheother/. *blush*.

Re:Unix blinders?

Aristotle on 2008-02-16T15:22:43

Heh. That’s what I meant by “Unix blinders” – and not pejoratively, as I used to wear them for the longest time myself. Unix is so nicely simple: no volumes, no distinct namespaces for file and directory names, just one uniform hierarchy… after a while one forgets the bizarre things that exist elsewhere.

Re:Unix blinders?

jplindstrom on 2008-02-14T16:01:59

I think it's actually spelled:

use File::Basename;
dirname($file);

Avoid the extra file?

Aristotle on 2008-02-14T12:50:44

How about a method that returns the serialiser code as a string (with selectable package name), which can then be evaled by the test file for testing?

Re:Avoid the extra file?

Ovid on 2008-02-14T13:18:45

I don't want to embed a string of code. I'm doing a huge amount of that already to do code generation and it makes a lot of my code hard to follow, despite aggressive refactoring. If I keep these packages in real packages, I get the benefits of my t/00-load.t test:

#!perl

use Test::More;
use File::Spec::Functions 'splitdir';
use File::Find;

sub path_to_module {
    my $path = shift;
    $path =~ s/\.pm\z//;
    my @components = splitdir($path);
    shift @components;   # discard lib/
    return join '::' => @components;
}

BEGIN {
    my @packages;
    find({
        no_chdir => 1,
        wanted   => sub {
            if ( /\.pm\z/ ) {
               push @packages => path_to_module $File::Find::name;
            }
        }
    }, 'lib');

    plan tests => scalar @packages;

    foreach my $package (@packages) {
        use_ok $package or BAIL_OUT("Cannot use ($package)");
    }
}

diag("Testing Bermuda $Bermuda::VERSION, Perl $], $^X");

I always forget to add the modules to my load tests, to this ensures I can never forget them. Plus, I effectively get a compile-time check against the relocatable code instead of the runtime check against embedded strings of code.

It's a trade-off, but if I can get the relocatable code bit working (and it's working on Solaris), then this is a nice win ... I think.

Re:Avoid the extra file?

Aristotle on 2008-02-16T01:11:48

Put it in a SelfLoader module and add a method to pull out the source via the package’s DATA handle? That way you avoid all the path diddling gymnastics and it automatically works as a standalone module.