Module::Build

取自 PerlChina.org - wiki

跳转到: 导航, 搜索
  • 翻 译:[[]]
  • 审 校:[[]]
  • 出 处:中国 Perl 协会 FPC - PerlChina.org
  • 原 名:Module::Build
  • 中 文:Module::Build
  • 作 者:Dave Rolsky
  • 原 文: http://www.perl.com/pub/a/2003/02/12/module1.html
  • 发 表:2003-02-12
  • Perlchina 提醒您:请保护作者的著作权,维护作者劳动的结晶。


目录


Dave Rolsky is a co-author of the recently released Embedding Perl in HTML with Mason.

Dave Rolsky 是最近出版的 Embedding Perl in HTML with Mason 这本书的协作作者。

If you've ever created a Perl module for distribution on CPAN, you've used the ExtUtils::MakeMaker module. This venerable module goes back to the dawn of modern Perl, which began with the release of Perl 5.000.

如果你曾经创建了在 CPAN 上发布的 Perl 模块的话,你一定用过 ExtUtils::MakeMaker 模块。这个传奇的模块把我们带回到 Perl 的童年,也就是 Perl 5.000 开始发行的时候。

Recently, Ken Williams has created a potential replacement for ExtUtils::MakeMaker called Module::Build, which was first released in August of 2002. Hugo van der Sanden, the pumpking for the current development version of Perl, has expressed interest in replacing ExtUtils::MakeMaker with Module::Build for the 5.10 release of Perl, and Michael Schwern, the current ExtUtils::MakeMaker maintainer, agrees with him. ExtUtils::MakeMaker won't go away any time soon, but we can hope for a gradual transition to a new and improved build system.

最近 Ken Williams 创建了一个 ExtUtils::MakeMaker 的有力的替代品,名叫 Module::Build,最初在 2002 年 8 月发行。Hugo van der Sanden 作为最近的 Perl 开发版本的协调人表示有兴趣在 5.10 版本用 Module::Build 来替代 ExtUtils::MakeMaker。Michael Schwern 作为 ExtUtils::MakeMaker 的维护人,也表示赞同。ExtUtils::MakeMaker 不会很快过时,但是我们可以期望有个新改进过的 build system 以备顺利转换。

[编辑] 为什么 ExtUtils::MakeMaker 很重要

The ExtUtils::MakeMaker module, along with the h2xs script, has been a huge boon to the Perl community, as it makes it possible to have a standard way to distribute and install Perl modules. It automates many tasks that module authors would otherwise have to to implement by hand, such as turning XS into C, compiling that C code, generating man pages from POD, running a module's test suite, and of course, installing the module.

ExtUtils::MakeMaker 模块和 h2xs 脚本都曾为 Perl 社区鞠躬尽瘁,他使得标准的分发安装 Perl 模块方式成为可能。他使得原来需要手工操作的事情(好比把 XS 转化为 C、编译 C 代码、生成 POD、运行测试用例,当然还有安装模块)得以自动化。

ExtUtils::MakeMaker is a huge part of what makes PAUSE and CPAN possible, and it is quite a programming feat. Python did not have a similar utility until the September, 1999 release of distutils, PHP's PEAR didn't begin until mid-2000, and Ruby is just beginning to work on a standard library distribution mechanism.

ExtUtils::MakeMaker 使得 PAUSE 和 CPAN 的大多数事情有了着落,不愧是大师的作品。Python 直到 1999 年 9 月才有类似的基础设施,PHP 的 PEAR 直到 2000 年中期才开始出现,Ruby 刚刚开始一个标准库分发机制。

ExtUtils::MakeMaker 的吓人一面

ExtUtils::MakeMaker works by generating a makefile that contains targets for the various tasks needed when maintaining or installing a module. Here's an example target:

ExtUtils::MakeMaker 通过生成一个为维护和安装模块环节中各种任务服务的 makefile 来支持我们。下面就是一个任务的例子:

  install :: all pure_install doc_install

If you're familiar with makefile syntax, you'll realize that all this target does is call three other targets, which are named "all", "pure_install", and "doc_install". These targets in turn may call other targets, or use system commands to do whatever action is needed, in this case installing the module and its associated documentation.

如果你熟悉 makefile 的语法,你就会明白这个任务其实是依赖于对其他三个任务(all、pure_install 和 doc_install)的调用。这些任务同样也会调用其他的任务,或者是用系统命令来做需要的事情,在这个例子里面是安装模块和相应的文档。

