Xcode Cloudとは
Xcode Cloudとは、Appleが提供するCI/CDサービスです。
他のCI/CDサービスと比べて独特な仕様もありますが、比較的安価だったり、純正なだけあって証明書まわりの管理が楽になるなどのメリットがあり、弊社のiOSアプリ開発でも利用しています。
SonarQube Cloudとは
SonarQube Cloudとは、コードの品質やセキュリティの問題を自動的に検出してくれるツールです。例えばPull Requestを作成すると差分のコードを解析してPRにコメントしてれたりします。
ただし、テストカバレッジが知りたい場合は自動で計算してくれないので、CIサービスなどから送信する必要があります。
本記事では、Xcode Cloudでのテスト実行からSonarQube Cloudへのデータ送信までのプロセスと、その過程で直面した課題について解説したいと思います。
Xcode Cloudのテスト結果からコードカバレッジを取得
Xcodeでのビルド結果からカバレッジを見つけるのはそんなに苦労しませんが、Xcode Cloud上で取得するのは一筋縄ではいきません。少し強引なワークアラウンドを使ってようやく取得できたので紹介したいと思います。
カスタムスクリプト
Xcode CloudではWorkflowの一連の処理で、カスタムスクリプトを実行できるカスタマイズポイントが 3 つあります
post_clone.sh
(クローン後、依存解決前)pre_xcodebuild.sh
(依存解決後、ビルド前)post_xcodebuild.sh
(ビルド後)
ビルド後の .xcresult からカバレッジデータを取り出したいのでpost_xcodebuild.sh
にスクリプトを記述していきます。
テストアクション
Xcode Cloudでテストアクションを実行すると build-for-testing
とtest-without-build
という2回のフェーズで Xcode Build Action が実行されます。
カバレッジデータはtest-without-build
の実行後に生成されるので、下記のように"build-for-testing"のタイミングでは早期終了する方法で実装してみました。
if [ "$CI_XCODEBUILD_ACTION" = "build-for-testing" ]; then exit 0 fi
カバレッジレポートを生成
.xcresultファイルはCI_RESULT_BUNDLE_PATH
という環境変数で取得することができます。しかしそのままでは送信できないので
SonarQubeCloudへカバレッジデータを送信するためにxmlファイルへの変換が必要です。
github.com
↑を参考にスクリプト内で実装できそうです。
あとはSonarQubeCloudへ送信するだけ、、、?
あとはSonarScanを実行するとうまくいくはずです。 そのためにいくつかプロパティを設定する必要があります。
その中でもソースのパスは必須です。 ソースのパスは環境変数が用意されているので簡単です。
sonar.sources=${CI_PRIMARY_REPOSITORY_PATH}
しかしここに罠が潜んでいました。
罠: ソースコードはどこ?
実はtest-without-build
のタイミングではCI_PRIMARY_REPOSITORY_PATH
の環境変数が取得できません、いやそもそもクローンしないのでソースコード自体がありません、、、
ただci_scripts
が存在するのでバックアップは取れるのかもしれないと考えました。
build-for-testing
の段階でソースコード全体を別のディレクトリにバックアップを試みましたが残念ながらうまくいきませんでした。
test-for-building | testing-without-building | |
---|---|---|
ソースコード | ある | ない |
カバレッジ | 取れない | 取れる |
八方塞がりです。。。
ワークアラウンド
苦肉の策ですがカスタムスクリプトでもう一度テストビルドを実行するワークアラウンドを見つけました。
テストを2度実行するのは本当はやりたくありません。CIの待ち時間は可能な限り減らしたいはずです。 しかし幸いカバレッジデータはバージョン毎に取得する程度の頻度で済みそうなので、今回はこのワークアラウンドを採用してみようと思います。
スクリプトでテスト実行
今度はbuild-for-testing
のタイミングでのみ実行したいので、先ほどの早期終了の判定を修正します。
if [ "$CI_XCODEBUILD_ACTION" = "test-without-build" ]; then exit 0 fi
では早速テストビルドの設定をしていきます。
resultBundlePath
にはワークフローのテストアクションの.xcresultファイルが上書きされないようにしておきます。
# テスト実行 RESULT_BUNDLE_PATH=$CI_DERIVED_DATA_PATH/Logs/Test/ResultBundle.xcresult xcodebuild \ -scheme "$CI_XCODE_SCHEME" \ -destination "id=$CI_TEST_DESTINATION_UDID" \ -derivedDataPath $CI_DERIVED_DATA_PATH \ -enableCodeCoverage YES \ -resultBundlePath $RESULT_BUNDLE_PATH \ clean test
しかしこれでは実行できませんでした。
実はCI_TEST_DESTINATION_UDID
が空っぽなのです。
罠: シミュレータを探せ
ワークフローの設定でテストアクションのために適切なシミュレータの設定をしているはずなので、できればそれを使いたいのですがCI_TEST_DESTINATION_UDID
が空っぽで利用できません。
post_xcodebuild.sh
のタイミングではシミュレータを閉じてしまっているようです。
そこで利用可能なシミュレータを下記のスクリプトで探し出しました。
シミュレーターの機種は変えたくなる可能性があるので、ソースを触らないで良いようにワークフローの環境変数COVERAGE_BUILD_DEVICE_NAME
を用意しておきます。
# シミュレーター設定 SIMULATOR_ID=$(xcrun simctl list devices | grep "$COVERAGE_BUILD_DEVICE_NAME" | grep -oE '[0-9A-F-]{36}' | head -n 1) [ -z "$SIMULATOR_ID" ] && { echo "Error: $COVERAGE_BUILD_DEVICE_NAME <Simulator ID: $SIMULATOR_ID> not found." exit 1 }
見つかったらビルドするためにbootしておきます。
echo "Boot $COVERAGE_BUILD_DEVICE_NAME <Simulator ID: $SIMULATOR_ID>" xcrun simctl boot $SIMULATOR_ID
これでようやくビルドが実行できました。
あとは先ほどと同じようにカバレッジレポートを生成してSonarScanを実行することでようやくカバレッジの送信ができました🎉🎉
まとめ
少し強引な方法ですが、Xcode CloudからSonarQube Cloudへのカバレッジデータ送信までのプロセスを確立することができました。この方法は完璧とは言えませんが、バージョン毎にカバレッジデータを取得する目的では十分に機能しそうです。
今後の改善点としては、テストの二重実行を避ける方法や、より効率的なカバレッジデータの取得方法を探ることが考えられます。また、Xcode CloudやSonarQube Cloudのバージョンアップにより、将来的にはよりスムーズなプロセスが実現される可能性もあります。
この記事が、同様の課題に直面している開発者の方々にとって参考になれば幸いです。