これがXcodeでのバージョニングの決定版になるかも

2014年5月19日
xcode / ios /

xcode_versioning

概要

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

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

必要な設定

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

経緯

これまで、

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

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

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

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

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

Preprocess Info.plist file

preprocess_info_plist

ぼくは今回初めて知ったのですが、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のサンプルを以下に示します。

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

スクリプトの中身は、

となっています。

実行結果

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

versioning_sample_debug

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

versioning_sample_release

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

2014/05/19 追記

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

versioning_sample_failed

こんなかんじでビルド番号が壊れてしまうことがあります。必ず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})にするのが良さそうです。

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を使う方が安心だと思います。

まとめ

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

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

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

Related Entries
Latest Entries