Dist::Zilla on Travis CI

With Dist::Zilla (dzil), testing Perl projects on Travis CI can be a bit tricky. Here's my approach.

Travis CI is a continuous integration service (free for open source projects on GitHub!). They have wonderful support for Perl, and even pre-install a couple of common modules like Dist::Zilla, Moose, …. However, Dist::Zilla (dzil) can be a bit fickle at times. As an authoring tool, it can afford to be somewhat unstable: Module authors are likely to have a recent perl installed and would know how to troubleshoot problems.

In my particular case, I wanted to run dzil test --all under perl-5.10. That doesn't work since Dist::Zilla-6 requires perl-5.14 to run (a quite reasonable requirement, I might add).

The “normal” approach would be to pre-build the dist before Travis CI sees it, but that would mean checking in generated files into Git. I previously did this with the Git::CommitBuild plugin since I can erase that branch afterwards. In most cases, it is better to maintain a Makefile.PL in the source dir. To avoid duplication regarding dependencies between the Makefile.PL and the dist.ini, you can copy the generated Makefile.PL from the build directory with the CopyFilesFromBuild plugin:

[CopyFilesFromBuild]
copy = Makefile.PL

That plugin is great, I also use it to generate a LICENSE file from the license metadata in the dist.ini.

While this is great for most cases, this stops working if your Perl source files are modified during the dist building process. In my case, I use Pod::Weaver and Pod::Elemental::Transformer to simplify POD boilerplate and then run Test::Pod (via the PodSyntaxTests plugin) to ensure my POD looks alright. Those tests are useless on the un-woven source code.

Additionally, it is important to me that the entire build process is part of the test suite. The whole point of using Travis is that I can detect and prevent “but it works on my machine” errors. That means I'll have to find a way to get dzil to run on Travis' perl-5.10 container…

The solution to all your Perl version problems is Perlbrew. Travis CI uses Perlbrew to select and provide its different Perl versions. Through a bit of annoying trial and error, I've found out that Travis doesn't offer floating versions like stable or blead, and that the latest version that's installed on all containers is 5.20. That gives us access to a sufficiently modern Perl for dzil.

My plan now is:

  • under the modern perl version:

    1. install dzil and all authordeps of my module.
    2. build the dist
  • under the targeted perl version:

    1. install module dependencies
    2. install extra dependencies for the author-tests
    3. run the tests

Perlbrew allows us to run a command under a particular perl version with the perlbrew exec command. My .travis.yml now contains this before_install hook:

before_install:
  - perlbrew list
  - "perlbrew exec --with $stableperl 'cpanm --quiet --notest Dist::Zilla'"
  - "perlbrew exec --with $stableperl 'dzil authordeps | cpanm --quiet --notest'"
  - "perlbrew exec --with $stableperl 'dzil build --in $builddir'"

The $stableperl variable is set to 5.20 since that's the most recent available Perl on all Travis containers. The $builddir variable tells dzil where to put the generated files. Usually, it selects a name based on the module version, but a fixed name is better for our build script.

Now that we've built our dist, we can use the selected perl to install any dependencies and run the tests:

install:
  - '(cd $builddir && cpanm --quiet --notest --installdeps .)'
  # extra deps for author tests:
  - cpanm --quiet --notest Test::Perl::Critic Test::Pod
script:
  - '(cd $builddir && prove -lr t)'

In order for all tests to execute, we'll have to set a couple of environment variables. The help message for dzil test --all lists RELEASE_TESTING, AUTOMATED_TESTING, EXTENDED_TESTING, and AUTHOR_TESTING. I think they are also documented by the CPAN toolchain gang in documents like the Lancaster Consensus. This means we've got to set those, and also define the other env variables we are using:

env:
  global:
    - builddir=./build-CI
    - stableperl=5.20
    - RELEASE_TESTING=1
    - AUTOMATED_TESTING=1
    - EXTENDED_TESTING=1
    - AUTHOR_TESTING=1

Together, my complete .travis.yml looks something like this:

language: perl
perl:
  - "5.24"
  - "5.22"
  - "5.16"
  - "5.12"
  - "5.10"
env:
  global:
    - builddir=./build-CI
    - stableperl=5.20
    - RELEASE_TESTING=1
    - AUTOMATED_TESTING=1
    - EXTENDED_TESTING=1
    - AUTHOR_TESTING=1
before_install:
  - perlbrew list
  - "perlbrew exec --with $stableperl 'cpanm --quiet --notest Dist::Zilla'"
  - "perlbrew exec --with $stableperl 'dzil authordeps | cpanm --quiet --notest'"
  - "perlbrew exec --with $stableperl 'dzil build --in $builddir'"
install:
  - '(cd $builddir && cpanm --quiet --notest --installdeps .)'
  # extra deps for author tests:
  - cpanm --quiet --notest Test::Perl::Critic Test::Pod
script:
  - '(cd $builddir && prove -lr t)'
sudo: false