製品用のビルドと、社内テスター向けのベータビルドなど、目的のビルドによっては少しプログラムを変更したいということがたまにあります。1つのコードから有料版と無料版といった2種類のプロダクトをリリースしている場合なんかもそうです。
Objective-Cのソースファイルの場合は #if 〜 #endif などのプリプロセッサディレクティブを使うことで処理を切り分けられます。
# if BETA_BUILD
NSString *serverURL = @"http://.....";
# else
NSString *serverURL = @"https://.....";
# endif
こんな感じの切り分けをソースファイルじゃないものでもやっちゃいましょうというのが今回の話。例えば、アプリバンドルに持たせているデータファイルとかですね。(※ただしテキストに限る)
やりたいこと
例えば、こんな感じのJSONで書かれたデータ、guides.jsonがあるとしましょう。
{
"guides": [
{
"id": "mes001",
"message": "もう一度やり直すにはここをタップ!",
"when": "back"
},
{
"id": "mes002",
"message": "ともだちに教えてあげよう!",
"when": "finish"
},
{
"id": "mes003",
"message": "行き詰まったらちょっと休憩してみる?",
"when": "back"
}
]
}
ベータビルドでは、このうち、真ん中のmes002のデータを除外することになりました。
そこで、こう書いておきます。
{
"guides": [
{
"id": "mes001",
"message": "もう一度やり直すにはここをタップ!",
"when": "back"
},
# if !BETA_BUILD
{
"id": "mes002",
"message": "ともだちに教えてあげよう!",
"when": "finish"
},
# endif
{
"id": "mes003",
"message": "行き詰まったらちょっと休憩してみる?",
"when": "back"
}
]
}
すると、ベータビルド時にはこうなってほしい。
{
"guides": [
{
"id": "mes001",
"message": "もう一度やり直すにはここをタップ!",
"when": "back"
},
{
"id": "mes003",
"message": "行き詰まったらちょっと休憩してみる?",
"when": "back"
}
]
}
では、やってみましょう
心配ありません。Mac OS Xには最初から(?)、unifdefというコマンドラインツールがインストールされています。こいつは、プリプロセッサマクロ定義を与えると、その定義に応じて選択的に #if 〜 #endif を削除するツールです。
#if だけでなく、 #ifdef, #ifndef, #elif, #else なんかも大丈夫ですし、 #if HOGE > 10 とか #if AAA && BBB といった演算子を使った式にも対応しているスグレモノです。
今回の例であれば、ターミナルから
unifdef -DBETA_BUILD guides.json
と実行すれば、標準出力に結果が出力されます。 -o オプションでファイルに出力させることもできます。
詳細は man unifdef で表示されるマニュアルを参照。
もうちょっとだけ詳しく
unifdefにプリプロセッサマクロが定義されていることを伝えるオプションが -D定義名=値 です。複数定義したければ、このオプションを複数指定します。値の指定を省略すると1を指定したことになります。
unifdef -DBETA_BUILD -DAPI_LEVEL=3 file
逆に、プリプロセッサマクロが定義されていないことを伝えるオプションが -U定義名 です。こちらも複数指定できます。
unifdef -UBETA_BUILD -UEVALUATION file
ところで、unifdefは指定したプリプロセッサマクロだけを処理します。 -D でも -U でも指定しなかったものについては、 #if 〜 #endif が残ったままになります。
例えば、こんなファイルin.txtに対して
# if HOGE
あああ
# else
いいい
# endif
# if FUGA
ううう
# endif
unifdef -UHOGE -o out.txt in.txt
と、 HOGE が定義されていないことだけ伝えてやると、結果のout.txtは、こうなります。
いいい
# if FUGA
ううう
# endif
その他の注意点として、unifdefはデフォルトでC言語のコメントや(行末のバックスラッシュによる)行の連結を解釈します。例えば /* 〜 */ のコメント内に書かれた #ifdef は無視されてそのまま残ります。 /* や // に特別な意味を持たせたくないときは、 -t オプションを付ければOKです。