The makefiles generated by ExtUtils::MakeMaker are fairly complex. For example, using version 6.05 of ExtUtils::MakeMaker to install my Exception::Class module, a pure Perl distribution containing just one module, creates a makefile with about 390 lines of makefile code. Figuring out what this makefile actually does is no simple feat, because it consists of a maze of twisty macros, all alike, many of which simply call Perl one-liners from the command line to perform some task.

ExtUtils::MakeMaker 创建的 makefile 大多是非常复杂的。例如在使用 6.05 版本的 ExtUtils::MakeMaker 来安装我的 Exception::Class 模块(一个纯 Perl 模块的发行)的时候,会生成 390 行 makefile 代码。弄明白这个 makefile 实际上做了什么可不是轻活,因为它包含很多难懂的宏,看上去很难彼此区分,其中很多是调用单行 Perl 脚本来在命令行完成任务的。

The ExtUtils::MakeMaker code itself is extremely complicated, as it works on many operating systems (almost as many as Perl itself), and needs to accommodate their file systems, command line shells, different versions of make, and so on. And this is all done with an extra layer of indirection in place, because it is generating a makefile which does all the work.

ExtUtils::MakeMaker 的代码本身也是很复杂的,因为它要在很多种操作系统(几乎和 Perl 一样多)上工作,同样需要适应他们的不同文件系统、命令行 shell、各种版本的 make 等等。这都是通过一个额外的间接影射来实现的,就是他所产生的万能 makefile。

If you want to customize the module build or installation process, good luck. To do this, you must subclass ExtUtils::MakeMaker, override methods that generate specific makefile targets, and then tweak the generated text to include your custom behavior, all the while preserving the basic behavior of the target. Considering that there is no documentation describing what to expect from these targets, and that the actual target text may change between releases of ExtUtils::MakeMaker or between different OS's, this can be quite painful to implement and maintain.

如果你幸运到想要定制模块的 build 或安装过程,就得继承 ExtUtils::MakeMaker,覆盖生成特定 makefile 任务的方法,然后搬弄已经生成的文本来加入你的特殊行为,同时还保持任务的基础行为。你得明白这些文本得适应 ExtUtils::MakeMaker 的各个版本和 OS 的各个版本。这会让设计和维护非常痛苦。

And by the way, you can't really subclass ExtUtils::MakeMaker, instead you are subclassing the MY package. This is a deeply strange hack, but the end result is that you can only override certain pre-defined methods in ExtUtils::MakeMaker.

另外你不能真的继承 ExtUtils::MakeMaker,而只能继承 MY 包。这是一个非常让人诧异的 hack,事情的结局是你只能覆盖 ExtUtils::MakeMaker 中的一部分预定以方法。

For example, the HTML::Mason module includes this snippet:

