Featured image of post Carton/cpanfile - モダンなPerl依存関係管理

Carton/cpanfile - モダンなPerl依存関係管理

CartonとcpanfileはモダンなPerl依存関係管理ツールです。チーム開発での環境統一、CI/CD、Docker環境での利用方法を実践的なワークフロー例とともに詳しく解説します

なぜ依存関係管理が重要なのか

プロジェクトが成長すると、使用するCPANモジュールの数も増えていきます。開発環境、ステージング環境、本番環境で同じバージョンのモジュールが動作することを保証するのは、思いのほか難しい問題です。

よくある問題

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 開発者Aの環境
# Mojolicious 9.30 がインストールされている
use Mojolicious;  # 問題なく動作

# 開発者Bの環境  
# Mojolicious 8.50 がインストールされている
use Mojolicious;  # 一部の機能が動かない!

# 本番環境
# Mojolicious 9.35 がインストールされている  
use Mojolicious;  # 開発環境と微妙に動作が違う...

このような「私の環境では動くのに…」という問題を解決するのが、Cartoncpanfile です。

cpanfileとは - 依存関係の宣言

cpanfileは、プロジェクトが必要とするCPANモジュールを宣言するための仕様です。Ruby の Gemfile、Node.js の package.json、Python の requirements.txt に相当します。

基本的な書き方

プロジェクトのルートディレクトリに cpanfile という名前のファイルを作成します:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# cpanfile
requires 'Mojolicious', '9.30';
requires 'DBIx::Class', '0.082842';
requires 'Plack', '1.0048';

# テスト時のみ必要
on 'test' => sub {
    requires 'Test::More', '0.98';
    requires 'Test::MockModule';
};

# 開発時のみ必要
on 'develop' => sub {
    requires 'Perl::Critic';
    requires 'Perl::Tidy';
};

cpanfileの文法は非常にシンプルで、Perlそのものです。requires 関数でモジュールとバージョンを指定するだけです。

バージョン指定の方法

cpanfileでは、柔軟なバージョン指定が可能です:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# cpanfile

# 正確なバージョン指定
requires 'Plack', '== 1.0048';

# 最小バージョン指定(これ以上)
requires 'Mojolicious', '>= 9.0';

# 範囲指定
requires 'DBI', '>= 1.643, < 2.0';

# バージョン指定なし(最新版を使用)
requires 'JSON';

# 特定バージョン以上、かつ特定バージョン未満
requires 'DBIx::Class', '>= 0.082840, < 0.083000';

フィーチャーによるオプショナルな依存関係

特定の機能を使う場合にのみ必要なモジュールは、feature を使って宣言できます:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# cpanfile

requires 'Mojolicious';

feature 'mysql', 'MySQL support' => sub {
    requires 'DBD::mysql', '4.050';
};

feature 'postgresql', 'PostgreSQL support' => sub {
    requires 'DBD::Pg', '3.14.0';
};

feature 'redis', 'Redis cache support' => sub {
    requires 'Redis', '2.0';
    requires 'Redis::Fast';
};

インストール時に、必要なフィーチャーを選択できます:

1
2
3
4
5
# MySQLサポートを含めてインストール
carton install --deployment --with-feature=mysql

# 複数のフィーチャーを有効化
carton install --with-feature=mysql --with-feature=redis

Cartonとは - 依存関係の分離管理

Cartonは、cpanfileに記述された依存関係を、プロジェクトごとに分離してインストール・管理するツールです。

なぜCartonが必要なのか

CPANモジュールを cpanm で直接インストールすると、システム全体(またはユーザー環境全体)にインストールされます。これには以下の問題があります:

  1. バージョンの競合: プロジェクトAはMojolicious 8.x、プロジェクトBは9.xが必要な場合、どちらか一方しか満たせない
  2. 環境の再現性: 「このモジュールのこのバージョンがインストールされている」状態を他の環境で再現するのが困難
  3. クリーンな環境: プロジェクトを削除してもモジュールが残り続ける

Cartonは、これらの問題を local/ ディレクトリへのローカルインストールで解決します。

Cartonのインストール

1
2
3
4
5
6
# cpanmを使ってCartonをインストール
cpanm Carton

# インストール確認
carton -v
# carton v1.0.34

