Dist::Zilla::Starter - Guide to getting started with Dist::Zilla
This is a guide to the inner workings of Dist::Zilla for managing and releasing CPAN distributions effectively, in particular with the [@Starter] and [@Starter::Git] bundles. See their documentation for specific usage.
Dist::Zilla is a CPAN distribution management tool, which itself just defines a set of phases relevant to the distribution building, testing, and releasing process, and relies on plugins and commands for specific behavior.
The sections below will walk through all of the essentials for using Dist::Zilla effectively. If you are already familiar with general CPAN distribution authoring, skip ahead to "DZIL AND BUNDLES".
A modern CPAN distribution should have a basic well-defined structure so its contents and attributes can be recognized by CPAN infrastructure and tooling.
The historical progression of CPAN distribution authoring tools that led to the development of Dist::Zilla.
Dist::Zilla plugin bundles that can provide a starting point or a ready-made complete distribution management tool.
The infrastructure of Dist::Zilla provides the framework for plugins and commands to define specific behavior in a configurable and extensible way.
Configuring a Dist::Zilla project and the plugins for it to use in dist.ini.
Taking action with a configured Dist::Zilla project via built-in and installable commands.
Dist::Zilla provides the ability to generate new distribution skeletons using the same core infrastructure and a minting profile.
Commands execute a series of phases, which each execute any actions registered by plugins for that phase. The known phases and their usage are listed here.
Before getting into how Dist::Zilla manages CPAN distributions, it's important to understand what a CPAN distribution is. If you are here, you probably know that CPAN is a repository of Perl modules that can easily be downloaded, installed, and updated. But for all of that to work smoothly, these modules must be uploaded with certain structure; one can't just upload a tarball of a GitHub repository and expect the CPAN toolchain to understand it.
Each tarball uploaded to CPAN is a release of a distribution containing one or more modules. Modules are declared by the package statement, and loaded from the filesystem by the use statement. Package names don't necessarily need to match the name of the file, but for the file and package to work together effectively as a module, they should match, with the namespace separators (
::) represented by directory separators in the filesystem, and the module file itself having the
.pm file extension.
These module names are the basis of CPAN's module index, which is maintained by a system called PAUSE. When a new release is uploaded to PAUSE, it discovers all packages declared in the distribution, and determines whether they are intended to be indexed, whether the uploader has permissions for each package name, and whether it has a newer version than any currently indexed module of that name. If so, it indexes them, so that future attempts to install any of those modules by name will install that release of that distribution, until such time as a new release successfully indexes those names. By this method, users are able to easily install and update the latest version of any module, and CPAN installers are able to satisfy a distribution's prerequisites on other modules.
After downloading a certain release of a distribution for installation, the CPAN installer will consult the contents of the distribution itself to do so. In order to provide infinite flexibility to how distributions can be installed, a file called Makefile.PL consisting of regular Perl code is run, and is expected to generate a Makefile that dictates the later steps of building, testing, and installing. (Note: this is user-side building, a separate task from the author-side distribution build that will be discussed later.) While this is traditionally implemented by a module called ExtUtils::MakeMaker, a slightly different process was conceived using a module called Module::Build (and later Module::Build::Tiny), in which a Build.PL is provided which generates a perl script named Build to perform these tasks instead. While both methods have been extended and improved using different modules, for Dist::Zilla authors the details of user-side installation are largely unimportant unless custom configuration or installation tasks are required. As such, Dist::Zilla generates this install script itself via
-InstallTool plugins such as [MakeMaker] or [ModuleBuildTiny].
In addition to the install script, the other important component of a well-formed CPAN distribution is the metadata. This was originally contained in a file called META.yml, which implemented the CPAN meta spec versions 1.0 through 1.4. When CPAN meta spec version 2 was conceived, it was implemented using a new file called META.json that can coexist with the legacy format. In either format, this metadata describes aspects of the distribution as a whole such as name, author(s), prerequisites, repository and bugtracking resources, and optionally the name and version of each included package (which if provided will be trusted over PAUSE's own manual scan). Well-formed metadata is important to both ensuring the modules can be correctly and painlessly installed, and to provide important ancillary information to tools such as MetaCPAN when it displays the distribution. Dist::Zilla generates these files as well via the [MetaYAML] and [MetaJSON] plugins.
The meat of the CPAN distribution, the code, tests, and documentation, are generally expected to be in a certain structure. Modules (.pm) and documentation (.pod) are placed in the lib/ directory, with a file path corresponding to the module name, as they will be ultimately installed. Tests (.t) are placed in the t/ directory, and do not get installed. Executables to install have no strict convention but are usually put in bin/ or script/. You may see distributions with these important files in other places, usually because they predate these conventions or for compatibility with old versions of ExtUtils::MakeMaker that required .xs files to be in the distribution root. Other directories may be included in the CPAN distribution, but have no specific meaning by default to the standard installers.
A BRIEF HISTORY OF AUTHORING
Originally, the same tool which allows end users to install the distribution could also be used by the author to manage it and create a release. ExtUtils::MakeMaker contains author-side configuration and commands such as
make manifest and
make dist. Indeed, many authors still use this as their primary authoring tool, though its close-to-metal nature and extreme backcompat requirement has led many to develop more author-friendly wrappers.
One such wrapper is Module::Install. This differed from many attempts in that it was designed to bundle itself with the distribution, so the end user would install using the bundled code rather than relying on the interface of their possibly outdated ExtUtils::MakeMaker. This was a revolutionary idea and the wrapper interface was well liked, but it introduced other problems. Due to being bundled with the release, it too became outdated when the author did not regularly release new versions of the distribution, so bugs would linger forever that the end user could not workaround by updating their own toolchain. This was notably a significant issue when Perl 5.26 stopped loading modules from the current working directory by default, a mechanism which the Module::Install code bundled with hundreds of distributions relied upon to load itself. Additionally, it had a rudimentary plugin system but no way to formally declare what plugins a distribution was using, meaning that given a Module::Install-based Makefile.PL, the author or contributor would have to guess what plugins needed to be installed or bundled for the end user to make it work. On top of (or perhaps due to) these problems, it has become mostly unmaintained and thus has fallen far behind the progress of the CPAN toolchain.
Module::Build was created to provide a
make-free alternative to ExtUtils::MakeMaker, with the additional goal of being more modern and easier to customize and add features to. Unfortunately this proved to be a lofty goal, as due to widespread use it succumbed to the same stagnation as ExtUtils::MakeMaker; it too has become extremely complex and obtuse, is no longer driven to evolve by its primary author, and must maintain backcompat religiously. This has weakened most of its benefits considerably, as ExtUtils::MakeMaker still remains more compatible with ancient end-user Perls, but Module::Build is still used by some who prefer its interface over ExtUtils::MakeMaker, or who have complicated compilation requirements that are easier to customize in Module::Build.
More recently, Module::Build::Tiny was created as an extremely simple implementation of the Build.PL spec pioneered by Module::Build. A significant reason for its simplicity is that unlike the previously mentioned tools, it is only an install tool; it does not provide any authoring features. A separate tool called mbtiny provides these features in a minimal package, or it works great with dedicated authoring tools like Dist::Zilla.
Dist::Zilla came to be one of the first widespread author-side-only tools, and as such it shed many of the limitations of its overworked predecessors. It does not have to concern itself with minimalism and compatibility, because the end user does not need to install it; it can use everything that modern CPAN has to offer, and rely on the generated install file to be lightweight and compatible if needed. The generated install file also has a simpler job, as it doesn't need to generate the distribution's metadata aside from any dynamic prerequisites, or implement any author-side commands for convenience, as Dist::Zilla does these tasks itself. Learning from the mistakes of Module::Install, it has a formal plugin configuration system, and can deterministically find what plugins are needed to build the distribution, using the
dzil authordeps command.
DZIL AND BUNDLES
Dist::Zilla is a CPAN distribution authoring framework. At its core, it only organizes tasks; it requires plugins to implement the specifics of building, testing, and releasing the distribution. The author can manually specify in dist.ini the set of plugins that suits their needs, or even write new plugins, but there are also ready-made tools built upon it that require very little study to use effectively.
The most basic of these is the core bundle [@Basic]. This bundle contains plugins to perform the most common authoring tasks, and is used throughout most tutorials including dzil.org, but it has unfortunately become outdated in practice and, as of this writing, cannot be fixed without breaking changes.
The prolific creator of cpanm, Carton, cpanfile, and other wonderful convenience tools created a "convention over configuration" tool called Dist::Milla. Dist::Milla is a bundle, minting profile, and command-line wrapper of Dist::Zilla which provides a complete opinionated authoring tool. It requires a certain modernized structure, and in return removes all of the guesswork in managing and releasing distributions. Its sister project Minilla is also notable for being able to achieve almost the same tasks without a dependency on Dist::Zilla, though as a result it cannot be extended with arbitrary plugins from the Dist::Zilla ecosystem.
The [@Starter] bundle and minting profile is a middle ground. The intention is to provide an unopinionated tool which can be used effectively in almost any distribution similarly to
[@Basic], but that can be updated at the author's request to newer conventions, and can also optionally provide further modern convenience with slightly stricter and more opinionated structural requirements. The [@Starter::Git] bundle and minting profile are variants that provide some extra functionality for a git-based workflow.
THE DZIL CORE
Dist::Zilla relies almost entirely on its plugin and command system to provide specific functionality, but these are tied together by a few core systems.
The Dist::Zilla object itself is at the basis of the whole operation. It contains the basic properties of the distribution, such as name, version, and the whole set of plugins that will be run. But it doesn't do anything itself; it is up to other parts of the system to utilize this information.
The Dist::Zilla::Dist::Builder object is a subclass of the main Dist::Zilla object that is able to build the distribution by executing all of the relevant plugins. Additionally it can execute the appropriate plugins to test, install, or release the built distribution. These functions are generally accessed via "COMMANDS", and primarily work by executing "PHASES" in a certain order for each task.
The Dist::Zilla::Dist::Minter object is another subclass of the main Dist::Zilla object, this time to implement the "MINTING" of new distributions using minting profiles. It is much simpler as it only does one thing: run all of the plugins in the minting profile by executing a few "PHASES", and write out the resulting build to the filesystem.
Dist::Zilla::MVP::Assembler is a Config::MVP-based configuration parser that is the core of how Dist::Zilla's plugin "CONFIGURATION" works. It reads a dist.ini (or profile.ini) and constructs the requested set of plugins and configuration, and stores them in the Dist::Zilla object.
Dist::Zilla::App is an App::Cmd-based command system that allows the dzil script to load and execute any installed "COMMANDS" in the
Dist::Zilla::App::Command namespace, such as
dzil test or
Test::DZil is a framework that facilitates testing of Dist::Zilla builds and minting profiles. It contains constructors for test-appropriate subclasses of Dist::Zilla::Dist::Builder and Dist::Zilla::Dist::Minter, as well as for generating simple dist.ini text. This is primarily useful for plugin bundle and minting profile authors, so is out of scope for this document.
The Dist::Zilla configuration for a distribution is a standard INI-format file called dist.ini in the root of the distribution. Section names, which are lines in square brackets (
), indicate plugins to be used, and lines in the format
name = value specify configuration. The "main section", meaning any configuration before the first section name, configures the distribution as a whole, with directives such as
version. Each following section both adds a plugin to be used, and allows configuration to be passed to that plugin. Comments start with a semicolon.
name = Acme-Dist-Name ; comment [MyPlugin] configuration = for MyPlugin ; line comment
There are a few conventions in section names that Dist::Zilla recognizes in order to determine how plugins are loaded. The standard section name will simply be appended to
Dist::Zilla::Plugin:: to form the class name of the plugin to load. If the section name ends in
/ followed by other text, that will be the alias for that instance of the plugin; otherwise, the alias is the same as the section name. This is important because each section in an INI file is unique, and because plugins are looked up by this name, so to include the same plugin twice, at least one of them must have an alias. Additionally, some plugins use the alias to set configuration, but this a bit overly magical.
[MyPlugin / FirstPlugin] [MyPlugin / SecondPlugin]
If the section name is prefixed with
@, this indicates a plugin bundle, which is prepended with
Dist::Zilla::PluginBundle:: to be loaded. A plugin bundle is essentially just a plugin that can only load other plugins, rather than taking action in any build phases.
[@MyBundle] configuration = for the bundle
Plugins loaded by the bundle will be given an alias in the form
@BundleName/PluginName and so will not conflict if the same plugin is loaded elsewhere.
A similar convention with the prefix of
- indicates a role to be prefixed with
Dist::Zilla::Role::, but these are not configured directly in dist.ini; roles implement both general interfaces of Dist::Zilla as well as the "PHASES" in which plugins take action.
If the section name is prefixed with
=, the section name is used as the plugin class name directly, without prepending
The configuration for each section is generally passed directly to that plugin or bundle to handle however it wishes, but a special configuration key
:version is recognized in each section by Dist::Zilla itself. In the main section, it will require at least that version of Dist::Zilla to build the distribution; in a plugin or bundle section, it will require at least that version of the plugin or bundle.
:version = 6.0 [MyPlugin] :version = 0.005
Of additional note is that the
[@Starter] bundles (as well as Dist::Milla) compose the -PluginBundle::PluginRemover and -PluginBundle::Config::Slicer roles, which allow plugins within a bundle to be configured directly in dist.ini. The PluginRemover role recognizes the
-remove option and omits those plugins by name, and the Config::Slicer role recognizes options in the form of
PluginName.option and passes through that option to the named plugin within the bundle. This prevents having to add manual pass-through code to the bundle for every possible option that the included plugins accept. But there is an oddity with options that can be specified multiple times, as Config::Slicer does not know in advance which options allow this. See "CONFIGURATION SYNTAX" in Config::MVP::Slicer for full details on config slicing.
[@Starter] regular = configuration -remove = Test::Compile GatherDir.exclude_filename[a] = foo GatherDir.exclude_filename[b] = bar
Occasionally you may need to specify an authordep (module required for using the dist.ini itself) that is not explicitly listed as a plugin. For this purpose, authordeps can additionally be specified as comments.
; authordep Pod::Weaver::PluginBundle::DAGOLDEN
After setting up the configuration, Dist::Zilla operations are executed by running commands. Dist::Zilla has a pluggable command system with many core commands and many more available from CPAN. There are only a few needed for the basic operation of creating and releasing a distribution.
One of the most common commands is
dzil test, a wrapper of "test" in Dist::Zilla::Dist::Builder. This will build the distribution in a temporary directory, and then run the configure, build, and test phases of the "Phases" in CPAN::Meta::Spec. It will also execute any other
-TestRunner phase plugins such as [RunExtraTests], which runs any author-side tests from the
xt/ directory. The
--release option can be passed to run release tests in addition to standard and author tests.
The second most important command is
dzil release, a wrapper of "release" in Dist::Zilla::Dist::Builder. This will build the distribution, and then upload the archive to PAUSE with the
-Releaser phase. The
--trial option will mark the upload as a trial release, so it will not be indexed as stable.
Two commands that are important when starting work on a Dist::Zilla distribution for the first time are
dzil authordeps and
authordeps lists the Dist::Zilla plugins required by the dist.ini, and
listdeps lists the dependencies of the distribution itself. The output of each can be piped into cpanm to install any modules needed for working on the distribution. Note that the
authordeps requirements must be installed for most other
dzil commands to succeed, including
listdeps. The non-core command
dzil installdeps wraps this process into a single command.
Some other core commands for distribution management are
dzil build, which builds the distribution and stores the tarball and extracted directory locally;
dzil run, which builds the distribution temporarily like
dzil test but runs the supplied command instead;
dzil install, which builds the distribution temporarily and then installs it with a CPAN installer; and
dzil clean which cleans up the Dist::Zilla .build directory and any built distribution directories or archives.
Another common task is to copy updated versions of generated files from the distribution build to the source tree, such as generated README files. Often this is done whenever the distribution is built with [CopyFilesFromBuild], but this can be disruptive when trying to run tests or other operations. By using the regenerate option for
[@Starter], or the [Regenerate] or [Regenerate::AfterReleasers] plugins, the
dzil regenerate command can be used for this purpose instead, so the distribution can be built without modifying the source tree, and the files can be regenerated as needed.
Dist::Zilla is primarily a distribution management tool, but it leverages the same command and plugin system to provide a minting tool. A minting profile can be configured to automatically set up the basic framework for new distributions. The Starter and Starter::Git minting profiles provide a starting point and instructions for customizing profiles, as does dzil.org.
Some metadata such as author, license, and copyright is set from global configuration by default for new distributions. This configuration is stored in
~/.dzil/config.ini, which can be initialized using the
dzil setup command.
The process of minting a distribution primarily involves the
dzil new command, invoked with the name of the module or distribution to create. By default, the local profile in
~/.dzil/profiles/default will be used if present, and otherwise a default
[@Basic] profile. The
-p option can be used to specify the profile name to use, and the
-P option can specify an installed minting profile module as the provider. The default profile or provider can be set in the
~/.dzil/config.ini configuration as documented for
Plugins included in dist.ini are ordered, but they do not necessarily execute in order. Dist::Zilla::Dist::Builder or Dist::Zilla::Dist::Minter methods as invoked by "COMMANDS" will run one or more phases, which will execute any code that plugins have registered for that phase, in the order those plugins were used. The command
dzil dumpphases can be used to see a full listing of the phases that the plugins in a dist.ini will run in.
The distribution build consists of several phases, starting with -BeforeBuild, for any build preparation. No plugins in
[@Starter] execute during this phase by default.
The -BeforeMint phase is executed at the beginning of the minting process for a new distribution source tree instead of
In the -FileGatherer phase, many plugins add files to the distribution; in
[@Starter] this includes the plugins [GatherDir], [MetaYAML], [MetaJSON], [License], [Pod2Readme], [PodSyntaxTests], [Test::ReportPrereqs], [Test::Compile], [MakeMaker] (or the configured installer plugin), and [Manifest]. This phase is also executed by the minting process to add files to a new distribution source tree.
In the -EncodingProvider phase, a plugin may set the encoding for gathered files. No plugins in
[@Starter] execute during this phase by default. This phase is also executed by the minting process.
In the -FilePruner phase, gathered files may be removed from the distribution. In
[@Starter] this is handled by the plugins [PruneCruft] and [ManifestSkip]. This phase is also executed by the minting process.
In the -FileMunger phase, files in the distribution may be modified. In
[@Starter] the plugin [Test::Compile] runs during this phase in order to update its test file to test all gathered modules and scripts. With the managed_versions option, the [RewriteVersion] and [NextRelease] plugins also operate during this phase. This phase is also executed by the minting process.
The -AfterMint phase is executed to conclude the minting process for a new distribution source tree. The build process instead continues with several more phases.
In the -PrereqSource phase, plugins can add prerequisites to the distribution. In
[@Starter] the plugins [PodSyntaxTests], [Test::ReportPrereqs], [Test::Compile], and [MakeMaker] (or the configured installer plugin) add prereqs during this phase. Plugins used to configure the distribution's prereqs such as [Prereqs], [Prereqs::FromCPANfile], and [AutoPrereqs] will normally run in this phase.
The -AfterBuild phase concludes the distribution build, for any post-build cleanup. No plugins in
[@Starter] execute during this phase by default.
The -BuildRunner phase executes the configure and build phases of the "Phases" in CPAN::Meta::Spec (not to be confused with the Dist::Zilla build that creates the distribution). This is primarily used to prepare the distribution for testing or installing. In
[@Starter], [MakeMaker] or the configured installer plugin handles this phase.
The -TestRunner phase executes the test phase of the "Phases" in CPAN::Meta::Spec and runs any author-side testing. In
[@Starter], [MakeMaker] (or the configured installer plugin) and [RunExtraTests] handle this phase.
The -BeforeArchive phase is executed after building the distribution and before packaging it into an archive, such as for the
dzil build command or before releasing. No plugins in
[@Starter] execute during this phase by default.
The -BeforeRelease phase prepares a built distribution for release. In
[@Starter], the plugins [TestRelease], [ConfirmRelease], and [UploadToCPAN] (to ensure PAUSE username and password are available) execute during this phase. In
[@Starter::Git], the plugin [Git::Check] executes during this phase. Other pre-release checks like [CheckChangesHasContent] also execute in this phase.
The -AfterRelease phase concludes the distribution release process, performing any post-release cleanup or tagging. No plugins in
[@Starter] execute during this phase by default. With the managed_versions option, the [NextRelease] and [BumpVersionAfterRelease] plugins execute during this phase. With the regenerate option, the [CopyFilesFromRelease] plugin executes during this phase. In
[@Starter::Git], the plugins [Git::Commit], [Git::Tag], and [Git::Push] execute during this phase.
The -NameProvider phase executes when required rather than at a specific time, to determine the name of the distribution. No plugins in
[@Starter] execute during this phase by default, and the name can be provided directly by setting
name in dist.ini. The [NameFromDirectory] plugin can act as a name provider.
The -VersionProvider phase executes when required rather than at a specific time, to determine the version of the distribution. No plugins in
[@Starter] execute during this phase by default, and the version can be provided directly by setting
version in dist.ini. With the managed_versions option, the [RewriteVersion] plugin acts as the version provider.
The -ReleaseStatusProvider phase executes when required rather than at a specific time, to determine the "release_status" in CPAN::Meta::Spec of the distribution. No plugins in
[@Starter] execute during this phase by default. It defaults to
stable and is usually set to another value by passing the
--trial option to
dzil release or using a version containing an underscore, but can also be set by a plugin in this phase.
The -LicenseProvider phase executes when required rather than at a specific time, to determine the license of the distribution. No plugins in
[@Starter] execute during this phase by default, and the license can be provided directly by setting
license in dist.ini, or otherwise guessed from the main module's POD.
The -MetaProvider phase executes when required rather than at a specific time, to generate the distribution's CPAN metadata. In
[@Starter], the [MetaConfig], [MetaProvides::Package], and [MetaNoIndex] plugins provide metadata in this phase. Other metadata providers like [MetaResources] also execute in this phase.
The -ExecFiles phase executes when required rather than at a specific time, to configure files for installation as executables. In
[@Starter], the [ExecDir] plugin marks a directory as containing executables, which may be used by installer plugins such as [MakeMaker].
The -Regenerator phase is a non-core extension which executes when
dzil regenerate is run, intended for generating files in the source tree outside of a standard distribution build. With the regenerate option for
[@Starter], the [Regenerate::AfterReleasers] plugin promotes [CopyFilesFromRelease] to also execute during this phase.
Dan Book <email@example.com>
COPYRIGHT AND LICENSE
This software is Copyright (c) 2016 by Dan Book.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)