例如,HTML::Mason 模块有这段代码:

  package MY;

  sub test {
      my $self = shift;

      my $test = $self->SUPER::test(@_);

      # %MY::APACHE is set in makeconfig.pl.
      # If we are not going to test with Apache there is no harm in
      # setting this anyway.

      # The PORT env var is used by Apache::test.  Don't delete it!
      my $port = $MY::APACHE{port} || 8228;
      $MY::APACHE{apache_dir} ||= ";

      my $is_maintainer = main::is_maintainer();

      # This works for older MakeMakers
      $test =~ s/(runtests \@ARGV;)/\$\$ENV{MASON_VERBOSE} == 
	  \$(TEST_VERBOSE) ? \$(TEST_VERBOSE) : \$\$ENV{MASON_VERBOSE}; 
	  \$\$ENV{PORT}=$port; 
	  \$\$ENV{APACHE_DIR}=q^$MY::APACHE{apache_dir}^; 
	  \$\$ENV{MASON_MAINTAINER}=$is_maintainer; $1/;

      my $bs = $^O =~ /Win32/i ? " : '\\';

      # This works for newer MakeMakers (5.48_01 +)
	  $test =~ s/("-MExtUtils::Command::MM" "-e" ")
	  (test_harness\(\$\(TEST_VERBOSE\).*?\)"
	  \$\(TEST_FILES\))/$1 $bs\$\$ENV{MASON_VERBOSE} == \$(TEST_VERBOSE) ?
	  \$(TEST_VERBOSE) : $bs\$\$ENV{MASON_VERBOSE}; $bs\$\$ENV{PORT}=$port;
	  $bs\$\$ENV{APACHE_DIR}=q^$MY::APACHE{apache_dir}^;
	  $bs\$\$ENV{MASON_MAINTAINER}=$is_maintainer; $2/;

      return $test;
  }

The goal of all this code is to pass some additional environment information to the test scripts when they are run, so we can do tests with a live Apache server. It accommodates several versions of ExtUtils::MakeMaker, and attempts to work properly on multiple operating systems (at least Win32 and *nix), and it has to be careful about escaping things properly so that it executes properly from the shell.

这段代码的目标是要给测试脚本发送额外的环境信息,这样我们就可以对鲜活的 Apache 服务器做测试。这里我们的脚本力图适应多个版本的 ExtUtils::MakeMaker,并竭力在各个版本的操作系统(起码是 Win32 和 *nix)上面工作,还得确保在 shell 命令里面能很好的执行。

[编辑] 为何不用 Perl?

All of this prompts the question of "why not just use Perl itself for all of this?" That's exactly the question that Ken Williams answered with Module::Build. The goal of Module::Build is to do everything useful that ExtUtils::MakeMaker does, but to do this all with pure Perl wherever possible.

所有这些都在提醒我们“为什么不用 Perl 自己的方法来做呢?”这正是 Ken Williams 用 Module::Build 来回答的问题。设计 Module::Builde 的目的就是为了不但要做到 ExtUtils::MakeMaker 做到的每件事情,还要尽可能用 Perl 来做。

This greatly simplifies the build system code, and Module::Build works on systems which don't normally include make, like Win32 and Mac OS. Of course, if a module installation requires the compilation of C code, you'll still need an external C compiler.

这就极大的简化了 build 部分的代码,还使得 Module::Build 得以在没有 make 的系统(如 Win32 和 Mac OS)上面工作。当然,如果一个模块的安装需要 C 代码编译,你还是得有个额外的 C 编译器。

Additionally, customizing Module::Build's behavior is often quite trivial, and only requires that you know Perl, as opposed to knowing make syntax and possibly having to learn about multiple command line environments.

更进一步,Module::Build 的行为定制也常常很简单,只需要你懂得 Perl,而不需要你了解 make 语法和各种迥异的命令行环境。

Module::Build also aims to improve on some of the features provided by ExtUtils::MakeMaker. One example is its prerequisite-checking system, which provides much more flexibility than what ExtUtils::MakeMaker allows. While these features could be added to ExtUtils::MakeMaker, it's risky to make major changes to such an important module, especially one with such complex internals.

Module::Build 也努力改进了 ExtUtils::MakeMaker 提供的一些功能。例如它的依赖判别系统就比 ExtUtils::MakeMaker 更加宽泛了。尽管理论上来说这些功能也可以加入 ExtUtils::MakeMaker 里面,但是因为模块本身的重要性使得这看上去很冒险,尤其是模块内部的复杂性又这么高。

[编辑] 使用 Module::Build

From an end-user perspective, a module that uses Module::Build looks quite a bit like one using ExtUtils::MakeMaker, and intentionally so. So to install a module using Module::Build you'd type the following lines from the command line:

从最终用户角度来说,使用 Module::Build 的系统非常类似使用 ExtUtils::MakeMaker 的,从方法学上来说也是同样类似。所以安装一个用了 Module::Build 的模块的时候,你得用下面的命令行:

  perl Build.PL
  ./Build
  ./Build test
  ./Build install

The Build.PL script tells Module::Build to create a Build script. During this process, Module::Build also writes some files to a _build/ directory. These files are used to store the build system's state between invocations of the Build script. This script, when invoked, simply loads up Module::Build again, and tells it to perform the specified action. An action is the Module::Build version of a makefile target, and actions are implemented in pure Perl whenever possible.

Build.PL 脚本用 Module::Build 来创建 Build 脚本。在这个过程中,Module::Build 同时还在 _build/ 目录下面创建一些文件,它们是用来跟踪 build 系统的状态在对 Build 脚本的一系列调用之间的变化的。在被调用的时候,这个脚本只是简单的再次调用 Module::Build 并且让它去做细节的事情(action)。Action 对 Module::Build 来说类似于 makefile 里面的目标,只是在这里是尽可能用 Perl 来实现的。

A bare bones Build.PL script might look like this:

Build.PL 脚本的骨架看来很像这样:

  use Module::Build;

  Module::Build->new
      ( module_name => 'My::Module',
        license => 'perl',
      )->create_build_script;

The "module_name" parameter is like the ExtUtils::MakeMaker "NAME" parameter.

module_name 参数很像 ExtUtils::MakeMaker 的 NAME 参数。

The "license" parameter is new with Module::Build, and its intended use it allow automated tools to determine under which license your module is distributed.

license 参数是 Module::Build 引入的,加入这个参数是为了让自动化工具能够判断你的模块使用什么授权方式来分发的。

To determine your module's version, Module::Build looks in the module specified by the "module_name" parameter, though this can be overridden either by specifying a different module to look in, or by providing the version number directly.

为了判断你的模块的版本,Module::Build 查看那个有匹配的 module_name 参数的模块,你可以给它另外一个模块,或者直接给它你的版本号。

Of course, there are more options than those. For example, Module::Build implements a prerequisite feature similar to that implemented by ExtUtils::MakeMaker, so we can write:

当然还有其他的选择。例如 Module::Build 用与 ExtUtils::MakeMaker 类似的方式来实现依赖判断,这样我们可以写:

  Module::Build->new
      ( module_name => 'My::Module',
        license => 'perl',
        requires => { 'CGI' => 0,
                      'DBD::mysql' => 2.1013,
                    },
      )->create_build_script;

If you have any experience with ExtUtils::MakeMaker, you can probably figure out that this says that our module requires any version of CGI, and version 2.1013 or greater of DBD::mysql. So far, this looks just like what ExtUtils::MakeMaker provides, but Module::Build goes further.

如果你有使用 ExtUtils::MakeMaker 的经验,你就能大致明白这个模块依赖于任何版本的 CGI 和 2.1013 版本以上的 DBD::mysql。到此为止都很像 ExtUtils::MakeMaker 所提供的功能,但是 Module::Build 还能做到更多。

Consider what happens if we know that we need some piece of functionality first present in DBD::mysql 2.1013. But perhaps a release after this, 2.1014, introduced a new bug that breaks our application. If this bug is fixed in version 2.1015, we could simply require version 2.1015, but this is not ideal. There's no reason to force someone who already has 2.1013 to upgrade because of a bug in a version they don't have.

想想看如果我们需要某个细微的功能,首先在 DBD::mysql 的 2.1013 版本出现。但是可能后来 2.1014 功能引入了一个 bug 破坏了我们的应用。如果这个 bug 在 2.1015 版本被修正了,我们可能会简单的宣告必须使用 2.1015,但这不完美。没有道理去强迫 2.1013 版本的用户为了一个没有的 bug 而升级。

Fortunately, Module::Build provides a more flexible version specification option that handles exactly this situation, so we can write:

幸运的是 Module::Build 提供了一种更加灵活的版本声明机制来恰到好处的解决这个问题,我们现在可以写:

  Module::Build->new
      ( module_name => 'My::Module',
        license => 'perl',
        requires => { 'CGI' => 0,
                      'DBD::mysql' => '>= 2.1013, != 2.1014',
                    },
      )->create_build_script;

This says that we need a version greater than 2.1013 that is not version 2.1014. Users who have version 2.1013 or version 2.1015 or greater are not forced to upgrade, but anyone with 2.1014 will be.

这声明我们需要一个高于 2.1013 的版本,但不是 2.1014。那些使用 2.1013 或 2.1015 的用户都不必升级,只有 2.1014 的用户例外。

If we knew that version 3.0 didn't work with your module, we could change our specification:

如果我们还知道 3.0 版本不会正常工作,我们就得修改我们的声明:

  Module::Build->new
      ( module_name => 'My::Module',
        license => 'perl',
        requires => { 'CGI' => 0,
                      'DBD::mysql' => '>= 2.1013, != 2.1014, < 3.0',
                    },
      )->create_build_script;

If the user does have version 3.0 or greater installed, it will at least let them know that it won't work with our module. Unfortunately, the only possible way to use our module at this point is for the end user to manually downgrade their installation of DBD::mysql, since Perl does not allow multiple versions of a module to co-exist peacefully. Still, this is better than letting the module be installed, only to fail at runtime when it tries to use an outdated API for DBD::mysql.

如果用户真的安装了 3.0 版本,这起码会提醒他们不适配。不幸的是,唯一的解决方法是让他们手工降级 DBD::mysql 来适应我们的模块,因为 Perl 不允许一个模块的多个版本同时并存。然而,这还是比允许用户安装并且直到运行时候访问过时的 API 而导致崩溃才发现问题来得好。

There are also other options related to prerequisites, such as "recommends" and "build_requires", which can be helpful for prerequisites that are required to build the module but don't need to be present after installation. There is also a "conflicts" option which can be used to warn a user about potential conflicts between the module they are installing and one they already have.

还有其他的选择来解决依赖性相关的事情,比如 recommends 和 build_requires,这些都有助于解决安装过程中零时需要解决的依赖性(安装以后不再需要)。还有一个 conflicts 选项用于警告正在安装的模块和已安装模块之间的冲突。

[编辑] 动手!

As of release 1.15, Module::Build implements the following actions, most of which are based on existing ExtUtils::MakeMaker functionality:

在 1.15 版本里面,Module::Build 实现了下面的行为,绝大多数是基于已有的 ExtUtils::MakeMaker 功能:

   * build
     This is the default action, and is what happens if you run ./Build without any additional arguments. It is responsible for creating the blib/ directory and copying files into it, as well as compiling XS and C files. If you have any scripts like lib/My/Module.pm.PL, these are also run during this action.

这是缺省行为,也是没有参数的调用 ./Build 的时候的默认行为。它的责任是创建 blib/ 目录并拷贝文件进去,类似编译 XS 和 C 文件。如果你有名字类似 /My/Module.pm.PL 的脚本,也会在这个阶段调用。

   * test, testdb
     Runs the module's tests using the Test::Harness module. The "testdb" action can be used to run the tests under Perl's debugger. Equivalently, a "debugger" parameter can be passed to the "test" action to get the same effect.

使用 Test::Harness 模块来运行模块的测试。testdb 行为可以用于在 Perl 的调试器里面运行测试。同样可以用一个 debugger 参数来发送到 test 行为来达到次效果。

   * clean, realclean
     Both actions delete any files created by the "build" action. The "realclean" action also deletes the existing Build script.

这些行为会导致 build 行为创建的文件被删除,realclean 行为会更进一步删除 Build 脚本。

   * diff
     This action is used to compare the files about to be installed with any corresponding files that already exist. This feature is unique to Module::Build.

这个行为用来比较将要安装的文件和已经存在的文件之间的差异。这个功能是 Module::Build 仅有的。

   * install
     Installs the module files. As of version 0.15, this doesn't yet create or install any man pages.

安装模块文件,在 0.15 里面,这还不会安装任何 man 页。

   * fakeinstall
     Tells you what the "install" would do.

告诉你 install 会做什么。

   * dist
     Creates a gzip'd tarball of your distribution.

创建一个 gzip 过的 tar 文件来包装你的发行版本。

   * manifest
     Creates a MANIFEST file for your distribution.

创建一个 MANIFEST 文件来声明你的发行版本。

   * distcheck
     Tells you what files are in the build directory but not in the MANIFEST file, and vice versa.

告诉你 build 目录里面有,但是 MANIFEST 文件里面没有的文件,还有反过来的那些文件。

   * skipcheck
     Tells you what files will not be added to the MANIFEST by the "manifest" action, based on the contents of your MANIFEST.SKIP file.

告诉你哪些文件会被 manifest 行为加到 MANIFEST 文件里面去(参照 MANIFEST.SKIP 文件的内容)。

   * distclean
     This is a shortcut for "realclean" followed by "distcheck".

这是 realclean 后面跟着 distcheck 的一个简写。

   * distdir
     Creates a directory based on your distribution name and version, and then copies all the files listed in MANIFEST to that directory. This directory is what people will see when they download and unpack your distribution.

依据你的发行的名字和版本号创建一个目录,把 MANIFEST 文件里面罗列的文件拷贝到那个目录。这个目录就是下载和解包的用户将会看到的。

     Module::Build also creates a file called META.yaml which contains meta-data about your distribution. In the future, it may be possible to use a command line tool (written in Perl, of course) to read this file and use its contents to install your distribution, without running the Build.PL script. It also makes the meta-data more readily available to tools like search.cpan.org or the CPAN shell.

Module::Build 还创建一个 META.yaml 文件保存你的发行的元数据。将来这个文件会有可能被一个命令行的工具(当然用 Perl 来写)读取并安装,而不必运行 Build.PL 脚本。这也使得元数据离 search.cpan.org 或 CPAN shell 需要的形式更近。

   * disttest
     This performs the "distdir" action, switches to the newly created directory, and then runs perl Build.PL, ./Build, and ./Build test. This lets you make sure that your distribution is actually installable.

这个行为会触发 distdir 行为,进入新建目录,运行 perl Build.PL,./Build 和 ./Build test。这确保你的模块真是可安装的。

   * help
     Tells you what actions are available. If additional actions are implemented in a distribution, then these are listed here.

告诉你有什么行为是可用的。如果发行版本有什么额外的行为,这些都在这里列出。

Any of these options can be overridden through straightforward subclassing, so our HTML::Mason example from earlier in this article might be written something like this:

这里面的每个选项都可以用直接的继承来覆盖,所以我们早先的 HTML::Mason 例子可以这么写:

  package MyFancyBuilder;

  use base 'Module::Build';

  sub ACTION_test {
      my $self = shift;

      # %MY::APACHE is set in makeconfig.pl.

      $ENV{PORT}             = $MY::APACHE{port}       || 8228;
      $ENV{APACHE_DIR}       = $MY::APACHE{apache_dir} || ";
      $ENV{MASON_VERBOSE}  ||= $self->{properties}{verbose};
      # _is_maintainer_mode would be another method of our subclass
      $ENV{MASON_MAINTAINER} = $self->_is_maintainer_mode();

      return $self->SUPER::ACTION_test(@_);
  }

This version is actually readable, and is unlikely to break regardless of changes in the Module::Build internals. This highlights just how difficult it was to accomplish a simple task using ExtUtils::MakeMaker, and how natural the pure-Perl solution can be.

这个版本更加易读,而且不易于因为 Module::Build 的内部变化而崩溃。这与 ExtUtils::MakeMaker 的难于使用形成了鲜明的对比,从而现实了纯 Perl 的解决方案会多么自然。

The Larger Picture and Backwards Compatibility / 大视角和向后兼容

One difficulty in getting Module::Build into widespread use is the fact that support for ExtUtils::MakeMaker is so tightly integrated into the CPAN.pm module, which is the de facto standard for automated module installation.

由于对 ExtUtils::MakeMaker 的支持已经如此紧密的渗入了 CAPN.pm 模块(这正是模块自动化安装的事实标准),在推广 Module::Build 的时候还是有些难度。

Some readers may be familiar with the new CPANPLUS.pm module, which is intended to replace CPAN.pm. Version 0.041 of CPANPLUS.pm, which was released in January of 2003, incorporates support for Module::Build, so that it recognizes a Build.PL script and does the right thing. Perl version 5.10 is likely to include CPANPLUS.pm, and the possibility of renaming CPLANPLUS.pm to CPAN.pm, thus replacing the existing implementation entirely, has been discussed.

有些读者可能对新的 CPANPLUS.pm 模块比较熟悉,这是用来替代 CPAN.pm 的。在 2003 年一月发布的 CPANPLUS.pm 的 0.041 版本中已经有了对 Module::Build 的支持。Perl 5.10 版本也会包含 CPANLUS.pm,还讨论了用 CPAN.pm 来作为名字的可能,这样就完全的替代了现有的设计。

But CPAN.pm is still in extremely widespread use, and as of this writing, it has no support for Module::Build. Support for Module::Build may be added in the future, but users won't necessarily upgrade CPAN.pm before attempting to install a distribution that relies on Module::Build.

但是 CPAN.pm 还是在广泛的被使用,直到目前他还没有开始支持 Module::Build。以后也许会加上这个支持,但是用户不一定会乐意为了安装一个依赖于 Module::Build 的模块而去升级 CPAN.pm。

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 CPAN.pm will give up when your distribution doesn't install properly.

有很多办法可以扰过这个问题。最简单的就是在你的模块里面带上 Build.PL 脚本并且在 README 或者 INSTALL 文件里面说清楚。这很容易做到,但是依靠 CPAN.pm 来搞定一切的用户可能会失败并有挫折感。

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.

另一个办法是创建有同样功能的 Build.PL 和 Makefile.PL 文件。如果你是因为需要 Module::Build 特有的对安装过程的处理能力而从 ExtUtils::MakeMaker 切过来的话,这就会给你迎头一击。而起在两个地方做同一件事情也是从来不让人喜欢的。

Then there's the funky hack 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.

然后就有人做了一个有趣的事情,就是用 Makefile.PL 来在需要的时候安装 Module::Build,然后生成一个 Makefile 来调用 ./Build 脚本处理所有事情。

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 a lot of the dirty work needed for this approach.

我认为这个解决方案很好的解决了问题,是我推荐的方法。Module::Build 发行包里面有个 Module::Build::Compat 模块,它已经实现了很多我们眼前需要的功能。

Here's an example of such a Makefile.PL script, taken from the Module::Build::Compat documentation:

这就是我们从 Module::Build::Compat 的文档里面学来做为例子的 Makefile.PL 脚本:

  use Cwd;
  use File::Spec;

  unless ( eval "use Module::Build::Compat 0.2; 1" ) {
      require ExtUtils::MakeMaker;

      print "This module require Module::Build to install itself.\n";

      my $yn =
          ExtUtils::MakeMaker::prompt
              ( '  Install Module::Build from CPAN', 'y' );

      if ($yn =~ /^y(es)?/i) {
          # save this cause CPAN will chdir all over the place.
          my $cwd = cwd();
          my $makefile = File::Spec->rel2abs($0);

          require CPAN;
          CPAN->install('Module::Build');

          chdir $cwd
              or die "Cannot chdir to $cwd: $!";

          exec( $^X, $makefile, @ARGV )
      } else {
          warn "Cannot install this module " .
               "without Module::Build.  Exiting ...\n";
          exit 0;
      }
  }

  Module::Build::Compat->run_build_pl( args => \@ARGV );
  Module::Build::Compat->write_makefile;

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.2; 1" ) {
      require ExtUtils::MakeMaker;

      print "This module require Module::Build to install itself.\n";

      my $yn =
          ExtUtils::MakeMaker::prompt
              ( '  Install Module::Build from CPAN', 'y' );

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, so this part could be removed, and we could just forge ahead.

这个脚本尝试调用 0.02 版本或更高版本的 Module::Build::Compat 模块。如果没有安装我们就需要安装 Module::Build。按照礼节,我们问用户是否需要安装 Module::Build 来继续安装。有些人不喜欢交互式的安装,所以这个部分可以避免,我们可以继续平稳前进。

Assuming that the user agrees to install Module::Build, this is what comes next:

假设用户同意安装 Module::Build,下面就是紧接着的:

          # save this cause CPAN will chdir all over the place.
          my $cwd = cwd();
          my $makefile = File::Spec->rel2abs($0);

          require CPAN;
          CPAN->install('Module::Build');

          chdir $cwd
              or die "Cannot chdir to $cwd: $!";

          exec( $^X, $makefile, @ARGV )

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. We also save the absolute path to the file (Makefile.PL) being executed, because we'll need to execute it again in a moment.

我们需要用 CPAN.pm 来安装 Module::Build,但是我们首先需要记住当前目录,因为 CPAN.pm 会多次调用 chdir(),我们需要保证安装了 Module::Build 之后仍然保持原来的位置。我们还保存正在执行的 Makefile.PL 的绝对路径,因为我们过会需要再次调用它。

Then we load CPAN.pm and tell it to install Module::Build. After that, we chdir() back to our original directory and call exec() on ourself. This replaces the current process with a new one, which happens to be the same script as was just being executed! But since we just installed Module::Build, running the script again should cause us to go down a different execution path. This time, when we try to load Module::Build::Compat, it should succeed, so we'll end up running this code:

然后我们调入 CPAN.pm 并让它安装 Module::Build。然后,我们回到原来的目录并且对自己调用 exec()。这就把当前的进程换成另一个,可是恰巧调用的还是原来的脚本!但是因为已经安装了 Module::Build,这次我们会走另外一条执行路径。这次我们对 Module::Build::Compat 的调用也会成功,我们开始运行下面的代码:

  Module::Build::Compat->run_build_pl( args => \@ARGV );
  Module::Build::Compat->write_makefile;

This code simply tells Module::Build::Compat to run the Build.PL script, and then to write out a "pass-through" Makefile. Module::Build::Compat will attempt to convert ExtUtils::MakeMaker style arguments, like "PREFIX", to arguments that Module::Build can understand.

这段代码简单的告诉 Module::Build::Compat 去运行 Build.PL 脚本,然后输出一个 pass-through 类型的 Makefile。Module::Build::Compat 会尝试吧 ExtUtils::MakeMaker 类型的参数(如 PREFIX)转换成 Module::Build 可以理解的参数。

The "pass-through" Makefile that Module::Build::Compat generates looks something like this:

Module::Build::Compat 生成的 pass-through 类型的 Makefile 看来好像这样:

  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".

这个 .DEFAULT 目标在没有明确的目标与命令行参数匹配的时候调用。他用 make 变量 $@ 来获取传给 make 的目标名称。好比在我们调用 make install 的时候,$@ 里面就会存放 install,然后实际上就调用了 ./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).

