Sign In/My Account | View Cart  
advertisement


Listen Print

Module::Build
by Dave Rolsky | Pages: 1, 2, 3

There are a couple workarounds for this problem. The simplest is to just include a Build.PL script and document this in the README or INSTALL files included with your distribution. This has the appeal of requiring of very little work to implement, but the downside is that people who expect things to just work with a CPAN shell will give up when your distribution doesn't install properly.

Another possibility is to create functionally equivalent Build.PL and Makefile.PL scripts. If you're using Module::Build because you need to customize installation behavior in a way that is difficult to do with ExtUtils::MakeMaker, this pretty much defeats the purpose of using Module::Build at all, and in any case having two separate pieces of code that do the same thing is always unappealing.

Then there's the approach which involves using a Makefile.PL script that simply installs Module::Build if needed, and then generates a Makefile that passes everything through to the ./Build script. This is known as the "passthrough" method.

I think this approach gives the best result for the effort involved, and is the method I prefer. The Module::Build distribution includes a Module::Build::Compat module, which does the dirty work needed for this approach.

Simply add create_makefile_pl => 'passthrough' to the Build.PL parameters and a Makefile.PL will be created as part of the Build dist process.

Here's an example of such a Makefile.PL script:

    # Note: this file was auto-generated by Module::Build::Compat version 0.03

    unless (eval "use Module::Build::Compat 0.02; 1" ) {
      print "This module requires Module::Build to install itself.\n";
      
      require ExtUtils::MakeMaker;
      my $yn = ExtUtils::MakeMaker::prompt
        ('  Install Module::Build now from CPAN?', 'y');
      
      unless ($yn =~ /^y/i) {
        die " *** Cannot install without Module::Build.  Exiting ...\n";
      }
      
      require Cwd;
      require File::Spec;
      require CPAN;
      
      # Save this 'cause CPAN will chdir all over the place.
      my $cwd = Cwd::cwd();
      
      CPAN::Shell->install('Module::Build::Compat');
      CPAN::Shell->expand("Module", "Module::Build::Compat")->uptodate
        or die "Couldn't install Module::Build, giving up.\n";
      
      chdir $cwd or die "Cannot chdir() back to $cwd: $!";
    }
    eval "use Module::Build::Compat 0.02; 1" or die $@;
    
    Module::Build::Compat->run_build_pl(args => \@ARGV);
    require Module::Build;
    Module::Build::Compat->write_makefile(build_class => 'Module::Build');

