<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[てくにかるむ]]></title><description><![CDATA[「エラーをなくすことは非常に有益で時には新しい真実や事実を作り上げるよりも勝る」
ー チャールズ・ダーウィン]]></description><link>http://multix.jp/</link><generator>Ghost 0.6</generator><lastBuildDate>Thu, 30 Apr 2026 19:22:34 GMT</lastBuildDate><atom:link href="http://multix.jp/tag/activeperl/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[ActivePerlのオフライン構築]]></title><description><![CDATA[<p>ActivePerl 本体は実行インストーラで何時でもインストールできるが、ppm や cpan で組み込む追加モジュールはオンライン構築が前提になっている。だが世間から断絶された秘匿ネットワークや、そもそもインターネットに接続するという概念のない PCやサーバに納品したり、機材リプレースで開発環境を再構築しなければならない場面では、USBメモリや DVD-Rからのオフラインインストールが出来なければ話にならない。そもそもバージョンが数世代古くなるとダウンロード元の ppmレポジトリが有料プラン専用に切り替えられて、いつもで気軽に再ダウンロードすることができなくなってしまう。それでは10年以上メンテ契約が続くような案件では、開発当時の状況保存は結構重要な問題になるので予防線を張っておくことは必要なのだ。</p>

<hr>

<h3 id="ppmx">.ppmxファイルの保存</h3>

<p>ppm install を実行すると、レポジトリから .ppmxファイルが tempディレクトリにダウンロードされてインストールされる。だがこのファイルはコマンド終了時に全削除されるため、普段目にすることがない。.ppmxファイル自体は ppm install の引数に渡すことでオフラインインストールできるため、消される前にファイルのコピーを別の場所に取っておけば、あとでおなじ環境を再構築することが容易くなる。</p>

<pre><code class="language-brush:perl gutter:true title:Client.patch">--- C:/Perl64/lib/ActivePerl/PPM/Client.pm      Tue Feb 16 06:02:11 2016
+++ D:/Perl64/lib/</code></pre>]]></description><link>http://multix.jp/activeperl-offline-installation/</link><guid isPermaLink="false">db8a724c-e2cd-49a8-9217-2a6cf62cc337</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><category><![CDATA[ActivePerl]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Fri, 10 Mar 2017 04:08:26 GMT</pubDate><content:encoded><![CDATA[<p>ActivePerl 本体は実行インストーラで何時でもインストールできるが、ppm や cpan で組み込む追加モジュールはオンライン構築が前提になっている。だが世間から断絶された秘匿ネットワークや、そもそもインターネットに接続するという概念のない PCやサーバに納品したり、機材リプレースで開発環境を再構築しなければならない場面では、USBメモリや DVD-Rからのオフラインインストールが出来なければ話にならない。そもそもバージョンが数世代古くなるとダウンロード元の ppmレポジトリが有料プラン専用に切り替えられて、いつもで気軽に再ダウンロードすることができなくなってしまう。それでは10年以上メンテ契約が続くような案件では、開発当時の状況保存は結構重要な問題になるので予防線を張っておくことは必要なのだ。</p>

<hr>

<h3 id="ppmx">.ppmxファイルの保存</h3>

<p>ppm install を実行すると、レポジトリから .ppmxファイルが tempディレクトリにダウンロードされてインストールされる。だがこのファイルはコマンド終了時に全削除されるため、普段目にすることがない。.ppmxファイル自体は ppm install の引数に渡すことでオフラインインストールできるため、消される前にファイルのコピーを別の場所に取っておけば、あとでおなじ環境を再構築することが容易くなる。</p>

<pre><code class="language-brush:perl gutter:true title:Client.patch">--- C:/Perl64/lib/ActivePerl/PPM/Client.pm      Tue Feb 16 06:02:11 2016
+++ D:/Perl64/lib/ActivePerl/PPM/Client.pm      Fri Mar 10 12:12:03 2017
@@ -1328,6 +1328,9 @@
                    if ($save_len != $len) {
                        die "Aborted download ($len bytes expected, got $save_len).\n";
                    }
