Dist::Zilla (dzil, for short) has been a fabulous addition to my Perl toolbox. I’ve seen some blog posts and IRC comments that have a range of reactions along with some general confusion over what the big deal is, so I’m adding my own thoughts to the debate.
Dist::Zilla is modular, not monolithic 🔗︎
Part of the confusion about dzil is that it can do a lot of different things and not everyone likes everything it can do. At its core, dzil is just a CPAN distribution release manager:
- Collect distribution files into a distribution directory
- Create a tarball of the directory
This is no big deal – it duplicates the functionality of ExtUtils::MakeMaker, Module::Build and Module::Install. The difference is that it doesn’t require running Makefile.PL or Build.PL to work.
What does this do for us? How does this help a Perl programmer release stuff to CPAN? (Hi, TIOBE!)
With dzil, a developer can customize the release process outside Makefile.PL or Build.PL, which means that end-users never need to know about it. They don’t need prerequisites just to run the PL file, they don’t need things tucked away in inc/, they don’t need to write Makefile snippets, and so on.
Instead, developers can extend dzil with plugins that stay on their development machine. Almost everything in dzil is a plugin – there is no distinction between “core” behavior and “add-ons”. Unlike Module::Build, which is easy to subclass once but makes it hard to “mix-in” subclass functionality, dzil is built from the ground up to coordinate the behavior of multiple plugins. This lets a developer mix and match useful functions whenever desired.
For example, it’s easy to add a plugin to upload things to CPAN automatically. Once added, here’s what dzil does for me:
- Collect distribution files into a distribution directory
- Create a tarball of the directory
- Upload the tarball to CPAN
Replacing existing release scripts with Dist::Zilla 🔗︎
Before dzil, I already had some personal scripts that I wrote years ago to manage my uploads. They worked, but were hard to extend and customize to do different things for different distributions. I used them to do some useful sanity checks on my distributions before uploading.
But dzil can provide the same functionality through yet more plugins, which lets dzil manage my distributions like this:
- Collect distribution files into a distribution directory
- Make sure everything is checked into git
- Make sure distribution tests pass
- Tag the release with git
- Push the tag to my git repo
- Create a tarball of the directory
- Upload the tarball to CPAN
If I find that someone has already written other useful pre-release plugins, I can just drop them in:
- Make sure additional tests in the xt/ directory pass
- Make sure I’ve written something in the Changes file
One of the knocks on dzil is that the files in your repository aren’t the same as the final release but note that in the examples above no files have been modified before release.
dzil can do that – and I’ll show how and why I use it that way – but that’s a choice that every developer can make. Even without it, I can benefit from all the release management plugins that people write – something that I never could do with my homegrown release scripts. Cool!
Letting Dist::Zilla automate busywork 🔗︎
If you’re ready to take the next step with dzil, you can have it produce boilerplate files in your distribution – things that ought to be there, but that you don’t really need in the repository or that should be generated on the fly. This can include simple text files like LICENSE, but when you realize that you no longer need your Makefile.PL or Build.PL file to build your distribution, you can let dzil fill in the blanks of a generic template for those, too! You can even let it take a guess at your prerequisites, eliminating one more thing to remember to update before release.
Now my release process in dzil might look like this:
- Collect distribution files into a distribution directory
- Examine the code to find prerequisites (including minimum versions)
- Generate a Makefile.PL (or Build.PL)
- Generate a MANIFEST
- Generate a META.yml and/or META.json files
- Generate a LICENSE file
- Generate a README file from the main module’s Pod
- *Generate some generic .t files
- Make sure everything is checked into git
- Make sure distribution tests pass
- Make sure additional tests in the xt/ directory pass
- Make sure I’ve written something in the Changes file
- Tag the release with git
- Create a tarball of the directory
- Upload the tarball to CPAN
And I still haven’t modified any files – I’m only adding files in the distribution directory so I don’t have to generate them in the first place or maintain them by hand anymore.
But wait! Without a Makefile.PL or a Build.PL, how do you test the distribution?
You can do it one of two ways:
$ prove -l t $ dzil test
The first will just run your tests using the files in lib/ under prove (add the -v flag if you want, or specify a particular file to test). The second will go through all the steps of building the distribution directory and generating files and then will run Makefile.PL (or Build.PL) and “make test” (“Build test”) within the generated distribution.
If you’re willing to take the next step and let dzil modify your files as it puts them into the distribution directory, you can start leaving out boilerplate from your files and have dzil add it automatically. It can also figure out easily computed things – like the next version number to use – and add those too.
Here are some of the things I let it do for me:
- Calculate the next version and write $VERSION into my files
- Find an abstract and write a NAME section in the Pod for every module
- Write the $VERSION into the Pod for every module
- Add AUTHORS and COPYRIGHT sections to the Pod for every module
- Add a formal copyright and license statement to every file
- Translate the WikiDoc Pod dialect I prefer into regular Pod
- Add the $VERSION and timestamp to my Changes file
That’s a lot of busy-work I no longer have to think about. I can just focus on code and tests and leave all the scaffolding to dzil. As the number of distributions I maintain grows, that’s a huge time savings and it enforces rigor and consistency to my releases. I will never again forget to timestamp a Changes file, or rather, I can now always forget and just let dzil do it for me.
It does mean that sometimes I’ll get a bug report or a patch against the release and I have to do a little extra work to translate it back to a fix in the repository when line numbers don’t quite match. This is a cost that I’m willing to bear in exchange for the productivity boost I get the rest of the time.
Whether to use Dist::Zilla and how you can get started 🔗︎
Hopefully, I’ve shown how dzil is useful and how you can choose to use as much or as little as you’d like. Nevertheless, here are some reasons why you might not want to use it, or at least not yet:
- There is limited support (so far) for customizing the build process, supporting dynamic (e.g. OS-specific) prerequisites and building XS modules. This is getting better as authors write new plugins – see Dist::Zilla::Plugin::MakeMaker::Awesome as an example.
- There are lots of plugins to keep track of and configuring them manually in each distribution can feel a bit tedious. Some authors have been writing PluginBundles to encapsulate their default configuration. Will every author need one of these or will some sort of global configuration be possible? These are questions still being worked out.
- Collaboration is a bit harder. Ideally, any collaborators will install the same set of dzil plugins that you have and can work with dzil the same way you do, but that’s still extra work that you’re asking of contributors, particularly if they aren’t already fans of dzil.
- dzil is still evolving rapidly and over the last few months, it’s not been uncommon to have some upgrade of dzil break a bunch of plugins. Hopefully this will be less common as the API stabilizes and the pace of core dzil development slows. Because of the pace of development, documentation has also lagged a bit.
If you do want to give it a try, my advice on getting up the learning curve is to start with a very simple dzil dist.ini file that you maintain by hand. Don’t use dzil PluginBundles until you’re more familiar with the action of each plugin. The dzil tutorial is a great reference for getting started. See the section on converting an existing distribution for some good starter examples.
Alternatively, if you want to see an example dist.ini that does all the things I mention above in this article, see Dist::Zilla::PluginBundle::DAGOLDEN. In the documentation for the PluginBundle, I show the equivalent dist.ini. You can comment out parts you don’t understand and then selectively re-enable them to see what they do.
See Walking through a real dist.ini for another good example.
Important: While you’re playing around learning dzil, replace the “UploadToCPAN” plugin with “FakeRelease” in your dist.ini. This will simulate a release without actually uploading to CPAN, which could save you some potential embarrassment.