I have a distribution (XML-Easy) which can optionally use XS. It tries
to detect whether C building capability is available at build time,
and overrides ->find_xs_files to return an empty hash if the capability
is missing. This is causing problems by an obscure route. Here is the
story, as I reconstruct it:
00. User runs Build.PL.
01. Build.PL generates MyModuleBuilder.pm.
02. Build.PL calls MyModuleBuilder->new.
03. Module::Build::Base::new runs.
04. Module::Build::Base::new calls $self->check_prereq.
05. Module::Build::Base::check_prereq calls $self->find_xs_files, to
find out whether it needs to have C support enabled.
06. MyModuleBuilder::find_xs_files runs.
07. MyModuleBuilder::find_xs_files calls $self->cbuilder, in an eval
block, to check whether C support is available.
08. Module::Build::Base::cbuilder loads ExtUtils::CBuilder, uses it to
build a cbuilder object, and caches that in $self->{properties}.
09. Module::Build::Base::cbuilder returns the cbuilder to
MyModuleBuilder::find_xs_files, which is satisfied and passes the
job of finding XS files on to Module::Build::Base::find_xs_files.
10. The list of XS files is returned to Module::Build::Base::check_prereq,
which finds that it does need C support, checks that that is enabled,
and is satisfied.
11. Module::Build::Base::check_prereq is satisfied about all dependencies,
and returns to Module::Build::Base::new, which does some other work
and returns the now-complete $self.
12. Build.PL calls ->create_build_script on the module builder object.
13. Module::Build::Base::create_build_script calls $self->write_config.
14. Module::Build::Base::write_config writes out the file
_build/build_params, which among other things contains a dump of
$self->{properties}. This includes a dump of the cbuilder object,
expressed in the form bless({...}, 'ExtUtils::CBuilder').
15. Module::Build::Base::create_build_script writes the build script.
16. Build.PL terminates successfully.
17. User runs Build.
18. Build calls MyModuleBuilder->resume.
19. Module::Build::Base::resume calls $self->read_config.
20. Module::Build::Base::read_config reads in and evals the contents of
_build/build_params, populating $self->{properties}.
21. The rest of the resume process executes, resulting in a complete
module builder object. This object includes a cached cbuilder object,
blessed into ExtUtils::CBuilder, even though ExtUtils::CBuilder
hasn't been loaded in this process.
22. Build figures out that it's meant to be building code, including
some XS.
23. XS file gets found (via MyModuleBuilder::find_xs_files), and
translated into C.
24. $self->compile_c gets called to compile the C file.
25. Module::Build::Base::compile_c calls $self->cbuilder to get hold of
the cbuilder object.
26. Module::Build::Base::cbuilder notices that it has a cached cbuilder
object, and returns it forthwith.
27. Module::Build::Base::compile_c attempts to call a method on the
cbuilder object.
28. Method dispatch fails because the ExtUtils::CBuilder class, into
which the cbuilder object is blessed, doesn't contain any methods,
because it's still not loaded.
29. Build attempt falls over in a big heap.
I actually ran into this problem early on, as soon as I tried to make
the XS stuff optional, but I didn't understand it, and I fiddled a bit
to find an empirical workaround. XML-Easy-0.00{0,1} have a "require
ExtUtils::CBuilder" statement in MyModuleBuilder::find_xs_files, which
gets around the problem, on Unix, by loading EU:CB at step 23, rescuing
the cached cbuilder object just before it gets used. The statement is
commented "observed to fail without this", reflecting the fact that at
the time I didn't know why this fixed it.
David Golden's automated tests picked up the same problem on Strawberry
Perl. Apparently my workaround didn't work there. On a quick look,
I expect this is because EU:CB:Platform::Windows::new modifies @ISA.
This would explain why blessing an object and loading EU:CB is not
sufficient: the new method needs to be called to get a functional
C builder. (Incidentally, it looks like that code will add to @ISA
every time the new method is called, so constructing multiple C builder
objects would make @ISA arbitrarily long. I haven't tested this.)
For a proper fix, I reckon Module::Build needs to not dump the cached
cbuilder to _build/build_params. It should be dumping parameters, not
a cache, or at least not a cache of complex objects. I don't offhand
see anything other than _cbuilder that's likely to cause trouble, but
others will be able to figure this out better than I can.
For a workaround in my code, it's clear that remedially loading EU:CB
isn't going to be enough, wherever I put it. (Wrapping ->cbuilder would
be cleaner than the current hack in ->find_xs_files, but ineffective
on Windows.) Mr Golden suggested (based on empirical work) that I test
for the availability of C building facilities by attempting to load
ExtUtils::CBuilder myself, rather than by calling $self->cbuilder. This
obviously works by avoiding creating a cbuilder early enough to get into
_build/build_params. I'm dubious about it, though. I don't care about
the availability of EU:CB myself; it is neither necessary nor sufficient.
I care, precisely, about whether M:B is offering C building facilities.
The other option that I see is to delete $self->{properties}->{_cbuilder}
at some opportune moment, possibly by wrapping ->write_config.
-zefram