I've run into a problem building a CPAN distribution using Module::Build,
in an interesting way that raises questions about optional dependencies.
Specifically, I'm installing Crypt-MySQL-0.04, which comes with
both a Build.PL that uses Module::Build and a Makefile.PL that uses
ExtUtils::MakeMaker.  Building the EU::MM way works perfectly, and is
obviously what the author does himself.  Building the M::B way works
except for an undeclared dependency.

Crypt-MySQL includes some C code in an XS source file.  When M::B comes
to compile this, it wants to use ExtUtils::CBuilder.  I've only just
started using M::B, and didn't have EU::CB installed.  The Build.PL
script therefore emitted a warning, "Module::Build is not configured
with C_support".  However, that warning is only human-readable,
not machine-readable.  I was doing this install via the CPAN module.
CPAN went on to (I presume) request a prereq_report, but this report
doesn't say anything about EU::CB either.  CPAN then attempted to
build Crypt-MySQL, and the build immediately failed because of the lack
of EU::CB.

So, we have a dependency problem: Crypt-MySQL's build process required a
certain feature of M::B (compiling XS); that feature depends on EU::CB;
but the dependency is not installed.  C-MS is unaware of the dependency,
as it's only using the M::B interface, and M::B appears to be fully
installed.  C-MS could declare the dependency itself, as build_requires
=> { ExtUtils::CBuilder => "0.15" }, and I've submitted a patch to the
author to do that.  But this seems a dubious way to do things.  Other than
in this possible dependency declaration, the C-MS distribution doesn't
know anything about EU::CB; that name is not mentioned anywhere in the
C-MS tarball.  It uses EU::CB only indirectly.

For the specific case of this EU::CB dependency, there are some
workarounds available.  For a start, I've installed the module on my
system, so unless the version requirement changes no distribution will
run into this particular problem again.  A more interesting possibility
is that M::B could treat this as an implicit build_requires for modules
that include XS files, so that the dependency gets declared in META.yml
and the prereq_report.  This doesn't solve the general case, though,
which is what I'm interested in.

The general problem is that a distribution may depend on an `optional
feature' of another distribution.  At the moment it only gets to declare
the direct dependency on (the main features of) the other distribution.
For example, Crypt-MySQL could declare build_requires => { Module::Build
=> 0 }.  (Actually it doesn't have such a declaration, but arguably
all Module::Build-using distributions should, at least in META.yml.
There's another thing that M::B could do implicitly, but probably
shouldn't because of the complexity around M::B subclasses.)  To ensure
that the optional feature is available, it needs to make the indirect
dependencies available, which at the moment it can only do by making
them direct dependencies.  That requires knowing what the dependencies
of the foreign module are, violating encapsulation.

I think there ought to be a way to explicitly represent a dependency on
an optional feature of another module.  Supposing the META.yml files all
contained the "optional_features" entry that the spec documents (though
I see M::B itself doesn't use that key, only the vague "recommends").
Crypt-MySQL could then have a dependency declaration along the lines
of build_requires => { "Module::Build/C_support" => 0 }, to indicate
that it requires the C_support feature of the Module::Build module.
(I'm blurring the distinction between module and distribution a bit here,
because dependencies are usually on modules but the optional_features
data is per-distribution.)  In principle, the CPAN module or something
similar could trace these feature dependencies through the META.yml files
and figure out which actual modules they translate to.  Knowledge is all
appropriately localised, and the dependency can be handle automatically.
What do you think?

I have some modules myself that would benefit from a distinction between
the dependencies of optional features.  For example, Date::ISO8601
currently declares hard prerequisites (using PREREQ_PM in EU::MM) of
certain versions of Math::BigInt and Math::BigRat, but it can be used
perfectly well without any bignum modules.  I don't want to downgrade the
requirement to a "recommends", though, if it means the version requirement
won't be automatically handled when some other module knows that it *will*
be doing bignum date arithmetic.

-zefram

Reply via email to