Humanity

Edit the world by your favorite way

extlibに依存モジュールを突っ込もうとしたらXS依存モジュールのせいでCan't locate loadable objectと言われたでござるの巻

拙作のはてだらっぱー(もう読み方とかどうでもいいよ)をついカッとなってコアでないモジュールに依存させてしまったんだけど、
コアでないモジュールをextlibに突っ込めばいい、というのをどこかで聞いたなと思って再度ググったら牧大輔さんのブログがヒットした。
それで公開されてるスクリプトをお借りして色々自分用に改造したのがこちら。

#!/usr/bin/env perl

# from http://mt.endeworks.jp/d-6/2009/02/extlib.html
# - CPANに登録されてないモジュールをCPAN::Shell->expandany()したときundefになることに対応
# - File::Tempが削除できるようにカレントディレクトリに戻るようにした
# - スキップしたモジュールを最後に表示
# TODO
# - モジュールがインストールされてるならCPANに取りにいかないようにする


use strict;
use warnings;
use CPAN;
use Module::CoreList;
use Module::ScanDeps;
use File::Find::Rule;
use File::Spec;
use File::Temp qw(tempdir);
use Cwd qw(getcwd);

if (scalar @ARGV != 2) {
    print <<EOM;
Usage: deps2extlib.pl target [extlib]

target - the directory or script that you want to compile 
         dependencies against.
extlib - the path where you want to install your dependecies.

EOM
    exit 1;
}

my $target = $ARGV[0];
my $dir = File::Spec->rel2abs($ARGV[1] || 'extlib');

unshift @INC, $dir;

my @files;
if (-d $target) {
    @files = File::Find::Rule
        ->file()
        ->or(
            File::Find::Rule->name('*.pl'),
            File::Find::Rule->name('*.pm')
        )
        ->in($target);
} else {
    @files = ($target);
}

my $h = scan_deps_runtime(
    compile => 1,
    files => \@files,
    recurse => 1,
);

my %seen;
my $corelist = $Module::CoreList::version{ $] };

CPAN::HandleConfig->load unless $CPAN::Config_loaded++;
my $home = tempdir(CLEANUP => 1);
local $CPAN::Config->{cpan_home} = $home;
local $CPAN::Config->{keep_source_where} = File::Spec->catdir($home, 'sources');
local $CPAN::Config->{build_dir} = File::Spec->catdir($home, 'build');
local $CPAN::Config->{prerequisites_policy} = 'follow';
local $CPAN::Config->{makepl_arg} = "INSTALL_BASE=$dir";
local $CPAN::Config->{mbuild_arg} = "--install_base=$dir";
local $CPAN::Config->{mbuild_install_arg} = "--install_base=$dir";

my $cwd = getcwd;
my @skipped;

foreach my $key (sort keys %$h) {
    next if $key =~ /^unicore/;
    next if $key =~ /^auto/;

    $key =~ s/\//::/g;
    $key =~ s/\.pm$//;

    # コアモジュールであればスキップ
    next if exists $corelist->{$key};

    my $mod = CPAN::Shell->expandany($key);
    unless (defined $mod) {
        # 自分用に作ったCPANにないモジュールなど
        push @skipped, $key;
        print "skipped $key...\n";
        sleep 1;
        next;
    }

    my $dist = $mod->cpan_file;
    if ($dist =~ /\/perl-/) {
        warn "won't try to install perl, but we found dependency on $dist";
        next;
    }
    next if $seen{ $dist }++;

    CPAN::Shell->install($dist);
}


chdir $cwd or warn "can't chdir to $cwd: $!";

print "\n\n\ninstalled all dependency files of $target to $dir.\n";

if (@skipped) {
    warn sprintf "skipped to install %s\n", join ', ', @skipped;
}

で、早速extlibに依存してるモジュールを入れてみた。が・・・

その後Strawberry Perlの方で検証したら「Can't locate loadable object for module Want in @INC」という見たこともないエラーが出た。
perldoc perldiagで見てみると

(F) The module you loaded is trying to load an external library. like for example. "foo.so" or "bar.dll", but the DynaLoader module was unable to locate this library. See DynaLoader.

外部モジュール?ってことはどうやらXSに依存してる・・・らしい。えー。


解決策

  • それぞれの環境でコンパイルしたモジュールを置いておく
    • めんどくさいし自分の手元にある環境はLinuxWindowsだけなので却下。
  • 自動で依存モジュールをインストールするビルドスクリプト(Build.PLなど)を置く
    • ネットワーク環境がない場合にまずい
    • インストールがめんどい&英語
    • 最新のモジュールがテストでこけたり互換性がなかったら\(^o^)/

最後のはめったにないと思う。
この方法が一番現実的?

  • コアでないモジュールに依存しない
    • もうコアモジュールしか使えないのはいやだお・・・(AA略)

コアモジュールのみでがりがり書くのがかっこいいと思っていた内は良かったけどいい加減めんどくさくなってきた。
Perl6::Sayが使えないので代わりにputsにしてる。
間接ファイルハンドルとかは処理してなくてただ改行をくっつけるだけ。
1byte多いしsayの方がタイプしやすいよ!
(勘違いしない為にputsにしてきたけどこれはsayにしてもいいかも)


結局は

Build.PLを置くことにした。
大体のインストールはEnter押してれば済むので。


あと元のはてダラもコアでないモジュールをロードしてたのを思い出した。
なーんだ。よーしお父さんMoose使っちゃうぞー^^