Perl Hackers Hub

第65回依存モジュールの更新 ―update-cpanfile、GitHub Actionsで実現!(2)

前回の(1)こちらから。

継続的な更新

モジュールの更新は一度やったら終わりではなく、継続的に行うべきものです。新しいモジュールがリリースされるたびに繰り返し発生する作業で、管理すべきモジュールの数はアプリケーションの機能追加や規模増大とともに増えていきます。こうした繰り返し発生する作業を自動化することで、将来にわたって時間と手間を節約し、煩雑な手順を手作業で行うことによるミスを減らせることが期待できます。

Node.jsやGo、Dockerなどを使って開発している場合は、RenovateDependabotなどのツールを使うと、これらのツールのbotが自動的に依存モジュール更新のPull Requestを送ってくれるので、手間を軽減できます。Perlを使って開発している場合も、update-cpanfileという筆者が開発しているPerl製のCLICommand Line Interfaceツールを使ってcpanfileの継続的な更新を実現できます。update-cpanfileは、cpanfileを書き換え、モジュールの要求を最新化する機能を持っています。

以降では、update-cpanfileを使った更新手順を紹介します。

update-cpanfileでcpanfileを更新する

まずはupdate-cpanfileをインストールします。執筆時点ではバージョン1.0.0がインストールされます。

$ cpanm App::UpdateCpanfile

依存関係を固定して扱いやすくする

cpanfileにバージョン指定を書いていない場合、carton installするタイミングで最新のバージョンがインストールされるため、実行するタイミングによって結果が不定となります。このことは、バージョン管理や継続的な更新と相性が悪いため、update-cpanfileを使い始める際には、まずは依存関係を固定します。

pinコマンドは、cpanfile.snapshotに記録されているモジュールのバージョンをcpanfileに書き戻すコマンドです。冒頭のcpanfileに対してupdate-cpanfile pinを実行すると、3つのモジュールのバージョンが固定されます。

依存モジュールの固定
$ update-cpanfile pin
Pinned 3 modules
- JSON::XS 3.0
- List::MoreUtils 0.419
- Test::More 0.98

cpanfileを確認すると、ファイルが書き換わっていることを確認できます。3つのモジュールが==を使った指定となり、cpanfile.snapshotに記録されていたバージョンに固定され、将来にわたってインストール結果が不変なものとなります。DateTimeはもともと1.50でバージョンが固定されているため、ここでは差分が出ません。

依存モジュールのバージョンが固定された
requires 'List::MoreUtils', '== 0.419';
requires 'DateTime', '== 1.50';
requires 'JSON::XS', '== 3.0';

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

依存関係を更新する

updateコマンドは、cpanfileでのバージョン要求指定を、CPANの最新版へと更新するコマンドです。update-cpanfile updateを実行すると、cpanfileでの4つのモジュールのバージョン指定が最新化されます。

依存モジュールの最新化
$ update-cpanfile update
Updated 4 modules
- DateTime 1.52
- JSON::XS 4.02
- List::MoreUtils 0.428
- Test::More 1.302181

cpanfileを確認すると、4つのモジュールすべてがCPANで公開されている最新のバージョンへと書き換えられています。

依存モジュールのバージョンが更新された
requires 'List::MoreUtils', '== 0.428';
requires 'DateTime', '== 1.52';
requires 'JSON::XS', '== 4.02';

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

この時点では要求を新しくしただけで、新しいモジュールはインストールされていないことに注意してください。carton installを実行すると、新しいモジュールがインストールされ、cpanfile.snapshotが更新されます。

CIを使った継続的な更新

ここまで、update-cpanfileを使った更新操作を見てきました。手作業でcpanfileを編集するよりは楽になりましたが、更新を実行し忘れる、コマンドを間違うなど、手間のかかる手順であることには変わりありません。そこで、更新作業を人間が手動で実行する代わりにCI環境から機械的に実行させることで、定期的に決まった手順を繰り返し、手間を軽減します。

その際、update-cpanfileのpinは最初に一度だけ、updateは何度も行うものですので、初回のpinは手動で行い、そのあとのupdateはCI環境から実行するとよいでしょう。