So what exactly is going on here? A good question indeed. Let's walk through some of the code.

    unless (eval "use Module::Build::Compat 0.02; 1" ) {
      print "This module requires Module::Build to install itself.\n";
      
      require ExtUtils::MakeMaker;
      my $yn = ExtUtils::MakeMaker::prompt
        ('  Install Module::Build now from CPAN?', 'y');
      
      unless ($yn =~ /^y/i) {
        die " *** Cannot install without Module::Build.  Exiting ...\n";
      }

This first attempts to load version 0.02 or greater of the Module::Build::Compat module. If it isn't installed we know we need to install Module::Build. Because we're polite, we ask the user if they would like to install Module::Build before going further. Some people dislike interactive installations, but fortunately the promp() command is pretty smart about detecting if there's a user at the end of the line.

Assuming that the user agrees to install Module::Build (if they don't the installer has to give up) this is what comes next:

      # Save this 'cause CPAN will chdir all over the place.
      my $cwd = Cwd::cwd();
      
      CPAN::Shell->install('Module::Build::Compat');
      CPAN::Shell->expand("Module", "Module::Build::Compat")->uptodate
        or die "Couldn't install Module::Build, giving up.\n";
      
      chdir $cwd or die "Cannot chdir() back to $cwd: $!";

We want to use CPAN.pm to actually install Module::Build, but we need to first save our current directory, because CPAN.pm calls chdir() quite a bit, and we'll need to be in the same directory as we started in after installing Module::Build.

Then we load CPAN.pm and tell it to install Module::Build. After that, we chdir() back to our original directory.

    eval "use Module::Build::Compat 0.02; 1" or die $@;
    
    Module::Build::Compat->run_build_pl(args => \@ARGV);
    require Module::Build;
    Module::Build::Compat->write_makefile(build_class => 'Module::Build');

First it checks that the Module::Build install worked. Then it simply tells Module::Build::Compat to run the Build.PL script, and to write out a "passthrough" Makefile. Module::Build::Compat will attempt to convert ExtUtils::MakeMaker style arguments, like "PREFIX", to arguments that Module::Build can understand, like "--prefix".

The "passthrough" Makefile that Module::Build::Compat generates looks something like this:

  all :
          ./Build
  realclean :
          ./Build realclean
          rm -f \$(THISFILE)
  .DEFAULT :
          ./Build \$@
  .PHONY   : install manifest

The ".DEFAULT" target is called when there is no matching make target for the one given on the command line. It uses the "$@" make variable, which will contain the name of the target that was passed to make. So if "make install" is called, then "$@" contains "install", and it ends up running "./Build install".

The generated Makefile also contains a comment which specifies the module's prerequisites, because this is how CPAN.pm figures out what a module's prerequisites are (scary but true).

This approach is the most elegant of all, but the code that translates ExtUtils::MakeMaker arguments to something Module::Build understands is quite minimal and won't handle all possibilities.

I have used this approach for one CPAN module, Thesaurus.pm, and in my limited testing it did work. If you are inclined to try installing this module, please send bug reports to me or the Module::Build users list, module-build-general@lists.sf.net.

Recently, Autrijus Tang submitted a more complex Makefile.PL script which implements several pieces of additional functionality. First of all, it makes sure that the script is running as a user that can actually install Module::Build. Second, it prefers CPANPLUS.pm to CPAN.pm.

Autrijus' script looks promising, but since it hasn't yet been tested, I've chosen not to include it here. It's quite likely that some version of his script will be documented in future versions of Module::Build

Custom Behavior

As was hinted at earlier, you can directly subclass Module::Build in order to implement custom behavior. This is a big topic unto itself, and will be the topic of a future article here on perl.com.

The Future

There is plenty of work left to be done on Module::Build. Off the top of my head, here are some things that still need to be done:

The installation phase does not yet create man pages based on POD included in the distribution.

Module::Build needs to implement a "local install" feature like the one provided by the ExtUtils::MakeMaker "PREFIX" argument. The logic that implements this in ExtUtils::MakeMaker is Byzantine, but only because doing this correctly is quite complicated. This logic needs to be implemented for Module::Build as well.

Module::Build needs better backwards compatibility with ExtUtils::MakeMaker. The argument translation in Module::Build::Compat is currently just a placeholder. Things like "PREFIX", "LIB", and "UNINST=1" all need to be translated by Module::Build::Compat, and the equivalent functionality needs to be implemented for Module::Build

CPANPLUS.pm could take advantage of more Module::Build features. For example, it currently ignores the "conflict" information that Module::Build makes available, and it doesn't attempt to distinguish between "build_requires", "requires", or "recommends".

Some of what Module::Build provides is intended for use by external tools, such as the meta-data provided by the META.yaml file. CPANPLUS.pm could use this to avoid having to run the Build.PL and Build scripts, thus avoiding the need to install any of the "build_requires" modules. Package managers like rpm or the Debian tools could also use it to construct installable packages for Perl modules more easily.

Adding at least basic support for Module::Build to CPAN.pm would be nice. If anyone is interested in pursuing this, I have an old patch that may provide a decent start on this. Contact me if you're interested.

More Information

If you want to learn more about Module::Build, the first thing you should do is install it (it will install itself just fine under CPAN.pm) and read the docs for the Module::Build and Module::Build::Compat modules. There is a project on SourceForge for Module::Build at http://www.sourceforge.net/projects/module-build. The source is in CVS on SourceForge, and is accessible via anonymous CVS and and online.

Finally, if you're interested in using or helping to develop Module::Build, please sign up on the module-build-general@lists.sf.net email list. See http://lists.sourceforge.net/lists/listinfo/module-build-general for more details.

Thanks

Thanks to Ken Williams for reviewing this article before publication, and for writing Module::Build.