Unity ビルド自動化 - build pipeline & addressable
Unity ビルド自動化 - build pipeline & addressable
Build シリーズ (2 / 7)
- Unity ビルド自動化 - fastlane
- Unity ビルド自動化 - build pipeline & addressable
- Unity Addressable ビルドエラー解決 - Animator が動作しない問題
- Unity ビルド自動化 - Jenkins
- Unity iOS 〜 Xcode ビルドパイプラインガイド
- Unity iOS - Xcode コード署名ガイド (証明書とプロビジョニングプロファイル)
- Jenkinsビルドパイプライン - プラグインリスト
目次
はじめに
- Unity プロジェクトをビルドする際に、batchmode で CI/CD ツール (Jenkins, fastlane) を使ってバックグラウンド実行する方法を指す。
- 前回の fastlane 記事で説明した FastFile の unity plugin では、
execute_methodで static 関数を呼ぶ形が一般的。 - 引数をビルドスクリプトで柔軟に受けたい場合、fastlane は制約があるため Jenkins shell 実行の方が向く場合がある。
ビルドスクリプト構成
BuildScript.csは Unity プロジェクトのAssets/Editorに配置。- Unity Build Pipeline 用クラスと execute method の関数は
static必須。
1
2
3
4
5
6
7
public static class ProjectBuilder
{
public static void BuildAndroid()
{
}
}
1. AOS ビルド
.apkか.aabかを区別する。開発向けは.apk、Google Play の内部/公開テストやリリースは.aab。- keystore は生成/保管を厳密に。紛失・破損すると Unity 側ビルド失敗や Play Console 認証問題が発生する。
keystore 参考: »こちら«
- 生成した keystore はプロジェクト内フォルダで管理し、SVN 管理するのが安全。
aos execute method 全体コード
BuildPlayerOptionsを作成し、PlayerSettingsとEditorUserBuildSettingsを設定してBuildPipeline.BuildPlayerを呼ぶ。System.Environment.GetCommandLineArgs()で引数を受ける処理は Jenkins shell から変数を渡す想定。- fastlane 実行ではログ確認や挙動が不安定なことがあるため、引数運用したい場合は Jenkins batchmode 実行が確実。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public static void BuildAndroid()
{
// Jenkins arguments. Added for build_num assignment.
string[] args = System.Environment.GetCommandLineArgs();
int buildNum = 0;
foreach (string a in args)
{
if (a.StartsWith("build_num"))
{
var arr = a.Split(":");
if (arr.Length == 2)
{
int.TryParse(arr[1], out buildNum);
}
}
}
Debug.Log("hhh args : " + string.Join(",", args));
Debug.Log("Build Started hhh");
// BuildPlayerOptions configuration
BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
buildPlayerOptions.scenes = FindEnabledEditorScenes();
buildPlayerOptions.locationPathName = "/Users/YOUR_USERNAME/Build/toyverse_apk/toyverse.aab";
buildPlayerOptions.target = BuildTarget.Android;
EditorUserBuildSettings.buildAppBundle = true;
// PlayerSettings configuration
PlayerSettings.Android.bundleVersionCode = buildNum;
PlayerSettings.Android.useCustomKeystore = true;
PlayerSettings.Android.keystoreName = "Keystore/toyverse.keystore";
PlayerSettings.Android.keystorePass = "toyverse";
PlayerSettings.Android.keyaliasName = "com.coconev.toyverse";
PlayerSettings.Android.keyaliasPass = "toyverse";
Debug.Log("Build Player Started hhh");
Debug.Log("PlayerSettings hhh keystoreName : " + PlayerSettings.Android.keystoreName);
Debug.Log("PlayerSettings hhh keyaliasName : " + PlayerSettings.Android.keyaliasName);
Debug.Log("PlayerSettings hhh keystorePass : " + PlayerSettings.Android.keystorePass);
Debug.Log("PlayerSettings hhh keyaliasPass : " + PlayerSettings.Android.keyaliasPass);
var report = BuildPipeline.BuildPlayer(buildPlayerOptions)
if (report.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded) Debug.Log("Build Success");
if (report.summary.result == UnityEditor.Build.Reporting.BuildResult.Failed) Debug.Log("Build Failed");
}
private static string[] FindEnabledEditorScenes()
{
List<string> EditorScenes = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if (!scene.enabled) continue;
EditorScenes.Add(scene.path);
}
return EditorScenes.ToArray();
}
2. iOS ビルド
- iOS ビルド手順は簡潔に言うと:
- Unity プロジェクトビルド ->
xcworkspace(cocoapods 必須) とxcodeproj出力 - Xcode ビルド ->
.ipa出力
- Unity プロジェクトビルド ->
AOS と違って実質 2 回ビルドが必要。
- この理由で iOS はビルド後処理がほぼ必須。
IPostprocessBuildWithReportを実装した PBR クラスを作る。PBXProjectとProjectCapabilityManager等で後処理を適用する。ENABLE_BITCODE、Entitlements 生成、framework 追加、Capability 自動化は特にハマりやすい。
ios execute method 全体コード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public static void BuildIOS()
{
// same pattern as AOS up to here
BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
buildPlayerOptions.scenes = FindEnabledEditorScenes();
buildPlayerOptions.locationPathName = "/Users/YOUR_USERNAME/Xcode";
buildPlayerOptions.target = BuildTarget.iOS;
var report = BuildPipeline.BuildPlayer(buildPlayerOptions);
if (report.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded) Debug.Log("Build Success");
if (report.summary.result == UnityEditor.Build.Reporting.BuildResult.Failed) Debug.Log("Build Failed");
// post process after BuildPlayer
#if UNITY_IPHONE
PBR pbr = new PBR();
pbr.OnPostprocessBuild(report);
#endif
}
#if UNITY_IPHONE // required to avoid Android target compile errors
class PBR : IPostprocessBuildWithReport
{
public void OnPostprocessBuild(BuildReport report)
{
if (report.summary.platform == BuildTarget.iOS)
{
Debug.Log("OnPostProceeBuild");
string projectPath = report.summary.outputPath + "/Unity-iPhone.xcodeproj/project.pbxproj";
var entitlementFilePath = "Entitlements.entitlements";
var project = new PBXProject();
project.ReadFromFile(projectPath);
var manager = new ProjectCapabilityManager(projectPath, entitlementFilePath, null, project.GetUnityMainTargetGuid());
manager.AddPushNotifications(true);
manager.WriteToFile();
var mainTargetGuid = project.GetUnityMainTargetGuid();
project.SetBuildProperty(mainTargetGuid, "ENABLE_BITCODE", "NO");
project.SetBuildProperty(mainTargetGuid, "CODE_SIGN_ENTITLEMENTS", entitlementFilePath);
project.AddFrameworkToProject(mainTargetGuid, "UserNotifications.framework", false);
project.WriteToFile(projectPath);
}
}
}
#endif
projectPath: ビルド出力の.xcodeprojを右クリック -> Show Package Contents ->project.pbxprojを指定。Entitlementsは Apple Developer 側設定 (Push Notification / IAP など) を Xcode Signing&Capabilities に反映するためのファイル。ProjectCapabilityManagerで entitlements パス設定と capability 追加を行う。PBXProject.SetBuildPropertyで entitlement / framework を設定 (Firebase Push 用)。
参考リンク
3. Addressable ビルド
- Addressable ビルドも Unity プロジェクトを fastlane から呼ぶ方式で同様に運用する。
1
2
3
4
5
6
7
8
9
desc "Build Addressable"
lane :addrressable do
unity(
build_target: "iOS",
execute_method: "ProjectBuilder.BuildAddressable_IOS",
unity_path: "/Applications/Unity/Hub/Editor/2022.3.4f1/Unity.app/Contents/MacOS/Unity",
project_path: "/Users/YOUR_USERNAME/.jenkins/workspace/ios_fastlane"
)
end
unity plugin から
ProjectBuilder.BuildAddressable_AOS / IOSstatic 関数をバックグラウンド実行する。- Addressable ビルド前に Addressable Profile を確認。
- モバイルでは主に 3 種で運用:
Default(ビルド同梱),Remote_aos(Android remote),Remote_ios(iOS remote)
- Manage Profile で Profile / Variable を追加できる。
- BuildPath / LoadPath URL 組み立てでこの変数が重要。
1
# []
[]を使うと、まず括弧内変数を優先解決し、その後 Variable 変数と組み合わせる。- これら Profile 名をビルドスクリプト内で使う。
Addressable ビルド全体コード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#region Build Addressable
public static string build_script = "Assets/AddressableAssetsData/DataBuilders/BuildScriptPackedMode.asset";
public static string profile_aos_name = "Remote_aos"; // must match profile names in Addressable Groups
public static string profile_ios_name = "Remote_ios";
public static string profile_default = "Default";
public static void BuildAddressable_AOS()
{
Debug.Log("Addressable Build Started haha");
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings;
settings.activeProfileId = settings.profileSettings.GetProfileId(profile_default);
IDataBuilder builder = AssetDatabase.LoadAssetAtPath<ScriptableObject>(build_script) as IDataBuilder;
Debug.Log("Addressable Load AssetPath haha");
int index = settings.ActivePlayerDataBuilderIndex = settings.DataBuilders.IndexOf((ScriptableObject)builder);
Debug.Log($"Addressable index number : {index}");
if (index > 0)
{
settings.ActivePlayerDataBuilderIndex = index;
}
else if (AddressableAssetSettingsDefaultObject.Settings.AddDataBuilder(builder))
{
settings.ActivePlayerDataBuilderIndex = AddressableAssetSettingsDefaultObject.Settings.DataBuilders.Count - 1;
}
else
{
Debug.LogWarning($"{builder} could not be found");
}
Debug.Log($"Addressable Build Content Started!! hh");
AddressableAssetSettings.BuildPlayerContent(out AddressablesPlayerBuildResult result);
bool success = string.IsNullOrEmpty(result.Error);
if (!success)
{
Debug.LogError("Addressable build error encountered : " + result.Error);
}
else
{
Debug.Log("Addressable Build Success!!!");
}
}
public static void BuildAddressable_IOS()
{
Debug.Log("Addressable Build Started haha");
AddressableAssetSettings settings = AddressableAssetSettingsDefaultObject.Settings;
settings.activeProfileId = settings.profileSettings.GetProfileId(profile_default);
IDataBuilder builder = AssetDatabase.LoadAssetAtPath<ScriptableObject>(build_script) as IDataBuilder;
Debug.Log("Addressable Load AssetPath haha");
int index = settings.ActivePlayerDataBuilderIndex = settings.DataBuilders.IndexOf((ScriptableObject)builder);
Debug.Log($"Addressable index number : {index}");
if (index > 0)
{
settings.ActivePlayerDataBuilderIndex = index;
}
else if (AddressableAssetSettingsDefaultObject.Settings.AddDataBuilder(builder))
{
settings.ActivePlayerDataBuilderIndex = AddressableAssetSettingsDefaultObject.Settings.DataBuilders.Count - 1;
}
else
{
Debug.LogWarning($"{builder} could not be found");
}
Debug.Log($"Addressable Build Content Started!! hh");
AddressableAssetSettings.BuildPlayerContent(out AddressablesPlayerBuildResult result);
bool success = string.IsNullOrEmpty(result.Error);
if (!success)
{
Debug.LogError("Addressable build error encountered : " + result.Error);
}
else
{
Debug.Log("Addressable Build Success!!!");
}
}
- 注意:
1
settings.activeProfileId = settings.profileSettings.GetProfileId(profile_default);
GetProfileIdに渡すプロファイル名が Addressable Groups の設定と一致しているか必ず確認。Defaultは fastest、Remoteは Use Existing Build を設定すること。
この記事は著者の CC BY 4.0 ライセンスの下で提供されています。