Carton自体はシステム全体(またはユーザー環境)にインストールしますが、各プロジェクトの依存関係は local/ に分離されます。

Cartonの基本的な使い方

ワークフロー例

実際のプロジェクトでの使い方を見ていきましょう。

1. プロジェクトの初期化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# プロジェクトディレクトリを作成
mkdir my-app
cd my-app

# cpanfileを作成
cat > cpanfile << 'EOF'
requires 'Mojolicious', '9.30';
requires 'DBI';
requires 'DBD::SQLite';

on 'test' => sub {
    requires 'Test::More', '0.98';
};
EOF

2. 依存関係のインストール

1
2
3
4
5
6
7
8
# cpanfileに基づいてモジュールをインストール
carton install

# 実行後のディレクトリ構造
# my-app/
# ├── cpanfile
# ├── cpanfile.snapshot  # 自動生成される
# └── local/             # 依存モジュールがここにインストールされる

carton install を実行すると:

  1. cpanfileを読み取る
  2. 必要なモジュールを local/ ディレクトリにインストール
  3. インストールされた正確なバージョン情報を cpanfile.snapshot に記録

3. アプリケーションの実行

Cartonでインストールしたモジュールを使うには、carton exec を使います:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 通常の実行(システムのモジュールを使用)
perl app.pl  # NG: local/ のモジュールが見つからない

# Carton経由で実行(local/ のモジュールを使用)
carton exec -- perl app.pl  # OK!

# plackupの場合
carton exec -- plackup app.psgi

# スクリプトの場合
carton exec -- perl -Ilib script/myapp.pl

carton exec は、local/ ディレクトリをモジュール検索パス(@INC)に追加して、指定されたコマンドを実行します。

アプリケーション例

実際のアプリケーションでCartonを使った例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env perl
# app.pl
use strict;
use warnings;
use Mojolicious::Lite;
use DBI;

# データベース接続(DBD::SQLiteを使用)
my $dbh = DBI->connect('dbi:SQLite:dbname=app.db');

get '/' => sub {
    my $c = shift;
    $c->render(text => 'Hello from Carton-managed app!');
};

app->start;
1
2
3
4
5
6
# 依存関係をインストール
carton install

# アプリケーションを実行
carton exec -- morbo app.pl
# Server available at http://127.0.0.1:3000

cpanfile.snapshot - 完全な再現性

cpanfile.snapshot は、carton install 実行時に自動生成されるファイルで、インストールされたすべてのモジュールの正確なバージョンとその依存関係を記録します。

cpanfile と cpanfile.snapshot の違い

1
2
# cpanfile(開発者が書く)
requires 'Mojolicious', '>= 9.0';  # 「9.0以上」という緩い指定
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# cpanfile.snapshot(Cartonが生成)
# DATE: 2025-12-10T12:00:00
# PERL_VERSION: 5.38.0

DISTRIBUTIONS
  Mojolicious-9.37
    pathname: S/SR/SRI/Mojolicious-9.37.tar.gz
    provides:
      Mojo 9.37
      Mojo::Base 9.37
      # ... 全サブモジュールの正確なバージョン
    requirements:
      ExtUtils::MakeMaker 0
      IO::Socket::IP 0.37
      # ... 依存関係の正確なバージョン

cpanfile.snapshotがあることで、どの環境でも完全に同じバージョンのモジュールをインストールできます。

デプロイ時の使用

本番環境やCI環境では、--deployment オプションを使用します:

1
2
3
4
5
6
7
# デプロイメントモード(cpanfile.snapshotを使用)
carton install --deployment

# このモードでは:
# - cpanfile.snapshotに記録された正確なバージョンをインストール
# - cpanfile.snapshotがない場合はエラー
# - cpanfileとcpanfile.snapshotの不整合があればエラー

これにより、開発環境と全く同じバージョンのモジュール構成を本番環境に展開できます。

チーム開発での活用

推奨ワークフロー

開発者Aの作業(新しい依存関係を追加)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 1. cpanfileに新しい依存関係を追加
echo "requires 'JSON::XS';" >> cpanfile

# 2. インストール(cpanfile.snapshotが更新される)
carton install

# 3. 動作確認
carton exec -- perl app.pl

