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

Reply via email to