Created
July 17, 2025 14:50
-
-
Save y-hirakaw/67cdd5f0b321f346a5501dc6936bd8f3 to your computer and use it in GitHub Desktop.
Bitrise CI最適化案
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # iOS CI/CD ビルド時間短縮ガイド - Bitrise & Fastlane編 | |
| ## 概要 | |
| iOSアプリのCI/CDにおいて、ビルド時間の短縮は開発生産性に直結する重要な課題です。本ガイドでは、BitriseとFastlaneを使用した環境でのビルド時間短縮のベストプラクティスをまとめています。 | |
| ## 1. Fastlane最適化 | |
| ### 1.1 scanアクションの最適化 | |
| #### 並列テストの活用 | |
| ```ruby | |
| lane :test do | |
| scan( | |
| scheme: "MyApp", | |
| parallel_testing: true, # 並列テストを有効化 | |
| concurrent_workers: 2, # 並列実行数を指定 | |
| max_concurrent_simulators: 2, # 同時シミュレータ数 | |
| devices: ["iPhone 15"], # デバイスは最小限に | |
| ) | |
| end | |
| ``` | |
| #### 不要な処理の省略 | |
| ```ruby | |
| lane :test do | |
| scan( | |
| scheme: "MyApp", | |
| output_types: "", # レポート生成を省略 | |
| suppress_xcode_output: true, # ログ出力を抑制 | |
| disable_slide_to_type: true, # "Slide to type"プロンプトを無効化 | |
| prelaunch_simulator: true, # シミュレータを事前起動 | |
| skip_slack: true, # Slack通知を省略 | |
| ) | |
| end | |
| ``` | |
| ### 1.2 条件分岐による最適化 | |
| #### 変更ファイルに応じたテスト実行 | |
| ```ruby | |
| lane :pr_test do |options| | |
| # PR更新時の変更ファイルを検出 | |
| changed_files = `git diff --name-only HEAD~1`.split("\n") | |
| # Podfileが変更された場合のみpod install実行 | |
| if changed_files.any? { |f| f.include?("Podfile") } | |
| cocoapods | |
| end | |
| # 変更箇所に応じてテストを限定 | |
| if changed_files.any? { |f| f.include?("Login") } | |
| scan(only_testing: ["MyAppTests/LoginTests"]) | |
| elsif changed_files.any? { |f| f.include?("Network") } | |
| scan(only_testing: ["MyAppTests/NetworkTests"]) | |
| else | |
| scan # フルテスト | |
| end | |
| end | |
| ``` | |
| ### 1.3 ビルド設定の最適化 | |
| ```ruby | |
| lane :optimized_test do | |
| scan( | |
| scheme: "MyApp", | |
| configuration: "Debug", # Releaseより高速 | |
| xcargs: [ | |
| "-parallelizeTargets", | |
| "-maximum-concurrent-test-simulator-destinations 2", | |
| "COMPILER_INDEX_STORE_ENABLE=NO", # インデックス作成を無効化 | |
| "SWIFT_COMPILATION_MODE=wholemodule" # 最適化 | |
| ].join(" ") | |
| ) | |
| end | |
| ``` | |
| ### 1.4 環境準備の最適化 | |
| ```ruby | |
| before_all do | |
| # Fastlane自体の高速化 | |
| ENV["FASTLANE_SKIP_UPDATE_CHECK"] = "1" | |
| ENV["FASTLANE_HIDE_TIMESTAMP"] = "1" | |
| # シミュレータの事前起動 | |
| sh "xcrun simctl boot 'iPhone 15' || true" | |
| # キーボード設定の最適化 | |
| sh "defaults write com.apple.iphonesimulator ConnectHardwareKeyboard -bool NO" | |
| end | |
| ``` | |
| ## 2. Bitrise設定の最適化 | |
| ### 2.1 効果的なキャッシュ設定 | |
| ```yaml | |
| workflows: | |
| primary: | |
| steps: | |
| # キャッシュを最初に取得 | |
| - cache-pull@2: | |
| # Bundlerインストール(キャッシュ活用) | |
| - bundle-install@1: | |
| inputs: | |
| - gemfile_path: "./Gemfile" | |
| - bundle_path: "./vendor/bundle" | |
| # CocoaPodsインストール(キャッシュ活用) | |
| - cocoapods-install@2: | |
| inputs: | |
| - install_cocoapods_version: "false" | |
| - fastlane@3: | |
| inputs: | |
| - lane: test | |
| # キャッシュを保存 | |
| - cache-push@2: | |
| inputs: | |
| - compress_archive: "true" | |
| - cache_paths: | | |
| # Bundler(ほぼ不変) | |
| ./vendor/bundle -> ./Gemfile.lock | |
| # CocoaPods(ライブラリ更新時のみ変更) | |
| ./Pods -> ./Podfile.lock | |
| # SPM(比較的安定) | |
| .build -> ./Package.resolved | |
| ~/Library/Caches/org.swift.swiftpm | |
| ``` | |
| ### 2.2 キャッシュ効果の実測値 | |
| ``` | |
| 初回ビルド(キャッシュなし): | |
| - bundle install: 30-60秒 | |
| - pod install: 60-120秒 | |
| - SPM resolve: 30-90秒 | |
| 合計: 2-4分 | |
| 2回目以降(キャッシュあり): | |
| - bundle install: 2-5秒 ✨ | |
| - pod install: 5-10秒 ✨ | |
| - SPM resolve: 3-8秒 ✨ | |
| 合計: 10-20秒 | |
| ``` | |
| ## 3. SPMモジュール化による高速化 | |
| ### 3.1 モジュール構成例 | |
| ```swift | |
| // Package.swift | |
| let package = Package( | |
| name: "MyAppModules", | |
| platforms: [.iOS(.v15)], | |
| products: [ | |
| .library(name: "Core", targets: ["Core"]), | |
| .library(name: "Networking", targets: ["Networking"]), | |
| .library(name: "UI", targets: ["UI"]), | |
| .library(name: "LoginFeature", targets: ["LoginFeature"]) | |
| ], | |
| dependencies: [ | |
| .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0") | |
| ], | |
| targets: [ | |
| .target(name: "Core"), | |
| .target(name: "Networking", dependencies: ["Core", "Alamofire"]), | |
| .target(name: "LoginFeature", dependencies: ["Core", "Networking", "UI"]), | |
| .testTarget(name: "LoginFeatureTests", dependencies: ["LoginFeature"]) | |
| ] | |
| ) | |
| ``` | |
| ### 3.2 モジュール別テスト実行 | |
| ```ruby | |
| lane :modular_test do | |
| # 変更されたモジュールを検出 | |
| changed_modules = sh("git diff HEAD~1 --name-only | grep 'Sources/' | cut -d'/' -f2 | sort -u").split("\n") | |
| if changed_modules.empty? | |
| UI.message "No module changes detected, skipping tests" | |
| else | |
| changed_modules.each do |module| | |
| UI.message "Testing module: #{module}" | |
| sh "swift test --filter #{module}Tests" | |
| end | |
| end | |
| end | |
| ``` | |
| ## 4. 実践的な最適化Fastfile | |
| ```ruby | |
| # Fastfile | |
| default_platform(:ios) | |
| platform :ios do | |
| before_all do | |
| # 環境最適化 | |
| ENV["FASTLANE_SKIP_UPDATE_CHECK"] = "1" | |
| ENV["FASTLANE_HIDE_TIMESTAMP"] = "1" | |
| # キャッシュ状態の確認 | |
| if File.exist?("./vendor/bundle") | |
| UI.success "✅ Bundler cache found!" | |
| end | |
| if File.exist?("./Pods") | |
| UI.success "✅ CocoaPods cache found!" | |
| ENV["SKIP_POD_INSTALL"] = "true" | |
| end | |
| end | |
| desc "PR用の高速テスト" | |
| lane :pr_test do | |
| # 依存関係のインストール(必要時のみ) | |
| unless ENV["SKIP_POD_INSTALL"] | |
| cocoapods( | |
| clean_install: false, | |
| verbose: false | |
| ) | |
| end | |
| # 高速テスト実行 | |
| scan( | |
| scheme: "MyApp", | |
| devices: ["iPhone 15"], | |
| parallel_testing: true, | |
| concurrent_workers: 2, | |
| output_types: "", | |
| suppress_xcode_output: true, | |
| disable_slide_to_type: true, | |
| prelaunch_simulator: true, | |
| xcargs: "-parallelizeTargets COMPILER_INDEX_STORE_ENABLE=NO" | |
| ) | |
| end | |
| desc "ベンチマーク用レーン" | |
| lane :benchmark do | |
| start_time = Time.now | |
| UI.message "Starting tests..." | |
| test_time = benchmark_action { pr_test } | |
| UI.important "Total time: #{test_time}s" | |
| end | |
| def benchmark_action | |
| start = Time.now | |
| yield | |
| Time.now - start | |
| end | |
| end | |
| ``` | |
| ## 5. コスト vs 効果の判断基準 | |
| ### Bitriseプラン変更 vs GitHub Actions移行 | |
| **現状維持 + 最適化を選ぶべきケース:** | |
| - 待ち時間が許容範囲内(10-15分程度) | |
| - 予算制約が厳しい | |
| - 最適化の余地がまだある | |
| **GitHub Actions移行を選ぶべきケース:** | |
| - 待ち時間による機会損失が大きい | |
| - エンジニアが今後も増える予定 | |
| - CI/CD改善が経営課題 | |
| ### 段階的アプローチ | |
| 1. **Phase 1**: 本ガイドの最適化を実施(1-2週間) | |
| 2. **Phase 2**: 効果測定と追加最適化(2-4週間) | |
| 3. **Phase 3**: それでも不足なら移行検討 | |
| ## 6. 期待される効果 | |
| 本ガイドの施策を実装することで: | |
| - **初回ビルド**: 8-10分 → 6-8分(20-30%短縮) | |
| - **2回目以降**: 8-10分 → 4-6分(40-50%短縮) | |
| - **部分テスト**: 8-10分 → 2-3分(70-80%短縮) | |
| 特に依存関係のキャッシュとテストの並列化の組み合わせが効果的です。 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment