What is your release process?

For me, it goes something like this.

First, I edit Changes and dist.ini. Some people automate filling their Changes entry from 'git log', I find this inappropriate since Changes are meant for users, not developers. I also still bump version number manually (though only in one place, dist.ini). Perhaps someday I'll automate it; my version numbering scheme is mostly a boring 0.01-increments anyway.

This modification to two files will be in the commit message that marks the release.

Then I "push the one-click release process button", by running a simple Perl script, without any arguments, in the dist's top-level directory. Thanks to Dist::Zilla, the script is fairly simple. It just needs to:

  1. Grab version number $ver from dist.ini.
  2. Run "dzil test".
  3. Determine if we're online, currently by attempting to DNS-resolve a known host like google.com.
  4. If we're online, run "dzil release". Otherwise, just "dzil build" and run another script to archive the release to some directory. BTW, dzil's release plugin also run this script, so releases are archived nonetheless.
  5. Run "git commit -am 'Release -v$ver' && git tag 'v$ver'"

The script bails if error is encountered in any of the steps.

Pushing to github (and/or gitorious, and/or bitbucket) is done using git's post-commit hook. The hook is also a Perl script. Aside from git-pushing, the script also notifies an internal forum and backups the repo whenever a backup media is mounted (I don't want to lose work due to a faulty hard drive, ever again).

I used a Twitter dzil plugin for a while, but stopped doing so mainly because the plugin used to broke every now and then. Now I think tweeting a release is not that useful anyway.

Starting from this week, I also run lint-prereqs before "dzil test", because so far the majority of my CT failure reports are about missing or circular dependencies. I do maintain dependencies manually because: 1) Some deps are dynamic and the in-stock tools cannot detect them anyway; 2) I want to be more aware and refrain from using needless dependencies as much as possible. 3) I want precise version requirements (instead of just requiring the newest version for everything).

Even though there is CPAN and BackPAN, I archive all my releases because: 1) sometimes I work offline and need offline access to the old releases; 2) my releases are usually tiny (less than 50-100KB); 3) using dzil means a lot of things get generated during release process instead of being put in the repo; 4) some releases are private, not targetted to CPAN.

The usual release process takes less than a minute, unless the Changes entry is quite long (e.g. more than a week's worth of work). The release script itself usually runs in 5-10 seconds or less (could be faster had dzil had decent startup speed :) ).

Some distributions, like this, is automated even more. Creating a new release is done entirely by a script. I aspire to do this for most of my distributions :)

Please share your own release process. Do you have a one-click release button? Do you archive your releases (and if so, where do you put the files)? How much time do you spend doing a typical release? How do you manage your dependencies? What other cool things you do during/after release?

11 Comments

There are Dist::Zilla plugins for at least most of these tasks, I think – maybe all of them. (And if not, they could probably be written.) That would cut all of this down to dzil release after editing dist.ini and Changes. (I’m with you that I prefer those not to be automatic.)

I've previously described my release process as part of PerlMonks: My Perl Module Toolchain.

I don't actually any more, but use perlbrew. The principle is the same though - it's mostly just to make sure that the tarball isn't fatally broken before it gets uploaded.

I’m a little more cautious, in that a lot of my process is manual. I do use Dist::Zilla heavily, but committing to GitHub and tagging is a manual process.

I believe that the module that is in the repo on GitHub should be identical to the one that you can download from CPAN, down to the comments and documentation.

I have my own pluginbundle that I just started using - https://metacpan.org/module/Dist::Zilla::PluginBundle::JAITKEN

  1. I update the $version in my main_module. D::Z::P:: VersionFromModule extracts the version from there, so I don’t have to update it in my dist.ini as well. Before I used this module I had forgot to update it in both places before… Ooops.

    I use http://semver.org for my version numbering philosophy.

  2. I update Changes by hand. I agree that extracting that from git commits is a Bad Idea™

  3. I run dzil build, and pull the generated-from-POD readme.markdown out of the build, replace all “search.cpan.org” links with “metacpan.org” links (I should automate this step…). I then commit this in the root of my GitHub repo, so I have a nicely formatted readme when someone visits the GitHub page.

  4. dzil release… And wait.

  5. Once I get the email that indexing was successful, I push a tag to the GitHub repo. I don’t want to tag a version if there was an error on uploading to CPAN / indexing, so this step is manual.

  6. I then retweet the announcement of my module release from cpan_new to the few perl people who follow me and care :)

  7. After a few hours, I will install my released module (and any other outdated ones) to my perlbrewed perl with: cpan-outdated | xargs cpanm --sudo

All in all a pretty similar process, but with less automation. I don’t think archiving releases is necessary. The CPAN is an “archive” after all, and I have my GitHub and local git repos. If my local HD dies, I still have my generic system backup, AND the remote repo on GitHub.

