概要
サムザップ Advent Calendar 2021 14日目の記事です。
本稿は業務改善系の記事になります。
経緯
所属プロジェクトの業務フローにアプリ申請作業があります。
その中でいくつか改善できそうな問題点がありました。
問題点
- 申請用パッケージ作成に必要な各種ツールの操作が手作業
- ダブルチェック体制のため、エンジニア2人が長時間拘束される
- 新しくチームへ参加した人にオペレーションを伝授する場合、エンジニア3人体制になる
- 業務時間の大半が持っていかれる
申請が手作業なのは珍しくないとは思っていますが、機械的な手続きを自動化すればエンジニア2〜3人が長時間拘束される状況は改善できそうだなと思いました。
今回はfastlaneの導入をもって機械的な手続きの自動化を行いました。
fastlaneとは
fastlaneはアプリ申請に関わるあらゆる手続きがアクションとして実行できるようになっているRuby製の開発支援ツールです。
2015年くらいから関連記事があるので割と歴史のあるツールのようですが、今でも定期的にアップデートがあるので安心して使えそうです。
やりたかったこと
- アプリのビルド〜申請を自動化
- iOS証明書管理も自動化(fastlane match)
- 進捗をSlackで通知
実際にできたこと
実際にできたことは部分的です。
今後もマイルストーンを設定して改善していけたらと思っています。
- アプリのビルドは自動化、申請は手動(iOSはxcworkspace書き出しまで)
- 証明書は手動管理
- 特定環境下のアプリはAppCenterのアップロードまで自動化
- 進捗をSlackで通知
fastlane導入
bundlerを使ってfastlaneのインストールをしました。
bundlerのインストール
$ sudo gem install bundler
プロジェクトルートにGemfile作成
$ cd /path/to/projectRoot
$ touch Gemfile
$ vi Gemfile
source "https://rubygems.org"
gem "fastlane"
gem "dotenv" # ローカル環境変数を使用するため
gem "cocoapods"
gem "xcode-install" # Xcodeバージョン指定するため
所属プロジェクトではdotenv、cocoapods、xcode-installなども追加しています。
インストール
Gemfileを定義した場所でインストールコマンドを実行します。
$ bundle install
Rubyのエラー回避
上記コマンド実行時にエラーが出る場合、rubyのバージョンを改める必要がある模様です。
macOS Catalinaでも同様のエラーが発生しました。
rbenvから2.6.3をインストールしてパスを通してあげたら問題なくインストールできました。
macOS Mojave 'ruby/config.h' file not found
実装
共通定義
Configファイルを作って、共通で使いたい定数のようなものはまとめるようにしました。
module Unity
def self.path
'/path/to/Unity.app/Contents/MacOS/Unity' # Unityのパス
end
end
module Xcode
def self.version
'12.3'
end
def self.ipa_name
'exampleApp'
end
end
module Slack
def self.url
'https://path/to/slack/webhook/url' # SlackのWebhook URL
end
def self.channel
'#fastlane_notification' # Slackのチャンネル名
end
def self.icon_url
'https://path/to/fastlane_icon.png' # アイコンURL(一応記述してるけどSlack側の設定を見ているような・・?)
end
end
module Git
def self.base_branch
'develop'
end
end
以下のように記述することでConfigファイルを読み込めるようです。
load './Config'
Slack通知
fastlaneはiosとandroidプラットフォームに分けてレーンを定義する必要があるようです。
各レーンの開始と終了の成功可否を監視したかったため、before_eachとafter_eachにアクションを書きました。
load './Config'
before_all do
ENV["SLACK_URL"] = "#{Slack.url}"
ENV["FL_SLACK_ICON_URL"] = "#{Slack.icon_url}"
ENV["FL_SLACK_CHANNEL"] = "#{Slack.channel}"
end
error do |lane, exception|
slack(
message: "エラーが発生しました",
success: false,
payload: {
"Output" => exception.to_s
}
)
end
platform :ios do
before_each do |lane, options|
# optionsがArrayで渡ってきた場合、versionとbuildNumberをチェック
if !options.nil? && options.is_a?(Array) && options.size > 0 && options[0].include?(:version)
slack(
message: "#{lane} を開始します",
payload: {
"Build Date" => Time.new.to_s,
"version" => "#{options[0][:version]}",
"buildNumber" => "#{options[0][:buildNumber]}"
}
)
else
slack(
message: "#{lane} を開始します",
payload: {
"Build Date" => Time.new.to_s,
}
)
end
end
after_each do |lane, options|
slack(
message: "#{lane} が完了しました",
)
end
# 中略
end
Unityのビルド
optionsという引数を使って、ビルド環境やバージョン、出力先を指定できるようにしています。
ビルド後、cocoapodsアクションでpod installを実行しています。
# 中略
platform :ios do
# 中略
desc 'Productionパッケージ作成'
lane :production do |options|
options[:env] = 'production'
options[:version] = UI.input("バージョンを入力してください: ") if options[:version].nil?
options[:buildNumber] = UI.input("ビルド番号を入力してください: ") if options[:buildNumber].nil?
options[:out_path] = "path/to/output/"
# Switch Platform実行
switch_unity(options)
# Reimport Assets実行
reimport_assets(options)
# ビルド実行
build_unity(options)
# ProductionはUnityのビルドまで(Xcodeの作業は手動で行う)
end
desc "Switch Platform to iOS"
private_lane :switch_unity do |options|
# 割愛:UnityのSwitchPlatformメソッドをバッチモードで呼び出す
end
desc "Reimport Assets"
private_lane :reimport_assets do |options|
# 割愛:Assets配下のReimport Assetsをバッチモードで呼び出す
end
desc "Build Unity (iOS)"
private_lane :build_unity do |options|
# 割愛:パッケージのビルドメソッドをバッチモードで呼び出す
# cocoapods install
cocoapods(
clean_install: true,
podfile: "#{options[:out_path]}/Podfile"
)
end
# 中略
end
ipaのビルド
特定環境下のみXcodeのビルドも行っています。
xcversionアクションでXcodeのバージョンを指定しています。
gymアクションでipaのビルドを行っています。
# 中略
platform :ios do
# 中略
desc "ipaのビルド"
private_lane :build_ipa do |options|
# xcodeバージョン指定
xcversion(version: "#{Xcode.version}")
# ビルド
gym(
workspace: "#{options[:out_path]}Unity-iPhone.xcworkspace",
configuration: "Release",
scheme: "Unity-iPhone",
silent: true,
clean: true,
archive_path: "#{options[:out_path]}Unity-iPhone.xcarchive",
output_directory: "#{options[:out_path]}",
output_name: "#{Xcode.ipa_name}.ipa",
export_method: "development",
export_options: {
signingStyle: "manual",
provisioningProfiles: {
"#{ENV['APP_IDENTIFIER']}" => "#{ENV['APP_PROVISIONING_PROFILE_UUID']}",
"#{ENV['NOTI_BUNDLE_ID']}" => "#{ENV['NOTI_PROVISIONING_PROFILE_UUID']}"
}
}
)
end
desc "ipaファイルの再署名(Xcode12.4以下で端末にインストールできない問題の対応)"
private_lane :resign_ipa do |options|
# 解凍先はxarchiveやInfo.plistの格納先に合わせる
sh "unzip -o #{options[:out_path]}#{Xcode.ipa_name}.ipa"
sh "codesign -dvvvvv ./Payload/#{Xcode.ipa_name}.app"
sh "codesign -s '#{ENV['SIGNING_IDENTIFY']}' -f --preserve-metadata --generate-entitlement-der ./Payload/#{Xcode.ipa_name}.app"
sh "zip -ru #{Xcode.ipa_name}_resign.ipa Payload"
sh "rm -rf Payload/"
sh "mv #{options[:out_path]}#{Xcode.ipa_name}.ipa #{options[:out_path]}#{Xcode.ipa_name}_base.ipa"
sh "mv #{Xcode.ipa_name}_resign.ipa #{options[:out_path]}#{Xcode.ipa_name}.ipa"
end
# 中略
end
AppCenterへのアップロード
AppCenterアクションを定義したプラグインがあったので、そちらを利用しました。
https://github.com/microsoft/fastlane-plugin-appcenter
# 中略
platform :ios do
# 中略
desc "AppCenterへアップロード"
private_lane :upload_to_appcenter do |options|
UI.user_error!("optionsに必要なパラメータがありません") if options.nil?
appcenter_upload(
api_token: "#{ENV['APPCENETR_TOKEN']}",
owner_name: "#{ENV['APPCENTER_OWNER_NAME']}",
owner_type: "#{ENV['APPCENTER_OWNER_TYPE']}", # Default is user - set to organization for appcenter organizations
app_name: "example_app_ios",
file: "#{options[:out_path]}#{Xcode.ipa_name}.ipa",
release_notes: "#{options[:version]}",
notify_testers: false # Set to false if you don't want to notify testers of your new release (default: `false`)
)
end
end
結果
今まで細かい工程をメモしながら行っていた作業が、以下コマンドを打つだけで概ね完了するようになりました!
(実行前にgitコマンド打ったりはしてます)
$ bundle exec fastlane ios production
2〜3人のエンジニアを長時間拘束していた作業を、プロジェクト独自の知見を持たない人でも実行できて短時間で完了するようにできました。
まとめ
今まで誰かが構築したCIツールの操作をすることはあっても、実際にコードを書いたりフローを考えたりすることが無かったのでとても勉強になりました。
ただ冒頭で前述した通り、まだまだ「自動化」「効率化」とは言い難く、課題は残っています。
- 申請準備の半自動化はできたが、完全な自動化ではない
- ダブルチェックの必要な手作業が残っている(Xcodeまわり)
- iOSとAndroidをシーケンシャルもしくは並列でビルドできていない
- ターミナルからiOSとAndroidのビルドコマンドを手打ちしてる
作った人がいなくても回るような仕組みを作れるまで引き続き改善してきたいと思っています。