生成的 Makefile 还有一些注释用于标明模块的依赖,因为这是 CPAN.pm 获取模块依赖性的方式,有点吓人但很真实。

This approach is the most elegant of all, but it hasn't been widely tested. For example, I'm not sure if the "$@" make variable exists in non-GNU make implementations. Additionally, the code that translates ExtUtils::MakeMaker arguments to something Module::Build understands is quite minimal.

这是最最优雅的一个方案,但是还没有有效的测试过。例如我就不知道 $@ 变量在非 GNU 的 make 里面是不是有效。另外把 ExtUtils::MakeMaker 参数转化成 Module::Build 参数的代码也很少。

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.

我试着在一个模块(Thesaurus.pm)里面使用了这个方案,在有限的测试里面成功了。如果你有兴趣安装这个模块,请把 bug 报告发送给我或者 Module::Build 用户列表 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.

最近唐宗汉提交了一些更加复杂的 Makefile.PL 脚本(里面实现了更多的功能)。首先它确认脚本的运行用户确实可以安装 Module::Build。其次他优先使用 CPANPLUS.pm 而非 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.

他的脚本看来很强壮,但是因为还没有被测试过,我决定在这里先不介绍了。看来他的脚本的某个版本会在 Module::Build 的将来版本进入文档。

[编辑] 定制行为

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.