今回は、GitHub Actionsを用いて実現する方法を紹介します。GitHub Actionsは、GitHubと統合されたCIサービスです。update-cpanfileのリポジトリにはサンプルとして、update-cpanfile自体の依存モジュールを更新するGitHub Actions用の設定ファイルを同梱しています。この設定ファイルでは、次の手順を実行してモジュールを更新します。

  • ❶1日に一度タスクを実行する
  • ❷actions/checkoutアクションを使ってリポジトリをcloneする
  • ❸Docker上でupdate-cpanfileをインストールする
  • ❹update-cpanfile update --output jsonを実行して依存モジュールの要求を更新し、結果をJSONとして出力する
  • ❺Pull Requestの本文やタイトルを組み立てるため、実行結果のJSONをjqコマンドでMarkdownに加工する
  • peter-evans/create-pull-requestアクションを使ってPull Request化する

このサンプルによって生成されたPull Requestは、図1にあるように1行1モジュールのリストとなっていて、metacpanへのリンクやChangesへのリンクを掲載しています。開発者は、CIのチェックが通っていることを確認し、Changesを読んでモジュールの変更履歴を把握したうえでマージできるようになっています。定期的に動き、Pull Requestの用意まで自動化されているため、手作業でのミスが入り込む余地のない構成となっています。

図1 自動生成されたPull Request本文
図1 自動生成されたPull Request本文

苦労なく更新するための工夫

筆者のチームでは、update-cpanfileとGitHub Actionsを使った継続的な更新のしくみを運用し、4ヵ月で74件の更新に成功しました。4ヵ月の活動を通じて、苦労なく、継続的にモジュールを更新するために工夫したことを紹介します。

テストや開発用のモジュールは事前に更新しておく

テストに使うモジュールや、アプリケーションの開発のために利用するモジュールは、動作がアプリケーションのユーザーに影響しません。これらのモジュールについては、継続的な更新を行う前処理として、手作業で一気にまとめて更新しておくと、以降の継続的な更新が楽になります。

原始的な方法ですが、update-cpanfile updateコマンドを実行したあとに、生成されたcpanfileから、on develop(開発用のモジュールが記されるセクション)on test(テスト用のモジュールが記されるセクション)の中だけdiffを残す形にテキストエディタで編集することで、開発用のモジュールだけを手動で更新できます。

CIから1個ずつ更新する

update-cpanfileはlimitオプションを実装しており、一度に更新するモジュールの個数を制限できます。筆者のチームでは、毎日のCIからの実行では--limit 1のオプションを付けて、一度に1モジュールずつ更新するPull Requestを作っています。

開発用やテスト用のモジュールと違ってユーザーへの影響がある部分なので、1件ずつ動作確認しながらリリースしています。

更新できないモジュールはスキップする

アプリケーションの都合やモジュールの破壊的な変更などの事情でバージョンを固定したいモジュールがあったとしても、update-cpanfileは容赦なく更新してしまいます。update-cpanfileはignore-filterオプションを持っているので、事情があって更新したくないモジュールの更新はスキップできます。

JSON::XSとDateTimeの更新をスキップする
$ update-cpanfile update --ignore-filter '^(JSON::XS|DateTime)$'
Updated 2 modules
- List::MoreUtils 0.428
- Test::More 1.302181

ただし、このままではモジュールが古びてしまうので、あくまで一時的なスキップという認識で使い、将来的には更新することが望ましいでしょう。

snapshotをCIから作る

新しいモジュールを使ってPull Requestのテストを実行するため、update-cpanfile updateの実行後に、CI上でcarton installを実行し、cpanfile.snapshotを生成してコミットするようにしています。開発者が自由に作業できる手もとの環境ではなく、決まった手順で動くCI環境から生成することで、生成結果を固定でき、意図しないモジュールがcpanfile.snapshotに入り込んでしまうトラブルを避けやすくなります。

筆者のチームでは、GitHub Actions上でcpanfile.snapshotを構築し、Pull Requestに追加でコミットしています。本連載の第61回GitHub ActionsとAmazon ECSを使ったDockerアプリケーションの自動デプロイgeneratecpanfile-snapshot.shを参考にしてください。

レビュアーをランダムにアサインする

モジュールの更新は終わりのない作業ですので、属人化せず、誰にでも運用できる体制を作ることが重要です。筆者のチームではGitHubのTeam Reviewers機能を使って、レビュアーをランダムにアサインしています。ランダムにアサインすることで、メンバー間の負担や知見の偏りを防いでいます。

まとめ

アプリケーションの依存モジュールの管理方法と、管理を楽にする手法について紹介しました。本稿が少しでも参考になり、みなさんのアプリケーションを1日でも長生きさせられることを願っています。

さて、次回の執筆者はkolukuさんで、テーマは「Perlの時間モジュール」です。お楽しみに。

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