シンプルな依存関係管理システム Carthage

Carthage は Objective-C と Swift のプロジェクトでシンプルさを一番に考えた Cocoa アプリケーションにフレームワークを追加する依存関係管理システムです。Carthage は Xcode と Git に仕事を任せることによってできるだけ小さくするコンセプトの元作られたツールです。よって、普段使われているツールとそのまま一緒に使うことができます。この発表では、Carthage とは何なのか、使い方について、プロジェクトが影響を受けているそのシンプルさの元となる思想などについて説明されています。Cocoa プラットフォームで以前からある CocoaPods と比較しながらの発表となります。また、Carthage の裏側のアーキテクチャがどのようになっているのかや Swift で書かれていることから得られる利点などについても触れられています。発表で使われているコードは GitHub にあります!


Carthage とは? (0:00)

Carthage は Objective-C と Swift のプロジェクトでシンプルさを一番とした Cocoa アプリケーションにフレームワークを追加する依存関係管理システムです。Swift を公式に初めてサポートした依存関係管理システムで Carthage は完全に Swift で書かれています。

問題点 (1:22)

なぜ依存関係管理システムに問題があるのでしょうか? プログラマとしてまず第一に考えることは出来るだけコードを再利用することです。コードを再利用するために、ライブラリやフレームワークとしてまとめておくことがしばしあります。CocoaPods や Carthage が登場する前までは、Cocoa のライブラリはファイルをコピーしたり、zipバイナリ、SVN externals、Git submodule/subtree などで配布していました。しかし、これら全てはそれぞれ短所となるところがありました。依存関係管理システムは指定したバージョンをダウンロードしてき、使い始めるまでのセットアップまで行ってくれます。それに、最近のモダンな開発プラットフォームには必ずと言っていいほど依存関係管理システムがあります。また、よくその言語を作った同じチームが作ることが多いです。

長い間 GitHub for Mac の開発では Xcode のサブプロジェクト や Git Submodule を使って依存するライブラリをインポートし使っていました。これは、それぞれのライブラリが共有している依存パッケージが2つ以上になるまではうまく機能していました。私たちの場合は Mantle と ReactiveCocoa がいくつかのライブラリの中で使われていました。ここに問題がありました。アプリが特定の一つのバージョンのみダウンロードしてくることになり、異なるプロジェクトが Mantle や ReactiveCocoa の違うバージョンを必要となるとき問題が発生します。Git Submodule ではこの問題を解決できませんでした。

なぜ新しく作ったのか?

CocoaPods ではない理由 (3:47)

CocoaPods を使おうとは思いませんでした。ライブラリの作者は CocoaPods で配布時に必要となる PodSpec をプロジェクトに追加することに面倒さを感じていました。私たちにとって、ここに記述する情報は Xcode と Git にすでにある情報でしたし、なぜ重複させる必要があるのか疑問に思いました。また CocoaPods がプロジェクトのセットアップを勝手にやってしまうことに対しても不満を持っていました。私たちは Xcode プロジェクトをセットアップする方法を知っているので、ツールに任せてしまうと柔軟性が失われてしまいます。

記事の更新情報を受け取る

CococaPods には Xcodeプロジェクトを勝手に操作しないようにする --no-integrate オプションが確かにあります。しかし、ReactiveCocoa が Xcode プロジェクトを作った場合、依然として作成された Pods プロジェクトを使う必要があります。Pods プロジェクトを使う以外にライブラリを取り入れる方法はないです。必ず CocoaPods の仕組みを使わなればいけません。また、CocoaPods に集まったライブラリも保守しなければならず、ライブラリ作者の仕事を増やしています。アップデートの際、他の場所にもデプロイする責任がでてきます。

私たちの目標 (5:38)

完全に哲学が違うツールが欲しかったのです。Git と Xcode をシンプルに取りまとめてくれるようなものが良かったです。このツールは、全ての依存関係に対し互換性のあるバージョンを調べ、対象の依存するものを Git を使いチェックアウトし、そして Xcode を使ってフレームワークをビルドします。

