Perl Hackers Hub

最終回 Carmelによる依存モジュール管理
CPANモジュールの更新を高速⁠安全に(1)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはcpanm、Plackの作者としても知られる宮川達彦さんで、テーマは「Carmelによる依存モジュール管理」です。

Carmelとは

規模にかかわらず、Perlでアプリケーションを開発すると、CPANモジュールに依存することは避けられません。書き捨てのワンライナーならともかく、作成したアプリケーションをデプロイ、メンテナンスしていくうえでは、依存しているモジュールのバージョンも管理することが重要になります。ある日、依存しているCPANモジュールに互換性のない変更が入ったせいで、自分のアプリケーションでエラーが出るようになった、という経験は思い当たる方が多いのではないでしょうか。

この問題を解決するソフトウェアがCarmelです。Carmelを使うと、プロジェクトで依存するCPANモジュールを、バージョンも含めて管理できます。開発環境やプロダクション環境にデプロイする際には、開発環境やCIContinuous Integration、継続的インテグレーション)環境で利用していたものとまったく同じバージョンのモジュールをインストールできます。

Carmelは筆者が開発しているソフトウェアで、同じく筆者が開発、メンテナンスしているCarton[1]と互換性があります。

Carmelの使い方

この節では、Carmelの使い方を解説します。

インストール

Carmelは通常のCPANモジュールと同様に、cpanmコマンドなどでインストールできます。CarmelをCPANからインストールします。

> cpanm Carmel
...
Successfully installed Carmel-v0.1.56
1 distribution installed

本稿では、執筆時点(2023年7月)で最新安定版のバージョンv0.1.56を対象に解説します。

cpanfileの作成

Carmelを利用するには、cpanfileの作成が必須になります。cpanfileはプロジェクトが依存しているモジュールを記述するDSLDomain Specific Language、ドメイン特化言語)ファイルで、通常はプロジェクトのルートディレクトリ直下に保存します。基本的には、利用しているモジュールをrequiresでリストしていきます。

cpanfile
requires 'Plack', '1.0048';
requires 'Path::Tiny';
requires 'LWP';

requiresは引数を1つ、または2つ取ります。第1引数には、依存するモジュール名を指定します。特に依存するバージョンがない場合は、第2引数を省略できます。特定の機能やバグ修正など、決まったバージョン以上が必要になる場合には、第2引数にバージョンの範囲を指定します。

上記のcpanfileでは、Plackモジュールのバージョン1.0048以上への依存を指定しています。'1.0048'と記述した場合、⁠1.0048またはそれ以上」という意味になります。特定のバージョンのみを指定したい場合は、'== 1.0048'のように==で指定します[2]。また、複数の条件を,(カンマ)でつなげることもできます。

cpanfile
requires 'Plack', '>= 1.0024, != 1.0036'

この例では、⁠Plackのバージョン1.0024またはそれ以上、ただし1.0036は含まない」という指定になります。

carmelコマンドの使い方

cpanfileを作成したら、carmelコマンドを実行してモジュールを管理していきます。

carmel install ─⁠─ 依存モジュールのインストール

carmel installコマンドは、cpanfileを読み込み、必要となるモジュールをインストールします。

> carmel install
(省略)
Using WWW::RobotRules (6.02)
Using Path::Tiny (0.144)
Using Plack (1.0050)
(省略)
---> Complete! 3 cpanfile dependencies. 45 modules installed.

このとき、cpanfileと同じディレクトリ(この場合カレントディレクトリ)cpanfile.snapshotというファイルが作成されます。

cpanfile.snapshot
# carton snapshot format: version 1.0
DISTRIBUTIONS
  Apache-LogFormat-Compiler-0.36
    pathname: K/KA/KAZEBURO/Apache-LogFormat-Compiler-0.36.tar.gz
    provides:
      Apache::LogFormat::Compiler 0.36
    requirements:
      Module::Build::Tiny 0.035
      POSIX 0
      POSIX::strftime::Compiler 0.30
      Time::Local 0
      perl 5.008001
  Class-Inspector-1.36
    pathname: P/PL/PLICEASE/Class-Inspector-1.36.tar.gz
    provides:
      Class::Inspector 1.36
      Class::Inspector::Functions 1.36
    requirements:
      ExtUtils::MakeMaker 0
      File::Spec 0.80
