モダンPerlの世界へようこそ

第26回ShipIt:モジュールのリリースをもっと手軽に

CPANにモジュールをアップロードする前に

第23回から続けてきたシリーズの一環として、今回はモジュールをCPANにアップロードするときのツールをまとめてみます。おそらくこのようなツールに興味を持つような方なら当然 perlnewmodperlmodlibあるいはCPANやPAUSEについてのよくある質問のページ[1]くらい読んでいるでしょうから大丈夫だろうとは思っていますが、今回とりあげるツールは使い方を間違えると周囲に多大な迷惑をかける可能性があります。一度CPANにアップロードされたファイルは、たとえミスであっても数日中には世界中のミラーにコピーされますし、BackPANと呼ばれる保管庫には半永久的に保存されますので、うっかり無意味な(あるいは部外秘の)モジュールを公開してしまうことがないよう、また、ほかの人の迷惑になるような名前空間を占有してしまわないよう、実際に試してみる場合はなるべくそれぞれのモジュールのソースコードを読んで、おおよその挙動を把握してからにすることをおすすめします。また、ここからはみなさんが何かCPANにアップロードできるモジュールを持っているという前提で話をしますので、そのようなモジュールがない方はまず先に名前をあげたドキュメントやFAQを参考に、何かモジュールを書いてみるところから始めてください。

リリース前の定型処理

さて、はじめてモジュールをアップロードするときにせよ、アップロード済みのモジュールを更新するときにせよ、モジュールをCPANにアップロードするときにはいくつかお決まりの手順をふむ必要があります。たとえば、CPANには名前もバージョンも同じディストリビューションは二度とアップロードできない決まりになっているので、少なくとも更新の際にはディストリビューションのバージョンを付け替える必要がありますし、同じ理由でMETA.ymlなどのメタデータや、Changesのような履歴も更新しなければなりません。壊れたモジュールをリリースしてしまうことがないようにリリース前にはかならずテストを実行しておきたいところですし、バージョン管理システムを使いこなしている人ならタグを切ったりアーカイブを保存したりといった作業も発生します。そこまですませていよいよファイルのアップロード、というこの作業自体も毎度おなじみの定型処理ですね。

ミスをしたときの影響の大きさを考えると、ある程度までは目視しながら手を動かしたほうが安心できますが、それにしてもこのような作業を毎回手で行うのはいささか面倒ですし、手作業のままでは作業漏れも起こります。そのため、この分野も昔から多かれ少なかれ処理を自動化する試みが繰り返されてきました。

たとえば、ExtUtils::MakeMakerを使っている場合、アーカイブを作成するときの処理の多くはmake distひとつで片付けられるようになっています。実際にどのような処理が行われているかはMakefileに書いてある通りですが、たとえばディストリビューションをアーカイブするための作業ディレクトリを掘って、そこにMANIFESTに指定されているファイルをコピーし、そのディレクトリ内でMETA.ymlを作成、そのMETA.ymlをMANIFESTに追加、そのディレクトリをtarで固めて作業ディレクトリを削除、そのtarをさらにgzipで圧縮、というのがよくある作業の内訳になりますModule::Buildの場合は./Build distで同じようなことができます⁠⁠。

