🙆‍♀️

UnityのiOSアプリをアーカイブまでJenkinsで自動化した話

2022/10/18に公開

Qiitaより転載
2017/1/16 初投稿


最近Resharperを使いたいが為に開発環境をMacからWindowsに変えた結果iOSのビルドが非常に面倒になった。なのでJenkinsを導入してWindows側でビルドからアーカイブまでほぼ全て自動化出来るように環境を整えてみた。

流れとしては

  1. WindowsでGit Push
  2. Mac側でGit Pull
  3. Unityビルド(このときにちょっとXcodeのplistとか書き換える)
  4. Xcodeアーカイブ
  5. Git commit + push

Jenkinsの導入

調べてください★

Jenkinsにジョブの登録

今回はプラグインを使わず素の状態でジョブを作成します。

まずGitから最新の状態のプロジェクトを読み込み

PROJECT_PATH="{プロジェクトの場所}"
cd "${PROJECT_PATH}"
git pull

でUnityのビルド。

/Applications/Unity/Unity.app/Contents/MacOS/Unity \
    -batchmode \
    -quit \
    -logFile ./build.log \
    -projectPath "${PROJECT_PATH}" \
    -executeMethod BuildClass.BuildiOS

でXcodeでアーカイブ

# 全部のルート
ROOT_FOLDER="{ルート}"
# ビルドが入ってるフォルダ
BUILD_FOLDER="{ビルドが入ってるフォルダ名}"

# 最新のビルドを取得
cd "${ROOT_FOLDER}/${BUILD_FOLDER}"
builds=()
for file in *; do
    builds=("${file}" "${builds[@]}")
done

# Scheme
TARGET="Unity-iPhone"
# アーカイブ先
OUTPUT_DIR_ARCHIVE="${ROOT_FOLDER}/build_archive/"
# プロビジョニングプロファイル
PROFILE_PATH="{プロビジョニングプロファイルの場所}"

if [ ! -d ${OUTPUT_DIR_ARCHIVE} ]; then
    mkdir "${OUTPUT_DIR_ARCHIVE}"
fi

xcodebuild -project "${ROOT_FOLDER}/${BUILD_FOLDER}/${builds[0]}/${TARGET}.xcodeproj" \
               -scheme "${TARGET}" \
               archive \
               -archivePath "${OUTPUT_DIR_ARCHIVE}/archive" \
               DEVELOPMENT_TEAM={自分のチームの10桁のコード}

これで指定した場所にarchive.xcarchiveっていうファイルが出来るので、それをダブルクリックして後はアップロードするだけ。

DEVELOPMENT_TEAMやPROFILE_PATHの値は以下参照:
[Xcode] CIなどを使用するときに必要な値を確認する方法

最後にUnityで行った変更点のPush

cd "${PROJECT_PATH}"
git commit -a -m "iOSのビルドに成功しました。"
git push

Unity側

Unityのビルドのところに書いた-executeMethod BuildClass.BuildiOS←これは自分で作成する。

場所はAssets/Editor/BuildClass.cs

BuildClass.cs
using UnityEditor;
using UnityEngine;

public class BuildClass
{

    /// <summary>
    /// iOSのビルドを行います
    /// </summary>
    public static void BuildiOS()
    {
        // バージョンのインクリメント
        var versionString = PlayerSettings.bundleVersion;
        Debug.Log("Version got: " + versionString);
        var verstionStrings = versionString.Split('.');
        int lastVersion;
        if(!int.TryParse(verstionStrings[2], out lastVersion)){
            Debug.Log("[Error!] Failed to increment version.");
            return;
        }
        var newVersion = lastVersion + 1;
        var newVersionStrings = new [] {verstionStrings[0], verstionStrings[1], newVersion.ToString()};
        var newVersionString = string.Join(".", newVersionStrings);
        PlayerSettings.bundleVersion = newVersionString;
        Debug.Log("New Version set: " + PlayerSettings.bundleVersion);

        // Bundleのインクリメント
        var buildString = PlayerSettings.iOS.buildNumber;
        Debug.Log("Build number got: " + buildString);
        int buildNum;
        if(!int.TryParse( buildString, out buildNum)) {
            Debug.Log("[Error!] Failed to increment build version.");
            return;
        }
        PlayerSettings.iOS.buildNumber = (++buildNum).ToString();
        Debug.Log("New Build number set: " + PlayerSettings.iOS.buildNumber);

        // ビルドフォルダの名前
        var buildFolderName = "alpha_" + newVersionString;

        string[] sceneList = {
            "./Assets/Scene/Main.unity",
            "./Assets/Scene/Menu.unity"
        };

        string errorMessage = BuildPipeline.BuildPlayer(
            sceneList,
            "{出力先}" + buildFolderName,
            BuildTarget.iOS,
            BuildOptions.None
        );

        if(!string.IsNullOrEmpty(errorMessage)){
            Debug.LogError("[Error!]" + errorMessage);
        } else {
            Debug.Log("[Success!]");
        }
    }
}

めんどくさいので中身の解説は省略。とりあえずバージョンのインクリメントとかそういうのをここで自動化できる。ただ僕の場合これだけだとTestFlightで内容を更新する際に審査が必要になるので、バージョンを一定にしたりとplistの編集が必要だったので、PostProcessBuildも書いてる。

これの場所はAssets/Editor/XcodeSettingsPostProcesser.cs

XcodeSettingsPostProcesser.cs
using System.IO;
using UnityEngine;
using UnityEditor;
using UnityEditor.iOS.Xcode;
using UnityEditor.Callbacks;
using System.Collections;

public class XcodeSettingsPostProcesser
{
    [PostProcessBuildAttribute (0)]
    public static void OnPostprocessBuild (BuildTarget buildTarget, string pathToBuiltProject)
    {
        // iOS以外のプラットフォームは処理を行わない
        if (buildTarget != BuildTarget.iOS) return;

        // Plist変更
        string plistpath = Path.Combine(pathToBuiltProject, "Info.plist");
        PlistDocument plist = new PlistDocument();
        plist.ReadFromFile(plistpath);

        // iOS10対策
        plist.root.SetString("NSCameraUsageDescription", "Unity/Admob Dependency");
        // Testflightの審査を端折る為
        plist.root.SetString("CFBundleShortVersionString", "0.0.5");

        // plist反映
        plist.WriteToFile(plistpath);
    }
}

以上

Unity側で少し内容を書き換えてるので更新の前にコンフリクトを解消しないと行けなかったりしてまだ面倒なところはあるけど、とりあえずビルドさせて風呂入って、戻ったら実機テストしてOKだったらアップロード、みたいな感じでだいぶ楽になった。

Discussion