(省略)

cpanfile.snapshotはスナップショットファイルと呼ばれ、carmel install実行時に解決したモジュールの一覧とそのバージョンなどが記録されます。このファイルをGitリポジトリに追加すれば、git cloneした別のマシンやディレクトリで実行しても、同じバージョンのモジュールが再現されることが保証されます。このファイルのアップデートによって依存するモジュールのバージョンを管理していくことになりますので、このファイルはGitリポジトリなど、ソースコード管理の対象にすることをお勧めします。

また、carmel installした際のローカル環境などを記録した.carmel/MySetup.pmというファイルも作成されます。ただし、こちらは実行したマシン固有のファイルとなりますので、.carmelディレクトリはGitなどのバージョン管理からは除外するとよいでしょう。

carmel listtree ─⁠─ 依存モジュールの一覧

実際にプロジェクトが依存しているモジュールの一覧を見るには、carmel listcarmel treeコマンドが便利です。

> carmel list
Apache::LogFormat::Compiler (0.36)
Class::Inspector (1.36)
Clone (0.46)
Cookie::Baker (0.11)
Cpanel::JSON::XS (4.36)
Date::Parse (2.33)
Devel::StackTrace (2.04)
Devel::StackTrace::AsHTML (0.15)
(省略)
> carmel tree
LWP (6.70)
Encode::Locale (1.05)
File::Listing (6.15)
HTTP::Date (6.05)
Date::Parse (2.33)
HTML::Parser (3.81)
HTML::Tagset (3.20)
HTTP::Message (6.44)
Clone (0.46)
IO::HTML (1.004)
LWP::MediaTypes (6.04)
URI (5.19)
HTTP::CookieJar (0.014)
HTTP::Cookies (6.10)
HTTP::Negotiate (6.01)
Net::HTTP (6.23)
Try::Tiny (0.31)
WWW::RobotRules (6.02)
Path::Tiny (0.144)
Plack (1.0050)
(省略)

listtreeコマンドともに、プロジェクトが直接依存しているモジュールや、そのモジュールがさらに依存しているモジュールが表示されます。treeコマンドでは、その関係がツリー状に表示されます。この例では、PlackやLWPモジュールが依存しているモジュールが数多くあることがわかります。

carmel exec ─⁠─ インストールされたモジュールを利用して実行する

carmel install直後にcpanfileに記述したモジュールを利用しようとすると、次のようなエラーになります。

myapp.pl
#!/usr/bin/env perl
use strict;
use Plack;
warn Plack->VERSION;
> perl ./myapp.pl
Can't locate Plack.pm in @INC (you may need to install the Plack module) ...

エラーが発生するのは、carmel installコマンドでインストールされたモジュールは、利用しているPerlのインクルードパス@INCやローカルディレクトリにインストールされるわけではなく、ユーザーのホームディレクトリ以下に作られるCarmelのリポジトリにインストールされているためです。Carmelで管理しているモジュールを使うには、carmel execコマンドを利用します。

> carmel exec perl ./myapp.pl
1.0050 at ./myapp.pl line 4.

carmel exec下で実行されたPerlランタイムではインクルードパスがオーバーライドされ、Carmelでインストール、管理されたファイルをCarmelリポジトリから読み込むようになります。

carmel update ─⁠─ 依存モジュールのアップデート

carmel installでインストールしたモジュールのバージョンは先述したようにcpanfile.snapshotに保存され、同じスナップショットファイルを利用する限り、同じバージョンがインストールされ続けます。これで依存モジュールが、テストしてないバージョンに意図せず変更されてしまうことを避けられますが、最新の機能やバグ修正を取り込みたいなど、依存モジュールのバージョンをアップデートしたいこともあります。このときに利用するのがcarmel updateコマンドです。

