TOKOROM BLOG

iOSとかVimとかその他日々の雑多な技術情報

これがXcodeでのバージョニングの決定版になるかも はてなブックマーク - これがXcodeでのバージョニングの決定版になるかも

Permalink

概要

この記事でできるようになること

  • 安定してInfo.plistの内容(ここではBuild番号)を変更できる
  • ふつうにRun Scriptで編集するとタイミングによってすぐにアプリに反映されないことがあったりしたがそれが解消される
  • Info.plistに差分がでないのでcommitのときに邪魔にならない

なお、この方法を教えてくれた熊谷さんがこの方法に行き着いた経緯や所感がこちらに詳しくまとめられています。詳細や考え方などをきちんと知りたいかたは是非、熊谷さんの記事をご一読ください!

必要な設定

  • Preprocess Info.plist file でInfo.plistをビルド前に確定させる
  • Run Scriptで${TEMP_DIR}/Preprocessed-Info.plistを編集する

以下、具体的な話をします。

経緯

これまで、

  • デバッグ用やArchive用のアプリのバージョンにだけgitのcommit番号とか最終更新日付を付ける

といったことをする場合に、Run ScriptでInfo.plistを(PlistBuddyなどで)編集してやるのが常だったと思うのですが、その場合、

  • Compile Sourcesより前にRun Scriptを設定してもScriptで編集した内容がアプリに反映されない場合がある
    • そのため、確実に内容を反映させるために2回ビルドを走らせたりとか…
  • 変更したInfo.plistに差分が出てソース管理上差分が出てしまう
    • 差分を元に戻せばいいのだけど、毎回それをやるのが面倒

といった課題があったりしました(少なくともぼくの手元では)。

そういったことを踏まえて、

potatotips 第7回 で「agvtoolで超かっこよくバージョニングできますか?」という発表をしたのですが、

その後のTwitterの議論(議論というかぼくは教えてもらっただけですが…)で、これぞというバージョニングの方法が生み出されました。

Preprocess Info.plist file

ぼくは今回初めて知ったのですが、Builde Settings -> Packaging の中に Preprocess Info.plist file という設定項目があり、これにYESを設定することでInfo.plistがBuild Phasesの他の何よりも先にInfo.plistが作成されることになります。

そのため、これまで困っていた「変更したInfo.plistの内容が即反映されない場合がある」といった課題は、じつはこの設定で解消されるものでした。

${TEMP_DIR}/Preprocessed-Info.plist

そして、Preprocess Info.plist fileYESにした場合、通常のInfo.plistと別に ${TEMP_DIR}/Preprocessed-Info.plist という名前で別のInfo.plistが作成されるようになります。あとは、Run ScriptでそのPreprocessed-Info.plistのほうを編集してあげれば、それがアプリに即反映される形になります(Run ScriptはCopy Bundle Resourcesより前に配置する)。

素晴らしいのは、この後、元のInfo.plistが変更されることはなく、しかも、${TEMP_DIR}/Preprocessed-Info.plistが別のディレクトリにあるためInfo.plistに差分が出ることはありません(もし、Preprocessed-Info.plistがプロジェクトディレクトリ内にできてしまう場合にはそれを.gitignoreに入れれば良い)。

また、良い副作用として、編集するInfo.plist名が${TEMP_DIR}/Preprocessed-Info.plistに固定できるというのもあります。ふつうにInfo.plistを編集する場合には、プロジェクトによってInfo.plistのファイル名が変わるため、プロジェクトごとにRun Scriptを変更する必要がありました。しかし、このファイル名が固定になるため、同じ挙動をさせるのでよければRun Scriptを変更することなくどのプロジェクトでも流用できるようになります。

Run Scriptのサンプルを以下に示します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if [ ${CONFIGURATION} = "Debug" ]; then

  plistBuddy="/usr/libexec/PlistBuddy"
  infoPlist="${TEMP_DIR}/Preprocessed-Info.plist"
  currentVersion=$($plistBuddy -c "Print CFBundleVersion" $infoPlist)

  versionPrefix="dev-"
  lastCommitDate=$(git log -1 --format='%ci')
  versionSuffix=" ($lastCommitDate)"

  versionString=$versionPrefix$currentVersion$versionSuffix

  $plistBuddy -c "Set :CFBundleVersion $versionString" $infoPlist

