Azure Devops PipelineでiOSアプリのCIを設定してみた

Azure Devopsが大変優秀だということで、リポジトリ管理からスプリント管理まで全部をこちらにまとめてしまおうという動きがアルバイト先でありました。

それに伴って、今までCircle CI動かしてたパイプラインをAzureのPipelineに移行しました。

その設定の際の知見をまとめたいと思います。

Azure Devopsについて

azure.microsoft.com

Azure Devopsでは、アジャイル計画ツールやGitリポジトリホスティングサービス等、チーム開発に必要なツールがひとつにまとまっていて、それをプロジェクトごとに管理することができます。

チームメンバー5人までなら無料で始めることができるので、最初からお金をかけたくない・少しずつ移行していきたいというチームにもおすすめ。

無料でもプライベートリポジトリは何個でも作ることができますが、Pipelineに関しては並列実行ができない・ビルド時間月1800分までという制限が付きます。

Pipelineの設定

azure-pipelines.ymlをプロジェクトのルートに作成し、その中にパイプラインの処理を書いていきます。

タスクは、画像のようなAzure Devops上のタスク一覧からGUIで追加すると、yamlファイルにコードで起こしてくれます。

f:id:aomathwift:20191111150702p:plain f:id:aomathwift:20191111150716p:plain

ローカルでyamlを編集するのでも良いのですが、Devops上で編集するとインデントのミス等をその場で指摘してもらえるのでとても便利です。

基本的な設定はドキュメントを見てもらうと良いので、私自身が苦戦した所・ややこしいところだけピックアップしてお話します。

躓いたポイント

xcWorkspacePathはオプションだが自分で指定しないといけない

Xcode BuildタスクのxcWorkspacePathという項目は自身で設定しなくても動くオプション項目です。

そのため最初は、こういうのは設定しなくてもよしなにxcworkspaceを見つけてきてくれるだろうと思ってそのまま動かしたら、何やら違うプロジェクトのxcworkspaceを開こうとして、指定されたSchemaがないと怒られる。

おかしいなと思ってxcWorkspacePathのデフォルト値を確認すると、**/*.xcodeproj/project.xcworkspaceになっている。

どうやらCarthageのCheckoutフォルダ配下のいずれかのライブラリのxcworkspaceを開こうとしてしまってたみたいですね。

ここをApp名.xcodeproj/project.xcworkspaceにしてあげることで解決しました。

ちなみに、Cocoapodsを使ってるプロジェクトの場合はApp名.xcworkspaceを指定してあげると良いです。

インストール先のPathを指定するとキャッシュがうまくいかない

bundle installする際はいつも --path vendor/bundle にpath指定でやってたのですが、ここでpathを指定するとキャッシュがうまくいきません。

bundle installすると(Pipeline.Workspace)/.bundle配下に入るので、そこを参照しに行くようになってるみたいです。

ちなみに、現在このキャッシュタスクはベータ版でキャッシュをクリアする機能がありません。

そのため、設定途中でクリアしたいとなったらバージョン名(v0, v1等)をつけたりしてキャッシュのキーを変えることで対応ができます。

ブランチの指定の仕方がややこしい

指定ブランチへのマージ・PRのオープン等を引き金としてテストを発火させることができるのですが指定するブランチがややこしい(主観)ので注意が要るなと感じました。

trigger:で指定したブランチは、そのブランチへのpushとマージで発火します。(ここは普通)

pr:で指定したブランチは、指定したブランチに向けたPRがオープンされたら発火します。(ここがややこしい)

完成形

ざっとiOSアプリのCIに必要な処理を書いた設定ファイルがこちらです。

bundle installはfastlaneを使った処理をあとから追加したくて書いてます。

trigger:
  - develop

pr:
  - develop
  - master

pool:
  vmImage: 'macos-latest'

variables:
  MINT_PATH: $(Pipeline.Workspace)/.mint
  BUNDLE_PATH: $(Pipeline.Workspace)/.bundle

stages:
  - stage: Build
    jobs:
    - job: Build
      steps:
      - task: CacheBeta@0
        inputs:
          key: v0 | gems | $(Agent.OS) | Gemfile.lock
          path: $(BUNDLE_PATH)
        displayName: 'Cache gems'
      - script: bundle install
        displayName: Bundle Install
      - task: CmdLine@2
        inputs:
          script: brew install mint
        displayName: Mint Install
      - task: CacheBeta@1
        inputs:
          key: v0 | mint | $(Agent.OS) | Mintfile
          path: $(MINT_PATH)
        displayName: Cache Mint
      - script: mint bootstrap
        displayName: Tools Install by Mint
      - task: CmdLine@2
        inputs:
          script: 'sudo xcode-select --switch /Applications/Xcode_11.1.app/Contents/Developer'
        displayName: Xcode select
      - task: CmdLine@2
        inputs:
          script: |
            # 秘密テキストの環境変数が指定できるので、そこに事前に取得したGithubのアクセストークンを設定しておく
            export GITHUB_ACCESS_TOKEN=$(GITHUB_ACCESS_TOKEN)
            mint run Carthage carthage bootstrap --no-use-binaries --platform iOS --cache-builds && echo '*** Resolved dependencies:' && cat 'Cartfile.resolved'
        displayName: Carthage Build
      - task: CmdLine@2
        inputs:
          # ライブラリを使ってテスト用のモッククラスを作成するためのスクリプト
          script: mint run SwiftyMocky swiftymocky generate
        displayName: MockFile Generate
      - task: Xcode@5
        inputs:
          actions: 'build'
          configuration: 'Debug'
          sdk: 'iphonesimulator13.1'
          xcWorkspacePath: 'App名.xcodeproj/project.xcworkspace'
          scheme: 'App名'
          xcodeVersion: 'specifyPath'
          xcodeDeveloperDir: '/Applications/Xcode_11.1.app'
          packageApp: false
          destinationPlatformOption: 'iOS'
          destinationSimulators: 'iPhone 8'
        displayName: Xcode Build
      - task: Xcode@5
        inputs:
          actions: 'test'
          configuration: 'Debug'
          sdk: 'iphonesimulator13.1'
          xcWorkspacePath: 'App名.xcodeproj/project.xcworkspace'
          scheme: 'App名'
          xcodeVersion: 'specifyPath'
          xcodeDeveloperDir: '/Applications/Xcode_11.1.app'
          packageApp: false
          destinationPlatformOption: 'iOS'
          destinationSimulators: 'iPhone 8'
        displayName: Xcode Test

なお今回は一旦無料の枠の中でやっているので、並列実行処理はありません。

ビルド→テストの処理はこの前リリースされたマルチステージ機能を活かし、ビルド成功時のみテストを実行するという処理に書き換えても良いかなと思います。

まだまだCI初心者なので、間違ってたりアドバイスなどあればコメントおねがいします!