+use File::Copy;
+my $File = File::Basename::basename($save, '.tgz');
+File::Copy::copy($save, $File.'.ppmx');
                }
                # XXX An MD5 checksum for the tarball would be a good thing
            }
</code></pre>

<p>5.18以降に通用するこのパッチを当てると、カレントディレクトリにダウンロードされた .ppmxファイルが残されるようになる。依存関係で複数のモジュールが取得された場合はその全てが残される。このコードを見れば解るが、 .ppmxファイルの実態は単なる .tgzアーカイブだ。わざわざ拡張子を代えているのは、そうしないと ppmコマンドがオフラインインストール用ファイル名として認識しないからである。</p>

<p>なお ppmコマンドは自前のデータベースとキャッシュでダウンロード履歴を管理しており、毎回必ずしもファイルダウンロードが発生するわけではない。情報は以下のフォルダに保存されているので、作業前にこれを空にしてやればよい。</p>

<pre><code>%LOCALAPPDATA%\ActiveState\ActivePerl
</code></pre>

<p>保存しておいた .ppmxファイルは、ppm install でインストールすることが出来るが、依存関係にあるサブモジュールを先にインストールしておかないと、オンラインで取りに行こうとしてしまう。そこで複数の .ppmxファイルから .ppdファイルを取り出し、そこに書かれた依存関係リストを読み出し、優先順位を付けて順次処理することになる。</p>

<pre><code class="language-brush:perl gutter:true title:LocalUpdate.pl">#!C:/Perl/bin/perl.exe
use utf8;  
use strict;  
use Archive::Tar;  
use File::Basename;  
use File::Find;  
use Cwd;

my $cwd = $ARGV[0] // getcwd;  
print "Search directory: ", $cwd, "\n";  
my(@ppmx_list, %ppmx_hash, %ppmx_path);  
find(sub {  
    if (m/\.ppmx$/io) {
        $ppmx_path{$_} = $File::Find::name;
        push @ppmx_list, $_;
    }
}, $cwd);
foreach my $ppmx (@ppmx_list) {  
    $ppmx_hash{$ppmx} = 9999;
}
foreach my $ppmx (@ppmx_list) {  
    my $tar = Archive::Tar-&gt;new();
    $tar-&gt;read($ppmx_path{$ppmx}, 1);
    foreach my $file ($tar-&gt;get_files()) {
        if ($file-&gt;{name} =~ /\.ppd$/io) {
            foreach my $line (split /\n/, $file-&gt;{data}) {
                if (my $require = ($line =~ m/REQUIRE NAME="([^\"]+)"/o)[0]) {
                    $require =~ s/::/-/go;
                    foreach my $hash (keys %ppmx_hash) {
                        $ppmx_hash{$hash}-- if 0 == index lc($hash), lc($require);
                    }
                }
            }
        }
    }
}
undef @ppmx_list;  
while (my($key, $var) = each %ppmx_hash) {  
    push @ppmx_list, sprintf "%04u %s", $var, $key;
}
foreach my $ppmx (sort {lc $a cmp lc $b} @ppmx_list) {  
    $ppmx =~ s/^\d+ //o;
    my $command = "ppm install ".$ppmx_path{$ppmx}." --nodeps";
    print $cwd, "&gt; ", $command, "\n";
    system $command and die "$!";
    print "\n";
}

1;  
__END__  
</code></pre>

<p>このコードは .ppmxファイルを含む親ディレクトリを引数に取る。引数は複数でもよく、省略されればカレントディレクトリを起点にする。-nオプションは ppmコマンドに --nodeps を付加する。-n オプションなしで起動すると依存解決に失敗した場合（依存する .ppmxが見つからない場合）レポジトリからのダウンロードが生じる。</p>

