LoginSignup
41
20

More than 3 years have passed since last update.

GitHub ActionsでUnityのAndroid, iOSビルドをやってみる

Last updated at Posted at 2019-12-23

この記事は Akatsuki Advent Calendar 2019 23日目の記事です。
前日は @ShaderError さんによる Unityでシェーダー描いてみたい でした。
アカツキ人事がハートドリブンに書く Advent Calendar 2019 もあるのでそちらもぜひ。

はじめに

以前に仕事でCircleCIを使ってUnityのAndroidとiOSビルドを行なっていたのでその事について書こうかなと思っていたのですが、最近GitHub Actionsという新機能がGitHubで公開されました。
これがいい感じにCircleCIでUnityビルドをしていた際に起きていた問題を解決していたので、試験的に試してみたまとめが本記事になります。

環境

GitHub Actions 12/23時点のワークフロー構文
Unity 2019.3.0f1
MacBook Pro (self-hostedで利用するマシン)

GitHub Actionsとは

GitHubによるCI/CDツールです。
詳しいことは GitHub Actions Documentation に書いてありますが、JenkinsCircleCIなどに替わる新しいツールとなるのか(個人的に)気になるサービスです。

GitHub Actions を導入する

前提:UnityProjectのリポジトリがGitHubに存在する状態であること
利用想定:self-hostedを使ってローカルにあるマシンをGitHub Actionsで利用する
前提の状態にするまでの解説は行いませんので、各自作業を行なってください。

GitHubのリポジトリの Setting -> Actions を選択して
Actions permissionsEnable local and third party Actions for this repositoryに変更してください
その後にSelf-hosted runnersAdd runner を選択
スクリーンショット 2019-12-22 22.28.51.png
そうするとrunnerを追加するためのCLIのコマンドが表示されます。
各コマンドを順に実行してください。
実行した後にGitHubのページを再読み込みをするとSelf-hosted runnersにCLIコマンドを実行したPCが表示されていると思います。
これでGitHub Actionsself-hostedが使えるようになりました。
スクリーンショット 2019-12-19 0.19.05.png
その後にGitHubのActionsタブにあるSimple workflowを使って動作テストをしてみます。
runs-onself-hostedにするのを忘れないでください。
変更が完了しコミットをすると、self-hostedbuild.ymlが実行されます。
なお、こちらに記載がありますが、ワークフローファイルは、リポジトリの.github/workflowsディレクトリに保存する必要があるのでご注意ください。
スクリーンショット 2019-12-18 23.31.15.png
実行結果はActionタブから確認できます。
おそらく以下の画像のような結果になっていると思います。
これでローカルにあるself-hostedに追加されたマシンから実行できるようになりました。
ひとまずこれで基本設定は完了になります。
スクリーンショット 2019-12-19 0.38.33.png

Unityのビルドスクリプトを作成する

ここからは本格的にビルド処理を実装していきます。

まずはUnityでビルドスクリプトを書きます。

using System;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.Build.Reporting;
using System.Collections.Generic;
using UnityEditor.iOS.Xcode;
using UnityEditor.Callbacks;

public class MobileBuild
{
    static string[] GetEnabledScenes()
    {
        return (
                   from scene in EditorBuildSettings.scenes
                   where scene.enabled
                   where !string.IsNullOrEmpty(scene.path)
                   select scene.path
               ).ToArray();
    }

    private static void BuildAndroid()
    {
        // Setting for Android
        EditorPrefs.SetBool("NdkUseEmbedded", true);
        EditorPrefs.SetBool("SdkUseEmbedded", true);
        EditorPrefs.SetBool("JdkUseEmbedded", true);
        EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
        PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);

        // Build
        bool result = Build(BuildTarget.Android);