While my process is similar to everyone else’s I have moved away from dzil lately and I wanted to mention how I get similar functionality using other tools.

  1. bump version and see new version number by running perl-reversion -bump (a utility that comes with Perl::Version module)

  2. Manually edit Changes file (Actually I typically add major changes as bullets as I go, then version and date at this point)

  3. podselect lib/path/to/module.pm > README.pod - freshen the README.pod for github’s sake

  4. perl Build.PL && Build dist && Build disttest - this freshens the META information and checks the dist

  5. release via PAUSE site or recently using the mojo utility which come with mojolicious

  6. Build realclean && rm *.tar.gz - cleanup

  7. make a git tag and then push to the github repo

  8. blog!

For the record I have nothing against dzil, but it had started to feel too heavy. I also like my repo to be as close to a release as possible, because I want to make contribution easy. Using dzil a contributor has to install lots of stuff before they can work. After I had found podselect and perl-reversion I found that most of what I liked from dzil, I could replicate without it. To each their own of course.

Yeah, I guess you are right.

In point of fact, that "contributor" was usually me on another computer or perlbrew-installation. dzil is good stuff, but once I learned other mechanisms they are easy enough too.

Care to explain? Doesn't this defeat the purpose of using dzil?

I do think, for practical reason, the modules in the repo should be directly loadable by Perl (e.g. using 'perl -Ilib') but this is not always possible: for example since I use DZP::OurPkgVersion, my modules in the repo only has '# VERSION'. Code that tries to 'use MyModule 0.123' will fail.

I oversimplified a little... Like you I think that the module in the repo should be directly loadable by perl. That's why I use a dzil plugin that takes the `$VERSION` from the module. Rather than specifying the version in dist.ini and having a `#VERSION` comment in the module that gets replaced on release. My way means that you can `use MyModule 0.123` with the module in the repo and it will still work.

Some items in a build are auto-generated by dzil (meta.json, Makefile.pl, etc) - so I don't count them as part of the "module". What's important to me is that the perl file you get from the repo, and the perl file you get from CPAN are identical.


(Note, this is just my opinion. I'm not suggesting it's the One True Way™)

I have a few oddities, and I do things MyWay™, but I have a few tricks I don't see employed often which I find incredibly useful for a number of reasons.

The most important to me, is the [Git::CommitBuild] plugin for DZil.

This gives me:

  • a second branch called build/master which is essentially a clone of the built distribution as it will eventually appear on CPAN, and this is generated/updated each and every time I dzil build or dzil test

  • a third branch called releases which is the same in concept as build/master, but is only updated when I actually ship a release.
( Dist::Zilla::PluginBundle::Author::KENTNL on Github showing the three branch histories )
  • All three ( master, build/master, and releases ) are tested on Travis CI.

  • master and build/master are both tested on all the major versions of Perl prior to releasing, via travis, so I can catch mistakes early.
  • master is tested using the dzil stack ( ie: install dzil and deps, dzil test --release )
  • build/master is tested via the native tooling in the built distribution to mimic deployment.
  • All of the above execute in 2 primary modes:
    1. Standard Testing mode to test for basic testing and perl-support

    2. "STERILE TREE" mode, where I've got a very aggressive toolkit that winds back the travis perl install to as close to a vanilla install as possible.

STERILE TREE on build/master is used predominantly to smoke out undeclared dependencies, and this has saved my bacon more times than I care to count, in places where I'd normally have to wait for CPAN smoke reports to come in before I found bugs to fix.

STERILE TREE on master gives me the assurance that others can actually clone my repository and start working on it without too many headaches.

Additionally, this setup helps me avoid accidentally depending on things that are too new, and not on CPAN yet, or depending on things where they'd accidentally require me to install a newer perl ( for instance, don't depend on a specific recent version of File::Spec, you'll have a very bad time atm )

Also, STERILE TREE mode gives me an accurate representation of the total dependency count of the distribution required to install on a given version of Perl, via the cpanm installation log, which can help me weed out dependencies.

And the fact I do STERILE TREE mode on master means I actually have a test suite that does a CLEAN INSTALL OF DZIL FOR EACH AND EVERY COMMIT I PUSH, so I have a realistic idea of the minimal timeframe to get a working commit environment for contributors, for which the installation time is a common complaint.

However, it should be said, that a Clean install of Dzil on 5.10 with --notest takes about 5 minutes. Or about 4 minutes 20 on a newer version of Perl.

Thats not a huge amount of time, so obviously, the testing part of the install chain largely contributes to the speed of installation.


Anyone interested in further details:

Leave a comment

About Steven Haryanto

user-pic A programmer (mostly Perl 5 nowadays). My CPAN ID: SHARYANTO. I'm sedusedan on perlmonks. My twitter is stevenharyanto (but I don't tweet much). Follow me on github: sharyanto.