<hr>

<h3 id="ppmx">.ppmxファイルの作成</h3>

<p>.ppmxファイルの実態は .tgzで固められた .ppdファイルと blibディレクトリだ。cpanでソースからビルドしたものを .ppmxファイルにして保存しておくと、再ビルドする手間を減らせる。cpanの作業ワークは <code>C:\Perl64\cpan\build</code> または <code>C:\Perl\cpan\build</code> にある。</p>

<p>例えば既に cpan install に成功した <code>Foo::Bar</code> モジュールに対応する <code>Foo-Bar-0.1010-4UofsT</code> というディレクトリがあったなら、まずそこに cd して <code>dmake ppd</code> を実行<sup id="fnref:1"><a href="http://multix.jp/activeperl-offline-installation/#fn:1" rel="footnote">1</a></sup>する。すると <code>Foo-Bar.ppd</code> というファイルが作成されるだろう。</p>

<pre><code class="language-brush:bash">&gt; cd C:\Perl64\cpan\build\Foo-Bar-0.1010-4UofsT
&gt; dmake ppd
&gt; type Foo-Bar.ppd
&lt;SOFTPKG NAME="Foo-Bar" VERSION="0.1010"&gt;  
    &lt;ABSTRACT&gt;foobar foobar&lt;/ABSTRACT&gt;
    &lt;AUTHOR&gt;Charlie Root &amp;lt;foobar@example.com&amp;gt;&lt;/AUTHOR&gt;
    &lt;IMPLEMENTATION&gt;
        &lt;PERLCORE VERSION="5,006,0,0" /&gt;
        &lt;REQUIRE NAME="Carp::" /&gt;
        &lt;REQUIRE NAME="Cwd::" VERSION="3.16" /&gt;
        &lt;REQUIRE NAME="Exporter::" /&gt;
        &lt;REQUIRE NAME="strict::" /&gt;
        &lt;REQUIRE NAME="vars::" /&gt;
        &lt;ARCHITECTURE NAME="MSWin32-x64-multi-thread-5.24" /&gt;
        &lt;CODEBASE HREF="" /&gt;
    &lt;/IMPLEMENTATION&gt;
&lt;/SOFTPKG&gt;  
</code></pre>

<p>REQUIRE 行は依存をあらわす。ARCHITECTURE 行はインストール先のプラットフォームを限定するものだが、ピュアPerlで書かれた環境非依存モジュールの場合は <code>noarch</code> に書き換えることでインストール先を選ばないようにすることもできる。</p>

<pre><code class="language-brush:xml ">&lt;ARCHITECTURE NAME="noarch" /&gt;  
</code></pre>

<p>.ppmx(.tgz)ファイルを作るための tar+gzアーカイバは、C:\Perl64\bin に <code>ptar.bat</code>としてインストールされている。すでにパスは通っているので次のようにすれば .ppmxファイルができあがる。</p>

<pre><code class="language-brush:bash ">&gt; ptar -v -c -C -z -f foo-bar.ppmx blib foo-bar.ppd
</code></pre>

<p>この際注意が必要なのは、.ppmxと .ppdのファイル名は一致させておかなければならない点だ。.ppmxファイル名にバージョン番号を付加する一般的なルールに従う場合は、.ppdファイル名にもバージョン番号を付加しておく。もちろんそのバージョン番号は SOFTPKG行の VERSIONと一致しているべきだ。</p>

<pre><code class="language-brush:bash ">&gt; ren foo-bar.ppd foo-bar-0.1010.ppd
&gt; ptar -v -c -C -z -f foo-bar-0.1010.ppmx blib foo-bar-0.1010.ppd
</code></pre>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>dmake.exe は cpan初期化時に C:\Perl64\sit\bin にインストールされているはずだ。 <a href="http://multix.jp/activeperl-offline-installation/#fnref:1" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item></channel></rss>