Hacking Thy Fearful Symmetry

Hacker, hacker coding bright

introducing Dist::Release

November 11, 2008
perl dist::release

(cross-posted from Hacking Thy Fearful Symmetry )

I know, I know, there's already more module release managers out there than there are Elvis impersonators in Vegas. Still, module releasing seems to be a very personal kind of itch, and like so many before I couldn't resist and came up with my very own scratching stick.

Of course, I've tried Module::Release. But, although it is intended to be customized to suit each author's specific needs, one has to dig fairly deep in the module's guts to do so. What I really wanted was something even more plug'n'play, something that would be brain-dead easy to plop new components in. Hence Dist::Release.

In Dist::Release, the release process is seen as a sequence of steps. There are two different kind of steps: checks and actions. Checks are non-intrusive verifications (i.e., they're not supposed to touch anything), and actions are the steps that do the active part of the release. When one launches a release, checks are done first. If some fail, we abort the process. If they all pass, then we are good to go and the actions are done as well.

Implementing a check

To create a check, all that is needed is one module with a 'check' method. For example, here is the code to verify that the distribution's MANIFEST is up-to-date:

    package Dist::Release::Check::Manifest::Build;
    use Moose;
    use IPC::Cmd 'run';
    extends 'Dist::Release::Step';
    sub check {
        my $self = shift;
        $self->diag( q{running 'Build distcheck'} )
        my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
        run( command => [qw#./Build distcheck #] );
        return $self->error( join '', @$full_buf )
            if not $success or grep/not in sync/ => @$stderr_buf;

Dist::Release considers the check to have failed if there is any call made to error(). If there is no complain, then it assumes that everything is peachy.

Implementing an action

Actions are only marginally more complicated than checks. The module implementing the action can have an optional check() method, which is going to be run with all the other checks, and must have a release(), which make the release-related changes.

For example, here's the CPANUpload action:

    use Moose;

    use CPAN::Uploader;
    sub check {
($self) = @_;
        # do we have a pause id?
  unless ($self->distrel->config->{pause}{id}
$self->distrel->config->{pause}{password} ) {
$self->error('pause id or password missing from config file');
    sub release {
        my $self =
        $self->diag('verifying that the tarball is
        my @archives = <*.tar.gz> or return $self->error('no tarball found');
        if ( @archives > 1 ) {
            return $self->error( 'more than one tarball file found: ' . join ',',
                @archives );
        my $tarball = $archives[0];
        $self->diag("found tarball: $tarball");
        $self->diag("uploading tarball '$tarball' to CPAN");
        my ( $id, $password ) =
            map { $self->distrel->config->{pause}{$_} } qw/ id password/;
        $self->diag("using user '$id'");
        my $args = { user => $id, password => $password };
        unless ( $self->distrel->pretend ) {
            CPAN::Uploader->upload_file( $tarball, $args );

As for the check(), Dist::Release figures out that a release() failed if there's a call to error().

Configuring for a module

Configuration is done via a 'distrelease.yml' file dropped in the root directory of the project. The file looks like this:

        id: yanick
        password: hush
        - VCS::WorkingDirClean
        - Manifest
        - GenerateDistribution
        - CPANUpload
        - Github

It's pretty self-explanatory. The checks and actions are applied in the order they are given in the file.

Crying havoc...

And once the configuration file is present, all that remains to be done is to run distrelease, sit back and enjoy the show:

$ distrelease 
Dist::Release will only pretend to perform the actions (use --doit for the real deal)
    running check cycle...
    regular checks
    VCS::WorkingDirClean              [failed]
    working directory is not clean
    # On branch master
    # Changed but not updated:
    #   (use "git add <file>..." to update what will be committed)
    #       modified:   Build.PL
    #       modified:   Changes
    #       modified:   README
    #       modified:   distrelease.yml
    #       modified:   lib/Dist/Release/Check/Manifest.pm
    #       modified:   script/distrelease
    # Untracked files:
    #   (use "git add <file>..." to include in what will be committed)
    #       STDOUT
    #       a
    #       blog
    #       xml
    #       xt/dependencies.t
    #       xxx
    no changes added to commit (use "git add" and/or "git commit -a")
    Manifest                          [failed]
    No such file: lib/Dist/Release/Action/DoSomething.pm
    Not in MANIFEST: a
    Not in MANIFEST: blog
    Not in MANIFEST: lib/Dist/Release/Action/Github.pm
    Not in MANIFEST: xml
    Not in MANIFEST: xt/dependencies.t
    Not in MANIFEST: xxx
    MANIFEST appears to be out of sync with the distribution
    pre-action checks
    GenerateDistribution              [passed]
    no check implemented
    CPANUpload                        [passed]
    Github                            [passed]
    2 checks failed
    some checks failed, aborting the release

Getting the good

A first release of Dist::Release is already waiting for you on CPAN. It's beta, has no documentation, is probably buggy as hell, but it's there. And the code is also available on Github. Comments, suggestions, forks and patches are welcome, as always.


comments powered by Disqus

About the author

Yanick Champoux
Perl necrohacker , ACP writer, orchid lover. Slightly bonker all around. he/him/his