Using Dist::Zilla
I don’t often publish new Perl modules, but when I do, it’s typically
with Dist::Zilla. Once configured, dzil
allows me to
focus on the development of my module, and not on how to release it.
Like most, in the early days of using dzil
, I cargo culted someone
else’s working config. Nowadays, I still cargo cult working configs, but
I understand better how and why they work. In this post, I’ll walk
through a working configuration for one of my modules and explain how it
allows me to make a release happen.
I gloss over some bits, and am a little hand-wavey in spots. If ever in doubt, please read the relevant perldoc for the given module or function. I have tried my best to include all relevant links.
Getting Started
One of the best things about Dist::Zilla
is the power and control you
have over the packaging and release process.
Unlike Palpatine, this power does not come from a dark place (unless you are Yanick).
I recently published an update to one of my modules,
Dancer2::Plugin::Minion.
As part of this release, I had to fix some earlier build problems. This
gave me a chance to start with a fairly basic dist.ini
:
name = Dancer2-Plugin-Minion
version = 1.0.0
author = Jason A. Crome <cpan@jason.cromedome.dev>
license = Perl_5
copyright_holder = Jason A. Crome
copyright_year = 2024
main_module = lib/Dancer2/Plugin/Minion.pm
[MetaResources]
bugtracker.web = https://github.com/cromedome/Dancer2-Plugin-Minion/issues
repository.url = git@github.com:cromedome/Dancer2-Plugin-Minion.git
repository.web = https://github.com/cromedome/Dancer2-Plugin-Minion
repository.type = git
x_IRC = irc://irc.perl.org/#dancer
[@Filter]
-bundle = @Basic
-remove = License
[Prereqs::FromCPANfile]
[PkgVersion]
[NextRelease]
filename = Changes
[@Git]
allow_dirty = Changes
allow_dirty = dist.ini
allow_dirty = README.md
add_files_in = Changes
add_files_in = dist.ini
add_files_in = README.md
[PruneFiles]
match = dist.ini
Your dist.ini
is the core configuration for how Dist::Zilla
should
build and package your module’s distribution. You can create one via a
minting profile (more on these later), by copying one that works from
another distribution, or start from scratch. For purposes of this blog
post, let’s start with my configuration above.
Understanding dist.ini
dist.ini
is intended to be easy to understand. There’s just a few
basic rules:
- Sections are denoted with
[]
- Section names starting with
@
are plugin bundles (searchDist::Zilla::PluginBundle
on MetaCPAN) - Section names starting with
=
does not do name expansion like@
does (I have never needed this) - Other section names enable plugins with the provided name (for
example,
PkgVersion
enablesDist::Zilla::Plugin::PkgVersion
) - Values in each section are configuration to the plugin or bundle named by the section title (generally, key/value format)
The topmost unnamed section contains configuration required by
Dist::Zilla
. You must provide name
, version
, abstract
, and
license
(author
is optional). Alternatively, at the top of the
primary module in your distribution, you can specify:
# ABSTRACT: Do awesome things
The MetaResources
section is used when your module is indexed by CPAN.
The plugin bundle [@Filter]
deserves a special mention. It loads other
plugin bundles, but with specified plugins removed. Since the
Dancer2::Plugin::Minion
repo provides its own license file, I don’t
need Dist::Zilla::Plugin::License
to add one for me.
The Filter bundle
allows for this; -bundle = @Basic
loads the
Basic bundle, except for
the license plugin, because we remove (“filter”) it with -remove = License
(you can do this for multiple plugins). Easy and powerful!
[PrereqsFromCPANFile]
tells Dist::Zilla
to look in your distribution’s cpanfile
for a list
of dependencies (these, in turn, get added to META.json
when your
distribution is built). You can also specify them in the [Prereqs]
section of your dist.ini
(not shown).
[PkgVersion]
automatically
takes the version specified in your dist.ini
and adds a line to each
module to set it’s $VERSION
. Complementing this is the
[NextRelease]
plugin, which automatically fills in version information
in your distributions Changes
file.
Finally, [PruneFiles]
ensures that listed files are not included in
your distribution.
Now that we have our config set up, let’s build our distribution!
Building Your Distribution
Running dzil build
from the command line will perform any build steps
needed by your distribution. This can include building any XS code in a
module, generating module Pod with Pod::Weaver
, builds a Makefile.PL
,
etc.
Sidebar: Using All Your Cores
You spent some bucks on all those processor cores, and you want to use
them, right? Dist::Zilla
makes this easy! Just specify -jX
on the
command line, where X
is the number of cores you’d like to devote to
the build process. Dist::Zilla
will do its best to use all of the
cores you allow it to use when running your distribution’s build step.
The machine I am writing this on has 16 cores, so my typical build step
is dzil build -j16
.
Testing
Running your test suite is as easy as building: dzil test
(what, you
expected something fancier here?).
Like dzil build
, you can use multiple cores to run your test suite:
dzil test -j8
. Keep in mind that some test suites do not run well in
parallel mode. In my own experience, this has rarely been a problem.
Finally, Release Time!
Did you leave UploadToCPAN
enabled in your dist.ini
? You’re almost
there! Just type dzil release
and your release tarball will be
uploaded to PAUSE. Boom!
The UploadToCPAN
plugin expects you will have credentials stored
in either your Dist::Zilla
config (~/.dzil/config.ini
) or ~/.pause
(personally I use the latter). If you are storing user and password
information in your home directory, make sure to set permissions on this
file to 0600
.
You don’t want to use dzil
to upload to CPAN? That’s ok - dzil build
gave you a tarball that you can upload via the PAUSE web interface or
whatever other tool you’d typically use.
We also added the Git plugin bundle
to our dist.ini
above (with [@Git]
). Default behavior includes:
- Making sure your repo is in a clean state
- Commits any files updated at build time
- Tags your released version
- Push the above to your project’s repo
The options specified above allow the Changes
, dist.ini
, and
README.md
to be dirty when dzil release
is run. Why dist.ini
? If
you are using the PkgVersion
plugin,
you’ll specify the version number being released in your config, and
that will be propagated to any modules in your distribution, as well as
your Changes
file.
The add_files_in
option will ensure that any specified files will be
included in the release commit. See the docs for the Git commit
plugin for
more information.
Other Topics
dzil
will run any steps that have yet to be performed prior to running
the step you asked for. As an example, if you run dzil test
, but you
haven’t run dzil build
, Dist::Zilla
will build your distribution
before trying to run your tests. Want to run before you walk? Type dzil release
and watch it build, test, and upload your dist to PAUSE, commit
dirty files to git, and add a release tag to your repo. If any step
fails, the process will halt, allowing you to resolve whatever issue
cropped up.
Dist::Zilla
also has a concept known as minting profiles. Minting profiles
allow you customize the way a new module/distribution is created. At
Best Practical, we have a minting profile for creating new RT
extensions, with configuration options for extensions that run database
migrations.
For more information on minting profiles, check out the Dist::Zilla documentation.
Recap
The high-level steps for using Dist::Zilla
to build and release a
distribution are:
- Set up your
dist.ini
dzil build -jX
dzil test -jX
dzil release
Final Thoughts
This post has only scratched the surface of what you can do with
Dist::Zilla
. For more information, check out the following resources:
Dist::Zilla
provides you nearly unlimited power for building and
releasing a Perl module. This power can add complexity; make sure to
find the right balance of these ingredients for your own projects.
Happy minting!