また、make distのかわりにmake disttestを実行すれば、ふだんの作業ディレクトリではなくアーカイブ用のディレクトリのなかでテストを実行できますし(このようにすることでテストが配布対象外のファイルに影響されなくなります⁠⁠、make manifestでMANIFESTファイルを更新したり、make distcheckでMANIFESTされていないファイルの確認をできたりもします(これらは実際にはExtUtils::Manifestというモジュールの機能を利用しています⁠⁠。Windows環境向けにzipで固めたアーカイブがほしければmake zipdist、ppm(Perl Package Manager)というPerl用パッケージ管理システム向けのメタファイルを作成したければmake ppdという具合に、CPAN以外のパッケージ管理システムのこともある程度までは考慮されています。

CPANへのアップロード

とはいえ、ExtUtils::MakeMaker(やModule::Build)が面倒を見てくれるのは汎用的なディストリビューションの作成(とインストール)まわりくらいなので、CPANへのアップロードにはもっぱら別のツールが利用されてきました。

Perlで書かれた最初期のものとしては、ニール・バウアーズ(Neil Bowers)氏が1998年に公開したcpan-uploadというスクリプトがあげられます。これはFTPでPAUSEのincomingディレクトリにファイルをアップロードしたあと、HTTPでPAUSEのフォームを叩いてアップロードしたファイルと作者を紐づけるというもので、PAUSEのIDやパスワードは毎回コマンドラインオプションで指定してもかまいませんが、ふつうはホームディレクトリに.pauseというファイルを用意して、そこにユーザ名やパスワード、⁠FTP時に送信する)メールアドレスなどを書いておきます(.pauseはほかのユーザから見えないようにパーミッションのチェックが行われますが、それでは不都合がある環境の場合はスクリプトに手を加えるか、毎回パスワードなどを入力するようにしてください⁠⁠。.pauseファイルが用意してある場合の一般的な作業の流れはおおよそこうです。

> cd Your-Distribution-Name
> perl Makefile.PL
> make
> make disttest
> make dist
> cpan-upload -verbose Your-Distribution-Name-0.01.tar.gz

このcpan-uploadは長らく標準的なアップロードの仕方として紹介されてきましたが、FTPでアップロードしたファイルをあとからHTTP経由で紐づけるというやり方は(歴史的には意味があったにせよ)いささか冗長です。そのため、2006年にはLiveJournalの創始者として知られるブラッド・フィッツパトリック(Brad Fitzpatrick)氏が直接HTTP経由で投稿できるように改造したものをcpan-upload-httpの名前でCPANにアップロードします。

これはファイル名(と内部の実装)が変わっただけで、使い方や設定の仕方についてはcpan-uploadと同じなのですが、cpan-upload-httpにもまだ弱みはありました。これらのスクリプトはCPANからインストールしようと思うと一手間かける必要がありましたし[2]⁠、ほかのモジュールやスクリプトから利用する場合には(移植性の低い)system関数などを経由しなければなりません。

そのため、2009年にはリカルド・シグネス(Ricardo Signes)氏がcpan-upload-httpをさらにリファクタリングして、CPAN::Uploaderというモジュールをリリースしました。これにもcpan-uploadというコマンドが付属していますが、こちらはニール・バウアーズ氏のオリジナルとは異なり、HTTP経由でファイルをアップロードしてくれるため、いまではcpan-upload-httpをわざわざダウンロードしてくる必要は(同名のコマンドを直接呼んでいるツールを利用する場合を除いて)ほとんどなくなりました。.pauseファイルの書式はもともとのcpan-upload(やcpan-upload-http)と同じですので、従来のスクリプトを利用していた方はそのまま移行できます。

ShipIt

cpan-upload(-http) は単体でも十分有用なスクリプトですし、コマンドラインから離れたがらないコアなモジュール作者たちにとっては生命線ともいえるものですが、ブラッド・フィッツパトリック氏はさらにリリースの手間を軽減するべく、翌2007年にはShipItというアプリケーションをリリースしています。氏は同年のYACP::Asiaにも来日してくれたので当時のブームを覚えている方も少なからずいると思いますが、これを使うと先ほどのリリースの手順はここまで短縮できます。

> cd Your-Distribution-Name
> shipit

ShipItをはじめて利用する場合はそのディストリビューション専用の設定ファイルがないといわれるかもしれません。前回紹介したツールのなかには最初からShipItの設定ファイルを用意してくれるものもありますが、ここでは表示される指示にしたがって「shipit --write-config」を実行しておきましょう。このコマンドを実行すると、このような設定ファイルが自動生成されます。

# auto-generated shipit config file.
steps = FindVersion, ChangeVersion, CheckChangeLog, DistTest, Commit, Tag, MakeDist

# svn.tagpattern = MyProj-%v
# svn.tagpattern = http://code.example.com/svn/tags/MyProj-%v

# CheckChangeLog.files = ChangeLog, MyProj.CHANGES

この.shipitという設定ファイルは、それぞれの必要に応じて適宜修正を加えてください。標準的にはメインモジュールのバージョンを探して(FindVersion)変更し(ChangeVersion⁠⁠、Changesのような更新履歴の修正が済んでいるか確認し(CheckChangeLog⁠⁠、リリーステストを行い(DistTest⁠⁠、バージョン管理システムにコミットし(Commit⁠⁠、タグを切り(Tag⁠⁠、リリース用のディストリビューションを生成する(MakeDist)という流れになっていますが、CPANにモジュールをアップロードしたい場合はstepsの最後に「, UploadCPAN」を付け加える必要がありますし、⁠git.push_to = origin」という行を追加すればgitのコミット先を指定することもできます。CPANにはTwitterにつぶやいたり、すべてのモジュールのバージョンが一致しているか確認するモジュールも登録されていますし、自分でプラグインを書けば、ShipItと同時に特定のパッケージ管理システムを更新したり、社内向けのIRCにメッセージを飛ばしたり、特定のアプリケーションを再起動したりといった作業も簡単にできます。

たとえば、リリースのときにかならずメールを送りたい相手がいるなら、このようなモジュールを書いておけばよいでしょう。ここでは.shipitで設定した相手に変更履歴の差分を送信していますが、もう少し一般的に説明すると、.shipitで設定したい値がある場合はinit経由で取り出し、実際の作業はrunのなかで実行する、というのがShipIt::Step::* の基本的な書き方です。また、あらかじめどのような動作になるか確認できるよう、--dry-runオプションがついているときは実際の処理をスキップするようにしておくのも大事なことです。

package ShipIt::Step::MySendmail;

use strict;
use warnings;
use base 'ShipIt::Step';
use Email::Sender::Simple 'sendmail';
use Email::MIME;
use Email::MIME::Creator;

sub init {
    my ($self, $conf) = @_;
    $self->{mailto} = $conf->value('Sendmail.to');
}

sub run {
    my ($self, $state) = @_;

    my $distfile = $state->distfile;
    my $msg = "New features of $distfile\n";
    $msg .= $state->vc->local_diff($_) for $state->changelog_files;
    my $email = Email::MIME->create(
        header => [
            From    => 'my_address@localhost',
            To      => $self->{mailto},
            Subject => "[Release] $distfile",
        },
        body => $msg,
    );

    if ($state->dry_run) {
        print "DRY-RUN.  ", $email->as_string, "\n";
        return;
    }

    sendmail($email);
}

1;

このようなモジュールは、汎用性があるなら独立したディストリビューションの一部としてもよいでしょうし、そのディストリビューションでしか使わないものなら、ディストリビューション内のリポジトリに入れたうえで、MANIFEST.SKIPや.gitignoreなどを利用してディストリビューションからは排除するようにしてもよいかもしれません。CPANにモジュールをアップロードしない場合でも案外便利に使えますので、これまで利用してこなかった方は一度試してみるとよいでしょう。

Dist::Zilla

このようなリリースまわりの面倒を見てくれるモジュールとしては、ほかにもブライアン・フォイ(brian d foy)氏のModule::Release(これはCPANだけでなく、SourceForgeへのリリースなども行ってくれます)ヤニク・シャンプー(Yanick Champoux)氏のDist::Releaseがありますが、ここではもうひとつ、リカルド・シグネス氏が2008年から開発を進めているDist::Zillaについても触れておきましょう。これもShipItと同じくリリース前にさまざまな処理をしてくれるアプリケーションですが、こちらは単にバージョンをあげたりファイルのアップロードを行ったりするだけでなく、前回とりあげたひな形作成ツールが用意してくれるようなMakefile.PLやREADMEなども自動生成しようとするのが大きな特徴で、既存のひな形作成ツールとの相性はあまりよくありませんが、Mooseのロールを使ってプラグインの実行順序などを細かく制御できるようになっているため、Mooseの流儀に慣れた人には拡張しやすいものになっています。

Dist::Zillaを使う場合は、プロジェクトごとにdist.iniという設定ファイルを用意します。一からプロジェクトを始める場合は、コマンドラインから「dzil new Foo::Bar」を実行すると、Foo-Barというディレクトリが作成され、その下にこのようなdist.iniファイルが生成されます(既存のプロジェクトをDist::Zilla管理に切り替える場合は、プロジェクトのディレクトリに移動したあと「dzil new .(ピリオド⁠⁠」を実行すると同様のdist.iniを生成できます⁠⁠。

name    = Foo-Bar
version = 1.000
author  = Kenichi Ishigaki
license = Perl_5
copyright_holder = Kenichi Ishigaki

[@Classic]

これはそれぞれディストリビューション名、最初のバージョン番号、作者名、Software::Licenseモジュールのサブクラス名、著作権保持者名の指定で、最後の [@Classic] はDist::Zilla::PluginBundle::Classicというプラグインバンドルを利用する、という指示です。これは、ディレクトリ内のファイルをかき集め(AllFiles⁠⁠、ドットファイルなどのよくある不要なファイル(PruneCruft)や、MANIFEST.SKIPで指定されているファイル(ManifestSkip)を除外し、META.yml(MetaYAML)やライセンス(License⁠⁠、README(Readme)を追加し、ディストリビューション内のすべてのモジュールにバージョン指定を加え(PkgVersion⁠⁠、PODにもバージョンを指定する一文を付け加え(PodVersion⁠⁠、PODテスト(PodTests)や作者用のテストなどを付け加え(ExtraTests⁠⁠、binディレクトリやshareディレクトリがあればそれもパッケージに含まれるようにし(InstallDirs⁠⁠、Makefile.PL(MakeMaker)とMANIFEST(Manifest)を生成する、という設定をひとまとめにしたものですが、特定の処理が気に入らない場合は、[@Classic] の行をこのように書きかえると、特定の設定だけ除外することができます。また、別のプラグインを追加したい場合は、dist.iniにプラグイン名を追加します(実行順序はそのプラグインに適用されているロールによるので、dist.ini内で記述の順序を気にする必要はありません⁠⁠。

[@Filter]
bundle = @Classic
remove = PodVersion

[AutoVersion]

Dist::Zillaを使ううえで注意が必要なのは、依存モジュールの扱いです。従来の書き方に慣れた人はついMakefile.PLなどで指定すればいいと思ってしまうところですが、デフォルトのDist::ZillaではMakefile.PLやMETA.ymlなどを作成するときにdist.iniのなかで指定した依存モジュールの一覧を参照するようになっているため、⁠Makefile.PLの自動生成をしたくない場合でも)このようなPrereqセクションを用意するか、あるいはジェローム・ケラン(Jerome Quelin)氏のAutoPrereqプラグインを利用して依存モジュールを機械的に検出する必要があります(現状ではbuild_requiresなどのオプションには対応していないようです⁠⁠。

[Prereq]
Test::More = 0.88

その他の使い方については、Dist-Zilla-PluginBundle-RJBSのように自分の流儀をまとめたプラグインバンドルがいくつか公開されているので、参考にしてみるとよいでしょう。

Githubからのリリース

ちなみに、CPANにモジュールを登録する場合はかならずこのようなアップロードツールを使わなければならない、ということはありません。昨年ピッツバーグで開催されたYAPC|10(YAPC::NA)では、マイク・シリ(Mike Schilli)氏がgithubのダウンロードファイルをPAUSEに取り込ませる方法を紹介して一部の話題になっていました。詳しくは氏のブログ記事やそのコメント欄にある通りですが、ふだんからgitリポジトリ内に適切なMETA.ymlやMANIFEST、incディレクトリなどを入れている(make dist時に自動生成したりせず、tar/gzipで圧縮するだけで過不足ないディストリビューションができるようにしている)場合は、gitでタグを切るだけでgithub上にダウンロード可能なファイルが生成されるので、そのURLを少々加工してPAUSEに渡してやれば、cpan-uploadのようなツールに頼らなくてもCPANにモジュールをリリースできる、というのがその仕組みです。いまのところそのやり方に追随しているモジュールもなく、あまり実用的なやり方とはいえませんが、自分のサイトなどでダウンロード可能なアーカイブを提供している方なら、コミット時のフックポイントに細工をして、タグを切ったらリリース可能なアーカイブを作成してCPANに登録、という仕掛けをつくってみるのもよいかもしれません。

おすすめ記事

記事・ニュース一覧