Skip to content

Instantly share code, notes, and snippets.

@githappens
Created August 30, 2021 09:42
Show Gist options
  • Save githappens/eff54ba63c749fef73d2559947effe72 to your computer and use it in GitHub Desktop.
Save githappens/eff54ba63c749fef73d2559947effe72 to your computer and use it in GitHub Desktop.
Build method that also triggers an Addressables content build, based on the original build method presented here: https://game.ci/docs/github/builder#buildmethod
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.Build.Reporting;
using UnityEngine;
namespace Ampersand.Editor
{
/// <summary>
/// This class provides a build method that creates a content build of Addressables before the player is built.
/// More info:
/// https://game.ci/docs/github/builder#buildmethod
/// https://forum.unity.com/threads/how-to-trigger-build-player-content-when-build-unity-project.689602/#post-6798410
/// </summary>
internal static class BuildGoAndDesign
{
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"]);
// Enable development build
if (bool.TryParse(options["devBuild"], out var isDevBuild))
{
EditorUserBuildSettings.development = isDevBuild;
EditorUserBuildSettings.allowDebugging = isDevBuild;
EditorUserBuildSettings.waitForManagedDebugger = isDevBuild;
}
// Apply build target
var buildTarget = (BuildTarget) Enum.Parse(typeof(BuildTarget), options["buildTarget"]);
switch (buildTarget)
{
case BuildTarget.Android:
{
EditorUserBuildSettings.buildAppBundle = options["customBuildPath"].EndsWith(".aab");
if (options.TryGetValue("androidKeystoreName", out string keystoreName) &&
!string.IsNullOrEmpty(keystoreName))
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;
break;
}
case BuildTarget.StandaloneOSX:
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Standalone, ScriptingImplementation.Mono2x);
break;
}
// Build addressables content
BuildAddressables($"{options["buildTarget"]}-{options["branch"]}");
// Custom build
Build(buildTarget, options["customBuildPath"]);
}
private static void BuildAddressables(string addressablesProfileName)
{
Console.WriteLine("Cleaning player content.");
AddressableAssetSettings.CleanPlayerContent();
AddressableAssetProfileSettings profileSettings =
AddressableAssetSettingsDefaultObject.Settings.profileSettings;
Console.WriteLine("Using active databuilder: " +
AddressableAssetSettingsDefaultObject.Settings.ActivePlayerDataBuilder.Name);
Console.WriteLine("Setting profile to: " + addressablesProfileName);
string profileId = profileSettings.GetProfileId(addressablesProfileName);
AddressableAssetSettingsDefaultObject.Settings.activeProfileId = profileId;
Console.WriteLine("Starting addressables content build.");
// Build addressable content
AddressableAssetSettings.BuildPlayerContent();
Console.WriteLine("Building player content finished.");
}
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))
{
EditorApplication.Exit(121);
}
if (!validatedOptions.TryGetValue("customBuildPath", out string _))
{
Console.WriteLine("Missing argument -customBuildPath");
EditorApplication.Exit(130);
}
const string defaultEnableDevBuildValue = "false";
if (!validatedOptions.TryGetValue("devBuild", out var isDevBuild))
{
Console.WriteLine($"Missing argument -devBuild, defaulting to {defaultEnableDevBuildValue}.");
validatedOptions.Add("devBuild", defaultEnableDevBuildValue);
}
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, 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
};
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;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment