using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using Ubisoft.Hotel.Addressables.Core.Editor; using Ubisoft.Hotel.Package; using Ubisoft.Hotel.Package.Editor; using HotelVersion = Ubisoft.Hotel.Package.Version; namespace Ubisoft.Hotel.PackageManager.Editor { public class AppSpaceBuildSuiteProvider : ScriptableObject, IBuildSuiteProvider { private static readonly string CONSUMER_PACKAGE_SUITE_BUILDER_NAMESPACE = "Ubisoft.Consumer.PackageManager.Editor"; public static readonly string CONSUMER_PACKAGE_SUITE_BUILDER_SHORT_TYPENAME = "ConsumerPackageSuiteBuilder"; private static readonly string CONSUMER_PACKAGE_SUITE_PROVIDER_TYPENAME = $"{CONSUMER_PACKAGE_SUITE_BUILDER_NAMESPACE}.{CONSUMER_PACKAGE_SUITE_BUILDER_SHORT_TYPENAME}"; public static readonly string CONSUMER_ASMDEF_FILENAME_NO_EXT = CONSUMER_PACKAGE_SUITE_BUILDER_NAMESPACE; public static readonly string CONSUMER_PACKAGE_SUITE_PROVIDER_FILENAME_NO_EXT = CONSUMER_PACKAGE_SUITE_BUILDER_SHORT_TYPENAME; public static readonly string CONSUMER_PACKAGE_SUITE_PROVIDER_FILENAME = $"{CONSUMER_PACKAGE_SUITE_PROVIDER_FILENAME_NO_EXT}.cs"; private static readonly string GAME_VERSION_FILENAME_NO_EXT = "gameVersion"; private static readonly string GAME_VERSION_FILENAME = $"{GAME_VERSION_FILENAME_NO_EXT}.asset"; private static readonly string BUILD_SETTINGS_FILENAME_NO_EXT = "buildSettings"; private static readonly string BUILD_SETTINGS_FILENAME = $"{BUILD_SETTINGS_FILENAME_NO_EXT}.asset"; [SerializeField] private List m_packageSuites; [SerializeField] private string m_currentPackageSuiteName; [NonSerialized] private string[] m_packageSuiteNames; [SerializeField] private VersionProperty m_gameVersionProperty; [SerializeField] private BuildSettings m_buildSettings; private PackageSuiteBuilder m_packageSuiteBuilder; public PackageSuiteBuilder PackageSuiteBuilder { get { if (m_packageSuiteBuilder == null) { m_packageSuiteBuilder = HtTypes.CreateInstance(CONSUMER_ASMDEF_FILENAME_NO_EXT, CONSUMER_PACKAGE_SUITE_PROVIDER_TYPENAME); } return m_packageSuiteBuilder; } } public List PackageSuites { get { return m_packageSuites; } private set { m_packageSuites = value; } } public string CurrentPackageSuiteName { get { return m_currentPackageSuiteName; } set { m_currentPackageSuiteName = value; } } public string[] PackageSuiteNames { get { if ((m_packageSuiteNames == null || m_currentPackageSuiteName == null) && PackageSuites != null && PackageSuites.Count > 0) { int count = PackageSuites.Count; m_packageSuiteNames = new string[count]; for (int i = 0; i < count; i++) { // Store the name to be used by the dropbox of the inspector m_packageSuiteNames[i] = PackageSuites[i].name; } } return m_packageSuiteNames; } private set { m_packageSuiteNames = value; } } public Dictionary GetPackagesHandledByConsumer() { return PackageSuiteBuilder.GetPackagesHandledByConsumer(); } protected List GetPackageSuiteNames() { return PackageSuiteBuilder.GetPackageSuiteNames(); } protected virtual PackageSuite GeneratePackageSuite(EPlatform platform, string packageSuiteName, Dictionary arguments = null) { return PackageSuiteBuilder.GeneratePackageSuite(platform, packageSuiteName, arguments); } protected List GeneratePackageSuites(EPlatform platform) { PackageManager.Log($"GeneratePackageSuites for platform {platform}... "); List returnValue = new List(); if (PackageSuiteBuilder.IsEnabled()) { PackageSuite packageSuite; List packageSuiteNames = GetPackageSuiteNames(); if (packageSuiteNames != null) { foreach (string name in packageSuiteNames) { packageSuite = GeneratePackageSuite(platform, name); if (packageSuite != null) { returnValue.Add(packageSuite); } } } } return returnValue; } public virtual bool IsEnabled() { return PackageSuiteBuilder.IsEnabled(); } public virtual bool IsEmpty() { return PackageSuites == null || PackageSuites.Count == 0; } public bool CanPerform() { // We consider that AppSpaceBuildSuiteProvider is enabled only if the consumer has defined any suites, // otherwise AppSpaceBuildSuiteProvider should have no impact in the build return IsEnabled() && !IsEmpty(); } public void Generate(EPlatform platform, string currentPackageSuiteName) { if (IsEnabled()) { // Recalculate the suite shown by the PackageManager editor PackageManagerEditor.Clear(); ResetPackageSuiteNames(); PackageSuites = GeneratePackageSuites(platform); // Serialize the suite inside this SO if (PackageSuites != null) { int count = PackageSuites.Count; for (int i = 0; i < count; i++) { PackageSuites[i].Serialize(this); } } // Check if after regenerating suites CurrentSettingsSuiteName is still a valid suite, if so then // we don't need to change it, otherwise we just set the first suite as the current one PackageSuite suite = GetPackageSuite(currentPackageSuiteName); if (suite == null) { if (PackageSuites != null && PackageSuites.Count > 0) { currentPackageSuiteName = PackageSuites[0].Name; } else { currentPackageSuiteName = null; } } CurrentPackageSuiteName = currentPackageSuiteName; HtAssetDatabase.RefreshObject(this); } } private string GetGameVersionDirectoryPath() { string directoryPath = AssetDatabase.GetAssetPath(this); return HtAssetDatabase.UnityPathGetDirectoryName(directoryPath); } public string GetGameVersionPath() { string directoryPath = GetGameVersionDirectoryPath(); string path = $"{directoryPath}/{GAME_VERSION_FILENAME}"; return path; } public VersionProperty GameVersionProperty { get { if (m_gameVersionProperty == null) { string path = GetGameVersionPath(); m_gameVersionProperty = AssetDatabase.LoadAssetAtPath(path, typeof(VersionProperty)) as VersionProperty; // Create gameVersionProperty ScriptableObject if (m_gameVersionProperty == null) { string directoryPath = GetGameVersionDirectoryPath(); m_gameVersionProperty = (VersionProperty)HtUnityEditorFactory.CreateScriptableObject(typeof(VersionProperty), directoryPath, GAME_VERSION_FILENAME_NO_EXT); m_gameVersionProperty.Version = new HotelVersion("0.0.1"); m_gameVersionProperty.VersionCode = 1; } } return m_gameVersionProperty; } } internal string GetBuildSettingsPath() { string directoryPath = AssetDatabase.GetAssetPath(this); directoryPath = HtAssetDatabase.UnityPathGetDirectoryName(directoryPath); return $"{directoryPath}/{BUILD_SETTINGS_FILENAME}"; } internal BuildSettings BuildSettings { get { if (m_buildSettings == null) { string directoryPath = AssetDatabase.GetAssetPath(this); directoryPath = HtAssetDatabase.UnityPathGetDirectoryName(directoryPath); string filename = BUILD_SETTINGS_FILENAME_NO_EXT; string path = GetBuildSettingsPath(); m_buildSettings = AssetDatabase.LoadAssetAtPath(path, typeof(BuildSettings)) as BuildSettings; // Create buildSettings ScriptableObject if (m_buildSettings == null) { m_buildSettings = (BuildSettings)HtUnityEditorFactory.CreateScriptableObject(typeof(BuildSettings), directoryPath, filename); } } return m_buildSettings; } } public void AddPathsToGitIgnore(HashSet paths) { if (CanPerform()) { foreach (PackageSuite packageSuite in PackageSuites) { if (packageSuite != null) { HashSet list = packageSuite.GetAllPropertiesOfType(); if (list != null) { AssetBuildProperty assetProperty; foreach (PackageProperty p in list) { assetProperty = (AssetBuildProperty)p; if (assetProperty != null) { AssetManager.AddOrderToGitIgnore(assetProperty.Order, paths); assetProperty.AddPathsToGitIgnore(paths); } } } } } } } public void ApplyToBuildSuite(EPlatform platform, BuildSuite buildSuite) { if (CanPerform()) { PackageSuite packageSuite = GetPackageSuiteAppliedForBuild(platform); PackageManager.Log($"PreBuilding packageSuite: {Debug.FormatTextInUserContext(packageSuite.Name)} for buildTarget: {Debug.FormatTextInUserContext(platform.ToString())}"); buildSuite.Setup(platform, GameVersionProperty?.Version); packageSuite.ApplyPackageSuiteSetup(platform, PackageSuiteBuilder.CreatePackageProperty); packageSuite.Apply(buildSuite); if (GameVersionProperty != null) { GameVersionProperty.Apply(buildSuite); } } } public List BuildPlayerPackageProperties(EPlatform platform) { List returnValue = new List(); if (CanPerform()) { // Add player properties from current package suite PackageSuite packageSuite = GetPackageSuiteAppliedForBuild(platform); if (packageSuite != null) { packageSuite.AddPackagePropertyPlayer(returnValue); } // Add a player property for the game version if (GameVersionProperty != null) { GameVersionProperty.AddPackagePropertyPlayer(returnValue); } // Add a player property for the pipeline job settings PipelineJob pipelineJob = PackageSuiteBuilder.GetPipelineJob(); Debug.EditorLog($"Pipeline job information: {pipelineJob}"); PlayerPipelineJobProperty pipelineJobProperty = CreateInstance(); pipelineJobProperty.name = "PlayerPackageProperty"; pipelineJobProperty.PipelineJob = pipelineJob; returnValue.Add(pipelineJobProperty); } return returnValue; } public void GenerateResources(EPlatform platform) { if (CanPerform()) { PackageSuite packageSuite = GetPackageSuiteAppliedForBuild(platform); if (packageSuite != null) { packageSuite.GenerateResources(); AssetDatabase.Refresh(); } } } private void ResetPackageSuiteNames() { m_packageSuiteNames = null; } private PackageSuite GetPackageSuite(string name) { PackageSuite returnValue = null; if (m_packageSuites != null && !string.IsNullOrEmpty(name)) { int count = m_packageSuites.Count; for (int i = 0; i < count && returnValue == null; ++i) { if (m_packageSuites[i] != null && m_packageSuites[i].Name == name) { returnValue = m_packageSuites[i]; } } } return returnValue; } public PackageSuite GetCurrentPackageSuite() { return GetPackageSuite(m_currentPackageSuiteName); } public int GetCurrentPackageSuiteIndex() { int returnValue = -1; if (PackageSuiteNames != null) { int count = PackageSuiteNames.Length; for (int i = 0; i < count && returnValue == -1; ++i) { if (PackageSuiteNames[i] == CurrentPackageSuiteName) { returnValue = i; } } } return returnValue; } public void BumpVersion() { if (GameVersionProperty != null) { GameVersionProperty.BumpPatch(); HtAssetDatabase.RefreshObject(GameVersionProperty); AssetDatabase.SaveAssets(); } } #region build /// Build arguments passed in to modify some properties of the suite to build. They are stored so we can recreate m_packageSuiteAppliedForBuild when it gets lost after recompiling. [SerializeField] private string m_rawBuildArguments; private string RawBuildArguments { get { return m_rawBuildArguments; } set { m_rawBuildArguments = value; } } /// Whether or not the build is manual. It's stored so we can recreate m_packageSuiteAppliedForBuild when it gets lost after recompiling. [SerializeField] private bool m_buildIsManual; private bool BuildIsManual { get { return m_buildIsManual; } set { m_buildIsManual = value; } } private PackageSuite m_packageSuiteAppliedForBuild = null; private PackageSuite GetPackageSuiteAppliedForBuild(EPlatform platform) { Dictionary buildArguments = BuildArgumentsStringToDict(RawBuildArguments); if (!buildArguments.TryGetValue(PackageSuiteBuilder.PROPERTY_TYPE_SUITE, out string packageSuiteName)) { packageSuiteName = CurrentPackageSuiteName; } //PackageManager.Log($"GetPackageSuiteAppliedForBuild CurrentPackageSuiteName = {CurrentPackageSuiteName} BuildArguments = {RawBuildArguments} packageSuiteName = {packageSuiteName}"); // Filter out the arguments that suites don't care about FilterOutBuildArguments(buildArguments); // We use the suite ScriptableObject for manual builds so consumer can test any changes she might have made in the ScriptableObjects if (BuildIsManual) { m_packageSuiteAppliedForBuild = GetPackageSuite(packageSuiteName); } else { m_packageSuiteAppliedForBuild = GeneratePackageSuite(platform, packageSuiteName, buildArguments); } return m_packageSuiteAppliedForBuild; } public bool IsPackageSuiteAppliedForBuildEmpty() { return m_packageSuiteAppliedForBuild == null; } protected string GetBuildFileNameWithoutExtension(PackageSuite packageSuite) { string returnValue = $"{packageSuite.name}"; if (CanPerform()) { string gameVersion = GameVersionProperty == null ? null : GameVersionProperty.VersionAsString; returnValue = PackageSuiteBuilder.GetBuildFileNameWithoutExtension(packageSuite, gameVersion, PackageSuiteBuilder.GetPipelineJob()); } return returnValue; } public void ApplyPackageSuiteForBuild(EPlatform platform, BuildSuite buildSuite) { if (CanPerform() && buildSuite != null) { buildSuite.Clear(); ApplyToBuildSuite(platform, buildSuite); buildSuite.CompileBuildProperties(); } } private Dictionary BuildArgumentsStringToDict(string value) { Dictionary returnValue = new Dictionary(); if (!string.IsNullOrEmpty(value)) { string[] tokens; string[] pairs = value.Split(','); foreach (string pair in pairs) { tokens = pair.Trim().Split(':'); returnValue.Add(tokens[0], tokens[1]); } } return returnValue; } private string BuildArgumentsDictToString(Dictionary value) { string returnValue = ""; if (value != null) { foreach (KeyValuePair pair in value) { if (returnValue.Length > 0) { returnValue += ","; } returnValue += $"{pair.Key}:{pair.Value}"; } } return returnValue; } /// /// Filter BuildArguments to skip arguments that suites don't care about. /// private void FilterOutBuildArguments(Dictionary buildArguments) { // PROPERTY_TYPE_SUITE is not added to arguments so that it is not considered a property of the package suite setup to modify if (buildArguments != null && buildArguments.ContainsKey(PackageSuiteBuilder.PROPERTY_TYPE_SUITE)) { _ = buildArguments.Remove(PackageSuiteBuilder.PROPERTY_TYPE_SUITE); } } public void BuildPlayer(BuildSuite buildSuite, EPlatform platform, Dictionary arguments, bool isManualBuild, bool isFullBuild, bool useDefaultPath, BuildBatchSettings batchSettings) { BuildIsManual = isManualBuild; RawBuildArguments = BuildArgumentsDictToString(arguments); // Save this scriptable object to store the variables above so we can resume building process after recompiling HtAssetDatabase.RefreshObject(this); string packageSuiteName = CurrentPackageSuiteName; if (arguments != null) { if (arguments.ContainsKey(PackageSuiteBuilder.PROPERTY_TYPE_SUITE)) { packageSuiteName = arguments[PackageSuiteBuilder.PROPERTY_TYPE_SUITE]; } } // Filter out keys that suite don't care about FilterOutBuildArguments(arguments); PackageSuite packageSuite = GetPackageSuiteAppliedForBuild(platform); if (packageSuite == null) { PackageManager.LogError($"ERROR: Build interrupted: No packageSuite defined for {Debug.FormatTextInCodeContext(packageSuiteName)}"); } else { PackageSuiteBuilder.ProcessBuildParameters(arguments); ApplyPackageSuiteForBuild(platform, buildSuite); BuildTarget buildTarget = PlatformUtils.EPlatformToBuildTarget(platform); string buildDestination; string buildName; string buildFolder; if (useDefaultPath) { string folder = PlatformUtils.EPlatformToKey(platform); buildName = GetBuildFileNameWithoutExtension(packageSuite); buildFolder = $"Builds/{folder}"; string fileName = (platform == EPlatform.iOS) ? "xcode" : buildName; buildDestination = $"{buildFolder}/{fileName}"; } else { buildDestination = batchSettings.location; string[] tokens = buildDestination.Split('/'); buildName = tokens[tokens.Length - 1]; buildFolder = buildDestination.Replace($"/{buildName}", ""); } if (Directory.Exists(buildFolder) && isFullBuild) { Directory.Delete(buildFolder, true); } string extension = null; switch (buildTarget) { case BuildTarget.Android: UnityBuildProperty unityBuildProperty = buildSuite.UnityBuildProperty; bool useAbb = EditorUserBuildSettings.buildAppBundle; if (unityBuildProperty != null && unityBuildProperty.IsBuildAppBundle != PackageProperty.EBoolean.AsUnitySettings) { useAbb = unityBuildProperty.IsBuildAppBundle == PackageProperty.EBoolean.True; } extension = useAbb ? ".aab" : ".apk"; break; case BuildTarget.StandaloneWindows64: extension = ".exe"; break; } // Add extension if there's one and it's not already part of the name and it's not a full build if (!string.IsNullOrEmpty(extension) && !buildDestination.EndsWith(extension) && !BuildSettings.ExportProject) { buildDestination += extension; } batchSettings.location = buildDestination; batchSettings.Save(); string message = $"Building for platform:{platform} buildTarget:{Debug.FormatTextInUserContext(buildTarget.ToString())},"; message += $" Suite: {Debug.FormatTextInUserContext(packageSuiteName)},"; message += $" Setup: {Debug.FormatTextInUserContext(packageSuite.PackageSuiteSetup == null ? "" : packageSuite.PackageSuiteSetup.GetSelectedValuesAsString(true))}"; message += $" in {Debug.FormatTextInUserContext(buildDestination)} ..."; PackageManager.Log(message); WriteBuildSettings(buildFolder, buildName, buildSuite, AddressablesBuildSettings.GetSettings(false)); /* // Commented out because running this OS command from Unity Editor is not working // Generate ipa string command = "./agent_build.sh iOS master_prod"; StreamReader output = OSUtility.ExecuteCommand(command); string line; // Read and display lines from the file until the end of // the file is reached. while ((line = output.ReadLine()) != null) { PackageManager.Log(line); } */ } } private void WriteBuildSettings(string unityBuildFolder, string buildName, BuildSuite buildSuite, AddressablesBuildSettings addressablesBuildSettings) { string platformBuildFolder = HtAssetDatabase.UnityPathToPlatformPath(unityBuildFolder); if (!Directory.Exists(platformBuildFolder)) { _ = Directory.CreateDirectory(platformBuildFolder); } platformBuildFolder = HtAssetDatabase.PlatformPathCombine(platformBuildFolder, "settings"); if (Directory.Exists(platformBuildFolder)) { Directory.Delete(platformBuildFolder, true); } _ = Directory.CreateDirectory(platformBuildFolder); UnityBuildProperty unityBuildProperty = buildSuite.UnityBuildProperty; // Bundle id WriteSettingsFile(platformBuildFolder, "bundle_id.txt", unityBuildProperty.BundleId); // Build Name WriteSettingsFile(platformBuildFolder, "build_name.txt", buildName); // Build Version WriteSettingsFile(platformBuildFolder, "build_version.txt", unityBuildProperty.Version); // Development team WriteSettingsFile(platformBuildFolder, "development_team.txt", unityBuildProperty.AppleDeveloperTeamID); // Provisioning id WriteSettingsFile(platformBuildFolder, "provisioning_id.txt", unityBuildProperty.IOSManualProvisioningProfileID); // Provisioning type WriteSettingsFile(platformBuildFolder, "provisioning_type.txt", unityBuildProperty.IOSManualProvisioningProfileTypeKey); // Deploy symbols WriteSettingsFile(platformBuildFolder, "deploy_symbols.txt", unityBuildProperty.DeploySymbols.ToString()); // Keystore Vault id WriteSettingsFile(platformBuildFolder, "keystore_vault_id.txt", unityBuildProperty.KeystoreVaultId); // Remote host settings if (addressablesBuildSettings != null) { WriteSettingsFile(platformBuildFolder, "remote_host_build_path.txt", addressablesBuildSettings.HostRemoteBuildPath); WriteSettingsFile(platformBuildFolder, "remote_host_load_path.txt", addressablesBuildSettings.HostRemoteLoadPath); WriteSettingsFile(platformBuildFolder, "remote_host_url_suffix.txt", addressablesBuildSettings.HostRemoteUrlSuffix); WriteSettingsFile(platformBuildFolder, "remote_host_bucket_id.txt", addressablesBuildSettings.HostRemoteBucketId); } } private void WriteSettingsFile(string platformBuildFolder, string fileName, string value) { string platformPath = HtAssetDatabase.PlatformPathCombine(platformBuildFolder, fileName); StreamWriter sw = File.CreateText(platformPath); sw.WriteLine(value); sw.Close(); } #endregion } }