        // Exit Editor
        EditorApplication.Exit(result ? 0 : 1);
    }

    private static void BuildIOS()
    {
        // Setting for iOS
        PlayerSettings.SetScriptingBackend(BuildTargetGroup.iOS, ScriptingImplementation.IL2CPP);
        EditorUserBuildSettings.iOSBuildConfigType = iOSBuildType.Debug;

        // Build
        bool result = Build(BuildTarget.iOS);

        // Exit Editor
        EditorApplication.Exit(result ? 0 : 1);
    }

    private static bool Build(BuildTarget buildTarget)
    {
        // Get Env
        string   outputPath   = GetEnvVar("OUTPUT_PATH");               // Output path
        string   bundleId     = GetEnvVar("BUNDLE_ID");                 // Bundle Identifier
        string   productName  = GetEnvVar("PRODUCT_NAME");              // Product Name
        string   companyName  = GetEnvVar("COMPANY_NAME");              // Company Name

        outputPath = AddExpand(buildTarget, outputPath);

        Debug.Log("[MobileBuild] Build OUTPUT_PATH :" + outputPath);
        Debug.Log("[MobileBuild] Build BUILD_SCENES :" + String.Join("", GetEnabledScenes()));

        // Player Settings
        BuildOptions buildOptions;
        buildOptions = BuildOptions.Development | BuildOptions.CompressWithLz4;

        if (!string.IsNullOrEmpty(companyName)) { PlayerSettings.companyName = companyName; }

        if (!string.IsNullOrEmpty(productName)) { PlayerSettings.productName = productName; }

        if (!string.IsNullOrEmpty(bundleId)) { PlayerSettings.applicationIdentifier = bundleId; }

        // Build
        var report = BuildPipeline.BuildPlayer(GetEnabledScenes(), outputPath, buildTarget, buildOptions);
        var summary = report.summary;

        // Build Report
        for (int i = 0; i < report.steps.Length; ++i)
        {
            var step = report.steps[i];
            Debug.Log($"{step.name} Depth:{step.depth} Duration:{step.duration}");

            for (int d = 0; d < step.messages.Length; ++d)
            {
                Debug.Log($"{step.messages[d].content}");
            }
        }

        if (summary.result == BuildResult.Succeeded)
        {
            Debug.Log("<color=white>[MobileBuild] Build Success : " + outputPath + "</color>");
            return true;
        }
        else
        {
            Debug.Assert(false, "[MobileBuild] Build Error : " + report.name);
            return false;
        }
    }

    private static string GetEnvVar(string pKey)
    {
        return Environment.GetEnvironmentVariable(pKey);
    }

    private static string AddExpand(BuildTarget buildTarget, string outputPath)
    {
        switch (buildTarget)
        {
            case BuildTarget.Android :
                outputPath += ".apk";
                break;
        }

        return outputPath;
    }
}

大まかに書くとこのような感じになるかと思います。
ビルド後に実行する処理等は書いていませんので、必要に応じて追加してください。

ローカルで上記のスクリプトが動作するか以下のコマンドをCLIで確認してください。

# 適宜書き換えてください
export COMPANY_NAME=""
export PRODUCT_NAME=""
export BUNDLE_ID=""
export OUTPUT_PATH =""

/Applications/Unity/Hub/Editor/2019.3.0f1/Unity.app/Contents/MacOS/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath ./ -executeMethod MobileBuild.BuildIOS -buildTarget iOS

/Applications/Unity/Hub/Editor/2019.3.0f1/Unity.app/Contents/MacOS/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath ./ -executeMethod MobileBuild.BuildAndroid -buildTarget Android

CLIが実行できたのであればいよいよGitHub Actionsを使った話へ進みます

GitHub Actions で Unity のビルドを行う

Android Build

まずはAndroidのビルドを行います。
Unity2019からAndroidのNDKやJDKがインストール時に追加できるようになりました!
なのでNDKやJDKの設定は各自でお願いします(いい時代になりましたね)

早速build.ymlファイルを書き換えましょう

name: ApplicationBuild

on: [push, pull_request]

env:
  OUTPUT_PATH: ""
  BUNDLE_ID: ""
  PRODUCT_NAME: ""
  COMPANY_NAME: ""
  UNITY_VERSION: 2019.3.0f1
jobs:
  build:
    runs-on: self-hosted
    steps:
    - uses: actions/checkout@v2
    - name: Android Build
      run: |
        /Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity -quit -batchmode -nographics -silent-crashes -logFile \
          -projectPath ./ -executeMethod MobileBuild.BuildAndroid -buildTarget Android

Androidビルドをするだけのシンプルな処理です。
環境変数は各自で適切に入力してください。

いくつかを説明を軽くしますと、

name

