GitHub Actions Macアプリのビルド方法+リリースの自動化方法

Macアプリのリリース手順が煩わしかったので、GitHub Actionsを利用してリリースを自動化しました。 その際の知見(Q&A)と実際の構成を共有します。


# Q&A

## Q: fastlane, bundlerはデフォルトで利用できる？

利用できる。(bundle installをしなくてもfastlane利用可能。)

## Q: passwordやp12ファイルなど機密情報はどのように扱う？

[暗号化されたシークレット](https://docs.github.com/ja/actions/reference/encrypted-secrets)参照

この資料にパスワードやp12ファイルなどの機密情報の設定方法およびワークフローファイルからの参照方法について記載されています。（以下は抜粋）

### シークレットの設定方法(リポジトリ単位）

![スクリーンショット 2021-09-06 15.44.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/4a1296d2-fe36-3e5a-583b-9ab7be9246e7.png)


### ワークフローファイルからのシークレットの参照方法

![スクリーンショット 2021-09-06 10.55.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/0c1a7cd6-8ae3-ec7a-5898-6cc07dca7abf.png)

## Q: workflowは手動起動できる？

できる。 パラメータも設定可能。
手動起動の際にbranchの指定も可能。

設定例

```yaml
on:
  # Workflowの手動起動の設定です。
  # version_numberを入力できるようにしています。
  workflow_dispatch:
    inputs: 
      version_number: 
        description: 'アプリのバージョン番号。入力例 0.3.0, 1.0.0 など。'
        required: true

# 中略

#version_numberの参照方法
bundle exec fastlane release version_number:${{ github.event.inputs.version_number }}

```

上の定義をすると、workflowの起動画面は下のようになります。

![スクリーンショット 2021-09-06 16.44.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/1f28d345-b40f-ec3d-59ec-6bddd7e5e0fd.png)

また手動起動のほか、pullreqのタイミングやbranchがpush時などに自動起動が可能です。

[ワークフローをトリガーするイベント](https://docs.github.com/ja/actions/reference/events-that-trigger-workflows)
[同資料中のworkflow_dispatch](https://docs.github.com/ja/actions/reference/events-that-trigger-workflows#manual-events)
[GitHub Actionsのメタデータ構文](https://docs.github.com/ja/actions/creating-actions/metadata-syntax-for-github-actions)

## Q: actions/checkout@v2 でcheckoutされるブランチは？

デフォルトではworkflowを実行したブランチ

[actions/checkout](https://github.com/actions/checkout)

## Q: ワークフローファイルの環境変数とコンテキストの違いは？

以下の通り

![スクリーンショット 2021-09-06 10.50.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/ef00bf9e-cd23-6958-aeb6-e97b66550dde.png)

[環境変数](https://docs.github.com/ja/actions/reference/environment-variables)

## Q: git tagの作成の仕方は？

一例

```yaml
      - name: Create git tag
        run: |
          git tag v${{github.event.inputs.version_number}}
          git push origin v${{github.event.inputs.version_number}}
```

## Q: GitHubのリリースページの作成の仕方は？

一例

※ actions/create-release@v1はarchivedされていてメンテナンスされていない。

```yaml
      - name: Create release    
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
        with:
          tag_name: v${{github.event.inputs.version_number}}
          release_name: Release v${{github.event.inputs.version_number}}
          body: |
            Changes in this Release
            - First Change
            - Second Change
          draft: true
          prerelease: false
```

[actions/create-release](https://github.com/actions/create-release)

## Q: GitHubのリリースページにzipファイルなどの成果物をアップロードするには

下の例は、action/create-release@v1を前提としている。

```yaml
      - name: Upload Release Assets
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
          asset_path: ./build/DokodemoDrag.app.zip
          asset_name: DokodemoDrag.app.zip
          asset_content_type: application/zip          
```

[actions/upload-release-asset](https://github.com/actions/upload-release-asset)

## Q: on: workflow_dispatchを設定しても, Actionsタブでワークフローが表示されない。

私の場合、GitHubから直接.github/workflow/aaa.yamlファイルを作成した場合に上の事象が発生しています。
(mainブランチ以外にワークフローファイルを作成すると表示されないかもしれない)
結構表示されないケースがある模様。

https://github.community/t/workflow-dispatch-workflow-not-showing-in-actions-tab/130088/29

## Q: expression中での三項演算子的なものは？

```yaml
${{ val1 ? val1 : "B" }}
```

的なことをしたい。

ドキュメント的には見当たらない。

動作検証の限りでは、||と&&はRubyやJavaScriptと同様の動作をするので、

```yaml
${{ val1 || "B" }} # val1の値が偽になるなら"B"を返す.
や
${{ (val1 && val2) || "B" }} # val1 && val2が偽でなければval2を返し、 偽なら"B"を返す。
```

といったことができる。

下記は実際の利用イメージ

```yaml
- name: 'Test'
  env: 
    # github.event.inputs.version_nameは 1.0.0などを想定
    TAG_NAME: v${{ github.event.inputs.version_name }}
  run: |
　　　　　　　　　# 式の結果が偽なら""を出力する。真なら v1.0.0などを返す。
    echo ${{ (github.event.inputs.version_name && env.TAG_NAME) }} 
```

## Q: ${{expression}}の評価がいまいち分からない

別記事を作成しました。

https://qiita.com/hmuronaka/items/0ff5339f20ca4716e62c

## Q: GitHub Actionsの完了時のメール通知を止めたい

Account Settings -> Notifications -> Actionsのセクションから設定を変更できる。

## 宣伝(DokodemoDragについて)

DokodemoDragは、個人的に作成中のアプリでMacのWindow移動を楽に行えるようにするツールです。よければ触ってみてください

※現時点ではnotarizationができておらず、macOSのGateKeeperに検出されます）。

![image01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/790f63c4-ece4-03f0-8f0c-e5a1afd5239a.gif)

https://github.com/hmuronaka/DokodemoDrag

実装の概要もこちらで公開しています。
https://zenn.dev/hmu/articles/4c17afaaf5012e

宣伝は以上で、以下は実際に自動化したGitHub Actionsの構成についてです。

# 実際のworkflowの構成

## 前提

- GitHubのランナー上のXcode versionは12.4
- プロジェクトの構成はworkspace下にprojectが2つ存在する

```sh
DokodemoDrag.xcworkspace/
|-DokodemoDrag.xcodeproj/
|-DokodemoDragLauncher/DokodemoDragLauncher.xcodeproj/
```

- リリース形式は、GitHub上のリリースページにappname.app.zipという形式で公開(Storeへの公開ではない）
- リリース対象のMac AppのCertificateは、Developer IdではなくDevelopmentを利用
- 証明書のimportにmatchは利用せず手動で行う
- プロジェクトのビルドにはfastlaneを利用する
- 現時点ではprovisioning profileは未使用
- 現時点ではfastlaneの設定はGitHub Actionsでの利用前提となってしまっています

## ワークフローの概要

1.ワークフローは実行者が手動で起動する
![スクリーンショット 2021-09-06 16.44.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/0f8b245f-21fa-2743-0897-393ace045444.png)
2.アプリのバージョンはWorkflow起動時に指定する
3.ビルド時にワークフロー起動時に指定されたバージョン番号を付与する
4.ビルドに成功したら　バージョンに対応するgitのtagを生成する
5.GitHubのリリースページをドラフトで作成する
6.後始末

## workflowファイル

説明は直接コメントに記載しました。

[release.yml](https://github.com/hmuronaka/DokodemoDrag/blob/main/.github/workflows/release.yml)

```yaml
name: Release app

on:
  # Allows you to run this workflow manually from the Actions tab
  # Workflowの手動起動の設定です。
  # version_numberを入力できるようにしています。
  workflow_dispatch:
    inputs: 
      version_number: 
        description: 'アプリのバージョン番号。入力例 0.3.0, 1.0.0 など。'
        required: true

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  release:
    # 実行環境をmacに設定。
    runs-on: macos-latest

    steps:
      - name: Show Xcode version
        run: xcodebuild -version    
        
      # defaultでWorkflowを実行したbranchがcheckoutされる。
      - uses: actions/checkout@v2

      - name: Show Git Branch
        run: git symbolic-ref --short HEAD
      
      # https://docs.github.com/ja/actions/guides/installing-an-apple-certificate-on-macos-runners-for-xcode-development
      # 上記のドキュメントにcertificateをGitHub Actionsのランナーにimport
      # する方法が記載されています。
      # 下の設定では、GitHubのsecretに設定したp12ファイルの内容を復元しています。
      # 実際のcertificateのkeychainへのimportは、fastlaneで行っています。
      - name: Generate p12 file
        env:
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
        # 以下で定義しているP12_PATHは、${{ runner.temp }}を利用することで、
        # env: 内で定義可能かもしれません。（未検証）
        run: |
          P12_PATH=$RUNNER_TEMP/build_certificate.p12
          echo $P12_PATH
          # シークレットから証明書とプロビジョニングプロファイルをインポートする
          echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $P12_PATH

      # commitに利用する情報を設定しています。     
      - name: git config
        run: |
          echo github.actor: ${{github.actor}}
          git config user.name ${{github.actor}}
      
      # fastlaneをbundle execで利用するためにbundlerを利用しています。
      - name: bundle install
        run: |
          bundle install
          
      # アプリのビルド処理です。
      - name: Build DokodemoDragLauncher & DokodemoDrag
        # ここもP12_PATHは ${{ runner.temp }} を利用した方が良いかもしれません。
        env:
          # これらの環境変数はfastlaneの処理で利用します。
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
        run: |
          export P12_PATH=$RUNNER_TEMP/build_certificate.p12
          echo $P12_PATH
          # certificateのimport, アプリのビルド, 成果物用のzipファイル生成を行います。
          bundle exec fastlane release version_number:${{github.event.inputs.version_number}}
          # .はかなり雑かもしれません。 
          git add .
          git commit -m "Release v${{github.event.inputs.version_number}}"
          git push origin
      
      # タグ付けをします。    
      - name: Create git tag
        run: |
          echo Create tag ${{github.event.inputs.version_number}}
          git tag v${{github.event.inputs.version_number}}
          git push origin v${{github.event.inputs.version_number}}
          
      # GitHubのリリースページを作成します。(draftで作成します）
      # actions/create-release@v1は既にメンテ終了しているので、
      # 他のアクションを利用した方が良いです。
      - name: Create release    
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
        with:
          tag_name: v${{github.event.inputs.version_number}}
          release_name: Release v${{github.event.inputs.version_number}}
          body: |
            Changes in this Release
            - First Change
            - Second Change
          draft: true
          prerelease: false

      # リリースページに成果物のzipファイルをアップロードします。          
      - name: Upload Release Assets
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
          asset_path: ./build/DokodemoDrag.app.zip
          asset_name: DokodemoDrag.app.zip
          asset_content_type: application/zip          
```

## fastfile

こちらも説明は直接コメントに記載しました。

[fastfile](https://github.com/hmuronaka/DokodemoDrag/blob/main/fastlane/Fastfile)

```ruby
default_platform(:mac)

XCODE_PROJ_PATH = %w(./DokodemoDragLauncher/DokodemoDragLauncher.xcodeproj ./DokodemoDrag.xcodeproj)
BUILD_PATH="build"
APP_NAME="DokodemoDrag.app"
KEYCHAIN_NAME="map_app_keychain" # macのtypo...
OUTPUT_PATH="build"

platform :mac do
  
  after_all do |lane|
    # GitHubホストランナーの場合は不要です。
    # create_keychainが呼ばれずにdelete_keychainを呼ぶと
    # エラーになってしまいます。
    delete_keychain(name: KEYCHAIN_NAME)
  end
  
  error do |lane|
    # GitHubホストランナーの場合は不要です。
    # create_keychainが呼ばれずにdelete_keychainを呼ぶと
    # エラーになってしまいます。
    delete_keychain(name: KEYCHAIN_NAME)
  end

  desc "release. option version: 0.3.0 ..."
  lane :release do |options|
      # プロジェクトにversion_numberを設定します。
      set_version_number(
        version_number: options[:version_number]
      )

      increment_all_app_build_numbers

      build
  end


  desc "set version number"
  lane :set_version_number do |options|
    # increment_version_numberだと主のschemeに対してのみ
    # 実行されるようなので、プロジェクト毎に設定しています。
    XCODE_PROJ_PATH.each do |path|
      increment_version_number(
        xcodeproj: "#{path}",
        version_number: options[:version_number]
      )
    end
  end

  desc "increment all app's build_numbers"
  lane :increment_all_app_build_numbers do
    # increment_xxx_numberだと主のschemeに対してのみ
    # 実行されるようなので、プロジェクト毎に設定しています。
    XCODE_PROJ_PATH.each do |path|
      increment_build_number(
        xcodeproj: "#{path}"
      )
    end
  end

  desc "build and zip app-file"
  lane :build do
    # certificateをimportしています。
    # ここはciの時のみ実行するなど分岐があったほうが良さそうです。
    import_my_certificate
    
    build_mac_app(
      workspace: "DokodemoDrag.xcworkspace",
      configuration: "Release",
      scheme: "DokodemoDrag",
      export_method: "development",
      clean: true,
      output_directory: BUILD_PATH,
      skip_package_pkg: true,
      output_name: APP_NAME
    )

    zip(
      path: "#{BUILD_PATH}/#{APP_NAME}",
      output_path: "#{OUTPUT_PATH}/#{APP_NAME}.zip"
    )

  end

  # ワークフローファイル側で設定しても問題ないと思いますが、fastlaneで設定しています。 
  # 参考: https://note.com/hayabusabusa/n/nbf4a1a095bf5
  private_lane :import_my_certificate do
    create_keychain(
      name: KEYCHAIN_NAME,
      password: ENV["KEYCHAIN_PASSWORD"],
      timeout: 1800
    )
    import_certificate(
      certificate_path: ENV["P12_PATH"],
      certificate_password: ENV["P12_PASSWORD"],
      keychain_name: KEYCHAIN_NAME,
      keychain_password: ENV["KEYCHAIN_PASSWORD"]
    )
  end  
end
```


# 参考資料

fastfileとワークフローファイルを作成時に参考したページです。
上の内容と一部重複しています。

## GitHub Actions

[GitHub Actionsについて学ぶ](https://docs.github.com/ja/actions/learn-github-actions/introduction-to-github-actions)
GitHub Actionsの概要を把握するために一読をお勧めします。

---

[Xcode 開発用の macOS ランナーに Apple 証明書をインストールする](https://docs.github.com/ja/actions/guides/installing-an-apple-certificate-on-macos-runners-for-xcode-development)

Github Actions上でCertificateのインストールする手順が記載されています。

今回はp12ファイルをGithub Actions上で生成するところを参考にし、keychainへの証明書のimportは、fastlaneを利用しています。

----
[GitHub Actionsのメタデータ構文](https://docs.github.com/ja/actions/creating-actions/metadata-syntax-for-github-actions)

アクションの入力パラメータの設定方法等が記載されています。
今回はworkflowを手動実行する際入力パラメータの設定の参考にしています。

----
[GitHub Actionsのワークフロー構文](https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions)

GitHub Actionsのworkflowのyamlファイルのリファレンスです。

----

[ワークフローをトリガーするイベント](https://docs.github.com/ja/actions/reference/events-that-trigger-workflows)

pullreqのタイミングやbranchがpushされた時などをtriggerとしてworkflowを起動できます。

[手動でworkflowを実行する設定(on: workflow_dispatch)](https://docs.github.com/ja/actions/reference/events-that-trigger-workflows#manual-events)も記載されています。

----

[環境変数](https://docs.github.com/ja/actions/reference/environment-variables)

GitHub Actionsで利用可能な環境変数についての説明。
ワークフローの定義ファイル中には環境変数とコンテキストの２種類の変数がありますが、その違いについても
記載されています。

![スクリーンショット 2021-09-06 10.50.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/ef00bf9e-cd23-6958-aeb6-e97b66550dde.png)

----

[暗号化されたシークレット](https://docs.github.com/ja/actions/reference/encrypted-secrets)

パスワードやp12ファイルなどの機密情報の設定方法およびワークフローファイルからの参照方法について記載されています。

シークレットの設定方法(リポジトリ単位）

![スクリーンショット 2021-09-06 15.44.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/4a1296d2-fe36-3e5a-583b-9ab7be9246e7.png)


ワークフローファイいるからのシークレットの参照方法

![スクリーンショット 2021-09-06 10.55.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/0c1a7cd6-8ae3-ec7a-5898-6cc07dca7abf.png)

----

[セルフホストランナーについて]
(https://docs.github.com/ja/actions/hosting-your-own-runners/about-self-hosted-runners)

通常ワークフローは、GitHub管理下で実行されますが、独自のワークフローのランナー環境が構築できるようです。

ドキュメント内では、GitHub管理下のランナーをGitHubホストランナー、独自のランナーをセルフホストランナーと表記されています。

![スクリーンショット 2021-09-06 11.09.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/e9e0d19e-90b3-6177-b23b-202a63f75fb5.png)

----

### Action

[actions/checkout](https://github.com/actions/checkout)

Gitのリポジトリをcheckoutするアクション。
デフォルトでは、workflowを実行したbranchを参照するようです。

----
[actions/create-release](https://github.com/actions/create-release)

GitHubのreleaseページを作成するために利用したのですが、
このリポジトリはarchivedされていてメンテナンスされていない模様。

----

[actions/upload-release-asset](https://github.com/actions/upload-release-asset)

GitHubのリリースページに成果物をアップロードするアクションです。

----


## Fastlane

[increment_build_number](https://docs.fastlane.tools/actions/increment_build_number/)

increment_build_numberは、デフォルトだと１projectにしか適用されないので

```ruby
increment_build_number(
  build_number: 75, # specify specific build number (optional, omitting it increments by one)
  xcodeproj: "./path/to/MyApp.xcodeproj" # (optional, you must specify the path to your main Xcode project if it is not in the project root directory)
)
```
のようにxcodeprojを明示しています。

----

[increment_version_number] (https://docs.fastlane.tools/actions/increment_version_number/)

workflow実行時に入力したバージョンを設定しています。
increment_version_numberは、デフォルトだと1projectのみ適用なので、全プロジェクトに
適用するようにしています。

----
[create_keychain]
(http://docs.fastlane.tools/actions/create_keychain/#create_keychain)

----

[delete_keychain](https://docs.fastlane.tools/actions/delete_keychain/)

今回の設定では、お行儀よくfastlaneの最後にkeychainを削除しました。
GitHubホストランナー上で実行する場合、この設定は不要です。

参考：[Xcode 開発用の macOS ランナーに Apple 証明書をインストールする](https://docs.github.com/ja/actions/guides/installing-an-apple-certificate-on-macos-runners-for-xcode-development)

![スクリーンショット 2021-09-06 11.11.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37443/ebf10650-8085-e175-8c31-59e7b96c2017.png)


----

[Technical Q&A QA1827
Automating Version and Build Numbers Using agvtool]
(https://developer.apple.com/library/archive/qa/qa1827/_index.html)


Github Actionsとは直接関係ありませんが、fastlaneのincrement_build_no, increment_version_numberは、Appleが提供しているagvtoolに依存しています。
またそのための設定をXcode のプロジェクトに行う必要があるため、その手順が記載されています。

注意点として、同一ディレクトリにxcodeprojが複数あるとagvtoolは動作しません。

(私の場合、過去にproject名をリネームしていてその残骸のxcodeprojが残っていたため、知らぬ間に同一ディレクトリに複数のxcodeprojが存在していて、agvtoolが実行できないというポカをしていました）

# 先人の知見

[GitHub ActionsでReleaseを自動化する方法としたときに得た学び](http://skawashima.com/blog/2019/12/github-actions-auto-release/#release%E7%99%BA%E8%A1%8C)

[Github Actions と fastlane で iOS アプリをアーカイブしてビルドを提出する](https://note.com/hayabusabusa/n/nbf4a1a095bf5)

[Github ActionsでiOSアプリのデプロイを自動化する](https://qiita.com/hirothings/items/0cd2561dbf6c53cf470c)

----

以上です。分かってしまえば１つ１つの設定は単純ですが、Actionsのデバッグに想像以上の時間がかかりました。 デバッグ時にはワークフロー中のcommitを省略するなどの仕組みが必要だったなぁとか、キャッシュは先に有効にしておくべきだったとか...改善の余地は多々あります。