使い方 (6:16)

  1. Cartfile の作成
    Mantle, ReactiveCocoa, ReactiveCocoaLayout を使いプロジェクトをセットアップするために取得する GitHub レポジトリを指定しています。Mantle は特定のバージョンと互換性のあるバージョンという意味でチルダで示され、ReactiveCocoa は2.4.7以上、ReactiveCocoaLayout は0.5.2を指定しています。
    github "Mantle/Mantle" ~> 1.5
    github "ReactiveCocoa/ReactiveCocoa" >= 2.4.7
    github "ReactiveCocoa/ReactiveCocoaLayout" == 0.5.2

  2. Carthage の実行
    依存するパッケージをダウンロードする carthage update を実行します。これは、他の依存しているパッケージがあるかどうかも調べてくれて、自動でダウンロードしてくれます。

  3. Framework のリンク
    Carthage によって一度これらのフレームワークをビルドすると Linked framework and Libraries にドラッグすれば良いです。

  4. 不要なアーキテクチャの除外 (iOS only)
    iOS の場合、もう一つしなければいけないステップがあります。App Store の問題でユニバーサルフレームワークのバイナリが許可されていません。Carthage には App Store にサブミットできるようにするためにそれらを取り除くコマンドがあります。

“徹底的にシンプルに” (8:36)

一般的に広く受け入れられているデザインゴールは、シンプルにすることです。できる限りシンプルなツールを作りたかったので、複雑さを招くようなことや余計な機能はすべて避けるよう取り組みました。シンプルと簡単さは決して同じではありません。簡単であるということは、親しみやすかったり入門しやすかったりすることを意味します。シンプルというのは、頭を悩ますような概念であったり問題が少ないことを意味します。これについて Clojure の作者である Rich Hickey による Made Easy という素晴らしい発表があります。

CocoaPods はここにある定義によると簡単です。CocoaPods はできるだけライブラリを簡単に探せ、使えるように作られています。しかし、これらのゴールを達成しようとすると複雑さが伴ってきます。簡単ですがシンプルではありません。Carthage はこれとは対照的にシンプルさを選んでいます。Carthageを使えば、かなりシンプルになります。なぜなら、シンプルさから得られる利点はたくさんあると考えているからです。

シンプルなツールは理解するのが簡単です。シンプルであるとき、裏でどのように動いているかモデル化するのが簡単です。例えば、Carthage を使っていて何か問題があれば、何が行われているのかやこれから何をしようとしているのかを理解しておくことは問題を解決する上で非常に助けとなります。従うのがかなり簡単なモデルなので、コードベースで全て理解する必要はありません。シンプルなツールはメンテナンスするのも簡単で、コントリビュートするのも簡単です。シンプルさを保つことによって、そんなに多くのエッジケースを扱う必要もないのです。Xcode や Git などの他のツールと統合することによって、それらに役割を任せています。このことで自分たちがする仕事を減らせます。複雑さが減ることにより、コードの量を減らせます。そして、もちろん複雑なものよりもシンプルなものの方がバグフィクスや新しい機能を素早く実装できます。

シンプルなツールは柔軟で組み合わせやすいです。他の人がそのソフトウェアをどのように使おうとするのか、全ての可能性を予測するのは不可能です。その分、一つしかユースケースがない場合は、Carthage が予測する必要はありません。Carthage に身を傾けるのは簡単で、複雑なツールよりもやりたいことが正確にできます。なぜなら動く部分が少ないからです。最終的に、簡単なツールは他のツールが改善されたときにその恩恵を受けます。Carthage はかなり小さなモジュール式になっています。Git と Xcode に役割を任せたおかげで、それらに変更があったときに恩恵が受けられます。少しの努力でそれを取り入れることができます。一方で、もし機能を重複させていれば、全体のシステムがより壊れやすくなり、他のツールに変更があったときでも何も反映されなくなります。

どのように動いているか?

  1. Cartfile の Parse (12:22)
    Cartfile は Ordered Graph Data Language(OGDL) という言語のサブセットで書かれています。かなり簡単な言語ですが、今回のような設定ファイルにはとても便利です。Carthage は OGDL を依存関係のリストにパースし、種類を特定し、バージョンを特定します。特定のブランチや特定のコミッットのように指定することもできます。

  2. 依存関係のグラフの解消 (13:22)
    次に追加していくパッケージのそれぞれが依存したバージョンとそれらの関係を表したグラフを作る必要があります。グラフを取得するところから始め、指定された中でできるだけ最新のバージョンを取得しようとします。ほとんどの場合、ここで取得したものが最終的なグラフとして使われます。しかし、必ずとは限りません。もしコンフリクトがあれば、グラフを一度捨て、次の可能性のあるバージョンで新しいグラフを作ります。これは非効率であると思うかもしれません。しかし、実際はかなり上手く機能します。なぜなら、グラフを捨てるのは不正とみなされたときだけで、ほとんどの場合、最初の解決策で終わらせることができるからです。Carthage は完全な制約の解消人ではありません。依存関係解消の問題を特定するだけです。

  3. 全ての依存するパッケージのダウンロード (15:58)
    全ての依存するパッケージからバージョンが特定できました。このグラフを Cartfile.resolved と呼ばれるファイルに書きこみます。ここからそれぞれ依存するパッケージをダウンロードしていきます。Carthage は依存関係のレポジトリのグローバルキャッシュを管理します。こ���でプロジェクト間で10回、同じものをダウンロードしてくることがなくなります。パッケージをダウンロードする最初の段階は、それぞれのキャッシュが最新のものであるかどうか確認します。その後は、Carthage の checkout フォルダの中で Git Checkout を行います。そして、ビルドするために全てのレポジトリのファイルをコピーします。

  4. フレームワークのビルド (16:41)
    全ての依存するパッケージがダウンロードされると、ビルドが行われます。プロジェクトに Carthage の Build フォルダというものができます。それと、依存するパッケージのフォルダにもそれぞれ作られます。これらはシンボリックリンクとなります。ルートにある Xcode プロジェクトのフレームワークのスキーマをリストアップしてきます。この時、ダイナミックフレームワークとしてビルドするものだけを探し、スタティックライブラリやアプリのプロジェクトのようなスキーマはすべて無視されます。xcodebuild が呼ばれ、それらのスキーマをビルドしていきます。一度ビルドすると、リンクさせることができるようになります。最後に Carthage の build フォルダにビルドしたフレームワークをコピーします。可能な時は、Carthage は一からビルドする代わりにバイナリをダウンロードしようとします。これでビルド時間を70%減らせます。

