Using Dist::Zilla dist.ini how can I set alternate dependencies? - perl

I am building a module that could work with Mojo::SQLite or Mojo::Pg indifferently. How do I handle the dependencies? I've checked Mojo::DB::Results::Role::Struct which works in the same way, and it does not list dependencies at a runtime level.
How should I go about this?

I would list both of these as a recommends requirement in your cpanfile. This way they are there as dependencies, but they don't get installed unless the user asks for it. I would then add a runtime check into the code that croaks if neither of them is available.
To map this into Dist::Zilla, there are probably a couple of different ways. You can write your own cpanfile and let dzil take it from there, or you can specify dependencies in your dist.ini, or rely on dzil to find them from your sources... or a combination of these.
For WWW::Mechanize, we use a combination. In our dist.ini we specify minimum versions using the Prereqs plugin.
[Prereqs / RuntimeRequires]
perl = 5.008
HTML::Form = 6.08
Scalar::Util = 1.14
[Prereqs / TestRequires]
HTTP::Daemon = 6.12
Test::Memory::Cycle = 1.06
Test::NoWarnings = 1.04
Test::Taint = 1.08
[Prereqs / DevelopRequires]
LWP::Protocol::https = 6.07
Perl::Critic = 0
Perl::Tidy = 0
This is not the complete list of requirements, because AutoPrereqs finds those for us. We just need to set explicit versions.
We then also use Prereqs::Soften1 to convert requirements down to a lower ... requiredness.
[Prereqs::Soften]
module = Compress::Zlib
to_relationship = recommends
This is probably what you would want to do for both of your database modules, too.
1) This dist is in need of being taken over, because the author Kent Fredric sadly passed away after an accident last year.

Related

Installing additional files at install time with ExtUtils::MakeMaker/Dist::Zilla (dzil)

