Kaiju Boss Battle: A Dist::Zilla Journey from Chaos to Co-Op

Last week I wrote about devel­op­ing a Perl mod­ule enabling me to out­put log entriesto macOS’ uni­fied log­ging sys­tem. This week­end’s adven­ture involved port­ing that mod­ule’s man­u­al process­es. These process­es includ­ed depen­den­cy man­age­ment, doc­u­men­ta­tion sync­ing, ver­sion bump­ing, and release. The goal was to make every­thing more auto­mat­ed and repeatable.

And maybe even… monstrous.

I was already famil­iar with the Dist::Zilla(DZil to its friends) suite of tools and plu­g­ins. I also knew that some Perl devel­op­ers view it as a huge bar­ri­er to entry. This per­cep­tion affects their will­ing­ness to con­tribute to oth­ers’ projects.

So even though Log-​Any-​Adapter-​MacOS-​OSLogwas a small mod­ule of inter­est to a lim­it­ed cod­ing audi­ence (macOS Perl users), I thought it wise to have both a mainbranch and sep­a­rate build/mainbranch for those pro­gram­mers who want­ed to work as though things had bare­ly changed:

  • Entire source code with full POD-​formatted documentation;
  • A Makefile.PL script to gen­er­ate a portable build­ing, test­ing, and installing Makefile;
  • Plus, every Perl dis­tri­b­u­tion should sup­ply the typ­i­cal README, MANIFEST, LICENSE, and CONTRIBUTING doc­u­men­ta­tion. This is essen­tial if it’s meant for pub­lic consumption.

A small dis­tri­b­u­tion would also give me a mod­el I can scale up for larg­er projects. At the very least, it was anoth­er learn­ing oppor­tu­ni­ty for me.

Boy, was it.

Why Dist::Zilla?

Because I know it can auto­mate away the boil­er­plate code and rep­e­ti­tion of infor­ma­tion that’s unfor­tu­nate­ly nec­es­sary in a mod­ern Perl mod­ule distribution:

  • the ver­sion num­bers in every mod­ule and script
  • the README that often includes the same intro­duc­to­ry text as the main mod­ule’s documentation
  • the nam­ing, order, and con­tent of POD sec­tions (via DZil’s sis­ter suite, Pod::Weaver), some of which repeat dis­tri­b­u­tion meta­da­ta like author, ver­sion, sup­port, copy­right, license, and so on

If you need fur­ther detail, Dan Book’s Dist::Zilla::Starteris, as its name sug­gests. an excel­lent and remark­ably thor­ough guide to the how and why of Dist::Zilla. It even cov­ers the basic struc­ture of CPAN dis­tri­b­u­tionsand the his­to­ry of Perl mod­ule author­ingtools.

Why not something else?

Because just as in the sto­ry of Goldilocks and the three bears, every­thing else I looked at seemed want­i­ng in some way:

  • Module::Build/​Module::Build::Tiny: Although min­i­mal, pure-​Perl, and easy to install, Module::Build prop­er still requires ship­ping extra boil­er­plate and dupli­cate meta­da­ta. ::Tiny shaves that down fur­ther but drops whole swaths of func­tion­al­i­ty. As an exam­ple, you can’t spec­i­fy at set­up time that users need a spe­cif­ic oper­at­ing sys­tem. It’s a deal-​breaker for a mod­ule that requires macOS 10.12 Sierra or newer.
  • Minilla: Opinionated con­ven­tion over con­fig­u­ra­tion, but I don’t share its opin­ions. Overriding direc­to­ry lay­out, test struc­ture, and README/​license han­dling would mean fight­ing Minilla’s defaults, DZil-​config style, with­out DZil’s plu­g­in ecosystem.
  • ShipIt: Simple, one-​file con­fig­u­ra­tion. But too sim­ple: it’s most­ly release automa­tion and not author­ing automa­tion. Everything I want­ed to tool away is still manual.
  • No or min­i­mal tool­chain: That was how I start­ed this mod­ule, with ExtUtils::MakeMaker. Totally man­u­al, with every con­trib­u­tor, includ­ing yours tru­ly, need­ing to remem­ber all the mov­ing parts by hand.

Off to see the lizard!

My DZil dist.inicon­fig­u­ra­tion file start­ed off sim­ply enough:

name    = Log-Any-Adapter-MacOS-OSLog
author  = Mark Gardner <[email protected]>
license = Perl_5
copyright_holder = Mark Gardner
version = 0.0.5 ; bump as appropriate

[MetaResources]
repository.type = git
bugtracker.web  = https://codeberg.org/mjgardner/perl-Log-Any-Adapter-MacOS-OSLog/issues