たとえば、Plack 1.0048が指定してあるcpanfile.snapshotがあるとします。

> carmel list | grep Plack
Plack (1.0048)

執筆時点でのPlackモジュールの最新版は1.0050ですので、これにアップデートします。

> carmel update Plack
---> Checking updates...
Using Plack (1.0050)
(省略)
---> Complete! 3 cpanfile dependencies. 45 modules installed.

> carmel list | grep Plack
Plack (1.0050)

最新版にアップデートされました。cpanfile.snapshotがGit管理されている場合にgit diffを実行すると、次のようにcpanfile.snapshotが更新されたことがわかります。

> git diff
--- a/cpanfile.snapshot
+++ b/cpanfile.snapshot
@@ -482,12 +482,12 @@ DISTRIBUTIONS
       strict 0
       warnings 0
       warnings::register 0
- Plack-1.0048
-   pathname: M/MI/MIYAGAWA/Plack-1.0048.tar.gz
+ Plack-1.0050
+   pathname: M/MI/MIYAGAWA/Plack-1.0050.tar.gz
    provides:
      HTTP::Message::PSGI undef
      HTTP::Server::PSGI undef

carmel updateを引数なしで実行すると、cpanfileで指定されているすべてのモジュールがアップデート対象となります。

carmel diff ─⁠─ 変更されたモジュールの表示

Gitリポジトリ内で作業している場合に、carmel updatecarmel installで変更されたモジュールのバージョンを見やすく表示するサブコマンドがcarmel diffです。

たとえば、筆者が運営しているCPAN Meta DBで最近carmel updateを実行した際の表示は、次のようになりました。

> carmel update
---> Checking updates...
Using Amazon::S3 (0.45)
Using Class::Accessor (0.51)
Using Digest::HMAC (1.04)
Using Digest::SHA (6.04)
Using Digest::MD5::File (0.08)
Using LWP (6.70)
(省略)
Using Router::Simple (0.17)
Using Starman (0.4016)
Using YAML (1.30)
---> Complete! 19 cpanfile dependencies. 80 modules installed.

> carmel diff
M CGI (4.54 -> 4.57)
A Clone (0.46)
M Cpanel-JSON-XS (4.30 -> 4.36)
M DBD-SQLite (1.70 -> 1.72)
A Digest-SHA (6.04)
M File-ShareDir-Install (0.13 -> 0.14)
M File-Slurper (0.013 -> 0.014)
M HTML-Parser (3.78 -> 3.81)
A HTTP-CookieJar (0.014)
M HTTP-Message (6.37 -> 6.44)
M JSON-MaybeXS (1.004003 -> 1.004005)
M libwww-perl (6.67 -> 6.70)
M Module-Build (0.4231 -> 0.4234)
M Module-Build-Tiny (0.039 -> 0.046)
M Module-CoreList (5.20220620 -> 5.20230423)
M Net-HTTP (6.22 -> 6.23)
M Net-Server (2.010 -> 2.014)
M Path-Tiny (0.122 -> 0.144)
M Plack (1.0048 -> 1.0050)
M Starman (0.4015 -> 0.4016)
M Test-Deep (1.130 -> 1.204)
M URI (5.10 -> 5.19)

表示される内容はcpanfile.snapshotgit diffを表示したものと同等ですが、どのモジュールが変更M⁠、追加A⁠、削除Dされたかなどがとてもわかりやすく表示されます。

依存モジュール管理のワークフロー

Gitで管理されたリポジトリをゼロから作り、Carmelを実行する様子を簡単にまとめると次のようになります。

> mkdir MyApp
> cd MyApp
> $EDITOR cpanfile
> carmel install
(省略)
> cat > .gitignore
/.carmel
> git add cpanfile cpanfile.snapshot .gitignore
> git commit -m "add Carmel and cpanfile"

<続きの(2)こちら。>

おすすめ記事

記事・ニュース一覧