fi

スクリプトの中身は、

  • ConfigurationがDebugのときだけ実行する
  • PlistBuddyで現在のビルド番号(CFBundleVersion)を取得
  • バージョンのPrefixとしてdev-を設定
  • gitの最後のcommitの日付を抽出してそれをバージョンのSuffixとする
  • 最終的に dev- + ビルド番号 + 最後のcommit日付 を新しいビルド番号(CFBundleVersion)として設定する

となっています。

実行結果

これらの設定をすることで、例えばアプリ内でビルド番号を表示したときに、Debugモードであれば、

と表示され、Releaseモードであれば、

と表示されるようになり、開発中のアプリにのみどのcommitまでが入っているかを自動的に埋め込むことができるようになります。

2014/05/19 追記

なお、Info.plistをスクリプトでいじるときに、上記のようにCFBundleVersionを読み込んでそれを使って同じCFBundleVersionを上書きするケースだと、前に作ったInfo.plistの値がインプットとして使われてしまい、

こんなかんじでビルド番号が壊れてしまうことがあります。必ずCleanしてからビルドすればこの問題は発生しないのですが、せっかく自動でバージョニングしているのに制約が付いてしまうのも不格好です。

これを避けるためのひとつの方法として、Build Phases の一番最後のRun Scriptで${TEMP_DIR}/Preprocessed-Info.plistを削除してしまうという方法が考えられます。

2014/5/20 熊谷さんにより良い方法をご提案いただいたので修正

これを避けるためには、熊谷さんの記事のとおり、インプットとなるInfo.plistを${TEMP_DIR}/Preprocessed-Info.plistでなく元のInfo.plistそのもの(${SRCROOT}/${INFOPLIST_FILE})にするのが良さそうです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if [ ${CONFIGURATION} = "Debug" ]; then

  plistBuddy="/usr/libexec/PlistBuddy"
  infoPlistFileSource="${SRCROOT}/${INFOPLIST_FILE}"
  infoPlistFileDestination="${TEMP_DIR}/Preprocessed-Info.plist"

  currentVersion=$($plistBuddy -c "Print CFBundleVersion" $infoPlistFileSource)

  versionPrefix="dev-"
  lastCommitDate=$(git log -1 --format='%ci')
  versionSuffix=" ($lastCommitDate)"

  versionString=$versionPrefix$currentVersion$versionSuffix

  $plistBuddy -c "Set :CFBundleVersion $versionString" $infoPlistFileDestination

fi

これにより、常にオリジナルのInfo.plistの内容がインプットとして使われ、書き出しは一時ファイルのPreprocessed-Info.plistのほうになり、綺麗なインプットを利用できそのインプットを汚さない最強の組み合わせと言えそうです。

なお、他にもビルド後にバンドルされたInfo.plist(${TARGET_BUILD_DIR}/${INFOPLIST_PATH})を直接編集する案などをTwitterやはてブコメントでいただきましたが、今のところ安全性が不明(そのあたりも熊谷さんの記事に詳しく書かれています)です。Preprocess Info.plist fileYESにするというワンステップ増えてしまいますが逆に言えばそれだけですので、少なくとも安全性がはっきりするまではPreprocess-Info.plistを使う方が安心だと思います。

まとめ

  • Preprocess Info.plist fileYESを設定する
  • Run Scriptで${TEMP_DIR}/Preprocessed-Info.plistを編集する

の2ステップでこういったバージョニングが安定して実現できるようになります。バージョニング以外でもInfo.plistを可変にしたい場合には等しくこの方法が有効かと思います。

教えていただいた きしかわさん さんと 熊谷さん のご両名に感謝です!

この設定を埋め込んだサンプルは ここ に置いてあります。

Comments