如同早些时候提到的,你可以直接继承 Module::Build 来实现定制的行为。这是一个大课题,我们将来会有一篇文章在 perl.com 上面讨论这个。

[编辑] 未来

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:

还有很多的期待的功能留给 Module::Build 实现。我觉得将来可以做的事情包括:

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

这个版本的安装阶段没有根据创建 POD 来创建 man 页。

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 需要实现一个本地安装的特性来模拟 ExtUtils::MakeMaker 的 PREFIX 参数。实现这个功能的逻辑很复杂(像拜占庭文化一样),但是也还是因为事情本身就很复杂。Moduile::Build 也需要实现相似的逻辑。

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.

Module::Build 需要更好的向后兼容 ExtUtils::MakeMaker。Module::Build::Compat 提供的参数翻译只能作为暂时替代品。PREFIX、LIB 和 UNINST=1 类型的事情都需要由 Module::Build::Compat 来翻译,而类似的功能在 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".

CPANPLUS.pm 可以从 Module::Build 里面得到很多的益处。好比现在它会忽略 Module::Build 提供的冲突信息,它也不区分 build_require、require 或是 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.

Moudle::Build 提供的一些信息是为了让外部工具使用而设计的,好比 META.yaml 文件提供的元数据。CPANPLUS.pm 可以用它来绕过运行 Build.PL 和 Build 脚本,这样也就避免了安装任何 build_requires 模块。类似 rpm 或者 Debian tools 这样的安装包管理器也可以用它来更容易地为 Perl 模块创建安装包。

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.

为支持 Module::Build 而为 CPAN.pm 增加基础支持会很好。如果有任何人有志于此,我恰好有一个老的补丁会很有用。联系我吧。

[编辑] 更多信息

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.

如果你想更多的了解 Module::Build,首先要做的就是安装它(在 CPAN.pm 下面会很容易)并阅读它和 Module::Build::Compat 的相关文档。SourceForge 上面有个 Module::Build 项目在 http://www.sourceforge.net/projects/module-build。源代码用 CVS 保存在 SourceForge,可以用匿名 CVS 和网页来获取。

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.

最后,如果你有兴趣使用或开发 Module::Build,请订 module-build-general@lists.sf.net 邮件列表。更多的信息在 http://lists.sourceforge.net/lists/listinfo/module-build-general。

[编辑] 感谢

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

感谢 Ken Williams 在发表之前审阅这篇文章和创作了 Module::Build。

Perl.com Compilation Copyright ? 1998-2005 O'Reilly Media, Inc.

个人工具