技術的な選定と将来的な展望 (20:10)

  • ダイナミックフレームワーク vs スタティクライブラリ
    Framework にすることには多くの利点があります。必要なすべてがその中に含まれていてリソースなどを含むことができます。それに Swiftコード をライブラリにしようとすると必須条件です。Swift コードではスタティックライブラリを作ることはできません。

  • Swift vs Objective-C
    Swift にはまだ少し問題はありますが、Objective-C を使う代わりに Swift を使うことでかなり大きな利点を感じてきました。ReactiveCocoa の RACSignal は NSArray に似ているところがあるので型安全であることでかなり便利になりました。Swiftを使うことで、Signal が Xcode の出力によるもなのか、ビルドフェーズの変化によるものなのか(型によって)区別できるようになりました。値型は、状態とミュータビリティを扱うときに非常に役に立ちました。また、プロトタイプを行ったり、方向を変えるのが簡単なので、作ろうとしているものが素早く作ることができます。それに Swift のアクセス修飾子が非常に良い機能です。

  • ReactiveCocoa
    これが一番、物議を呼ぶトピックだと思います。ReactiveCocoa は時間の経過とともに値のシグナルをキャプチャしプログラミングを行うためのフレームワークです。Carthage の中で ReactiveCocoa をかなり広く使っています。ストリームベースのものがかなり簡単に扱えたので、選んで正解だったと思っています。たとえば、ネットワーク処理は TCP か HTTP のストリームでやり取りされています。また Git と Xcode の操作をサブタスクとして扱っており、それらは、ストリームとしてアウトプットが返ってきます。これらのアプリ全て ReactiveCocoa にとってかなり良かったです。もうすぐリリースされる ReactiveCocoa 3.0 の Swift API をテストする目的もありました。

1.0 (24:02)

Carthage はまだ1.0のリリースはされていません。する必要があることの一つはプロジェクトの設定です。Carthage には一つのプラットフォームのみビルドするかや Git Submodule として依存するパッケージをチェックアウトするかなどたくさん設定可能な項目があります。現在、全てのフラグはコマンドラインで指定しなければいけません。これを、全てのプロジェクトで同じ設定が自動的に使えるように設定ファイルとして指定できるようにしたいです。また、公開されている API もすべて注意深くレビューしていく必要があります。1.0 は初めのメジャーリリースなので、リリース後は後方互換性を注意しながらコミットしていきたいと思っています。

Q&A (27:00)

Q: フレームーワークとしてセットアップされていないサードパーティのライブラリをサポートする予定はありますか?
Justin: 現在、いくつか解決策はあります。フォークし、自分でフレームワークのターゲットを追加できます。比較的簡単なことだと思います。また、Carthage の Checkouts フォルダを使うこともできます。対象となるプロジェクトを Xcode の subproject または workspace として含めます。シンプルさを崩すような使い方をサポートするつもりはありません。フレームワークはたくさんの恩恵をもたらしてくれます。異なるプロダクトタイプのサポートを追加すると複雑になってしまいます。

Q: Carthage に対応させるためにはライブラリの作者は何をする必要がありますか?
Justin: 簡単に説明すると Xcode のスキーマを共有する必要があります。CI サービスのようなものを使ったことがあるならそれほど難しいものではありません。またビルドできることを確認し、プロジェクトのプルを簡単にするためにタグを付ける必要があります。他にもいろいろありますが、README を見てください。