name:はワークフローの名前になります。
この名前がリポジトリのアクションページにワークフローに表示されます。
スクリーンショット 2019-12-23 14.04.47.png

on

on:はワークフローをトリガーするGitHubイベントの名前です。
イベントの種類は多くありこちらにドキュメントとしてまとめてあります。
今回はpushpull_requestをトリガーにしています。

env

  1. envはワークフローの全てのジョブから利用できる環境変数を定義します。
  2. jobs.<job_id>.env<job_id>ジョブから利用できる環境変数を定義します。
  3. jobs.<job_id>.steps.envはステップから利用できる環境変数を定義します。

先ほどのbuild.ymlは 2. のenvを使っています。
この後のiOSビルドでも使いますからね。

jobs.job_id.steps.uses

ジョブでステップの一部として実行されるアクションを選択します。
actions/checkoutはワークフローで使用できる標準アクションで、v2を指定することでチェックアウトアクションのv2を利用するという設定になります。(標準のアクションはこちらにまとまっています)

jobs.job_id.steps.run

オペレーティングシステムのシェルを使用してコマンドラインプログラムを実行します。
さらにshellキーワードを使用すると、環境のOSのデフォルトシェルを上書きできます。

iOS Build

続いてiOSビルドです。
今回は時間がなくなったのと複雑になるので、ipaファイルをビルドするところまでは行わず、xcodeprojをビルドするところまで行います。
では、build.ymlを書き換えましょう。

name: ApplicationBuild

on: [push, pull_request]

env:
  OUTPUT_PATH: ""
  BUNDLE_ID: ""
  PRODUCT_NAME: ""
  COMPANY_NAME: ""
  UNITY_VERSION: "2019.3.0f1"
jobs:
  android-build:
    runs-on: self-hosted
    steps:
    - uses: actions/checkout@v2
      with:
        path: android
    - name: Android Build
      run: |
        /Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity -quit -batchmode -nographics -silent-crashes -logFile \
          -projectPath ./android -executeMethod MobileBuild.BuildAndroid -buildTarget Android
  ios-build:
    runs-on: self-hosted
    steps:
    - uses: actions/checkout@v2
      with:
        path: ios
    - name: iOS Build
      run: |
        /Applications/Unity/Hub/Editor/$UNITY_VERSION/Unity.app/Contents/MacOS/Unity -quit -batchmode -nographics -silent-crashes -logFile \
          -projectPath ./ios -executeMethod MobileBuild.BuildIOS -buildTarget iOS

書き方が変わった箇所がありますね。
actions/checkoutwith:キーワードを使ってpath:の設定を渡しています。
こうしている理由は、AndroidとiOSで利用するフォルダを分けてインポート時間やビルド時間の高速化するためです。

この状態でpushしてみましょう。
self-hostedに登録したサーバーが動作してビルドが実行されるはずです。
AndroidとiOSのビルドが成功すると以下のような画面になります。
スクリーンショット 2019-12-23 16.58.55.png

暗号化されたシークレットを利用する

実際に運用しようとなると、ymlに書きたくない情報を渡したいケースがあると思います。
その場合は GitHub のリポジトリの Setting -> Secret ページへいき Add a new secret をクリック。
シークレットとは、暗号化された環境変数のことです。
NameValueをそれぞれ入力します。
スクリーンショット 2019-12-23 17.10.23.png
ここで設定した値をワークフローで利用するには

env:
  SOME_PARAM: ${{ secrets.SOME_PARAM }}

のように記述することで可能です。
これでpasswordTOKENの情報を渡すことができますね!
なお、シークレットの制限として
1. ワークフローで最大100のシークレットを持てる
2. シークレットの容量は最大64KB
の2点があります。
参考: 暗号化されたシークレットの作成と利用

終わりに

個人的にとてもいいツールだと感じました。
具体的には以下の点に可能性があると思います
1. マシンスペックをこちらで自由にカスタマイズできる(self-hostedの場合)
2. CI/CDがGitHubで完結する
3. Jenkinsから解放される(アセットバンドルをどうするのかという問題はありますが・・・)

他にもCircleCI(MacOS)と違ってUnityのインストールを毎回行う必要がなかったりと比較すると優れている点が多いなという印象です。
簡単な導入まででしたが、以上になります。
参考になれば幸いです。

41
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
20