OrePANを作っていて一番難しいのはディストリビューションからモジュールのパッケージ名とバージョンを抜き出す部分。ExtUtils::MakeMakerModule::MetadataPAUSEのソースを参考にして書いているところ。

例えば、Log::Minimalであれば、パッケージを展開して

$ tar zxf Log-Minimal-0.04.tar.gz
$ cd Log-Minimal-0.04
$ find -name "*.pm" | grep "VERSION"

のような処理をします。(実際はperl)

これが一筋縄ではいかない作業でかなり苦労している

最初に引っかかったのがcommon::sense。これは*.pmファイルがない。実際にはsense.pm.PLというファイルがあり、実行することでperlのバージョンにあったモジュールを作りだす。

まずこれに対応するために、対象とするファイルを

\.pm(\.PL)$

この正規表現にする必要がある。また、sense.pm.PLではDATA以下にpackageとVERSIONがあるので、DATA以下もパースしなければならない。Module::Metadataではこれに対応できなかった。

次に実行しないとバージョンがわからないタイプ。Encodeでは

our $VERSION = sprintf "%d.%02d", q$Revision: 2.42 $ =~ /(\d+)/g;

と、VERSION行が書かれている。このような場合はバージョン行を特定したあと、evalで実行してバージョンを得る必要がある。ExtUtils::MakeMakerだと

    if ( m{(?<!\\) ([\$*]) (([\w\:\']*) \bVERSION)\b .* =}x ) {
        my $eval = qq{
            package ExtUtils::MakeMaker::_version;
            no strict;
            BEGIN { eval {
                undef *version;
                require version;
                "version"->import;
            } }
            local $1$2;
            \$$2=undef;
            do {
                $_
            };
            \$$2;
        };
        local $^W = 0;
        $result = eval($eval);  ## no critic
}

こうやって実行している。OrePANではこのコードをコピーして使ってる

DBIもはまったものの一つ

# $Id: DBI.pm 14568 2010-12-14 15:23:58Z mjevans $
# vim: ts=8:sw=4:et
#
# Copyright (c) 1994-2010  Tim Bunce  Ireland
#
# See COPYRIGHT section in pod text below for usage and distribution rights.
#

require 5.008_001;

BEGIN {
$DBI::VERSION = "1.616"; # ==> ALSO update the version in the pod text below!
}

これがDBI.pmの頭からのコピー。package宣言より先にpackage名を含んだVERSIONが来ている。なんとなくVERSION宣言がpackage宣言よりあとにあることを期待してコードを書いていると裏切られる例。

どうやって対応したかというと、

while (<$fh>) {
    if ( m{^ \s* package \s+ (\w[\w\:\']*) (?: \s+ (v?[0-9._]+) \s*)? (?:\s+)?;  }x ) {
        push @pkgs, [$1, $2];
    }
    elsif ( m{(?<!\\) ([\$*]) (([\w\:\']*) \bVERSION)\b .* =}x ) {
        .. 実行する部分 ..
        push @pkgs, [$package, $version] if $package;
        $pkgs[-1]->[1] = $version;
    }
}

とりあえず見つけた順に配列に追加していって最後に判断する。package名がないVERSIONの場合はおそらくその前にpackage宣言があると仮定して、配列の一番最後のversionとして利用する。

DBIがそれなんだけど、1つのモジュール内で複数のpackage宣言がある場合、配列のなかから適したpackage名を選ぶ必要がある。そのために、OrePANではModule::Metadataを参考にファイル名と照らし合わせている

my $basename  = fileparse("$parsefile");
$basename =~ s/\..+$//;
my @candidates = sort { !$a->[1] <=> !$b->[1] }
    grep { $_->[0] =~ m/$basename$/  } @pkgs;
return @{$candidates[0]} if @candidates;

ぱっとみ分かりにくコードだけど、ファイル名と一致するモジュールをgrepして、バージョンがあるものを先に取り出している。そして見つかったものの最初を採用している。

ここまでやってきてまだ解決できないことがいくつかある。例えば Module::Setup::Flavor::PBP。コード中にバージョン名はなく、DATA以下のモジュールのテンプレート(フレーバ)に

package Module::Setup::Flavor::PBP;
use strict;
use warnings;
use base 'Module::Setup::Flavor::SelectVC';

1;
__DATA__
...
---
file: lib/____var-module_path-var____.pm
template: |
  package [% module %];

  use version; our $VERSION = qv('0.0.1');

このVERSION名を拾ってしまう。実際CPANでもそうなってる!

そしてこういうのも難しい(実際にみたわけではない

package Hoge;

use strict;
use warnings;

{
    package Hoge::Foo;
    our %foo = ();
}

our $VERSION = 0.01;

1;

こうなると構文を解析するか、実行してしまうしかないんじゃないかと思う。

かわった書き方をしたモジュールをCPANにアップロードした際、自分が指定したバージョンでインデックス登録されているか届くメールで確認したり、CPAN Searchで確認するのがいいかもしれない

このブログ記事について

このページは、Masahiro Naganoが2011年2月 7日 18:59に書いたブログ記事です。

ひとつ前のブログ記事は「OrePANとcpanmでCPANの部分ミラーを作ってCPANモジュールを管理する」です。

次のブログ記事は「MySQLのserver-idの振り方」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

OpenID対応しています OpenIDについて
Powered by Movable Type 4.27-ja