# 4. cpanfileとcpanfile.snapshotをコミット
git add cpanfile cpanfile.snapshot
git commit -m "Add JSON::XS dependency"
git push

開発者Bの作業(変更を取り込む)

1
2
3
4
5
6
7
8
# 1. 最新のコードを取得
git pull

# 2. 依存関係を更新(cpanfile.snapshotに従う)
carton install

# 3. そのまま開発を続行(追加の作業不要!)
carton exec -- perl app.pl

.gitignoreの設定

local/ ディレクトリはバージョン管理に含めず、cpanfile と cpanfile.snapshot だけを管理します:

1
2
3
# .gitignore
/local/
/.carton/

local/ ディレクトリには大量のファイルが含まれるため、Gitで管理するのは非効率です。cpanfile.snapshotがあれば、いつでも同じ環境を再現できます。

CI/CDでの利用

GitHub Actions での例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# .github/workflows/test.yml
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Perl
      uses: shogo82148/actions-setup-perl@v1
      with:
        perl-version: '5.38'
    
    - name: Cache Carton dependencies
      uses: actions/cache@v3
      with:
        path: local
        key: ${{ runner.os }}-carton-${{ hashFiles('cpanfile.snapshot') }}
        restore-keys: |
          ${{ runner.os }}-carton-
    
    - name: Install Carton
      run: cpanm -n Carton
    
    - name: Install dependencies
      run: carton install --deployment
    
    - name: Run tests
      run: carton exec -- prove -lr t/

--deployment オプションにより、cpanfile.snapshotに記録された正確なバージョンがインストールされます。キャッシュを活用することで、2回目以降のビルドが高速化されます。

GitLab CI での例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# .gitlab-ci.yml
image: perl:5.38

cache:
  paths:
    - local/

before_script:
  - cpanm -n Carton
  - carton install --deployment

test:
  script:
    - carton exec -- prove -lr t/

deploy:
  stage: deploy
  script:
    - carton install --deployment --without-develop
    - rsync -av --exclude 'local' ./ user@server:/path/to/app/
  only:
    - main

Dockerでの利用

Dockerコンテナでも Carton は有効です。特に、マルチステージビルドと組み合わせると効果的です。

シンプルなDockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
FROM perl:5.38

WORKDIR /app

# Cartonをインストール
RUN cpanm -n Carton

# cpanfileとcpanfile.snapshotをコピー
COPY cpanfile cpanfile.snapshot ./

# 依存関係をインストール
RUN carton install --deployment --without-develop --without-test

# アプリケーションコードをコピー
COPY . .

# アプリケーションを実行
CMD ["carton", "exec", "--", "plackup", "-s", "Starman", "--workers", "4", "app.psgi"]

マルチステージビルドの例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ビルドステージ
FROM perl:5.38 AS builder

WORKDIR /app

RUN cpanm -n Carton

COPY cpanfile cpanfile.snapshot ./
RUN carton install --deployment --cached

COPY . .

# 実行ステージ(軽量)
FROM perl:5.38-slim

WORKDIR /app

# ビルドステージからlocal/とアプリをコピー
COPY --from=builder /app/local ./local
COPY --from=builder /app .

# Cartonをインストール(execに必要)
RUN cpanm -n Carton

CMD ["carton", "exec", "--", "plackup", "-s", "Starman", "app.psgi"]

マルチステージビルドを使うことで、最終的なイメージサイズを小さくできます。

Docker Composeでの開発環境

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/app
      - carton-cache:/app/local
    environment:
      - PLACK_ENV=development
    command: carton exec -- plackup -R lib,app.psgi app.psgi

volumes:
  carton-cache:

ボリュームを使って local/ をキャッシュすることで、コンテナ再起動時の依存関係インストールを省略できます。

他の言語との比較

Carton/cpanfileは、他の言語の依存関係管理ツールと同様の思想で設計されています。

Ruby - Bundler/Gemfile

1
2
3
4
5
6
7
8
9
# Gemfile
source 'https://rubygems.org'

gem 'rails', '~> 7.0'
gem 'pg', '>= 1.0'

group :development, :test do
  gem 'rspec-rails'
end
1
2
bundle install
bundle exec rails server

対応するPerl版:

1
2
3
4
5
6
7
# cpanfile
requires 'Mojolicious', '~> 9.0';
requires 'DBD::Pg', '>= 3.0';

on 'test' => sub {
    requires 'Test::More';
};
1
2
carton install
carton exec -- morbo app.pl

Node.js - npm/package.json

1
2
3
4
5
6
7
8
9
{
  "dependencies": {
    "express": "^4.18.0",
    "pg": "^8.7.0"
  },
  "devDependencies": {
    "jest": "^29.0.0"
  }
}
1
2
npm install
npm start

Perlとの対応:

1
2
3
4
5
6
7
# cpanfile
requires 'Mojolicious', '>= 9.0';
requires 'DBD::Pg', '>= 8.0';

on 'develop' => sub {
    requires 'Test::More';
};
1
2
carton install
carton exec -- perl app.pl

Python - pip/requirements.txt

1
2
3
# requirements.txt
Django==4.2.0
psycopg2>=2.9.0
1
2
pip install -r requirements.txt
python manage.py runserver

Perl版:

1
2
3
# cpanfile
requires 'Mojolicious', '== 9.30';
requires 'DBD::Pg', '>= 3.14.0';
1
2
carton install
carton exec -- perl app.pl

ベストプラクティス

1. cpanfile.snapshotは必ずコミットする

1
2
git add cpanfile cpanfile.snapshot
git commit -m "Update dependencies"

cpanfile.snapshotがないと、チームメンバーや本番環境で同じバージョンを再現できません。

2. 定期的に依存関係を更新する

1
2
3
4
5
6
7
8
9
# 古いlocal/を削除
rm -rf local/

# 最新版で再インストール
carton install

# 動作確認後、cpanfile.snapshotをコミット
git add cpanfile.snapshot
git commit -m "Update dependencies to latest versions"

3. バージョン範囲を適切に指定する

1
2
3
4
5
6
7
8
# ✗ 悪い例:バージョン指定なし(予期しない破壊的変更のリスク)
requires 'Mojolicious';

# ○ 良い例:メジャーバージョンを固定
requires 'Mojolicious', '>= 9.0, < 10.0';

# ○ または最小バージョンのみ指定
requires 'Mojolicious', '>= 9.30';

4. 開発用/テスト用依存関係を分ける

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# cpanfile

# 本番環境でも必要
requires 'Mojolicious';
requires 'DBI';

# テスト時のみ
on 'test' => sub {
    requires 'Test::More';
    requires 'Test::MockModule';
    requires 'Devel::Cover';
};

# 開発時のみ
on 'develop' => sub {
    requires 'Perl::Critic';
    requires 'Perl::Tidy';
    requires 'Reply';  # REPL
};

デプロイ時は不要な依存関係を除外:

1
carton install --deployment --without-develop --without-test

5. プライベートCPANミラーの活用

社内プライベートモジュールがある場合:

1
2
3
4
5
6
7
# cpanfile
requires 'PublicModule::FromCPAN';

# プライベートリポジトリから
requires 'MyCompany::InternalModule',
    git => 'https://github.com/mycompany/internal-module.git',
    ref => 'v1.0.0';

または、mirror 設定でプライベートCPANミラーを使用:

1
2
3
# 環境変数で指定
export PERL_CARTON_MIRROR="https://cpan.internal.company.com"
carton install

トラブルシューティング

local/ディレクトリが壊れた

1
2
3
# local/を削除して再インストール
rm -rf local/ .carton/
carton install

cpanfile.snapshotとcpanfileの不整合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# --deploymentモードでエラーが出る場合
carton install --deployment
# Finding mirrors...
# ERROR: cpanfile.snapshot is out of date. Run `carton install` to update it.

# 開発環境で再インストール
carton install

# cpanfile.snapshotが更新されるのでコミット
git add cpanfile.snapshot
git commit -m "Update cpanfile.snapshot"

特定のモジュールのバージョンを固定したい

1
2
3
# cpanfile
# 正確なバージョンを指定
requires 'Mojolicious', '== 9.30';

その後、再インストール:

1
2
rm -rf local/
carton install

Carton経由とシステムのモジュールが競合

1
2
3
4
5
6
7
8
# Carton経由で実行されているか確認
carton exec -- perl -V

