game-ci / unity-builder

Build Unity projects for different platforms
https://github.com/marketplace/actions/unity-builder
MIT License
858 stars 253 forks source link

Android Bundle not build correcty in v2.2.0 #534

Closed Heurazio closed 1 year ago

Heurazio commented 1 year ago

Bug description When using v2.2.0 the generated bundle file does not contain the BundleConfig.pb file which is expected by Google Play. I documented more details here: https://discord.com/channels/710946343828455455/1103289364097802250

How to reproduce Use v2.2.0 and v2 as unity-builder versions and set the androidAppBundle: true OR androidExportType: androidAppBundle. Once the build is complete, the AAB file generated with v2.2.0 does not contain the expected BundleConfig.pb file.

Expected behavior The generated AAB bundle should contain the BundleConfig.pb

Additional details Can be found on Discord (https://discord.com/channels/710946343828455455/1103289364097802250). I'll try to keep this up to date with my findings.

Heurazio commented 1 year ago

After figuring out what the problem was (see discord) I was able to find a workaround which is a custom build script.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Build.Reporting;

namespace Assets.Editor
{
    public static class UnityBuilderCustomBuildScript
    {
        private static readonly string Eol = Environment.NewLine;

        private static readonly string[] Secrets =
            {"androidKeystorePass", "androidKeyaliasName", "androidKeyaliasPass"};

        public static void Build()
        {
            // Gather values from args
            Dictionary<string, string> options = GetValidatedOptions();

            // Set version for this build
            PlayerSettings.bundleVersion = options["buildVersion"];
            PlayerSettings.macOS.buildNumber = options["buildVersion"];
            PlayerSettings.Android.bundleVersionCode = int.Parse(options["androidVersionCode"]);

            // Apply build target
            var buildTarget = (BuildTarget)Enum.Parse(typeof(BuildTarget), options["buildTarget"]);
            EditorUserBuildSettings.buildAppBundle = options["customBuildPath"].EndsWith(".aab");
            if (options.TryGetValue("androidKeystoreName", out string keystoreName) &&
                !string.IsNullOrEmpty(keystoreName))
            {
                PlayerSettings.Android.useCustomKeystore = true;
                PlayerSettings.Android.keystoreName = keystoreName;
            }
            if (options.TryGetValue("androidKeystorePass", out string keystorePass) &&
                !string.IsNullOrEmpty(keystorePass))
                PlayerSettings.Android.keystorePass = keystorePass;
            if (options.TryGetValue("androidKeyaliasName", out string keyaliasName) &&
                !string.IsNullOrEmpty(keyaliasName))
                PlayerSettings.Android.keyaliasName = keyaliasName;
            if (options.TryGetValue("androidKeyaliasPass", out string keyaliasPass) &&
                !string.IsNullOrEmpty(keyaliasPass))
                PlayerSettings.Android.keyaliasPass = keyaliasPass;
            if (options.TryGetValue("androidTargetSdkVersion", out string androidTargetSdkVersion) &&
                !string.IsNullOrEmpty(androidTargetSdkVersion))
            {
                var targetSdkVersion = AndroidSdkVersions.AndroidApiLevelAuto;
                try
                {
                    targetSdkVersion =
                        (AndroidSdkVersions)Enum.Parse(typeof(AndroidSdkVersions), androidTargetSdkVersion);
                }
                catch
                {
                    UnityEngine.Debug.Log("Failed to parse androidTargetSdkVersion! Fallback to AndroidApiLevelAuto");
                }

                PlayerSettings.Android.targetSdkVersion = targetSdkVersion;
            }

            string androidExportType;
            if (options.TryGetValue("androidExportType", out androidExportType) && !string.IsNullOrEmpty(androidExportType))
            {
                // Only exists in 2018.3 and above
                PropertyInfo buildAppBundle = typeof(EditorUserBuildSettings)
                      .GetProperty("buildAppBundle", BindingFlags.Public | BindingFlags.Static);
                switch (androidExportType)
                {
                    case "androidStudioProject":
                        EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
                        if (buildAppBundle != null)
                            buildAppBundle.SetValue(null, false);
                        break;
                    case "androidAppBundle":
                        EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
                        if (buildAppBundle != null)
                            buildAppBundle.SetValue(null, true);
                        break;
                    case "androidPackage":
                        EditorUserBuildSettings.exportAsGoogleAndroidProject = false;
                        if (buildAppBundle != null)
                            buildAppBundle.SetValue(null, false);
                        break;
                }
            }

            string symbolType;
            if (options.TryGetValue("androidSymbolType", out symbolType) && !string.IsNullOrEmpty(symbolType))
            {
#if UNITY_2021_1_OR_NEWER
                switch (symbolType)
                {
                    case "public":
                        EditorUserBuildSettings.androidCreateSymbols = AndroidCreateSymbols.Public;
                        break;
                    case "debugging":
                        EditorUserBuildSettings.androidCreateSymbols = AndroidCreateSymbols.Debugging;
                        break;
                    case "none":
                        EditorUserBuildSettings.androidCreateSymbols = AndroidCreateSymbols.Disabled;
                        break;
                }
#elif UNITY_2019_2_OR_NEWER
        switch (symbolType)
        {
          case "public":
          case "debugging":
            EditorUserBuildSettings.androidCreateSymbolsZip = true;
            break;
          case "none":
            EditorUserBuildSettings.androidCreateSymbolsZip = false;
            break;
        }
#endif
            }

            // Determine subtarget
            int buildSubtarget = 0;
#if UNITY_2021_2_OR_NEWER
            if (!options.TryGetValue("standaloneBuildSubtarget", out var subtargetValue) || !Enum.TryParse(subtargetValue, out StandaloneBuildSubtarget buildSubtargetValue))
            {
                buildSubtargetValue = default;
            }
            buildSubtarget = (int)buildSubtargetValue;
#endif

            // Custom build
            Build(buildTarget, buildSubtarget, options["customBuildPath"]);
        }

        private static Dictionary<string, string> GetValidatedOptions()
        {
            ParseCommandLineArguments(out Dictionary<string, string> validatedOptions);

            if (!validatedOptions.TryGetValue("projectPath", out string _))
            {
                Console.WriteLine("Missing argument -projectPath");
                EditorApplication.Exit(110);
            }

            if (!validatedOptions.TryGetValue("buildTarget", out string buildTarget))
            {
                Console.WriteLine("Missing argument -buildTarget");
                EditorApplication.Exit(120);
            }

            if (!Enum.IsDefined(typeof(BuildTarget), buildTarget ?? string.Empty))
            {
                Console.WriteLine($"{buildTarget} is not a defined {nameof(BuildTarget)}");
                EditorApplication.Exit(121);
            }

            if (!validatedOptions.TryGetValue("customBuildPath", out string _))
            {
                Console.WriteLine("Missing argument -customBuildPath");
                EditorApplication.Exit(130);
            }

            const string defaultCustomBuildName = "TestBuild";
            if (!validatedOptions.TryGetValue("customBuildName", out string customBuildName))
            {
                Console.WriteLine($"Missing argument -customBuildName, defaulting to {defaultCustomBuildName}.");
                validatedOptions.Add("customBuildName", defaultCustomBuildName);
            }
            else if (customBuildName == "")
            {
                Console.WriteLine($"Invalid argument -customBuildName, defaulting to {defaultCustomBuildName}.");
                validatedOptions.Add("customBuildName", defaultCustomBuildName);
            }

            return validatedOptions;
        }

        private static void ParseCommandLineArguments(out Dictionary<string, string> providedArguments)
        {
            providedArguments = new Dictionary<string, string>();
            string[] args = Environment.GetCommandLineArgs();

            Console.WriteLine(
                $"{Eol}" +
                $"###########################{Eol}" +
                $"#    Parsing settings     #{Eol}" +
                $"###########################{Eol}" +
                $"{Eol}"
            );

            // Extract flags with optional values
            for (int current = 0, next = 1; current < args.Length; current++, next++)
            {
                // Parse flag
                bool isFlag = args[current].StartsWith("-");
                if (!isFlag) continue;
                string flag = args[current].TrimStart('-');

                // Parse optional value
                bool flagHasValue = next < args.Length && !args[next].StartsWith("-");
                string value = flagHasValue ? args[next].TrimStart('-') : "";
                bool secret = Secrets.Contains(flag);
                string displayValue = secret ? "*HIDDEN*" : "\"" + value + "\"";

                // Assign
                Console.WriteLine($"Found flag \"{flag}\" with value {displayValue}.");
                providedArguments.Add(flag, value);
            }
        }

        private static void Build(BuildTarget buildTarget, int buildSubtarget, string filePath)
        {
            string[] scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();
            var buildPlayerOptions = new BuildPlayerOptions
            {
                scenes = scenes,
                target = buildTarget,
                //                targetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget),
                locationPathName = filePath,
                //                options = UnityEditor.BuildOptions.Development
#if UNITY_2021_2_OR_NEWER
                subtarget = buildSubtarget
#endif
            };

            BuildSummary buildSummary = BuildPipeline.BuildPlayer(buildPlayerOptions).summary;
            ReportSummary(buildSummary);
            ExitWithResult(buildSummary.result);
        }

        private static void ReportSummary(BuildSummary summary)
        {
            Console.WriteLine(
                $"{Eol}" +
                $"###########################{Eol}" +
                $"#      Build results      #{Eol}" +
                $"###########################{Eol}" +
                $"{Eol}" +
                $"Duration: {summary.totalTime.ToString()}{Eol}" +
                $"Warnings: {summary.totalWarnings.ToString()}{Eol}" +
                $"Errors: {summary.totalErrors.ToString()}{Eol}" +
                $"Size: {summary.totalSize.ToString()} bytes{Eol}" +
                $"{Eol}"
            );
        }

        private static void ExitWithResult(BuildResult result)
        {
            switch (result)
            {
                case BuildResult.Succeeded:
                    Console.WriteLine("Build succeeded!");
                    EditorApplication.Exit(0);
                    break;
                case BuildResult.Failed:
                    Console.WriteLine("Build failed!");
                    EditorApplication.Exit(101);
                    break;
                case BuildResult.Cancelled:
                    Console.WriteLine("Build cancelled!");
                    EditorApplication.Exit(102);
                    break;
                case BuildResult.Unknown:
                default:
                    Console.WriteLine("Build result is unknown!");
                    EditorApplication.Exit(103);
                    break;
            }
        }
    }
}

I can then use it like this in the step

      - uses: game-ci/unity-builder@v2.2.0
        id: gameCiBuildAndroid
        with:
          unityVersion: auto
          targetPlatform: Android
          androidExportType: 'androidAppBundle'
          androidSymbolType: 'debugging'
          androidAppBundle: true
          androidKeystoreName: user
          androidKeystoreBase64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
          androidKeystorePass: ${{ secrets.ANDROID_KEYSTORE_PASS }}
          androidKeyaliasName: ${{ secrets.ANDROID_KEYALIAS_NAME }}
          androidKeyaliasPass: ${{ secrets.ANDROID_KEYALIAS_PASS }}
          buildMethod: Assets.Editor.UnityBuilderCustomBuildScript.Build

This is a valid workaround until version 2.2.X is released containing the correct fix.