Closed Heurazio closed 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.
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.