=pod
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.
=head2 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;
}
1;
Dist::Release considers the check to have failed if there is any call made
to C. If there is no complain, then it assumes that everything is
peachy.
=head2 Implementing an action
Actions are only marginally more complicated than checks. The module
implementing the action can have an optional C method, which is
going to be run with all the other checks, and must have a C,
which make the release-related changes.
For example, here's the CPANUpload action:
package Dist::Release::Action::CPANUpload;
use Moose;
use CPAN::Uploader;
extends 'Dist::Release::Action';
sub check {
my ($self) = @_;
# do we have a pause id?
unless ($self->distrel->config->{pause}{id}
and $self->distrel->config->{pause}{password} ) {
$self->error('pause id or password missing from config file');
}
}
sub release {
my $self = shift;
$self->diag('verifying that the tarball is present');
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 );
}
}
1;
As for the C, Dist::Release figures out that a C failed
if there's a call to C.
=head2 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:
pause:
id: yanick
password: hush
checks:
- VCS::WorkingDirClean
- Manifest
actions:
- GenerateDistribution
- CPANUpload
- Github
It's pretty self-explanatory. The checks and actions are applied in the order
they are given in the file.
=head2 Crying havoc...
And once the configuration file is present, all that remains to be done is to run C, 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 ..." 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 ..." 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: STDOUT
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
=head2 Getting the good
A L
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
L. Comments,
suggestions, forks and patches are welcome, as always. :-)