using System; using System.Collections.Generic; using UnityEngine; using Ubisoft.Hotel.Logger.Editor; using Ubisoft.Hotel.Package; using Ubisoft.Hotel.Package.Editor; namespace Ubisoft.Hotel.PackageManager.Editor { public abstract class PackageSuiteBuilder { private readonly Dictionary m_packages; private readonly List m_packageSuiteNames; private readonly string m_buildNamePrefix; private readonly bool m_addPipelineInfoAsSufix; /// /// Create a new PackageSuiteBuilder /// /// Dictionary containing pairs with the packages that PackageManager needs to handle. /// List of package suite names. /// Prefix for the file name of the artifact generated as a result of building. /// Add pipeline information (branchName_buildNumber) to the file name of the artifact generated as a result of building. public PackageSuiteBuilder(Dictionary packages, List packageSuiteNames, string buildNamePrefix = "", bool addPipelineInfoAsSufix = false) { m_packages = packages; m_packageSuiteNames = packageSuiteNames; m_buildNamePrefix = buildNamePrefix; m_addPipelineInfoAsSufix = addPipelineInfoAsSufix; // Former versions of HPM required to create PackageManager.ASSETS_HOTEL_EDITOR_PLUGGABLE_PACKAGES // This is not required anymore but we need to make sure that it will be deleted from consumer's space RemovePluggablePackages(); } private void RemovePluggablePackages() { if (HtAssetDatabase.ExistsFile(PackageManager.ASSETS_HOTEL_EDITOR_PLUGGABLE_PACKAGES)) { HtAssetDatabase.DeleteFile(PackageManager.ASSETS_HOTEL_EDITOR_PLUGGABLE_PACKAGES, false); } } public virtual bool IsEnabled() { return true; } public Dictionary GetPackagesHandledByConsumer() { return m_packages; } public List GetPackageSuiteNames() { return m_packageSuiteNames; } /// /// Process parameters passed in to BuildPlayer. This method can be used by consumers to process game specific parameters. /// /// public virtual void ProcessBuildParameters(Dictionary parameters) { } /// /// Generate a PackageSuite object given a package suite name and a collection of pairs (,) that /// must override the setup for the PackageSuite called packageSuiteName /// /// /// Platform to generate suites for. /// /// Name of the PackageSuite object to create. /// /// A dictionary containing a collection of pairs (,), which are meant to override /// the values defined for the setup of packageSuiteName. If null is passed in then the PackageSuite object /// corresponding to packageSuiteName will be returned as it is, with no properties overridden. /// This parameter is used to keep the amount of PackageSuite definitions manageable by letting you reuse the same PackageSuite /// definition when you only want to change some properties such as the scheme (debug, release, prerelease) or the network environment (dev, stage, prod). /// This parameter is usually filled in from CLI, typically by the CI/CD pipeline. /// /// A PackageSuite object containing a set of properties that configure the build for a particular situation. public virtual PackageSuite GeneratePackageSuite(EPlatform platform, string packageSuiteName, Dictionary overridingProperties = null) { PackageSuite returnValue = null; if (m_packageSuiteNames.Contains(packageSuiteName)) { PackageSuiteSetup packageSuiteSetup = GetPackageSuiteSetup(packageSuiteName); if (packageSuiteSetup != null) { // Create a PackageSuite object returnValue = CreatePackageSuite(packageSuiteName); packageSuiteSetup.Override(overridingProperties); Type type; int valueIndex; foreach (string key in packageSuiteSetup.Keys) { type = PackageSuiteSetup.GetTypeByKey(key); valueIndex = packageSuiteSetup.GetSelectedValueByKey(key); PackageProperty packageProperty = CreatePackageProperty(platform, packageSuiteName, type, packageSuiteSetup); if (packageProperty == null) { string typeValue = packageSuiteSetup.GetSelectedValueByKeyAsString(key); string msg = $"ERROR: No PropertyPackage definition found for property type: "; msg += $"{Debug.FormatTextInUserContext(key)} and value: {Debug.FormatTextInUserContext(typeValue)} "; msg += $"when generating {Debug.FormatTextInUserContext(packageSuiteName)} package suite. "; string methodName = $"{AppSpaceBuildSuiteProvider.CONSUMER_PACKAGE_SUITE_BUILDER_SHORT_TYPENAME}.CreatePackageProperty()"; msg += $" Make sure that the method {Debug.FormatTextInUserContext(methodName)} has a definition for such property "; methodName = $"{AppSpaceBuildSuiteProvider.CONSUMER_PACKAGE_SUITE_BUILDER_SHORT_TYPENAME}.GetPackageSuiteSetup()"; msg += $" otherwise delete this property from {Debug.FormatTextInUserContext(methodName)} if you don't need it"; Debug.EditorLogError(msg); } else { packageSuiteSetup.RegisterProperty(type, valueIndex, packageProperty); returnValue.AddProperty(packageProperty); } } returnValue.PackageSuiteSetup = packageSuiteSetup; } } else { Debug.EditorLogError($"ERROR: No package suite found for name: {packageSuiteName}"); } return returnValue; } /// /// Create a PackageSuiteSetup object that contains the values for the setup properties defined for the suite called packageSuiteName /// /// Name of the suite to create the PackageSuiteSetup object for. /// A PackageSuiteSetup object that contains the values for the setup properties defined for the suite called packageSuiteName protected virtual PackageSuiteSetup GetPackageSuiteSetup(string packageSuiteName) { // Default implementation provided. This method hasn't been declared as abstract because this approach is not mandatory. // Consumers can implement GeneratePackageSuite() instead. return new PackageSuiteSetup(null, null); } /// /// Return the file name, without the extension, to use for the artifact generated when building. /// /// PackageSuite object that contains the properties to apply. /// Game version to build. /// CICD pipeline job information. Populated ONLY for CICD builds. /// public virtual string GetBuildFileNameWithoutExtension(PackageSuite packageSuite, string gameVersion, PipelineJob pipelineJob) { string prefix = string.IsNullOrEmpty(m_buildNamePrefix) ? "" : $"{ m_buildNamePrefix}_"; string returnValue = $"{prefix}{packageSuite.name}"; if (!string.IsNullOrEmpty(gameVersion)) { returnValue += $"_{gameVersion}"; } if (m_addPipelineInfoAsSufix) { // Use encoded branch name instead of plain branch name in order to avoid trouble when copying the build file artifact around string branchName = pipelineJob.EncodedBranchName; if (!string.IsNullOrEmpty(branchName)) { returnValue += $"_{branchName}"; } string buildNumber = pipelineJob.BuildNumber; if (!string.IsNullOrEmpty(buildNumber)) { returnValue += $"_{buildNumber}"; } } return returnValue; } public PipelineJob GetPipelineJob() { EnvironmentVariables environmentVariables = GetEnvironmentVariables(); return new PipelineJob(environmentVariables.BranchName, environmentVariables.BuildNumber); } public EnvironmentVariables GetEnvironmentVariables() { return EnvironmentVariables.GetEnvironmentVariables(GetDefaultEnvironmentVariables()); } protected virtual EnvironmentVariables GetDefaultEnvironmentVariables() { return new EnvironmentVariables(); } #region property // ----------------------------------------------------------------------------------------------------------------------------------------------------- // Property Types // ----------------------------------------------------------------------------------------------------------------------------------------------------- public const string PROPERTY_TYPE_SUITE = "Suite"; private PackageSuite CreatePackageSuite(string packageSuiteName) { string name = GetPropertyName(packageSuiteName, "", ""); PackageSuite returnValue = PackageSuite.CreateInstanceWithParams(name); return returnValue; } /// /// Create a PackageProperty object that contains all properties required by the setup type passed in. /// /// Platform to build for. /// Name of this property's parent. Used to create this property's name so it conveys context. /// Setup type to create the PackageProperty object for. /// PackageSuiteSetup object that contains all setup property values since some setup properties /// need to know about other setup properties to create the appropriate properties. /// public virtual PackageProperty CreatePackageProperty(EPlatform platform, string parentName, Type type, PackageSuiteSetup packageSuiteSetup) { return null; } /// /// Create a PackagePropertyComposite object that can be used to nest other properties inside. /// /// Name of this property's parent. Used to create this property's name so it conveys context. /// Property name. /// public PackagePropertyComposite CreatePackagePropertyComposite(string parentName, string propertyName) { PackagePropertyComposite returnValue = ScriptableObject.CreateInstance(); returnValue.name = GetPropertyName(parentName, "composite", propertyName); return returnValue; } /// /// Create a UnityBuildProperty object that can be used to set values in Unity PlayerSettings and BuildSettings. /// /// Name of this property's parent. Used to create this property's name so it conveys context. /// Property name. /// public UnityBuildProperty CreateUnityBuildProperty(string parentName, string propertyName) { return UnityBuildProperty.CreateInstanceWithParams(GetPropertyName(parentName, "unity", propertyName)); } /// /// Create a SchemeProperty object that can be used to set some values that modify the suite purpose (debug, release, ...) /// /// Name of this property's parent. Used to create this property's name so it conveys context. /// Property name. /// public SchemeProperty CreateSchemeProperty(string parentName, string propertyName) { return SchemeProperty.CreateInstanceWithParams(GetPropertyName(parentName, "scheme", propertyName)); } /// /// Create a LoggerProperty object that can be used to customise Hotel Logger package behaviour /// /// Name of this property's parent. Used to create this property's name so it conveys context. /// Property name. /// Root Unity path ("Assets/...") to the folder that contains all ChannelsPreset objects. /// Name of the file in channelslPresetRootUnityPath that was copied into this object /// public LoggerProperty CreateLoggerProperty(string parentName, string propertyName, string channelslPresetRootUnityPath = null, string channelsPresetName = null) { return LoggerProperty.CreateInstanceWithParams(GetPropertyName(parentName, "logger", propertyName), channelslPresetRootUnityPath, channelsPresetName); } /// /// Create a PlatformBuildProperty_iOS object that can be used to set some iOS native properties such as frameworks, info.plist properties, ... /// /// Name of this property's parent. Used to create this property's name so it conveys context. /// Property name. /// public PlatformBuildProperty_iOS CreatePlatformBuildProperty_iOS(string parentName, string propertyName) { return PlatformBuildProperty_iOS.CreateInstanceWithParams(GetPropertyName(parentName, "ios", propertyName)); } /// /// Create a AssetBuildProperty object that can be used move some assets stored in Hotel/Assets folder into Assets folder so they can /// be included in the build. /// /// Name of this property's parent. Used to create this property's name so it conveys context. /// Property name. /// public AssetBuildProperty CreateAssetBuildProperty(string parentName, string propertyName) { return AssetBuildProperty.CreateInstanceWithParams(GetPropertyName(parentName, "assets", propertyName)); } /// /// Create a FirebaseBuildProperty object that can be used to ease the management of the Firebase Manifest file along with /// all files automatically generated by Firebase Editor component. /// /// Unity path where the Firebase Manifest is located inside a 'Hotel/Assets' folder. /// Pass in true is you want to be able to report all events to Firebase Analytics /// when Hotel FirebaseAnalytics package is installed. Otherwise only 'install' event (first_open) /// will be reported. Please, get Legal's approval befor enabling Analytics full mode,. /// public FirebaseBuildProperty CreateFirebaseBuildProperty(string parentName, string propertyName, string srcManifestUnityPath, bool enableAnalyticsFullMode = false) { return FirebaseBuildProperty.CreateInstanceWithParams(GetPropertyName(parentName, "firebase", propertyName), srcManifestUnityPath, enableAnalyticsFullMode); } /// /// Create a SocialBuildProperty object that can be used to ease the management of Orion SocialCore.PlayGames package. /// /// Unity path where game-ids.xml is located inside a 'Hotel/Assets' folder. /// public SocialCoreBuildProperty CreateSocialCoreBuildProperty(string parentName, string propertyName, string srcGamesIdsUnityPath) { return SocialCoreBuildProperty.CreateInstanceWithParams(GetPropertyName(parentName, "socialCore", propertyName), srcGamesIdsUnityPath); } /// /// Create an AppNameProperty object that can be used to localise the app name. /// /// Name of this property's parent. Used to create this property's name so it conveys context. /// Property name. /// public AppNameProperty CreateAppNameProperty(string parentName, string propertyName) { return AppNameProperty.CreateInstanceWithParams(GetPropertyName(parentName, "appName", propertyName)); } /// /// Create an AddressablesBuildProperty object that can be used to customise the way Addressables behave. /// /// Name of this property's parent. Used to create this property's name so it conveys context. /// Property name. /// .When true remote asset bundles are treated as if they were local. /// When true remote asset bundles are downloaded from the store CDN. Otherwise the app is responsible /// for storing the asset bundles in their own CDN. /// Use this mode when you don't want to push remote asset bundles to a CDN. /// .Use this parameter only when useStoreCdn is true and the app is using /// Ubisoft Cloud to store its remote asset bundles. /// public AddressablesBuildProperty CreateAddressablesBuildProperty(string parentName, string propertyName, bool useAllInstallTime = false, bool useStoreCdn = false, AddressablesBuildProperty.RemoteHostSettings remoteHostSettings = null) { AddressablesBuildProperty returnValue = AddressablesBuildProperty.CreateInstanceWithParams(GetPropertyName(parentName, "addressables", propertyName)); returnValue.UseAllInstallTime = useAllInstallTime ? PackageProperty.EBoolean.True : PackageProperty.EBoolean.False; returnValue.UseStoreCdn = useStoreCdn ? PackageProperty.EBoolean.True : PackageProperty.EBoolean.False; returnValue.HostSettings = remoteHostSettings; return returnValue; } /// /// Add a UpmBuildProperty object to property to add a particular implementation of a particular version of a package. /// /// PackagePropertyComposite object where to add this package to. /// Package name to add (i.e. "com.ubisoft.hotel.console") /// Package version to add (i.e. "0.0.1") /// Implementation of the package to select in case the package supports several implementations. /// Hotel packages might have different implementations available. Look up the package documentation to find out what implementations are available. /// Use PackageDefinition.EImplementation.Prod for non-Hotel packages and for Hotel packages which documentation don't mention alternative implementations. public bool AddPackageProperty(PackagePropertyComposite property, string packageName, string packageVersion = null, PackageDefinition.EImplementation implementation = PackageDefinition.EImplementation.Prod) { bool returnValue = m_packages.ContainsKey(packageName); if (returnValue) { // Use default version if packageVersion is not provided if (string.IsNullOrEmpty(packageVersion)) { packageVersion = m_packages[packageName]; } property.AddPackage(packageName, packageVersion, implementation); } return returnValue; } public string GetPropertyName(string parentName, string propertyType, string propertyName) { string returnValue = ""; if (!string.IsNullOrEmpty(parentName)) { returnValue = parentName; } if (!string.IsNullOrEmpty(propertyType)) { if (returnValue.Length > 0) { returnValue += ":"; } returnValue += propertyType; } if (!string.IsNullOrEmpty(propertyName)) { if (returnValue.Length > 0) { returnValue += ":"; } returnValue += propertyName; } return returnValue; } #endregion } }