Q: Carthage を Objective-C, Swift 以外で書くことを考えましたか?
Justin: 基本的に私たちは Cocoa デベロッパーです。できるだけ多くの Cocoa デベロッパーに親しみを持ってもらいたかったので、私たちがより親しみやすいものを選びました。他の言語、たとえば Ruby などでは、私には十分な経験がないです。

Q: CocoaPods は、依存関係の解決に Ruby で書かれたオープンソースの Molinillo というものを使っています。二つで共通の依存関係解決モジュールを使うために、これを採用することや Swift で実装することを考えましたか?
Justin: Ruby を使わなかった理由は、中央集結型のモデルを取っているからです。Carthage は中央集結型ではありません。この依存関係解決モジュールを使おうとすると、全てのライブラリのバージョンを事前に把握しておく必要があります。Carthage では、逐次 Git の操作を行い依存性を解決していくモデルとなります。基本的に私たちがとっているアプローチと両立しなかったのが理由です。

Q: Carthage のワークフローは、開発者の Mac でフレームワークをビルドしています。CI のビルドプロセスとは合いますか?
Justin: 基本的に、プロジェクトを CI ビルドするときと一緒だと思います。選んだ CI サーバーに Carthage をインストールすれば良いです。Carthage を皆さんに使うことを強制はしたくありません。たとえば、ReactiveCocoa と Mantle は Xcode のワークスペースとプロジェクトを持っています。それらはサブモジュールを管理するために Carthage を使っています。そして、プロジェクトそのもの自体も Carthage に対応しています。Carthage や CocoaPods を使わないワークフローたくさんの利点があります。正直に言うと、一番良いやり方だと思います。たくさんの Xcode プロジェクトやフレームワークを含めるために使っています。

Q: 開発者として、依存するコードのデバッグはどうすればいいのですか?
Justin: すこし手間がかかることです。どのように “debug symbol” が含まれていて、自動的に動いているかどうか理解する必要があります。デバッグのバージョンとライブラリのデバッグ設定を Carthage に指定してビルドできます。これらは取り除くことはできません。できることは Carthage がビルドしたバイナリを使用することだけです。チェックアウトしたサブモジュール切り替え、修正し、upstream にプッシュします。そして元に戻せばよいです。

Q: ライブラリの作者として、あなたが勧める方法は何ですか? PodSpec をメンテナンスして、Carthage の統合のテストをするべきだと思いますか?
Justin: ライブラリの作者として、CocoaPdos の統合のためのメンテナンスをしたことがありません。より適切な答えは、PodsSpec を書き CocoaPds の trunk に加えたいかどうかです。Carthage を使ってみると誰でも Xcode プロジェクトを加えるだけでいいことを確認できるかと思います。Carthage は基本的な Xcode の機能のスーパーセットなのです。

Q: Carthage はプロジェクトに該当するターゲットが一つしかないことを想定していますか?
Justin: ターゲットやフレームが必ずしも一つであるわけではありませんが、一つのプロジェクトからビルドしようとします。もし二つフレームワークを配布しようとすると複数のレポジトリが必要です。

Q: オープンソースでないライブラリを、フレームワークとして使うことができますか?
Justin: GitHub API の認証のために、HTTPS で行なわれる “Git credential cache” を使います。Git のメモリから読まれ、プライベートレポジトリも使用することができます。GitHub のエンタープライズは現在サポートされていません。しかし、近々サポートする予定ではあります。

Q: フレームワークかスタティックライブラリ、どちらの依存関係を選ぶかで得られる利点や違いは何ですか?
Justin: 一つの大きな違いは “duplicate symbols” を排除しているところです。長い間 CocoaPods が解決していた問題の一つでもあります。フレームワークは、ビルド時に動的にリンクできることを確認し、ランタイムで必要となります。もちろん、フレームワークはリソースを含んで配布することができます。

Q: プロジェクトをビルドするときチーム全ての開発者が Carthage をインストールする必要がありますか?
Justin: 今のところその必要はあります。多分、その手順を取り除く、誰でも実行可能になる shell script があると思います。しかし、それについては Carthage の仕様にはありません。このことについてはそれが解決してくれてると思います。他の方法として、コードをチェックアウトするためだけに Carthage を使い Carthage でビルドしない方法などです。結局は、あなたやチームがどういう方法が一番良いと思うかです。


Justin Spahr-Summers

Justin Spahr-Summers is the maintainer of several popular Cocoa projects, including ReactiveCocoa, Carthage, and Mantle. He works at Facebook’s London office.