CocoaPodsのプラグインを作る


## 概要

Cocoaのライブラリ管理ツールであるCocoaPodsですが、2013年11月14日にリリースされたバージョン0.28で、プラグイン機能が導入されました。

* [CocoaPods 0.28 - CocoaPods Blog](http://blog.cocoapods.org/CocoaPods-0.28/)

本記事ではこのプラグインの作り方を紹介します。

## CocoaPodsプラグインとは

CocoaPodsのプラグイン機能を利用すると、`pod`コマンドに任意のサブコマンドを追加することができます。標準サブコマンドの`pod install`や`pod update`と同列な、新たなサブコマンドの作成が可能です。

CocoaPodsはRubyで作られたツールなので、プラグインもRubyで書く必要があります。また、プラグインの配布にはRubyGemsを利用します。

このプラグインの仕組みそのものは、CocoaPodsプロジェクトが依存gemとして利用している[CLAide](https://github.com/CocoaPods/CLAide)によって[可能になりました](https://github.com/CocoaPods/CLAide/pull/3)。

## プラグインを作る

ここでは`check-latest`というサブコマンドを定義する、`cocoapods-check_latest`というgemを作ることにします。これは指定したライブラリのpodの最新バージョンが、そのライブラリのGitHubリポジトリの最新のバージョンタグと同一かどうかをチェックするコマンドです。

試しにCocoaPodsで導入してみたライブラリがうまく動作しないと思ったら、実は最新のpodspecで指定されているライブラリのバージョンが、そのライブラリの本当の最新バージョンではなかった（この表現わかりにくいですね）という場面がたまにあったりします。なので事前にpodspecが最新かどうかチェックしたいのですが、そのライブラリのGitHubのリポジトリをブラウザで開いてタグ一覧を確認、というのも割と面倒なルーチンワークなので、この作業を自動化することにします。

まずは新しくgemのプロジェクトを作成します。[Bundler](http://bundler.io/)を利用するのが良いでしょう。なお、gem名は`cocoapods-プラグイン名`にすることが公式に推奨されています。そうしないとプラグインとして動作しない訳ではありませんが、RubyGemsでの検索のしやすさも考慮した慣例になっています。RubyGemsを`cocoapods-`で[検索](http://rubygems.org/search?utf8=✓&query=cocoapods-)すれば、現在公開されているプラグインの一覧が見られる訳ですね。

```bash
$ bundle gem cocoapods-check_latest
...
$ cd cocoapods-check_latest
```

### コマンドクラスを作成する

`Pod::Command`クラスを継承したコマンドクラスを作成します。

```ruby:lib/pod/command/check_latest.rb
module Pod
  class Command
    # クラス名からサブコマンド名が自動生成される。
    # CamelCaseの単語間には"-"が挿入されるため、この場合"check-latest"になる。
    class CheckLatest < Command
      # 必須。`pod help`時に一行で表示される概要を記述する。
      self.summary = 'Check if the latest version of a pod is up to date'

      # 任意。`pod help check-latest`時に表示される、より詳細な説明を記述する。
      # 未指定の場合、summaryの内容が表示される。
      self.description = 'Some long description...'

      # コマンドラインから引数を受け取るコマンドの場合は必須。
      # `pod help check-latest`時に、
      #
      # Usage:
      #     $ pod check-latest [NAME]
      #                        ^^^^^^ ここに表示される。
      self.arguments = '[NAME]'

      # 引数を受け取るコマンドの場合は必須。
      # argvから、後の処理で必要な引数をインスタンス変数に取り出しておく。
      # この処理はsuper呼び出し前に行うこと。
      # argvは、CLAide::ARGVのインスタンス。
      # https://github.com/CocoaPods/CLAide/blob/v0.4.0/lib/claide/argv.rb
      def initialize(argv)
        @name = argv.shift_argument
        super
      end

      # 引数を受け取るコマンドの場合は必須。
      # 引数が不正な場合はhelp!メソッドにメッセージを渡して中断する。
      def validate!
        super
        help!('A pod name is required.') unless @name
      end

      # 実際の処理を記述する。
      def run
        # 長いので省略。実際の内容は以下を参照して下さい。
        # https://github.com/yujinakayama/cocoapods-check_latest/blob/v0.0.1/lib/pod/command/check_latest.rb
      end
    end
  end
end
```

ちなみに、CocoaPodsのサブコマンドを定義するということは、CocoaPods本体のAPIを利用できる訳ですが、現時点ではどのクラスやメソッドがpublic APIであるかという宣言がされていません。将来的にはこの辺りも整備されてくるとは思いますが、現状では利用対象のクラスやメソッドが存在しているかどうかのチェックや、例外処理などをこまめに行うしかない模様です。どちらにしてもCocoaPodsは未だにバージョン1.0未満の初期開発段階なので、[Semantic Versioning](http://semver.org/)的にもAPIの互換性はあまり重視されないフェーズではあるのですが。

### 作成したコマンドが、`pod`コマンド実行時に読み込まれるようにする

コマンドを作成しただけでは、CocoaPods本体がその存在を認識できません。

`$LOAD_PATH`直下（通常はプロジェクトの`lib`ディレクトリ直下）に、`cocoapods_plugin.rb`というファイルを作成し、その中で上記のコマンドクラスが記述されたファイルを`require`します。これによって`pod`コマンド実行時にカスタムコマンドクラスがロードされるようになります。

```ruby:lib/cocoapods_plugin.rb
require 'pod/command/check_latest'
```

## 動作確認をする

実際に`pod`コマンド経由で動くか確認してみましょう。

```bash
$ echo "gem 'cocoapods', '~> 0.28'" >> Gemfile
$ bundle install
# …
$ bundle exec pod help | grep check-latest
    * check-latest   Check if the latest version of a pod is up to date
$ bundle exec pod check-latest viewcontroller


-> AMBubbleTableViewController
   - Homepage: https://github.com/andreamazz/AMBubbleTableView
   - Latest pod version:0.5.1
   - Latest version in original repo:0.5.1


-> APPinViewController
   - Homepage: https://github.com/Alterplay/APPinViewController
   - Latest pod version:1.0.2
   - Latest version in original repo:1.0.2


-> ARGenericTableViewController
   - Homepage: https://github.com/arconsis/ARGenericTableViewController
   - Latest pod version:1.0.0
   - Latest version in original repo:1.0.1
   Outdated!

...
```

## プラグインを配布する

あとはRubyGemsのルールに則ってgemを公開しましょう。

`cocoapods-check_latest.gemspec`の`description`, `summary`, `homepage`あたりの項目を[記述](http://guides.rubygems.org/specification-reference/)し、BundlerのRakeタスクで公開します。

```bash
$ rake release
```

## おわりに

実際に作成した[cocoapods-check_latest](https://github.com/yujinakayama/cocoapods-check_latest)プラグインを公開しました。`gem install cocoapods-check_latest`でインストールができます。