🛠️

Azure PipelineでSwift Package Manager管理のライブラリのソースコードをキャッシュする

2022/04/24に公開

Azure Pipelineを使ってCIビルドしていて、Swift Package Manager(SPM)管理のライブラリをキャッシュしたいと考えていました。XCFrameworkを使わない限りビルド時間は短縮できなかったのですが、備忘録としてライブラリのソースコードのキャッシュ方法を残します。

注意事項

SPMではソースコードのキャッシュはできるものの、ビルドしたバイナリをキャッシュすることが現在のところできません。

  • 本ビルド時間を削減するのであれば、ビルド済みバイナリのキャッシュが効果的です[1]

    • Carthage (>= 0.37.0)などで依存ライブラリのXCFrameworkをビルドして、S3やCloud Storageなどから取得できるようにします。
    • もしくは、ライブラリ提供元がXCFrameworkを提供していれば、それらを利用します。
      • firebase-ios-sdkなどが該当します。
  • Azure pipelineの無料枠でざっと検証するため、Microsoftでホストされているビルド用エージェントではなく、self-hostedエージェントを使用しています[2]。昨年夏あたりに、新規組織での無料枠が許可制になったためです[3]。self-hostedエージェントではこの制限の影響を受けないため、self-hostedエージェントを利用しました。

サンプルコード

依存ライブラリにfirebase-ios-sdkのみ追加していて、アプリとしてはHello World表示するのみとなっています。
https://github.com/Niccari/azure-pipeline-spm-caching

本コードの設定は以下の通りです。

  • ビルドターゲット: iPhone(iOS) iOS 15以上のみ
  • ビルドスキーマ:DevelopDebug

作ったpipeline用yaml

以下のとおりです。テスト用のコードのため、署名はしていません。前述の通り、self-hostedエージェントを使っているので、poolには当該エージェント用のエージェントプールを指定しています。

trigger:
- main
pr: none

pool: SPMCacheBuildAgentPool

variables:
- name: AppName
  value: 'AzurePipelineSPMCaching'
- name: SchemeName
  value: 'DevelopDebug'
- name: SPMResolvedPath
  value: 'AzurePipelineSPMCaching.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved'
- name: CachePath
  value: 'SPMSourcePackages/'

steps:
- task: Cache@2
  inputs:
    key: '"spm" | "$(Agent.OS)" | $(SPMResolvedPath)'
    path: '$(CachePath)'
    restoreKeys: |
      spm | "$(Agent.OS)"
  displayName: Cache SPM packages

- task: Xcode@5
  inputs:
    actions: 'build'
    sdk: 'iphoneos'
    scheme: ${{ variables.SchemeName }}
    xcWorkspacePath: 'AzurePipelineSPMCaching.xcodeproj/project.xcworkspace'
    exportPath:  '$(Build.ArtifactStagingDirectory)'
    args: '-clonedSourcePackagesDirPath $(CachePath)'

キャッシュの設定

1. キャッシュ先の指定

Cache@2タスクのinputs.pathにキャッシュしたいディレクトリを指定します。Xcode@5でビルド時、当該パスに依存ライブラリのソースコードがpullされるよう、argsから-clonedSourcePackagesDirPath $(CachePath)を指定しています。

2. キャッシュ条件の指定

SPMのライブラリ管理は (アプリ名).xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolvedファイルで行われています。そのため、当該ファイルをCache@2タスクのkey末尾に指定します。すると、Pipeline実行時にPackage.resolveに差分があるとキャッシュが更新されます。

ソースコードキャッシュ有無でのビルド時間の差異

キャッシュオフで12分24秒、キャッシュありで17分53秒となりました。ビルド時間こそ確かに少なくなっているものの、それ以上にキャッシュの取得に時間がかかっている結果となりました。ビルド時間の短縮が70秒程度にとどまっているので、仮にキャッシュ取得が1分程度に収まっていてもあまり差はありませんでした。

キャッシュ状況 キャッシュ取得時間[秒] ビルド時間[秒] キャッシュ作成時間[秒] 合計[秒]
キャッシュオフ 4 566 161 744
キャッシュあり 564 495 4 1073

備考: FirebaseライブラリをXCFrameworkでimportした場合はどうなるか

公式から提供されているXCFrameworkを使ってビルドしてみました。上記サンプルコードのfeature/use_binary_firebaseブランチが当該コードになります(別記事で手順を紹介いたします)。ビルド時間は以下の通りとなりました。

キャッシュ状況 キャッシュ取得時間[秒] ビルド時間[秒] キャッシュ作成時間[秒] 合計[秒]
キャッシュオフ 4 566 161 744
キャッシュあり 564 495 4 1073
XCFramework使用 0 13 0 30

圧倒的に早いです。その代わり以下の課題があります。

  • XCFrameworkの管理を自分でしないといけない
  • XCFramework間で同じXCFrameworkを使っているときに依存関係を自分で解決しないといけない
    • Firebaseの場合、GTMSessionFetcherが該当します。いずれも同じバイナリかつバージョンでしたが、今後不一致になったときにどうするか検討する必要があります。

そのため、Carthageなどを使うか自前で管理するか適宜検討する必要があります。

脚注
  1. SwiftPMのCI向けキャッシュを考える ↩︎

  2. Azure DevOps で Self-hosted 環境を構築する方法 ↩︎

  3. 無料の許可のAzure Pipelinesに対する変更 ↩︎

Discussion