この記事は - スピカ Advent Calendar 2015 - 7日目の記事です。
弊社の アプリ では開発/本番時にどのように環境設定値を切り替えているかを話したいと思います。
はじめに
以前のプロジェクトでは環境変数を pbxproj
に定義していたのですが、以下の点で苦労していました。
- スキーマと変数定義が密接な関係のため、複数の開発環境向けにビルドするなどが大変
-
pbxproj
はファイル管理も含んでいるため、一時的に変更したローカルな定義値を間違えて一緒にコミットすることがあった- 差分もたくさん出るので、レビュー側も大変になる
- 秘密鍵などの情報もコミットしていた
そこで今回以下のような構成を採用しました。
まずフローを図示すると以下のような構成になります。
dotenv
環境によって変わる変数は .env
で管理しています。
中身はキーバリューで記述します。
※ 本来 .env
は環境毎にユニークなことが理想ですが、アプリでは同じ環境でデバッグ/リリースビルドを行うため、 .env
内で両方定義してあります。
SOME_SECRET_KEY_RELEASE = "hoge"
SOME_SECRET_KEY_DEBUG = "fugue"
FACEBOOK_APP_ID_RELEASE = "123456"
FACEBOOK_APP_ID_DEBUG = "654321"
※ .env
ファイル自体は秘密鍵などの情報があるため、今のところバージョン管理していません。ただどこにも管理されていないのは困るので Qiita:Team
の記事上で管理をしています。
ERB テンプレート
.env
は値の羅列のリストになり、どういった使われ方なのかわかりづらいため、必要な箇所で参照するためのテンプレートを用意しています。
ERB テンプレートはバージョン管理し、pbxproj
から分離されたので、変更がある際もレビューしやすくなりました。
あわせてテンプレートから実際のファイルを生成する処理を Xcode の Build Phase スクリプトに設定し、常に最新の環境設定になるようにしています。
xcconfig
xcconfig は YAML ベースでテンプレートを用意しています。
YAML の場合、継承関係の表現が可能なため、 xcconfig の定義に適しています。
Release: &Release
INFOPLIST_PREPROCESSOR_DEFINITIONS:
INFOPLIST_PREPROCESS: "YES"
FACEBOOK_APP_ID: "<%= ENV['FACEBOOK_API_KEY_RELEASE'] %>"
Debug: &Debug
<<: *Release
FACEBOOK_APP_ID: "<%= ENV['FACEBOOK_API_KEY_DEBUG'] %>"
OTHER_SWIFT_FLAGS: -DDEBUG
Env.swift
Swift の場合マクロ経由の値参照できないため、 Env.swift.erb
から必要な環境変数を参照します。
import Foundation
public struct _Env_Release {
/// Facebook APIキー
public static let FACEBOOK_API_KEY = "<%= ENV['FACEBOOK_API_KEY_RELEASE'] %>"
}
public struct _Env_Debug {
/// Facebook APIキー
public static let FACEBOOK_API_KEY = "<%= ENV['FACEBOOK_API_KEY_DEBUG'] %>"
}
#if DEBUG
public typealias Env = _Env_Debug
#else
public typealias Env = _Env_Release
#endif
おわりに
.env
を利用することで環境設定値とスキーマを分けて利用することが可能になりました。
※ Jenkins 環境ではジョブごとにそれぞれの環境を向いた .env
を注入して、ビルドをしてたりしています。
よろしければ、みなさんはどうしているか等コメント頂ければ幸いです。
付録
変換処理例
def load_xcconfig_yaml()
erb = File.open("./config/xcconfig.yaml.erb") { |f| ERB.new(f.read) }
yaml = YAML.load(erb.result(binding))
yaml.each{ |scheme, map|
File.open("./config/#{scheme}.xcconfig", "w") do |f|
if map
map.delete("<<") if map.key?("<<")
map.each { |k, v|
f.puts "#{k} = #{v}"
}
end
end
}
end