読者です 読者をやめる 読者になる 読者になる

CocoaPods Private SpecsでiOS用社内ライブラリを管理する

技術部のid:gfxです。iOSアプリ開発に欠かせないパッケージ管理ツールといえばCocoaPodsですが、これはPrivate Podsを作って社内ライブラリ専用のSpecs(private Specs)を管理することができます。

※ 2014/12/22追記 CocoaPods 0.35.0 でpod lintの--only-errorsが廃止されて--allow-warningsになったのでそのように変更しました

private SpecsがなくてもGit URLを指定することで社内用podspecを開発・管理することはできますが、private SpecsがあるとURL指定を簡略化したり依存関係の解決が簡単になるというメリットがあります。クックパッドでもすでに十数個のprivate podspecが登録されており、CookpadUIやAPI clientなどはpodspecとしてprivate Specsに登録されています。

そこで本エントリでは、Specsを運用している間に起きた問題などを共有します。なお、CocoaPodsのバージョンは v0.35.0 です。private Specsの仕様はまだ流動的なので、実際に運用に入る際は仕様をご確認ください。

概要

  • podspecでGit SSH URLを参照しているときは pod lib lint--allow-warnings をつける
  • pod repo push には --allow-warnings をつける
  • podspecでprivate Specsに依存する場合、pod lib lint には `--sources=...' でprivate SpecsのURLを与える

詳細

まずCocoaPods Specsの実体ですが、これはただの特別なパス構造を持ったgit repositoryです。仕様に従ったgit repositoryを用意すれば、検索こそできませんがPodfileでデフォルトのCocoaPods/Specs同様に使えます。

なお、本エントリでは以下の3つのgit repositoryを使います。

また、以下のようにprivate Specsを登録しておきます。PrivateSpecsExampleは空でかまいません。git repositoryの構成についてはpodコマンドがすべて面倒をみてくれるので、気にする必要がないのです。

$ pod repo add myspecs git@github.com:gfx/PrivateSpecsExample.git

podspecを作る

それでは、podspecをひとつ作り、private Specsに登録してみましょう。podspecのリポジトリはExamplePodです。

podspecは pod spec create ExamplePod で作り、生成されたExamplePodを修正します。 s.source はGitHub Enterpriseを想定しているのでGit SSH URLで指定しています。

-  s.source       = { :git => "http://EXAMPLE/ExamplePod.git", :tag => "0.0.1" }
+  s.source       = { :git => "git@github.com:gfx/ExamplePod.git", :tag => "0.0.1" }

ここでポイントその一ですが、pod lib lint は Git SSH URLだとfirewall越しにアクセスできない可能性があるというwarningsを出します 。そして、 pod lib lintはデフォルトでwarningsを致命的エラー扱いにします 。警告を致命的エラーにしないためには --allow-warnings オプションを指定しなければいけません。

このオプションのもとでの結果は以下のようになります。warningsは出ていますがvalidationはパスしています。

$  pod lib lint --allow-warnings

 -> ExamplePod (0.0.1)
    - WARN  | [source] Git SSH URLs will NOT work for people behind firewalls configured to only allow HTTP, therefore HTTPS is preferred.

ExamplePod passed validation.

なお、ここではやむを得ずwarningsを無視していますが、基本的にはwarningsを無視するべきではありません。他のwarningsが出ていないかの確認はするほうがいいでしょう。

podspecをpushする

次に、これをprivate Specsにpushしてみます。今回は同じrepositoryを使いますが、実際には社内のSpecsに対して行います。この時、前述の理由でGit SSH URLだとspec validationに失敗するのですが、このとき必要なオプションは--allow-warningsです。これが第二のポイントです。

# tagを打ってpodspecをpushする
$ git tag 0.0.1 && git push origin 0.0.1
$ pod repo push --allow-warnings  myspecs *.podspec

これでpodspecをprivate Specsに登録できました。あとはこのpodspecを使いたいプロジェクトのPodfileで以下のように source を指定すれば、そこに登録されたpodspecを使うことができます。

# Podfile
source 'git@github.com:gfx/PrivateSpecsExample.git'
source 'https://github.com/CocoaPods/Specs.git'

pod 'ExamplePod'

private podspecに依存したpodspecを作る

さて、このprivate podspecに更に依存したpodspecを作ってみます。内容はExamplePodと同じのExamplePod2をつくり、podspecの名前やパスを書き換えます。また、 s.dependency 'ExamplePod' を加えてprivate podspecへの依存を宣言します。

# ExamplePod2.podspec
  s.dependency "ExamplePod"

これを pod lib lint で検証すると、ExamplePodがみつからず以下の様なエラーになります。

$ pod lib lint

[!] Unable to find a specification for `ExamplePod` depended upon by `ExamplePod2`

ExamplePodはprivate podspecなので、そのままでは見つからないのです。しかしPodfileでは source でprivate Specsを指定できましたが、 pod lib lint によるpodspecの検証のときはPodfileは使われないので、private podspecを参照できないのです。そして pod repo add で指定したSpecsが暗黙のうちに使われるということもありません。

このため、pod lib lint--sources オプションでprivate Specsを指定する必要があります。デフォルトのCocoaPods/Specsも必要なので、カンマ区切りでURLのリストを渡します。これが三つ目のポイントです。

$ pod lib lint --allow-warnings --sources='git@github.com:gfx/PrivateSpecsExample.git,https://github.com/CocoaPods/Specs'

 -> ExamplePod2 (0.0.2)
    - WARN  | [source] Git SSH URLs will NOT work for people behind firewalls configured to only allow HTTP, therefore HTTPS is preferred.
    - WARN  | [iOS] Unable to find a license file

ExamplePod2 passed validation.

これで通常通り検証ができました。 pod repo push は検証のロジックがlintとは違うらしく、 --sources は不要です。

$ git tag 0.0.1 && git push origin 0.0.1
$ pod repo push --allow-warnings  myspecs *.podspec

これですべて問題なくできました。各種コマンドのオプションがが複雑なので、Makefileを作っておきましょう。CIでログなどをとるため、 --verbose もつけておくといいでしょう。

lint:
    pod lib lint --verbose --allow-warnings --sources='git@github.com:gfx/PrivateSpecsExample.git,https://github.com/CocoaPods/Specs'

release:
    pod repo push --verbose --allow-warnings  myspecs *.podspec

.PHONY: lint release

これでprivate Specsを使う準備はすべて整いました。

まとめ

CocoaPod private Specsはいくつか運用のポイントがあり、本エントリではそれらを紹介しました。private Specs周りはハマるところが多く、バージョンによって挙動が異なることも多いので、安定して運用できるよう情報を共有できればと思います。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/