目錄

名稱

ExtUtils::MakeMaker::FAQ - MakeMaker 常見問題

說明

ExtUtils::MakeMaker 的常見問題、技巧和提示。

模組安裝

如何將模組安裝到我的家目錄?

如果您不是 Perl 管理員,您可能沒有權限將模組安裝到其預設位置。處理此問題的方法(您需要手動執行的步驟少很多)為 perlbrewlocal::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";
如何讓 MakeMaker 和 Module::Build 安裝到相同的位置?

Module::Build(0.28 版)支援兩種方法,可將其安裝到與 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 vs INSTALL_BASE 來自 Module::Build::Cookbook

PREFIX 的行為很複雜,而且高度依賴於 Perl 的設定方式。安裝位置的結果會因機器而異,甚至同一台機器上的不同 Perl 安裝也會不同。因此,很難說明 prefix 會將模組放在哪裡。

相較之下,INSTALL_BASE 有可預測、容易說明的安裝位置。現在 Module::Build 和 MakeMaker 都具有 INSTALL_BASE,除了保留現有的安裝位置之外,幾乎沒有理由使用 PREFIX。如果您要開始新的 Perl 安裝,我們建議您使用 INSTALL_BASE。如果您有透過 PREFIX 安裝的現有安裝,請考慮將其移至與 INSTALL_BASE 相符的安裝結構,並改用該結構。

使用替換產生 *.pm 檔案,例如 $VERSION

如果您想要針對當地條件設定模組檔案,或自動插入版本號碼,可以使用 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/ 都已移除。

常見錯誤和問題

"沒有規則可以建立目標 `/usr/lib/perl5/CORE/config.h',由 `Makefile' 所需"

正如它所說的,您缺少該檔案。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 是什麼,它與 MakeMaker 有什麼關係?

Module::Build 是 Ken Williams 的一個專案,目的是取代 MakeMaker。它的主要優點是

  • 純 perl。沒有 make,沒有 shell 指令

  • 更易於自訂

  • 更簡潔的內部

  • 更少的雜質

Module::Build 長期以來一直是 MakeMaker 的官方繼承人。然而,近年來它的開發和採用速度都變慢了,目前尚不清楚它的未來會如何。話雖如此,Module::Build 為某個東西成為 MakeMaker 的繼承人奠定了基礎。MakeMaker 的維護人員長期以來一直表示它是一個死胡同,應該保持運作,同時謹慎地擴充新功能。

模組撰寫

如何在不手動重設的情況下,讓我的 $VERSION 保持最新狀態?

你經常想要在主模組發行版中手動設定 $VERSION,因為這是每個人在 CPAN 上看到的版本,而且你可能想要自訂它一點。但對於發行版中的所有其他模組,$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東西,它怎麼會出現在我的MANIFEST中?

META.yml 是由 Module::Build 開創的模組元資料檔案,並自動產生為「distdir」目標(因此為「dist」)的一部分。請參閱"Module Meta-Data" in ExtUtils::MakeMaker

若要關閉其產生,請將 NO_META 旗標傳遞給 WriteMakefile()

如何刪除MANIFEST中沒有的所有內容?

有些人驚訝於 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
     },
     "."
);
我應該在 Windows 上使用哪個 tar?

我們建議使用 Archive::Tar 中的 ptar,版本不低於 1.66,並加上「-C」選項。

我應該在 Windows 上使用哪個 zip 來進行「[ndg]make zipdist」?

我們建議使用 InfoZIP:http://www.info-zip.org/Zip.html

XS

如何防止「物件版本 X.XX 不符合引導參數 Y.YY」錯誤?

XS 程式碼對模組版本號碼非常敏感,如果 Perl 模組中的版本號碼不符,它會抱怨。如果你變更模組的版本號碼,但沒有重新執行 Makefile.PL,舊版本號碼將保留在 Makefile 中,導致 XS 程式碼使用錯誤的號碼建置。

為避免這種情況,你可以透過將這項內容新增至 WriteMakefile() 參數,強制 Makefile 在你變更包含版本號碼的模組時重新建置。

depend => { '$(FIRST_MAKEFILE)' => '$(VERSION_FROM)' }
如何讓兩個或以上的 XS 檔案共存在同一個目錄中?

有時你會需要在同一個套件中擁有兩個或以上的 XS 檔案。有三個方法:XSMULTI、獨立目錄,以及從另一個 XS 進行開機。

XSMULTI

將你的模組結構化,讓它們全部都位於 lib 底下,例如 Foo::Barlib/Foo/Bar.pmlib/Foo/Bar.xs 等。讓你的頂層 WriteMakefile 將變數 XSMULTI 設定為 true 值。

呃,就這樣。

獨立目錄

將每個 XS 檔案放入獨立目錄中,每個目錄都有自己的 Makefile.PL。請確定每個 Makefile.PL 都有正確的 CFLAGSINCLIBS 等。你會需要確定頂層 Makefile.PL 使用 DIR 參照每個目錄。

開機

假設我們有一個套件 Cool::Foo,其中包含 Cool::FooCool::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.pmFoo.xsBar.pmBar.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::CodeGenGlib::CodeGen 中可以看到實現此目的的另一種方法。

設計

MakeMaker 物件層級結構(簡化)

大多數人需要知道的事項(最上層為超類別)。

ExtUtils::MM_Any
        |
ExtUtils::MM_Unix
        |
ExtUtils::MM_{Current OS}
        |
ExtUtils::MakeMaker
        |
       MY

實際使用的物件屬於 MY 類別,它允許您透過宣告 MY::foo() 方法,在 Makefile.PL 中覆寫 MakeMaker 的位元。

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/makemaker@perl.org/msg00134.html 以了解一些歷史。

注意:當載入 ExtUtils::MM 時,它會根據目前的作業系統,從 ExtUtils::MM_* 模組中為 MM 選擇一個超類別。

注意:ExtUtils::MM_{Current OS} 代表 ExtUtils::MM_* 模組之一,但 ExtUtils::MM_Any 除外,它會根據您的作業系統進行選擇。

注意:MakeMaker 使用的主要物件是 PACK### 物件,*而非* ExtUtils::MakeMaker。它實際上是 MYExtUtils::MakeMakerExtUtils::Liblist 和 ExtUtils::MM_{Current OS} 的子類別。

注意:MY 中的方法會直接複製到 PACK### 中,而不是讓 MY 成為 PACK### 的超類別。我不記得其理由了。

注意:ExtUtils::Liblist 應該從繼承層級結構中移除,並直接作為函式呼叫。

注意:File::SpecExporter 等模組已省略,以增加清晰度。

MM_* 層級結構

                               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::SpecExporter 等模組已省略,以增加清晰度。

修補

如果您有任何問題想新增到常見問答集(無論您是否有答案),請

作者

makemaker@perl.org 的使用者。

另請參閱

ExtUtils::MakeMaker