[Repository]
web = https://codeberg.org/mjgardner/perl-Log-Any-Adapter-MacOS-OSLog

That [MetaResources]is the first evi­dence of using plu­g­ins. It pro­vides a nice tidy way to spec­i­fy the meta­da­ta. Automated tools can use this infor­ma­tion to index, exam­ine, pack­age, or install Perl distributions.

So far so good. But then the mon­ster attacked.

[@Filter]
-bundle = @Basic
-remove = GatherDir
-remove = MakeMaker
-remove = Readme
; License plugin still active here

[GatherDir]
include_dotfiles = 1
exclude_filename = .gitignore
exclude_filename = .build

[ReadmeAnyFromPod]
type = markdown
filename = README.md
location = build

[CopyFilesFromBuild]
copy = README.md

; various MakeMaker::*, Git::*, Pod::Weaver, etc. plugins

I was get­ting errors from the dzil --buildcom­mand as it attempt­ed to add the same file mul­ti­ple times. These were gen­er­at­ed files com­mit­ted to the mainver­sion con­trol branch and a build/mainbranch for non-​DZil contributors.

This brought me to a dead stop. I went down a rabbit-​hole exam­in­ing built files from rsync(1)with [Run::AfterBuild]., com­par­ing them to the branch I had set up.

Plus my .perlcriticrcfile was dis­ap­pear­ing from the build arti­facts despite that instruc­tion to [GatherDir]to include dot­files.

“Let them fight.”

LICENSE to kill (files)

Eventually I traced the LICENSE file dupli­ca­tion to duel­ing DZil plu­g­ins. The afore­men­tioned [GatherDir]was duti­ful­ly copy­ing the file from my work­ing root even as the [License]plu­g­in was gen­er­at­ing it. I did­n’t want to lose the lat­ter source of what­ev­er license I hap­pened to be using to dis­trib­ute this code.

And .perlcriticrc? It turns out the [PruneCruft]plu­g­in does­n’t lis­ten to its friend [GatherDir]and was hoover­ing it away.

In the end, I had to expand my manually-​configured plu­g­in ros­ter a lit­tle to keep them from fight­ing over what files came from where:

[@Filter]
-bundle = @Basic
-remove = GatherDir
-remove = PruneCruft
-remove = MakeMaker
-remove = Readme

[PruneCruft]
except = .perlcriticrc

[GatherDir]
include_dotfiles = 1
exclude_filename = .gitignore
exclude_filename = .build
exclude_filename = LICENSE

[ReadmeAnyFromPod]
type            = markdown
filename        = README.md

[CopyFilesFromBuild]
copy = README.md
copy = LICENSE

With these tweaks to Log-​Any-​Adapter-​MacOS-​OSLog’s dist.ini:

  • [License]gen­er­ates its file,
  • [CopyFilesFromBuild]copies it along with the README into the root for my commit,
  • And [GatherDir]does­n’t stomp on it like so many Tokyo city blocks.

The build/mainbranch for non-​DZil con­trib­u­tors would con­tain exact­ly what they and I expect­ed. It would be a match of the DZil work­ing copy plus arti­facts, git cloneable with no surprises.

Lessons learned, and what’s next?

I want­ed to serve two styles of devel­op­ment and had signed myself up for a com­pli­cat­ed author­ing process. I now have more knowl­edge of which plu­g­in runs in each phaseof the build. This allows me to decide between gen­er­at­ed and version-​controlled files.

CPAN has a rich vari­ety of per­son­al Dist::Zilla::PluginBundle::s fac­tor­ing indi­vid­ual authors’ pref­er­ences into a sin­gle place. Those authors don’t have to copy-​and-​paste DZil con­fig­u­ra­tions around. It’s past time for me to mint one of my own bun­dles for more con­sis­tent and well-​understood Perl dis­tri­b­u­tions. Then I can start spin­ning up new projects with­out revis­it­ing the same pain.


This week­end has brought on a men­tal shift. I moved from fight­ing DZil’s defaults to mak­ing it work myway. I also found sat­is­fac­tion in a pre­dictable, minimal-​effort Perl release pipeline.

The mon­ster was just a guy in a rub­ber suit all along.


Discover more from The Phoenix Trap

Subscribe to get the latest posts sent to your email.

Mark Gardner Avatar

Hi, I’m Mark.

Hi, I’m Mark Gard­ner, and this is my personal blog. I show software developers how to level up by building production-ready things that work. Clear code, real projects, lessons learned.

Your post must include a link to this post.

How does this work?
Write a response on your own website and include a link to this post. Then submit your response’s URL here. Your response may appear here after moderation. To update or remove it, edit or delete your original post and submit the same URL again. Learn more about Webmentions.