ExtUtils::MakeMaker::FAQ - MakeMaker 常见问题
有关 ExtUtils::MakeMaker 的常见问题、技巧和提示。
如果您不是 Perl 管理员,则可能没有权限将其模块安装到其默认位置。处理此问题(您手动操作少很多)的方法是 perlbrew 和 local::lib。
否则,您可以将其安装到您的主目录中以供您自己使用,如下所示
# Non-unix folks, replace ~ with /path/to/your/home/dir
perl Makefile.PL INSTALL_BASE=~
这会将模块放入 ~/lib/perl5,手册页放入 ~/man,程序放入 ~/bin。
为确保您的 Perl 程序能够看到这些新安装的模块,请将 PERL5LIB
环境变量设置为 ~/lib/perl5,或使用以下命令告诉每个程序在该目录中查找
use lib "$ENV{HOME}/lib/perl5";
或者,如果未设置 $ENV{HOME},并且您出于某种原因不想设置它,请使用较长的方式。
use lib "/path/to/your/home/dir/lib/perl5";
从 0.28 版开始,Module::Build 支持两种方法将模块安装到与 MakeMaker 相同的位置。
我们强烈推荐使用 install_base 方法,它最简单,并且最接近安装前缀的预期行为。
1) 使用 INSTALL_BASE / --install_base
MakeMaker(6.31 版及更高版本)和 Module::Build(0.28 版及更高版本)都可以使用“install_base”概念安装到相同的位置。有关详细信息,请参阅ExtUtils::MakeMaker 中的“INSTALL_BASE”。要让 MM 和 MB 安装到同一位置,只需在 MM 中设置 INSTALL_BASE,并在 MB 中将 --install_base
设置为同一位置即可。
perl Makefile.PL INSTALL_BASE=/whatever
perl Build.PL --install_base /whatever
当您指定前缀时,此方法与其他语言的行为最相似。我们推荐此方法。
2) 使用 PREFIX / --prefix
Module::Build 0.28 添加了对 --prefix
的支持,该支持类似于 MakeMaker 的 PREFIX。
perl Makefile.PL PREFIX=/whatever
perl Build.PL --prefix /whatever
我们强烈不推荐此方法。只有在您知道自己在做什么并且特别需要 PREFIX 行为时,才应使用此方法。PREFIX 算法很复杂,并且专注于匹配系统安装。
默认情况下,最新版本的 MakeMaker 仅在类 Unix 操作系统上安装手册页。要在非 Unix 操作系统上生成手册页,请制作“manifypods”目标。
对于单个模块
perl Makefile.PL INSTALLMAN1DIR=none INSTALLMAN3DIR=none
如果您想禁止为所有模块安装手册页,则必须重新配置 Perl,并在询问安装手册页的位置时告诉它“none”。
有两种方法。一种是正常构建模块...
perl Makefile.PL
make
make test
...然后使用 blib 让 Perl 指向已构建但未安装的模块
perl -Mblib script.pl
perl -Mblib -e '...'
另一种方法是在临时位置安装模块。
perl Makefile.PL INSTALL_BASE=~/tmp
make
make test
make install
然后将 PERL5LIB 设置为 ~/tmp/lib/perl5。当您有多个模块要处理时,此方法非常有效。它还确保模块完成其完整的安装过程,该过程可能会修改模块。同样,local::lib 可能会在此处为您提供帮助。
我们来看一下以下测试目录结构
t/foo/sometest.t
t/bar/othertest.t
t/bar/baz/anothertest.t
现在,在 Makefile.PL 中的 WriteMakefile()
函数中,使用 test
指令指定测试的位置
test => {TESTS => 't/*.t t/*/*.t t/*/*/*.t'}
字符串中的第一个条目将在顶级 t/ 目录中运行所有测试。第二个条目将在 t/ 下的任何子目录中运行所有测试文件。第三个条目将在 t/ 下的任何其他子目录中的任何子目录中运行所有测试文件。
请注意,您不必使用通配符。您可以明确指定运行测试的子目录
test => {TESTS => 't/*.t t/foo/*.t t/bar/baz/*.t'}
PREFIX 的行为很复杂,并且与 Perl 的配置方式密切相关。最终的安装位置因机器而异,甚至同一台机器上的不同 Perl 安装也会不同。因此,很难记录前缀将模块放置在何处。
相比之下,INSTALL_BASE 具有可预测且易于解释的安装位置。现在,由于 Module::Build 和 MakeMaker 都具有 INSTALL_BASE,因此除了保留现有安装位置之外,几乎没有理由使用 PREFIX。如果您正在启动新的 Perl 安装,我们建议您使用 INSTALL_BASE。如果您通过 PREFIX 安装了现有安装,请考虑将其移至与 INSTALL_BASE 匹配的安装结构,并改用该结构。
如果您想为本地条件配置模块文件,或自动插入版本号,可以使用 EUMM 的 PL_FILES
功能,它将自动运行它找到的每个 *.PL 以生成其基本名称。例如
# Makefile.PL:
require 'common.pl';
my $version = get_version();
my @pms = qw(Foo.pm);
WriteMakefile(
NAME => 'Foo',
VERSION => $version,
PM => { map { ($_ => "\$(INST_LIB)/$_") } @pms },
clean => { FILES => join ' ', @pms },
);
# common.pl:
sub get_version { '0.04' }
sub process { my $v = get_version(); s/__VERSION__/$v/g; }
1;
# Foo.pm.PL:
require 'common.pl';
$_ = join '', <DATA>;
process();
my $file = shift;
open my $fh, '>', $file or die "$file: $!";
print $fh $_;
__DATA__
package Foo;
our $VERSION = '__VERSION__';
1;
您可能会注意到上面未指定 PL_FILES
,因为将每个 .PL 文件映射到其基本名称的默认设置效果很好。
如果生成的模块是特定于体系结构的,您可以将上面的 $(INST_LIB)
替换为 $(INST_ARCHLIB)
,但如果您将模块放在 lib 下,则需要确保删除模块位置前面的任何 lib/
。
正如它所说,你缺少该文件。MakeMaker使用它来确定自Makefile生成以来perl是否已重建。它是一个小错误,导致安装停止。
某些操作系统不会在其基本perl安装中附带CORE目录。要解决此问题,你可能需要安装一个perl开发包,例如perl-devel(CentOS、Fedora和其他Redhat系统)或perl(Ubuntu和其他Debian系统)。
为什么MakeMaker要重新构建配置轮?为什么不直接使用autoconf或automake或ppm或Ant或...
原因有很多,但主要原因是跨平台兼容性。
Perl是有史以来移植最多的软件之一。它可以在我从未听说过的操作系统上运行(有关详细信息,请参阅perlport)。它需要一个可以在所有这些平台上运行的构建工具,并且可以与任何古怪的C编译器和链接器一起使用。
不存在这样的构建工具。即使make本身也有截然不同的方言。因此,我们必须构建自己的。
Module::Build是Ken Williams的一个项目,旨在取代MakeMaker。它的主要优点是
纯perl。没有make,没有shell命令
更容易定制
更简洁的内部
更少的冗余
Module::Build长期以来一直是MakeMaker的官方继承人。然而,近年来它的开发和采用速度有所放缓,目前尚不清楚它的未来如何。也就是说,Module::Build为某些东西成为MakeMaker的继承人奠定了基础。MakeMaker的维护者长期以来一直表示它是一个死胡同,应该保持其功能,同时谨慎地扩展新功能。
通常,您希望手动在主模块发行版中设置 $VERSION,因为这是每个人在 CPAN 上看到的版本,而且您可能希望对其进行一些自定义。但是对于您的 dist 中的所有其他模块,$VERSION 实际上只是记账,重要的是每次模块更改时它都会增加。手动执行此操作很痛苦,而且您经常会忘记。
最简单的做法可能是使用 Perl::Version 中的 perl-reversion
perl-reversion -bump
如果您的版本控制系统支持修订号(git 不容易),自动执行此操作的最简单方法是使用其修订号(您正在使用版本控制,对吧?)。
在 CVS、RCS 和 SVN 中,您使用 $Revision$(有关详细信息,请参阅您的版本控制系统的文档)。每次签入文件时,$Revision$ 都会更新,从而更新您的 $VERSION。
SVN 为 $Revision$ 使用一个简单的整数,因此您可以像这样将其调整为您的 $VERSION
($VERSION) = q$Revision$ =~ /(\d+)/;
在 CVS 和 RCS 中,版本 1.9 后面跟着 1.10。由于 CPAN 以数字方式比较版本号,因此我们使用 sprintf() 将 1.9 转换为 1.009,将 1.10 转换为 1.010,以便正确比较。
$VERSION = sprintf "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/g;
如果涉及分支(即 $Revision: 1.5.3.4$),则会稍微复杂一些。
# must be all on one line or MakeMaker will get confused.
$VERSION = do { my @r = (q$Revision$ =~ /\d+/g); sprintf "%d."."%03d" x $#r, @r };
在 SVN 中,$Revision$ 对于项目中的每个文件都应该相同,因此它们都将具有相同的 $VERSION。CVS 和 RCS 为每个文件都有不同的 $Revision$,因此每个文件将具有不同的 $VERSION。分布式版本控制系统(例如 SVK)可能根据签出文件的人员拥有不同的 $Revision$,从而导致每台机器上的 $VERSION 不同!最后,一些分布式版本控制系统(例如 darcs)根本没有修订号的概念。
META.yml 是由 Module::Build 开创的模块元数据文件,并作为“distdir”目标(以及“dist”)的一部分自动生成。请参阅 "ExtUtils::MakeMaker 中的“模块元数据”。
要关闭其生成,请将 NO_META
标志传递给 WriteMakefile()
。
有些人惊讶地发现 make distclean
不会删除 MANIFEST 中未列出的所有内容(从而进行干净的分发),而只是告诉他们需要删除的内容。这样做是因为它被认为太危险了。在开发模块时,您可能会编写一个新文件,不将其添加到 MANIFEST,然后运行 distclean
,然后会感到难过,因为您的新工作被删除了。
如果您真的想这样做,可以使用 ExtUtils::Manifest::manifind()
读取 MANIFEST 并使用 File::Find 删除文件。但您必须小心。这里有一个脚本可以做到这一点。使用时请自担风险。尽情享受在你的脚上打洞的乐趣。
#!/usr/bin/perl -w
use strict;
use File::Spec;
use File::Find;
use ExtUtils::Manifest qw(maniread);
my %manifest = map {( $_ => 1 )}
grep { File::Spec->canonpath($_) }
keys %{ maniread() };
if( !keys %manifest ) {
print "No files found in MANIFEST. Stopping.\n";
exit;
}
find({
wanted => sub {
my $path = File::Spec->canonpath($_);
return unless -f $path;
return if exists $manifest{ $path };
print "unlink $path\n";
unlink $path;
},
no_chdir => 1
},
"."
);
我们推荐使用不早于 1.66 的 Archive::Tar 中的 ptar,并使用“-C”选项。
我们推荐 InfoZIP:http://www.info-zip.org/Zip.html
XS 代码对模块版本号非常敏感,如果 Perl 模块中的版本号不匹配,它会发出警告。如果你更改模块的版本号,却没有重新运行 Makefile.PL,旧版本号将保留在 Makefile 中,导致 XS 代码使用错误的版本号进行构建。
为了避免这种情况,你可以通过向 WriteMakefile() 参数中添加以下内容,强制在每次更改包含版本号的模块时重新构建 Makefile。
depend => { '$(FIRST_MAKEFILE)' => '$(VERSION_FROM)' }
有时你需要在同一包中放入两个或更多个 XS 文件。有三种方法:XSMULTI
、单独的目录以及从另一个 XS 中自举。
对模块进行结构化,以便它们都位于 lib 下,例如 Foo::Bar
位于 lib/Foo/Bar.pm 和 lib/Foo/Bar.xs 等。让你的顶级 WriteMakefile
将变量 XSMULTI
设置为 true 值。
呃,就是这样。
将每个 XS 文件放入单独的目录中,每个目录都有自己的 Makefile.PL。确保每个 Makefile.PL 都具有正确的 CFLAGS
、INC
、LIBS
等。你需要确保顶级 Makefile.PL 使用 DIR
引用每个目录。
让我们假设我们有一个包 Cool::Foo
,其中包含 Cool::Foo
和 Cool::Bar
模块,每个模块都有一个单独的 XS 文件。首先,我们使用以下 Makefile.PL
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'Cool::Foo',
VERSION_FROM => 'Foo.pm',
OBJECT => q/$(O_FILES)/,
# ... other attrs ...
);
注意 OBJECT
属性。MakeMaker 在 Makefile 中生成以下变量
# Handy lists of source code files:
XS_FILES= Bar.xs \
Foo.xs
C_FILES = Bar.c \
Foo.c
O_FILES = Bar.o \
Foo.o
因此,我们可以使用 O_FILES
变量告诉 MakeMaker 将这些对象用于共享库。
这几乎就是全部了。现在编写 Foo.pm 和 Foo.xs、Bar.pm 和 Bar.xs,其中 Foo.pm 自举共享库,而 Bar.pm 只需加载 Foo.pm。
剩下的唯一问题是如何自举 Bar.xs。这是从 Foo.xs 完成的
MODULE = Cool::Foo PACKAGE = Cool::Foo
BOOT:
# boot the second XS file
boot_Cool__Bar(aTHX_ cv);
如果你有多个文件,这里是你应该从中自举额外 XS 文件的地方。
以下四个文件总结了迄今为止讨论的所有详细信息。
Foo.pm:
-------
package Cool::Foo;
require DynaLoader;
our @ISA = qw(DynaLoader);
our $VERSION = '0.01';
bootstrap Cool::Foo $VERSION;
1;
Bar.pm:
-------
package Cool::Bar;
use Cool::Foo; # bootstraps Bar.xs
1;
Foo.xs:
-------
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
MODULE = Cool::Foo PACKAGE = Cool::Foo
BOOT:
# boot the second XS file
boot_Cool__Bar(aTHX_ cv);
MODULE = Cool::Foo PACKAGE = Cool::Foo PREFIX = cool_foo_
void
cool_foo_perl_rules()
CODE:
fprintf(stderr, "Cool::Foo says: Perl Rules\n");
Bar.xs:
-------
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
MODULE = Cool::Bar PACKAGE = Cool::Bar PREFIX = cool_bar_
void
cool_bar_perl_rules()
CODE:
fprintf(stderr, "Cool::Bar says: Perl Rules\n");
当然,还有一个非常基本的测试
t/cool.t:
--------
use Test::More tests => 1;
use Cool::Foo;
use Cool::Bar;
Cool::Foo::perl_rules();
Cool::Bar::perl_rules();
ok 1;
此提示由 Nick Ing-Simmons 和 Stas Bekman 提供给你。
可以在 Gtk2::CodeGen 和 Glib::CodeGen 中看到实现此目标的另一种方法。
大多数人需要了解的内容(顶级超类)。
ExtUtils::MM_Any
|
ExtUtils::MM_Unix
|
ExtUtils::MM_{Current OS}
|
ExtUtils::MakeMaker
|
MY
实际使用的对象属于 MY 类,它允许你在 Makefile.PL 中通过声明 MY::foo() 方法来覆盖 MakeMaker 中的部分内容。
下面是它的实际工作方式
ExtUtils::MM_Any
|
ExtUtils::MM_Unix
|
ExtUtils::Liblist::Kid ExtUtils::MM_{Current OS} (if necessary)
| |
ExtUtils::Liblist ExtUtils::MakeMaker |
| | |
| | |-----------------------
ExtUtils::MM
| |
ExtUtils::MY MM (created by ExtUtils::MM)
| |
MY (created by ExtUtils::MY) |
. |
(mixin) |
. |
PACK### (created each call to ExtUtils::MakeMaker->new)
注意:是的,这是一个混乱。有关一些历史信息,请参见 http://archive.develooper.com/[email protected]/msg00134.html。
注意:当加载 ExtUtils::MM 时,它会根据当前操作系统从 ExtUtils::MM_* 模块中为 MM 选择一个超类。
注意:ExtUtils::MM_{Current OS} 表示 ExtUtils::MM_* 模块之一,但根据你的操作系统选择的是 ExtUtils::MM_Any。
注意:MakeMaker 使用的主要对象是 PACK### 对象,*不是* ExtUtils::MakeMaker。它实际上是 MY、ExtUtils::MakeMaker、ExtUtils::Liblist 和 ExtUtils::MM_{Current OS} 的一个子类。
注意:MY 中的方法只是被复制到 PACK### 中,而不是 MY 成为 PACK### 的超类。我不记得其基本原理。
注意:ExtUtils::Liblist 应该从继承层次结构中移除,并只需作为函数调用。
注意:为了清晰起见,已省略 File::Spec 和 Exporter 等模块。
MM_Win95 MM_NW5
\ /
MM_BeOS MM_Cygwin MM_OS2 MM_VMS MM_Win32 MM_DOS MM_UWIN
\ | | | / / /
------------------------------------------------
| |
MM_Unix |
| |
MM_Any
注意:每个直接 MM_Unix 子类也是一个 MM_Any 子类。这是一个临时的黑客手段,因为 MM_Unix 用 Unix 特定的代码覆盖了一些 MM_Any 方法。它允许非 Unix 模块看到原始的 MM_Any 实现。
注意:为了清晰起见,已省略 File::Spec 和 Exporter 等模块。
如果你有一个问题想要添加到常见问题解答中(无论你是否知道答案),请
对 MakeMaker github 存储库进行拉取请求
对 MakeMaker github 存储库提出问题
提交 RT 工单
发送电子邮件至 [email protected]
[email protected] 的居民。