# @INCを確認(local/が含まれているはず)
carton exec -- perl -e 'print "$_\n" for @INC'
# /path/to/project/local/lib/perl5
# /path/to/project/local/lib/perl5/x86_64-linux
# ...

必ず carton exec を使ってコマンドを実行してください。

インストールが遅い

1
2
3
4
5
# 並列インストールで高速化
carton install -j 4  # 4並列

# ミラーサイトを指定
carton install --mirror http://cpan.cpantesters.org/

実際のプロジェクト例

実際のWebアプリケーションプロジェクトでの構成例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
my-web-app/
├── cpanfile              # 依存関係定義
├── cpanfile.snapshot     # バージョンロック
├── app.psgi              # PSGIアプリケーション
├── lib/
│   └── MyApp/
│       ├── Controller/
│       └── Model/
├── t/                    # テスト
│   ├── 00_compile.t
│   └── 01_basic.t
├── public/               # 静的ファイル
│   ├── css/
│   └── js/
├── script/               # ユーティリティスクリプト
│   └── myapp.pl
├── .gitignore           # local/ を除外
├── Dockerfile           # Docker設定
└── README.md
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# cpanfile
requires 'Mojolicious', '>= 9.0';
requires 'Mojo::Pg';
requires 'DBIx::Class', '0.082842';
requires 'DateTime';
requires 'Email::Sender';
requires 'Crypt::Passphrase::Argon2';

on 'test' => sub {
    requires 'Test::More', '0.98';
    requires 'Test::Mojo';
    requires 'Test::PostgreSQL';
};

on 'develop' => sub {
    requires 'Perl::Critic';
    requires 'Perl::Tidy';
    requires 'Devel::NYTProf';  # プロファイラ
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 開発開始
carton install
carton exec -- morbo app.psgi

# テスト実行
carton exec -- prove -lvr t/

# 本番デプロイ
carton install --deployment --without-develop --without-test
carton exec -- plackup -s Starman --workers 10 app.psgi

まとめ

Carton/cpanfileは、モダンなPerl開発に欠かせないツールです:

Carton/cpanfileのメリット

  1. 環境の再現性: cpanfile.snapshotにより、どの環境でも同じバージョンのモジュール構成を再現
  2. プロジェクトの分離: local/へのインストールにより、プロジェクト間でモジュールバージョンが競合しない
  3. チーム開発の効率化: 「私の環境では動く」問題を解消
  4. CI/CDとの親和性: 自動化されたビルド・テストが容易
  5. 他言語との共通パターン: Bundler、npm等と同様のワークフローで学習コストが低い

導入のステップ

  1. Cartonをインストール: cpanm Carton
  2. cpanfileを作成: プロジェクトの依存関係を記述
  3. 依存関係をインストール: carton install
  4. アプリを実行: carton exec -- perl app.pl
  5. cpanfile.snapshotをコミット: git add cpanfile cpanfile.snapshot

他の選択肢

Carton以外にも、Perl依存関係管理ツールはいくつか存在します:

  • cpm: 高速な並列インストーラー(Cartonの代替として使える)
  • Pinto: プライベートCPANリポジトリ管理
  • Dist::Zilla: モジュール開発向けのツールチェーン(依存関係管理も含む)

しかし、チーム開発や本番環境での依存関係管理という観点では、Carton/cpanfileが最も広く使われているデファクトスタンダードです。

Perlの依存関係管理の進化

Perl の依存関係管理は、長い歴史の中で進化してきました:

  1. CPAN時代: モジュールを手動でインストール、バージョン管理なし
  2. cpanm時代: 自動依存関係解決、しかしプロジェクト単位の管理はなし
  3. Carton時代: プロジェクト単位での依存関係管理、バージョンロック(現在)

Carton/cpanfileは、Ruby の Bundler、Node.js の npm、Python の pip といった他言語のベストプラクティスに影響を受け、Perlに取り入れられた成果です。TMTOWTDI(やり方は一つじゃない)の精神を持つPerlですが、依存関係管理に関しては Carton/cpanfile がモダンな標準となっています。

参考リンク

Carton/cpanfileを活用して、安定したPerl開発環境を構築しましょう!チーム全体で同じ環境を共有できることは、開発効率の大幅な向上につながります。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。