// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Text; using System.Xml.Serialization; using System.IO; using EpicGames.Core; using UnrealBuildTool; using AutomationTool; using System.Linq; using System.Text.RegularExpressions; namespace Turnkey { public class CopySource { // @todo turnkey: for some reason the TypeConverter stuff setup in UnrealTargetPlatform isn't kicking in to convert from string to UTP, so do it a manual way [XmlAttribute("HostPlatform")] public string HostPlatformString = null; [XmlIgnore] public UnrealTargetPlatform? Platform = null; [XmlAttribute] // if this is set, then the actual copy operation will use this,m public string CopyOverride = null; [XmlText] public string Operation; /// /// Needs a parameterless constructor for Xml deserialization /// public CopySource() { } public CopySource(CopySource Other) { HostPlatformString = Other.HostPlatformString; Platform = Other.Platform; CopyOverride = Other.CopyOverride; Operation = Other.Operation; } public string GetOperation() { return TurnkeyUtils.ExpandVariables(CopyOverride != null ? CopyOverride : Operation); } public void PostDeserialize() { if (!string.IsNullOrEmpty(HostPlatformString)) { Platform = UnrealTargetPlatform.Parse(HostPlatformString); } // perform early expansion, important for $(ThisManifestDir) which is valid only during deserialization // but don't use any other variables yet, because UAT could have bad values in Environment CopyOverride = TurnkeyUtils.ExpandVariables(CopyOverride, bUseOnlyTurnkeyVariables: true)?.Trim(); Operation = TurnkeyUtils.ExpandVariables(Operation, bUseOnlyTurnkeyVariables: true)?.Trim(); } public bool ShouldExecute() { return !Platform.HasValue || Platform == HostPlatform.Current.HostEditorPlatform; } public bool NeedsExpansion(out string OperationForExpansion) { const string Prefix = "fileexpansion:"; bool bNeedsExpansion = ShouldExecute() && Operation.StartsWith(Prefix, StringComparison.InvariantCultureIgnoreCase); // get the bit after the expansion tag OperationForExpansion = bNeedsExpansion ? Operation.Substring(Prefix.Length) : null; return bNeedsExpansion; } public bool NeedsExpansion() { return NeedsExpansion(out _); } } public class FileSource { public enum SourceType { BuildOnly, AutoSdk, RunOnly, Full, Flash, Misc, Build, }; #region Fields [XmlElement("Platform")] public string PlatformString = null; [XmlIgnore] private List Platforms; [XmlElement("Type")] public string TypeString = null; [XmlIgnore] public SourceType Type = SourceType.Full; [XmlIgnore] public string SpecificSDKType = null; [XmlElement("Source")] public CopySource[] Sources = null; public string Version = null; public string Name = null; public string AllowedFlashDeviceTypes = null; // Project for Build types to filter on public string Project; public string BuildPlatformEnumerationSuffix; public static FileSource CreateCodeSpecifiedSource(string Name, string Version, UnrealTargetPlatform Platform) { FileSource NewSource = new FileSource(); NewSource.PlatformString = Platform.ToString(); NewSource.Platforms = new List() { Platform }; NewSource.Name = Name; NewSource.Version = Version; NewSource.Sources = new CopySource[] { }; return NewSource; } public FileSource CloneForExpansion(string NewValue, Action Setter) { FileSource Clone = new FileSource(); Clone.PlatformString = PlatformString; Clone.TypeString = TypeString; Clone.Version = Version; Clone.Name = Name; Clone.AllowedFlashDeviceTypes = AllowedFlashDeviceTypes; Clone.Project = Project; Clone.BuildPlatformEnumerationSuffix = BuildPlatformEnumerationSuffix; if (NewValue != null) { Setter(Clone, NewValue); } if (Sources != null) { List NewSources = new List(); foreach (CopySource Source in Sources) { // if we want to execute the installer, then copy it over if (Source.ShouldExecute()) { NewSources.Add(new CopySource(Source)); } } Clone.Sources = NewSources.ToArray(); } Clone.PostDeserialize(); return Clone; } #endregion // // // static string[] ExtendedVariables = // { // "Project", // }; // // // [Flags] // public enum LocalAvailability // { // None = 0, // AutoSdk_VariableExists = 1, // AutoSdk_ValidVersionExists = 2, // AutoSdk_InvalidVersionExists = 4, // // InstalledSdk_BuildOnlyWasInstalled = 8, // InstalledSdk_ValidVersionExists = 16, // InstalledSdk_InvalidVersionExists = 32, // Platform_ValidHostPrerequisites = 64, // Platform_InvalidHostPrerequisites = 128, // } // // // public bool NeedsFileExpansion() { return Sources.Any(x => x.NeedsExpansion()); } public bool SupportsPlatform(UnrealTargetPlatform Platform) { return Platforms.Contains(Platform); } public bool IsSdkType() { return Type == SourceType.BuildOnly || Type == SourceType.AutoSdk || Type == SourceType.RunOnly || Type == SourceType.Full || Type == SourceType.Flash; } public bool IsVersionValid(UnrealTargetPlatform Platform, DeviceInfo Device=null) { // Non-Sdk types are always valid, although this isn't meant for them if (!IsSdkType()) { return true; } if (!SupportsPlatform(Platform)) { return false; } // if (!CheckForExtendedVariables(false)) // { // // user canceled a choice // return false; // } // if (Type == SourceType.Flash) { UEBuildPlatformSDK SDK = UEBuildPlatformSDK.GetSDKForPlatform(Platform.ToString()); // if we are specific to any particular device type, use one of them to pass to the validation function (different device types may have different SDK versions) string DeviceTypeHint = string.IsNullOrEmpty(AllowedFlashDeviceTypes) ? null : AllowedFlashDeviceTypes.Split(",").First(); bool bIsValid = SDK.IsSoftwareVersionValid(Version, DeviceTypeHint); // if we were passed a device, also check if this Sdk is valid for that device if (Device != null) { bIsValid &= TurnkeyUtils.IsValueValid(Device.Type, AllowedFlashDeviceTypes, AutomationTool.Platform.GetPlatform(Platform)); } return bIsValid; } else { string SDKTypeHint = SpecificSDKType ?? Type.ToString(); return UEBuildPlatformSDK.GetSDKForPlatform(Platform.ToString()).IsVersionValid(Version, SDKTypeHint); } } public UnrealTargetPlatform[] GetPlatforms() { return Platforms.ToArray(); } public string GetCopySourceOperation() { // use the one matching copy source for this host platform, or null if there isn't one return Sources.FirstOrDefault(x => x.ShouldExecute())?.GetOperation(); } static public FileSource ChooseBest(List Sdks, UEBuildPlatformSDK PlatformSDK) { if (Sdks == null) { return null; } FileSource Best = null; UInt64 MainVersionInt; PlatformSDK.TryConvertVersionToInt(PlatformSDK.GetMainVersion(), out MainVersionInt); foreach (FileSource Sdk in Sdks) { if (Best == null) { Best = Sdk; } else { // bigger version is better UInt64 ThisVersion, BestVersion; if (PlatformSDK.TryConvertVersionToInt(Sdk.Version, out ThisVersion) && PlatformSDK.TryConvertVersionToInt(Best.Version, out BestVersion)) { // there is no MainVersion for flash (yet), so don't take it into account if (Sdk.Type == SourceType.Flash) { if (ThisVersion > BestVersion) { Best = Sdk; } } // always use MainVersion, otherwise, use largest version if MainVersion hasn't been found else if (ThisVersion == MainVersionInt || (BestVersion != MainVersionInt && ThisVersion > BestVersion)) { Best = Sdk; } } } } return Best; } static public FileSource FindMatchingSdk(AutomationTool.Platform Platform, SourceType[] TypePriority, bool bSelectBest, string DeviceType = null, string CurrentSdk = null, string SpecificType = null) { UEBuildPlatformSDK SDK = UEBuildPlatformSDK.GetSDKForPlatform(Platform.PlatformType.ToString()); foreach (SourceType Type in TypePriority) { List Sdks = TurnkeyManifest.FilterDiscoveredFileSources(Platform.IniPlatformType, Type); // check valid versions/device types // Sdks = Sdks.FindAll(x => x.Version == null || (Type == SourceType.Flash ? TurnkeyUtils.IsValueValid(x.Version, Platform.GetAllowedSoftwareVersions(), Platform) : SDK.IsVersionValid(x.Version, bForAutoSDK: x.Type == SourceType.AutoSdk))); Sdks = Sdks.FindAll(x => x.Version == null || (Type == SourceType.Flash ? SDK.IsSoftwareVersionValid(x.Version, DeviceType) : SDK.IsVersionValid(x.Version, (bSelectBest && x.Type == SourceType.AutoSdk) ? "AutoSDK" : SpecificType ?? "Sdk") )); if (DeviceType != null) { Sdks = Sdks.FindAll(x => TurnkeyUtils.IsValueValid(DeviceType, x.AllowedFlashDeviceTypes, Platform)); } if (SpecificType != null) { Sdks = Sdks.FindAll(x => TurnkeyUtils.IsValueValid(SpecificType, x.SpecificSDKType, Platform)); } // if none were found try next type if (Sdks.Count == 0) { continue; } // if one was found return it! if (Sdks.Count == 1) { return Sdks[0]; } // the best for a AutoSdk is the one that matches the desired version exactly FileSource Best = ChooseBest(Sdks, SDK); if (bSelectBest) { // select best one if requested return Best; } // find the current sdk, if any FileSource Current = null; if (CurrentSdk != null) { Current = Sdks.Find( x => x.Version == CurrentSdk); } // helper for getting the display name for the sdks as they are presented to the user string GetDisplayName( FileSource Sdk ) { string Result = Sdk.Name; if (Current != null && Sdk == Current) { Result += " (current)"; } if (Sdk == Best) { Result += " [Best Choice]"; } return Result; } // if unable to pick one automatically, ask the user int BestIndex = Sdks.IndexOf(Best); int Choice = TurnkeyUtils.ReadInputInt("Multiple Sdks found that could be installed. Please select one:", Sdks.Select(x => GetDisplayName(x)).ToList(), true, BestIndex >= 0 ? BestIndex + 1 : -1); // we take canceling to mean to try the next type if (Choice == 0) { continue; } return Sdks[Choice - 1]; } // if we never found anything, fail return null; } public bool DownloadOrInstall(UnrealTargetPlatform Platform, ITurnkeyContext TurnkeyContext, DeviceInfo Device, bool bUnattended, bool bSdkAlreadyInstalled) { // standard variables TurnkeyUtils.SetVariable("Platform", Platform.ToString()); TurnkeyUtils.SetVariable("Version", Version); if (Type == SourceType.AutoSdk) { // AutoSdk has some extra setup needed return SdkUtils.SetupAutoSdk(this, TurnkeyContext, Platform, bUnattended); } // if we have sources, make sure we can download something. if we have a null Sources, that indicates // it was code generated, and we don't need to download anything if (Sources.Length != 0) { // this will set the $(CopyOutputPath) variable which the Sdks will generally need to use string DownloadedSDK = TurnkeyContext.RetrieveFileSource(this); if (string.IsNullOrEmpty(DownloadedSDK)) { // TurnkeyContext.ReportError($"Unable to download anInstaller for {Platform}. Your Studio's TurnkeyManifest.xml file(s) may need to be fixed."); return false; } } // let the platform decide how to install Platform AutomationPlatform = AutomationTool.Platform.GetPlatform(Platform); if (!AutomationPlatform.InstallSDK(TurnkeyUtils.CommandUtilHelper, TurnkeyContext, Device, bUnattended, bSdkAlreadyInstalled)) { return false; } return AutomationPlatform.PostSDKSetup(TurnkeyContext, bUnattended); } // // // for all platforms this supports, get all devices // public DeviceInfo[] GetAllPossibleDevices() // { // List AllDevices = new List(); // foreach (var Pair in AutomationPlatforms) // { // DeviceInfo[] Devices = Pair.Value.GetDevices(); // if (Devices != null) // { // AllDevices.AddRange(Devices); // } // } // return AllDevices.ToArray(); // } // // public DeviceInfo GetDevice(UnrealTargetPlatform Platform, string DeviceName) // { // DeviceInfo[] Devices = AutomationPlatforms[Platform].GetDevices(); // if (Devices != null) // { // return Array.Find(AutomationPlatforms[Platform].GetDevices(), y => (DeviceName == null && y.bIsDefault) || (DeviceName != null && string.Compare(y.Name, DeviceName, true) == 0)); // } // return null; // } // public bool IsValid(UnrealTargetPlatform Platform, string DeviceName = null) // { // if (!SupportsPlatform(Platform)) // { // return false; // } // // if (!CheckForExtendedVariables(false)) // { // // user canceled a choice // return false; // } // // if (Type == SourceType.Flash) // { // bool bIsValid = TurnkeyUtils.IsValueValid(Version, AutomationPlatforms[Platform].GetAllowedSoftwareVersions(), AutomationPlatforms[Platform]); // // if we were passed a device, also check if this Sdk is valid for that device // // if (DeviceName != null) // { // DeviceInfo Device = GetDevice(Platform, DeviceName); // bIsValid = bIsValid && Device != null && TurnkeyUtils.IsValueValid(Device.Type, AllowedFlashDeviceTypes, AutomationPlatforms[Platform]); // } // return bIsValid; // } // else // { // return UEBuildPlatformSDK.GetSDKForPlatform(Platform.ToString()).IsVersionValid(Version, bForAutoSDK:(Type == SourceType.AutoSdk)); // } // } // // public bool Install(UnrealTargetPlatform Platform, DeviceInfo Device=null, bool bUnattended=false, bool bSkipAutoSdkSetup=false) // { // if (Type ==SourceType.AutoSdk && !bSkipAutoSdkSetup) // { // // AutoSdk has some extra setup needed // if (SdkInfo.ConditionalSetupAutoSdk(this, Platform, bUnattended)) // { // return true; // } // } // // if (!CheckForExtendedVariables(true)) // { // // user canceled a choice // return false; // } // // // standard variables // TurnkeyUtils.SetVariable("Platform", Platform.ToString()); // TurnkeyUtils.SetVariable("Version", Version); // // if (Device != null) // { // TurnkeyUtils.SetVariable("DeviceName", Device.Name); // TurnkeyUtils.SetVariable("DeviceId", Device.Id); // TurnkeyUtils.SetVariable("DeviceType", Device.Type); // } // // // custom sdk installation is enabled if ChosenSdk.CustomVersionId is !null // if (CustomSdkId != null) // { // // copy files down, which are needed to check if whatever is installed is up to date (only do it once) // if (CustomVersionLocalFiles == null && CustomSdkInputFiles != null) // { // foreach (CopyAndRun CustomCopy in CustomSdkInputFiles) // { // if (CustomCopy.ShouldExecute()) // { // if (CustomVersionLocalFiles != null) // { // throw new AutomationTool.AutomationException("CustomSdkInputFiles specified multiple locations to be copied for this platform, which is not supported (only one value of $(CustomVersionLocalFiles) allowed)"); // } // // CustomCopy.Execute(); // CustomVersionLocalFiles = TurnkeyUtils.GetVariableValue("CopyOutputPath"); // } // } // } // // // in case the custom sdk modifies global env vars, make sure we capture them // TurnkeyUtils.StartTrackingExternalEnvVarChanges(); // // // re-set the path to local files // TurnkeyUtils.SetVariable("CustomVersionLocalFiles", CustomVersionLocalFiles); // AutomationPlatforms[Platform].CustomVersionUpdate(CustomSdkId, TurnkeyUtils.ExpandVariables(CustomSdkParams), new CopyProviderRetriever()); // // TurnkeyUtils.EndTrackingExternalEnvVarChanges(); // } // // // now run any installers // if (Installers == null) // { // // if there were no installers, then we succeeded // return true; // } // // bool bSucceeded = true; // foreach (CopyAndRun Install in Installers) // { // // build only types we keep in a nice directory structure in the PermanentStorage (if the copy provider allows for choosing destination) // if (Type ==SourceType.BuildOnly) // { // // download subdir is based on platform // string SubDir = string.Format("{0}/{1}", Platform.ToString(), Version); // bSucceeded = bSucceeded && Install.Execute(CopyExecuteSpecialMode.UsePermanentStorage, SubDir); // } // else // { // // !@todo turnkey : verify it worked // bSucceeded = bSucceeded && Install.Execute(); // } // } // // return bSucceeded; // } // // public static bool ConditionalSetupAutoSdk(SdkInfo Sdk, UnrealTargetPlatform Platform, bool bUnattended) // { // bool bAttemptAutoSdkSetup = false; // bool bSetupEnvVarAfterInstall = false; // if (Environment.GetEnvironmentVariable("UE_SDKS_ROOT") != null) // { // bAttemptAutoSdkSetup = true; // } // else // { // if (!bUnattended) // { // // @todo turnkey - have studio settings // bool bResponse = TurnkeyUtils.GetUserConfirmation("AutoSdks are not setup, but your studio has support. Would you like to set it up now?", true); // if (bResponse) // { // bAttemptAutoSdkSetup = true; // bSetupEnvVarAfterInstall = true; // } // } // } // // if (bAttemptAutoSdkSetup) // { // AutomationTool.Platform AutomationPlatform = AutomationTool.Platform.GetPlatform(Platform); // // TurnkeyUtils.Log("{0}: AutoSdk is setup on this computer, will look for available AutoSdk to download", Platform); // // // make sure this is unset so that we can know if it worked or not after install // TurnkeyUtils.ClearVariable("CopyOutputPath"); // // // now download it (AutoSdks don't "install") on download // // @todo turnkey: handle errors, handle p4 going to wrong location, handle one Sdk for multiple platforms // Sdk.Install(Platform, null, bUnattended, bSkipAutoSdkSetup:true); // // if (bSetupEnvVarAfterInstall) // { // // // this is where we synced the Sdk to // string InstalledRoot = TurnkeyUtils.GetVariableValue("CopyOutputPath"); // // // failed to install, nothing we can do // if (string.IsNullOrEmpty(InstalledRoot)) // { // TurnkeyUtils.ExitCode = ExitCode.Error_SDKNotFound; // return false; // } // // // walk up to one above Host* directory // DirectoryInfo AutoSdkSearch; // if (Directory.Exists(InstalledRoot)) // { // AutoSdkSearch = new DirectoryInfo(InstalledRoot); // } // else // { // AutoSdkSearch = new FileInfo(InstalledRoot).Directory; // } // while (AutoSdkSearch.Name != "Host" + HostPlatform.Current.HostEditorPlatform.ToString()) // { // AutoSdkSearch = AutoSdkSearch.Parent; // } // // // now go one up to the parent of Host // AutoSdkSearch = AutoSdkSearch.Parent; // // string AutoSdkDir = AutoSdkSearch.FullName; // if (!bUnattended) // { // string Response = TurnkeyUtils.ReadInput("Enter directory for root of AutoSdks. Use detected value, or enter another:", AutoSdkSearch.FullName); // if (string.IsNullOrEmpty(Response)) // { // return false; // } // } // // // set the env var, globally // TurnkeyUtils.StartTrackingExternalEnvVarChanges(); // Environment.SetEnvironmentVariable("UE_SDKS_ROOT", AutoSdkDir); // Environment.SetEnvironmentVariable("UE_SDKS_ROOT", AutoSdkDir, EnvironmentVariableTarget.User); // TurnkeyUtils.EndTrackingExternalEnvVarChanges(); // // } // // // and now activate it in case we need it this run // TurnkeyUtils.Log("Re-activating AutoSDK '{0}'...", Sdk.DisplayName); // // EpicGames.Core.UEBuildPlatformSDK.GetSDKForPlatform(Platform.ToString()).ReactivateAutoSDK(); // // return true; // } // // return false; // } // // // // // // // // // private bool CheckForExtendedVariables(bool bIncludeCustomSdk) // { // foreach (string Var in ExtendedVariables) // { // if (!TurnkeyUtils.HasVariable(Var)) // { // if (NeedsVariableToBeSet(Var, bIncludeCustomSdk)) // { // // ask for it! // TurnkeyUtils.Log("An Sdk ({0}) needs a extended variable ({1}) to be set that isn't set. Asking now...", DisplayName, Var); // // if (Var == "Project") // { // // we need a project, so choose one now // List Options = new List(); // Options.Add("Engine"); // Options.Add("FortniteGame"); // // // we force the user to select something // int Choice = TurnkeyUtils.ReadInputInt("Select a Project:", Options, true); // // if (Choice == 0) // { // return false; // } // // // set the projectname // TurnkeyUtils.SetVariable(Var, Options[Choice - 1]); // } // } // } // } // // return true; // } // // private bool NeedsVariableToBeSet(string Variable, bool bIncludeCustomSdk) // { // string Format = "$(" + Variable + ")"; // bool bContainsVar = false; // if (bIncludeCustomSdk && CustomSdkId != null) // { // bContainsVar = bContainsVar || (CustomSdkParams != null && CustomSdkParams.Contains(Format)); // if (CustomSdkInputFiles != null) // { // foreach (CopyAndRun CustomInput in CustomSdkInputFiles) // { // bContainsVar = bContainsVar || (CustomInput.Copy != null && CustomInput.Copy.Contains(Format)); // bContainsVar = bContainsVar || (CustomInput.CommandPath != null && CustomInput.CommandPath.Contains(Format)); // bContainsVar = bContainsVar || (CustomInput.CommandLine != null && CustomInput.CommandLine.Contains(Format)); // } // } // } // if (Installers != null) // { // foreach (CopyAndRun Installer in Installers) // { // bContainsVar = bContainsVar || (Installer.Copy != null && Installer.Copy.Contains(Format)); // bContainsVar = bContainsVar || (Installer.CommandPath != null && Installer.CommandPath.Contains(Format)); // bContainsVar = bContainsVar || (Installer.CommandLine != null && Installer.CommandLine.Contains(Format)); // } // } // // return bContainsVar; // } // // // // static private Regex ExpansionRegex = new Regex(@"^listexpansion:(\w*)=(.*)$"); private List CheckForListExpansions(List Sources, Func Getter, Action Setter) { List NewSources = new List(); foreach (FileSource Source in Sources) { string Value = Getter(Source); if (Value == null) { NewSources.Add(Source); continue; } // get the value in question from the lambda, and see if it's what we are looking for Match Result = ExpansionRegex.Match(Value);// Getter(Source)); if (Result.Success) { // get the variable we are going to replace in the expansions string ExpansionVar = Result.Groups[1].Value; // remember the old value, in case it had one string OldVarValue = TurnkeyUtils.GetVariableValue(ExpansionVar); string[] NewValues = TurnkeyUtils.ExpandVariables(Result.Groups[2].Value).Split(",".ToCharArray()); foreach(string NewValue in NewValues) { TurnkeyUtils.SetVariable(ExpansionVar, NewValue); // now when we make a clone, it will have the ExpansionVar set, and will be used FileSource NewSource = Source.CloneForExpansion(NewValue, Setter); NewSources.Add(NewSource); } // and retore it TurnkeyUtils.SetVariable(ExpansionVar, OldVarValue); } else { NewSources.Add(Source); } } return NewSources; } internal List ConditionalExpandLists() { List Expansions = new List() { this }; Expansions = CheckForListExpansions(Expansions, x => x.PlatformString, (x,y) => x.PlatformString = y); Expansions = CheckForListExpansions(Expansions, x => x.Version, (x, y) => x.Version = y); Expansions = CheckForListExpansions(Expansions, x => x.AllowedFlashDeviceTypes, (x, y) => x.AllowedFlashDeviceTypes = y); Expansions = CheckForListExpansions(Expansions, x => x.Project, (x, y) => x.Project = y); // return null if nothing was actually expanded! if (Expansions.Count == 1 && Expansions[0] == this) { return null; } return Expansions; } internal List ExpandCopySource() { List ResultExpansions = new List(); // get Expansions for this host platform CopySource ExpandingSource = null; string ExpansionOperation = null; foreach (CopySource Source in Sources) { if (Source.NeedsExpansion(out ExpansionOperation)) { if (ExpandingSource != null) { throw new AutomationTool.AutomationException("FileSource {0} had multiple expansions active on this platform. This is not allowed", Name); } ExpandingSource = Source; } } // something wasn't right, leave if (ExpansionOperation == null) { return ResultExpansions; } // fixup the Operation like so: // fileexpansion:perforce://depot/CarefullyRedist/HostWin64/SomePlatform/$[ExpVersion]/Setup.* // becomes: // perforce://depot/CarefullyRedist/HostWin64/SomePlatform/*/Setup.* string FixedSourceOperation = ExpansionOperation; int CaptureLocation; Dictionary LocationToVariableMap = new Dictionary(); while ((CaptureLocation = ExpansionOperation.IndexOf("$[")) != -1) { int CaptureEnd = ExpansionOperation.IndexOf("]", CaptureLocation); if (CaptureEnd == -1) { throw new AutomationTool.AutomationException("FileSource operation {0} had malformed capture variables", ExpandingSource.Operation); } string Variable = ExpansionOperation.Substring(CaptureLocation + 2, (CaptureEnd - CaptureLocation) - 2); // track the location to figure out the index of the * (the index will be index of the *) LocationToVariableMap.Add(CaptureLocation, Variable); string CaptureVar = string.Format("$[{0}]", Variable); ExpansionOperation = ExpansionOperation.Replace(CaptureVar, "*"); FixedSourceOperation = FixedSourceOperation.Replace(CaptureVar, string.Format("$({0})", Variable)); }; ExpandingSource.Operation = FixedSourceOperation; // now go back and figure out the index of the variables int StarLocation = -1; int StarIndex = 0; Dictionary StarIndexToVariableMap = new Dictionary(); while ((StarLocation = ExpansionOperation.IndexOf("*", StarLocation + 1)) != -1) { string Variable; if (LocationToVariableMap.TryGetValue(StarLocation, out Variable)) { StarIndexToVariableMap.Add(StarIndex, Variable); } StarIndex++; } // now enumerate and get the values List> Expansions = new List>(); string[] ExpandedInstallerResults = CopyProvider.ExecuteEnumerate(ExpansionOperation, Expansions); // expansion may not work, expand it to nothing if (ExpandedInstallerResults == null) { return ResultExpansions; } if (Expansions.Count != ExpandedInstallerResults.Length) { throw new AutomationException(string.Format("Bad expansions output from CopyProvider ({0} returned {1} count, expected {2}, from {3}", ExpansionOperation, Expansions.Count, ExpandedInstallerResults.Length, string.Join(", ", ExpandedInstallerResults))); } // @todo turnkey: this will be used in Builds also, make it a function with a lambda // make a new SdkInfo for each expansion int MaxIndex = 0; for (int ResultIndex = 0; ResultIndex < ExpandedInstallerResults.Length; ResultIndex++) { // set captured variables to the values returned from the enumeration TurnkeyUtils.SetVariable("Expansion", ExpandedInstallerResults[ResultIndex]); // @todo turnkey: if there were multiple captures for the same variable name, make sure they are the same value: googledrive:/Foo/[$Ver]/Bar_[$Ver].zip for (int ExpansionIndex = 0; ExpansionIndex < Expansions[ResultIndex].Count; ExpansionIndex++) { string Variable; if (StarIndexToVariableMap.TryGetValue(ExpansionIndex, out Variable)) { TurnkeyUtils.SetVariable(Variable, Expansions[ResultIndex][ExpansionIndex]); } } // remember how many we beed to unset MaxIndex = Math.Max(MaxIndex, Expansions[ResultIndex].Count); // make a new Sdk for each result in the expansion ResultExpansions.Add(CloneForExpansion(null, null)); } // clear temp variables TurnkeyUtils.ClearVariable("Expansion"); StarIndexToVariableMap.Values.ToList().ForEach(x => TurnkeyUtils.ClearVariable(x)); return ResultExpansions; } internal void PostDeserialize() { // if the Type is a platform specific type, then if (!Enum.TryParse(TypeString, out Type)) { Type = SourceType.Full; SpecificSDKType = TypeString; } // validate if (Version == null && IsSdkType()) { throw new AutomationTool.AutomationException("FileSource {0} needs to have a version specified, since it's an Sdk type", Name); } if (Sources == null) { throw new AutomationTool.AutomationException("FileSource {0} has no acutal Source operations specified! This is a setup error.", Name); } PlatformString = TurnkeyUtils.ExpandVariables(PlatformString, true); Platforms = new List(); if (PlatformString != null) { string[] PlatformStrings = PlatformString.ToLower().Split(",".ToCharArray()); // parse into runtime usable values foreach (string Plat in PlatformStrings) { if (UnrealTargetPlatform.IsValidName(Plat)) { UnrealTargetPlatform TargetPlat = UnrealTargetPlatform.Parse(Plat); Platforms.Add(TargetPlat); } else { // allow for shared SDK "platform" types (we use the AutoSDK platform name in the SDK object to determine the shared SDK "platform" foreach (UEBuildPlatformSDK SDK in UEBuildPlatformSDK.AllPlatformSDKObjects) { // if the platforms contains a AutoSDK platform that doesn't match a real platform name, add the platform to the set if (PlatformStrings.Contains(SDK.GetAutoSDKPlatformName().ToLower())) { Platforms.Add(UnrealTargetPlatform.Parse(SDK.PlatformName)); } } } } } Version = TurnkeyUtils.ExpandVariables(Version, true); Name = TurnkeyUtils.ExpandVariables(Name, true); if (string.IsNullOrEmpty(Name)) { Name = string.Format("{0} {1}", PlatformString, Version); } AllowedFlashDeviceTypes = TurnkeyUtils.ExpandVariables(AllowedFlashDeviceTypes, true); Array.ForEach(Sources, x => x.PostDeserialize()); } private string Indent(int Num) { return new string(' ', Num); } public override string ToString() { return ToString(0); } public string ToString(int BaseIndent) { StringBuilder Builder = new StringBuilder(); Builder.AppendLine("{1}Name: {0}", Name, Indent(BaseIndent)); Builder.AppendLine("{1}Version: {0}", Version == null ? "" : Version, Indent(BaseIndent + 2)); Builder.AppendLine("{1}Platform: {0}", Platforms == null ? "" : string.Join(",", Platforms), Indent(BaseIndent + 2)); Builder.AppendLine("{1}Type: {0}", Type, Indent(BaseIndent + 2)); if (Type == SourceType.Flash) { Builder.AppendLine("{1}AllowedFlashDeviceTypes: {0}", AllowedFlashDeviceTypes, Indent(BaseIndent + 2)); } Builder.AppendLine("{0}Installers:", Indent(BaseIndent + 2)); foreach (CopySource Copy in Sources) { Builder.AppendLine("{1}HostPlatform: {0}", Copy.Platform, Indent(BaseIndent + 4)); Builder.AppendLine("{1}CopyOperation: {0}", Copy.Operation, Indent(BaseIndent + 6)); if (Copy.CopyOverride != null) { Builder.AppendLine("{1}CopyOverride: {0}", Copy.CopyOverride, Indent(BaseIndent + 6)); } } return Builder.ToString().TrimEnd(); } } }