tl;dr I want to ship a package.json with my Perl library, run yarn install (or npm install during the installation) and install the downloaded JavaScript dependencies with the Perl modules.
I have the following dist.ini:
name = Foobar
version = 1.2.3
license = Perl_5
copyright_holder = Yours Truly
copyright_year = 2018
[#Filter]
-bundle = #Basic
-remove = GatherDir
[Git::GatherDir]
[Web::FileHeader]
header_filename = EMM-include.pm
file_match = ^Makefile\.PL$
The file EMM-include.pm contains a MY::postamble method:
package MY;
use strict;
use Cwd qw(abs_path);
use File::Spec;
sub postamble {
my ($self) = #_;
my $here = Cwd::abs_path();
my $libdir = File::Spec->catdir($here, 'lib', 'Foobar');
chdir $libdir or die;
0 == system 'yarn', 'install' or die;
chdir $here or die;
return '';
}
The plug-in [Web::FileHeader] takes the file and patches it to the beginning of Makefile.PL.
Then there is a lib/Foobar/package.json:
{
"name": "foobar",
"version": "1.2.3",
"main": "index.js",
"dependencies": {
"ajv": "^6.5.4"
}
}
The MY::postamble section from EMM-include.pm invokes yarn install (replace it with npm install if you don't have yarn) and populate the directory lib/Foobar/node_modules with ajv and its dependencies.
Finally, there must be a module lib/Foobar.pm:
package Foobar;
# ABSTRACT: Just a test.
1;
That almost works as intended: The distribution can be created with dzil build. In the distribution directory, perl Makefile.PL invokes yarn install, the directory lib/Foobar/node_modules gets populated but the files in there are not installed with make install.
If I run perl Makefile.PL a second time, everything works, the JavaScript dependencies make it into blib/ and make install would install the JavaScript modules alongside the Perl modules.
Shipping the JavaScript dependencies with the distribution is not an option. They are already too many and they may have conflicting licenses (I am using GPLv3 here). Downloading the deps at runtime, after the installation will mostly fail because of missing privileges.
True, this has not that much to do with Dist::Zilla, it's rather a problem with ExtUtils::MakeMaker. But I am actually using Dist::Zilla here.
In case it matters, the real distribution is https://github.com/gflohr/qgoda and the last commit at the time of this writing is https://github.com/gflohr/qgoda/commit/3f34a3dfec8da665061432c3a8f7bd8eef28b95e.
First, instead of using [Web::FileHeader] to alter your Makefile.PL, replace [MakeMaker] (used by #Basic) with [MakeMaker::Awesome], which allows you to modify Makefile.PL directly, and correctly enables dynamic_config since your distribution needs it. Also, don't give your include-file a .pm extension since it's not a perl module, and exclude it from being gathered into the resulting distribution so it doesn't accidentally get installed.
[#Filter]
-bundle = #Basic
-remove = GatherDir
-remove = MakeMaker
[Git::GatherDir]
exclude_filename = EMM-include
[MakeMaker::Awesome]
header_file = EMM-include
I strongly suggest using my #Starter bundle instead of the outdated #Basic, but if not at least add [MetaJSON] so you have modern metadata.
[#Starter::Git]
revision = 3
installer = MakeMaker::Awesome
Git::GatherDir.exclude_filename[] = EMM-include
MakeMaker::Awesome.header_file = EMM-include
Regarding what needs to be done at install time. First I will caution that requiring an internet connection to install is not something you can always rely on, nor is having yarn available of course. But the Alien series of modules for installing external libraries does this sort of thing often. Since you don't need to compile this code you probably don't need the whole Alien::Build/Alien::Base setup, but it may turn out to be an easier way to solve your problem than Makefile hacking described below. Basically you would first release an Alien distribution which installs your javascript library if necessary, and then this distribution could depend on that to load the library. If you decide to pursue this direction, check out Alien::Build, and the IRC channel #native on irc.perl.org.
The postamble section for ExtUtils::MakeMaker is not for running arbitrary code; it's for adding custom rules to the Makefile it generates; this is the way you need to influence the make process. I know very little about Makefiles so I can't help you further here, all I can suggest is to read all of the EUMM docs and note that the postamble is a function from MM_Any which you override to add your text, among other options from MM_Any and MM_Unix. You may be able to find people to help you in this direction on the IRC channel #toolchain on irc.perl.org.

Is it possible to setup the cross module version in VERSION_FROM option (of Makefile.PL)?

I want to setup the same version for few projects in one place.
I've tried:
use ExtUtils::MakeMaker;
WriteMakefile(
VERSION_FROM => 'lib/project/version.pm',
...
In 'lib/project/version.pm':
package project::version;
use AnotherProject;
our $VERSION = AnotherProject->VERSION();
1;
Note: AnotherProject is located in separate directory, but could be loaded by 'use AnotherProject'. And contain 'our $VERSION="1.00"'.
$ perl Makefile.PL
WARNING: Setting VERSION via file 'lib/project/version.pm' failed
at /usr/lib64/perl5/5.18.2/ExtUtils/MakeMaker.pm line 599.
Can't parse version 'undef'
Is it possible to pass (get) the version string from another module?
Maybe there is another way to do it, please support me.
When you use VERSION_FROM, ExtUtils::MakeMaker doesn't run the file you point at, it parses it itself and tries to find a version number that way. In this case, that won't work. Using VERSION instead of VERSION_FROM in Makefile.PL and calling the other module from there should work.

Defining required packages and versions for a perl project

I am familiar with using package.json with node.js, Gemfile for Ruby, Podfile for Objective-C, et al.
What is the equivalent file for Perl and what is the syntax used?
I've installed a couple packages using cpanm and would like to save the package names and version in a single file that can be executed by team members.
For simple use cases, writing a cpanfile is a good choice. A sample file might look like
requires 'Marpa::R2', '2.078';
requires 'String::Escape', '2010.002';
requires 'Moo', '1.003001';
requires 'Eval::Closure', '0.11';
on test => sub {
requires 'Test::More', '0.98';
};
That is, it's actually a Perl script, not a data format. The dependencies can then be installed like
$ cd /path/to/your/module
$ cpanm --installdeps .
This does not install your module! But it makes sure that all dependencies are satisfied, so we can do:
use lib '/path/to/your-module/lib'; # add the location as a module search root
use Your::Module; # works! yay
This is usually sufficient e.g. for a git repository which you want others to tinker with.
If you want to create a tarball that can be distributed and installed easily, I'd recommend Dist::Zilla (although it's geared towards CPAN releases). Instead of a cpanfile we use a dist.ini:
name = Your-Module
version = 1.2.3
author = Your Self <you#example.com>
license = GPL_3
copyright_holder = Your Self
[#Basic]
[Prereqs]
Marpa::R2 = 2.078
String::Escape = 2010.002
Moo = 1.003001
Eval::Closure = 0.11
[Prereqs / TestRequires]
Test::More = 0.98
Then:
$ dzil test # sanity checks, and runs your tests
$ dzil build # creates a tarball
Dist::Zilla takes care of creating a Makefile.PL and other infrastructure that is needed to install the module.
You can then distribute that tarball, and install it like cpanm Your-Module-1.2.3.tar.gz. Dependencies are resolved, your packages are copied to a permanent location, and you can now use Your::Module in any script without having to specify the location.
Note that you should adhere to the standard directory layout for Perl modules:
./
lib/
Your/
Module.pm # package Your::Module
Module/
Helper.pm # package Your::Module::Helper
t/ # tests to verify the module works on the target syste,
foo.t
bar.t
xt/ # optional: Author tests that are not run on installation
baz.t
bin/ # optional: scripts that will later end up in the target system's $PATH
command-line-tool
Makefile.PL usually (along with a few other files; Perl has had packages for longer then any of the other languages you mention and suffers from a bit of inelegance here).
Module Starter is a sensible way to start writing a package. It has a getting started guide.

How to specify test prerequisites in the ExtUtils::MakeMaker Makefile.PL

PREREQ_PM specifies the runtime prerequisites, but how to specify which modules are required to run the test cases?
Should I use BUILD_REQUIRES for this?
As of ExtUtils::MakeMaker 6.64, there is a TEST_REQUIRES parameter.
use ExtUtils::MakeMaker 6.64;
WriteMakefile(
...,
TEST_REQUIRES => {
Test::More => 0.95,
},
...,
);
The CPAN::Meta::Spec defines how modules communicate their prerequisites to the toolchain. The version 2 spec revised the way prerequisites are listed. The test phase now has its own list of prerequisites.
But MakeMaker hasn't been updated for the v2 spec, and likely never will be. The only fully-compliant v2 distribution tool I know of is Dist::Zilla (and I recommend it for more reasons than that).
When CPAN::Meta::Converter converts from the v2 spec to v1.4, it merges the test requirements into build_requires.
So yes, if you stick with MakeMaker, any modules that are required to run the tests should be listed in BUILD_REQUIRES. PREREQ_PM should contain only modules that are still required after the module is installed.
If the tests fail without a module, then I list it in PREREQ_PM regardless of whether it's needed for testing or for running the module.
If I need modules for some tests, but they are not needed to run the module, I detect those when the tests are run, and I skip the tests (with a PASS) if I can't find them.
I don't think there is any field in ExtUtils::MakeMaker for what you want.

How can I check if a binary dependency is available in Perl?

Having volunteered to maintain a stagnant CPAN package (GnuPG) I'd like improve the install files such that they exit gracefully if the gpg binary (which GnuPG is a wrapper for) cannot be found. After a bit of seeking inspiration from other packages, I've come up with adding this to Makefile.PL:
my #paths = grep { -x "$_/gpg" } split /:/, $ENV{PATH}, $ENV{PGP_PATH};
unless ( scalar #paths ) {
print <<EOD;
I can't find the gpg binary on your system. If it's not installed in your usual PATH, set $ENV{PGP_PATH} to include where it can be found and try installing again.
EOD
exit(0);
}
WriteMakefile(
'NAME' => 'GnuPG',
'VERSION_FROM' => 'GnuPG.pm',
'EXE_FILES' => [ gpgmailtunl ],
'LICENSE' => 'GPL',
'LIBS' => [ #paths ],
);
Does that look sane?
If you're using Module::Install or part of that family, you can use do
requires_external_bin 'gpg';
See Module::Install::External for details.
No good reason to reinvent the wheel.
The general concept is fine - if what you need to work is not present, don't create the makefile. The CPAN Testers have rules about exiting with a zero status on failure (which annoys me a lot, but never mind; I hate failing with a success status!).
Question: do you keep a record of where PGP was found at install time, so that if somebody else uses the Perl module without the location on their path, the module can still run?
For DBD::Informix, I have rigid dependencies without which the module cannot be compiled; the Makefile.PL is a major production in its own right because of that. It also tries to handle versions of the software covering over 15 years; that complicates its life, too. If the pre-requisites (some Perl modules; some non-Perl software) is not available, it won't install.
Wouldn't it make more sense to just print a warning? Is gpg necessary for the installation itself?
The code itself looks fine by me. But perhaps there's a built-in 'which' functionality. :).
For better accuracy you should look at File::Which or at least use File::Spec->path().
File::Which would be a crossplatform solution. You will need to either bundle it into inc/ directory or require it to be installed with configure_requires. For EU::MM it can be done with
META_MERGE => {
configure_requires => {
'File::Which' => 0,
Module::Install is also a good solution, but you will need to release new version of distribution each time new version of Module::Install is released and changes are important.