Files
UnrealEngineUWP/Engine/Source/Programs/Shared/EpicGames.Build/UEBuildPlatformSDK.cs
michael karambelas 41478e76dc P4 MergeTool stomped the static variable when merging the file from Release-21.10
#rb John.Wilson
[FYI] Marcus.Wassmer

#ROBOMERGE-AUTHOR: michael.karambelas
#ROBOMERGE-SOURCE: CL 20394385 via CL 20394572
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246)

[CL 20398658 by michael karambelas in ue5-main branch]
2022-05-27 16:49:07 -04:00

1836 lines
61 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
namespace EpicGames.Core
{
/// <summary>
/// SDK installation status
/// </summary>
public enum SDKStatus
{
/// <summary>
/// Desired SDK is installed and set up.
/// </summary>
Valid,
/// <summary>
/// Could not find the desired SDK, SDK setup failed, etc.
/// </summary>
Invalid,
};
/// <summary>
/// Range of SDK versions that are valid for a platform
/// </summary>
public class SDKDescriptor
{
public string Name;
public string? Min;
public string? Max;
public string? Current;
// for SDKs where you need 1 of a set of SDKs (for instance, multiple toolchains for one platform), give them all the same GroupName
// and Turnkey will make sure that at least one of them is installed for the SDK setup to be valid. Leave null otherwise
public string? GroupName = null;
public UInt64 MinInt = 0;
public UInt64 MaxInt = UInt64.MaxValue;
public UInt64 CurrentInt = 0;
public SDKStatus Validity = SDKStatus.Invalid;
public SDKDescriptor()
{
Name = "Unknown";
}
public SDKDescriptor(string InName, string? InMin, string? InMax, string? InCurrent, string? InGroupName, SDKCollection? Collection)
{
Name = InName;
Min = InMin;
Max = InMax;
Current = InCurrent;
GroupName = InGroupName;
if (Collection != null)
{
MinInt = Collection.PlatformSDK.ConvertVersionToInt(Min, Name);
// null will convert to 0, but we want MaxValue on null
MaxInt = Max == null ? UInt64.MaxValue : Collection.PlatformSDK.ConvertVersionToInt(Max, Name);
CurrentInt = Collection.PlatformSDK.ConvertVersionToInt(Current, Name);
UpdateValidity(Collection);
}
}
public SDKDescriptor(string InName, string? InMin, string? InMax)
: this(InName, InMin, InMax, null, null, null)
{
}
public SDKDescriptor(string InName, string? InCurrent)
: this(InName, null, null, InCurrent, null, null)
{
}
public void UpdateCurrent(string InCurrent, string? Hint, SDKCollection? Collection)
{
Current = InCurrent;
if (Collection != null)
{
CurrentInt = Collection.PlatformSDK.ConvertVersionToInt(Current, Hint);
UpdateValidity(Collection);
}
}
private void UpdateValidity(SDKCollection Collection)
{
// instead of just checking the range numerically, we allow platforms to override the IsValid check, which makes this a little convoluted (calling back into the Collection
// so it can call back into the PlatformSDK with the proper SDK vs Software function)
Validity = (Current != null && Collection.IsValid(Current, this)) ? SDKStatus.Valid : SDKStatus.Invalid;
}
public string ToString(bool bIncludeCurrent=true)
{
return $"MinVersion={Min ?? ""}, MaxVersion={Max ?? ""}" + (bIncludeCurrent ? $", Current={Current ?? ""}" : "");
}
public string ToString(string Descriptor, string? CurrentDescriptor, bool bIncludeName)
{
string DisplayName = bIncludeName ? "_" + Name : "";
// if they are different, print something like:
// MinAllowed_Toolchain=1.0, MaxAllowed_Toolchain=2.0
if (Min != Max)
{
return $"Min{Descriptor}{DisplayName}={Min ?? ""}, Max{Descriptor}{DisplayName}={Max ?? ""}" + (CurrentDescriptor != null ? $", Current{CurrentDescriptor}{DisplayName}={Current ?? ""}" : "");
}
// if they are the same, print something like:
// Allowed_Toolchain=1.0
return $"{Descriptor}{DisplayName}={Min}" + (CurrentDescriptor != null ? $", Current{CurrentDescriptor}{DisplayName}={Current ?? ""}" : "");
}
}
public class SDKCollection
{
public virtual string DefaultName { get { return "Sdk"; } }
public virtual bool IsValid(string Version, SDKDescriptor Info)
{
return PlatformSDK.IsVersionValid(Version, Info);
}
public UEBuildPlatformSDK PlatformSDK;
public List<SDKDescriptor> Sdks = new List<SDKDescriptor>();
public List<SDKDescriptor> FullSdks
{ get { return Sdks.Where(x => string.Compare(x.Name, "AutoSdk", true) != 0).ToList(); } }
public SDKDescriptor? AutoSdk
{ get { return Sdks.FirstOrDefault(x => string.Compare(x.Name, "AutoSdk", true) == 0); } }
public SDKCollection(UEBuildPlatformSDK PlatformSDK)
{
this.PlatformSDK = PlatformSDK;
}
public SDKCollection(SDKCollection Other, UEBuildPlatformSDK PlatformSDK)
: this(PlatformSDK)
{
foreach (SDKDescriptor Desc in Other.Sdks)
{
SetupSDK(Desc.Name, Desc.Min, Desc.Max, Desc.Current, Desc.GroupName);
}
}
// convenience for single unnamed Sdk range
public SDKCollection(string? Min, string? Max, UEBuildPlatformSDK PlatformSDK)
: this(PlatformSDK)
{
Sdks.Add(new SDKDescriptor(DefaultName, Min, Max));
}
// convenience for single unnamed Sdk current value
public SDKCollection(string? Current, UEBuildPlatformSDK PlatformSDK)
: this(PlatformSDK)
{
if (Current != null)
{
Sdks.Add(new SDKDescriptor(DefaultName, Current));
}
}
public bool AreAllManualSDKsValid()
{
IEnumerable<SDKDescriptor>? Sdks = FullSdks;
if (Sdks == null)
{
return true;
}
// check if all the SDKs not in groups are valid (All returns true if empty set)
bool bAllUngroupedAreValid = Sdks
.Where(Desc => Desc.GroupName == null)
.All(Desc => Desc.Validity == SDKStatus.Valid);
// for each groupname (ToHashSet removes duplicates), check if at least 1 SDK with that group name is valid
bool bAllGroupsHaveOneValid = Sdks
.Where(Desc => Desc.GroupName != null)
.Select(Desc => Desc.GroupName)
.ToHashSet()
.All(GroupName => Sdks.Any(Desc => Desc.GroupName == GroupName && Desc.Validity == SDKStatus.Valid));
return bAllUngroupedAreValid && bAllGroupsHaveOneValid;
}
public bool IsAutoSDKValid()
{
return AutoSdk?.Validity == SDKStatus.Valid;
}
public string ToString(bool bIncludeCurrent=true)
{
// by default print something like "MinVersion=1.0, MaxVersion=2.0"
return ToString("Version", "Version");
}
public string ToString(string Descriptor, string? CurrentDescriptor=null)
{
if (Sdks.Count == 1)
{
return Sdks[0].ToString(Descriptor, CurrentDescriptor, false);
}
return string.Join(", ", Sdks.Select(x => x.ToString(Descriptor, CurrentDescriptor, true)));
}
public string ToMultilineString(bool bIncludeCurrent = true)
{
return $"";// MinVersion={Min ?? ""}, MaxVersion={Max ?? ""}" + (bIncludeCurrent ? $", Current={Current ?? ""}" : "");
}
public void SetupSDK(string Name, string? Min, string? Max, string? Current, string? GroupName)
{
Sdks.Add(new SDKDescriptor(Name, Min, Max, Current, GroupName, this));
}
public void SetupCurrent(string Name, string Current)
{
Sdks.Add(new SDKDescriptor(Name, null, null, Current, null, this));
}
public bool UpdateCurrentForSingle(string Name, string CurrentVersion)
{
if (PlatformSDK == null)
{
throw new Exception("Cannot call UpdateCurrentForSingle in a SDKCollection without a PlatformSDK set");
}
SDKDescriptor? Info;
// for ease, we assume that if there is only one "Sdk" or "Software" in this Collection, then any Name passed in will work with it
// so just use that one entry
if (Sdks.Count == 1 && (Sdks[0].Name == "Sdk" || Sdks[0].Name == "Software"))
{
Info = Sdks[0];
}
else
{
Info = Sdks.FirstOrDefault(x => string.Compare(x.Name, Name, true) == 0);
if (Info == null)
{
return false;
}
}
Info.UpdateCurrent(CurrentVersion, Name, this);
return true;
}
}
public class SoftwareCollection : SDKCollection
{
public SoftwareCollection(UEBuildPlatformSDK PlatformSDK)
: base(PlatformSDK)
{
}
public SoftwareCollection(string? Min, string? Max, UEBuildPlatformSDK PlatformSDK)
: base(Min, Max, PlatformSDK)
{
}
public SoftwareCollection(string? Current, UEBuildPlatformSDK PlatformSDK)
: base(Current, PlatformSDK)
{
}
public override string DefaultName { get { return "Software"; } }
public override bool IsValid(string Version, SDKDescriptor Info)
{
return PlatformSDK.IsSoftwareVersionValid(Version, Info);
}
}
/// <summary>
/// SDK for a platform
/// </summary>
abstract public class UEBuildPlatformSDK
{
// Public SDK handling, not specific to AutoSDK
protected readonly ILogger Logger;
public UEBuildPlatformSDK(ILogger InLogger)
{
Logger = InLogger;
}
#region Global SDK Registration
/// <summary>
/// Registers the SDK for a given platform (as a string, but equivalent to UnrealTargetPlatform)
/// </summary>
/// <param name="SDK">SDK object</param>
/// <param name="PlatformName">Platform name for this SDK</param>
public static void RegisterSDKForPlatform(UEBuildPlatformSDK SDK, string PlatformName, bool bIsSdkAllowedOnHost)
{
// verify that neither platform or sdk were added before
if (SDKRegistry.Count(x => x.Key == PlatformName || x.Value == SDK) > 0)
{
throw new Exception(string.Format("Re-registering SDK for {0}. All Platforms must have a unique SDK object", PlatformName));
}
SDKRegistry.Add(PlatformName, SDK);
SDK.Init(PlatformName, bIsSdkAllowedOnHost);
}
private void Init(string InPlatformName, bool bInIsSdkAllowedOnHost)
{
PlatformName = InPlatformName;
bIsSdkAllowedOnHost = bInIsSdkAllowedOnHost;
// if the parent set up autosdk, the env vars will be wrong, but we can still get the manual SDK version from before it was setup
string? ParentManualSDKVersions = Environment.GetEnvironmentVariable(GetPlatformManualSDKSetupEnvVar());
if (!string.IsNullOrEmpty(ParentManualSDKVersions))
{
// we pass along __None to indicate the parent didn't have a manual sdk installed
if (ParentManualSDKVersions == "__None")
{
// empty it out
CachedManualSDKVersions = new Dictionary<string, string>();
}
else
{
Console.WriteLine("Processing parent manual sdk: {0}", ParentManualSDKVersions);
CachedManualSDKVersions = JsonSerializer.Deserialize<Dictionary<string, string>>(ParentManualSDKVersions) ?? new Dictionary<string, string>();
}
}
else
{
CachedManualSDKVersions = new Dictionary<string, string>();
// if there was no parent, get the SDK version before we run AutoSDK to get the manual version
SDKCollection InstalledVersions = GetInstalledSDKVersions();
foreach (SDKDescriptor Desc in InstalledVersions.FullSdks)
{
CachedManualSDKVersions[Desc.Name] = Desc.Current!;
}
}
}
#endregion
#region Main Public Interface/Utilties
/// <summary>
/// Retrieves a previously registered SDK for a given platform
/// </summary>
/// <param name="PlatformName">String name of the platform (equivalent to UnrealTargetPlatform)</param>
/// <returns></returns>
public static UEBuildPlatformSDK? GetSDKForPlatform(string PlatformName)
{
UEBuildPlatformSDK? SDK;
SDKRegistry.TryGetValue(PlatformName, out SDK);
return SDK;
}
/// <summary>
/// Gets the set of all known SDKs
/// </summary>
public static UEBuildPlatformSDK[] AllPlatformSDKObjects
{
get { return SDKRegistry.Values.ToArray(); }
}
// String name of the platform (will match an UnrealTargetPlatform)
public string? PlatformName;
// True if this Sdk is allowed to be used by this host - if not, we can skip a lot
public bool bIsSdkAllowedOnHost;
public SDKCollection GetAllSDKInfo()
{
SDKCollection AllSdks = new SDKCollection(this);
// walk over each one version the platform supports, and get it's current installed info
foreach (SDKDescriptor Desc in GetValidVersions().Sdks)
{
AllSdks.SetupSDK(Desc.Name, Desc.Min, Desc.Max, CachedManualSDKVersions.GetValueOrDefault(Desc.Name), Desc.GroupName);
}
// now get current AutoSDK version (if autosdk is set up, then the GetInstalledSDKVersion will return AutoSDK version)
bool bIsAutoSDK = false;
string? CurrentAutoSDKVersion = (PlatformSupportsAutoSDKs() && HasRequiredAutoSDKInstalled() == SDKStatus.Valid && HasSetupAutoSDK()) ? GetInstalledVersion(out bIsAutoSDK) : null;
AllSdks.SetupSDK("AutoSdk", GetMainVersion(), GetMainVersion(), CurrentAutoSDKVersion, null);
// verify some assumptions
if (CurrentAutoSDKVersion != null && bIsAutoSDK == false)
{
throw new Exception($"AutoSDK was indicated to be setup ({CurrentAutoSDKVersion}), but GetInstalledSDKVersion returned false for bIsAutoSDK");
}
if (CurrentAutoSDKVersion != null && CurrentAutoSDKVersion != GetMainVersion())
{
throw new Exception($"AutoSDK was indicated to be setup, but the version if returned ({CurrentAutoSDKVersion}) doesn't equal the MainVersion ({GetMainVersion()}");
}
return AllSdks;
}
public SDKDescriptor? GetSDKInfo(string SDKName)
{
return GetAllSDKInfo().Sdks.FirstOrDefault(x => string.Compare(x.Name, SDKName, true) == 0);
}
public SDKDescriptor? GetSoftwareInfo(string? SDKName=null)
{
if (SDKName == null)
{
// todo: make this queryable?
SDKName = "Software";
}
return GetAllSoftwareInfo().Sdks.FirstOrDefault(x => string.Compare(x.Name, SDKName, true) == 0);
}
public SDKCollection GetAllSoftwareInfo(string? DeviceType=null, string? Current=null)
{
SDKCollection AllSoftwares = new SDKCollection(this);
// walk over the valid software ranges, potentially setting the current verison if one was passed in
foreach (SDKDescriptor Desc in GetValidSoftwareVersions().Sdks)
{
// are we restricting this to one type?
if (DeviceType == null || string.Compare(Desc.Name, DeviceType, true) == 0)
{
AllSoftwares.SetupSDK(Desc.Name, Desc.Min, Desc.Max, null, Desc.GroupName);
}
if (Current != null)
{
// set the current for this one (likely it will only be for DeviceType, but support passing in null DeviceType, and non-null Current
AllSoftwares.UpdateCurrentForSingle(Desc.Name, Current);
}
}
return AllSoftwares;
}
//public SDKCollection GetAllSoftwareInfo(string DeviceId)
//{
// SDKCollection AllSoftwares =
//}
public string? GetInstalledVersion(out bool bIsAutoSDK)
{
bIsAutoSDK = HasSetupAutoSDK();
return GetInstalledSDKVersion();
}
public string? GetInstalledVersion()
{
return GetInstalledSDKVersion();
}
public string? GetInstalledVersion(string SDKName)
{
return GetSDKInfo(SDKName)?.Current;
}
// public void GetInstalledVersions(out string? ManualSDKVersion, out string? AutoSDKVersion)
// {
// // if we support AutoSDKs, then return both versions
// if (PlatformSupportsAutoSDKs())
// {
// AutoSDKVersion = (HasRequiredAutoSDKInstalled() == SDKStatus.Valid) ? GetInstalledSDKVersion() : null;
//// AutoSDKVersion = GetInstalledSDKVersion();
// }
// else
// {
// AutoSDKVersion = null;
// if (CachedManualSDKVersion != GetInstalledSDKVersion())
// {
// throw new Exception("Manual SDK version changed, this is not supported yet");
// }
// }
// ManualSDKVersion = CachedManualSDKVersion;
// }
public virtual bool IsVersionValid(string? Version, string SDKType)
{
return IsVersionValidInternal(Version, SDKType, null);
}
public virtual bool IsVersionValid(string? Version, SDKDescriptor Info)
{
return IsVersionValidInternal(Version, null, Info);
}
public virtual bool IsSoftwareVersionValid(string Version, string DeviceType)
{
return IsSoftwareVersionValidInternal(Version, DeviceType, null);
}
public virtual bool IsSoftwareVersionValid(string Version, SDKDescriptor Info)
{
return IsSoftwareVersionValidInternal(Version, null, Info);
}
public void ReactivateAutoSDK()
{
// @todo turnkey: this needs to force re-doing it, as it is likely a no-op, need to investigate what to clear out
ManageAndValidateSDK();
}
#endregion
#region Platform Overrides
/// <summary>
/// Return the SDK version that the platform wants to use (AutoSDK dir must match this, full SDKs can be in a valid range)
/// </summary>
/// <returns></returns>
public abstract string GetMainVersion();
/// <summary>
/// Gets the valid string range of Sdk versions. TryConvertVersionToInt() will need to succeed to make this usable for range checks
/// </summary>
/// <param name="MinVersion">Smallest version allowed</param>
/// <param name="MaxVersion">Largest version allowed (inclusive)</param>
protected virtual void GetValidVersionRange(out string MinVersion, out string MaxVersion)
{
throw new Exception($"This platform's Sdk class ({GetType().Name}) must implement either GetValidVersionRange() or GetValidVersions(). If this triggers, and GetValidVersions() is implemented, that means GetValidVersionRange was called directly");
}
protected virtual SDKCollection GetValidVersions()
{
// if the platform doesn't override this, then it only has one sdk, so put it into the collection as the single sdk (this is very much the standard behavior)
string MinVersion, MaxVersion;
GetValidVersionRange(out MinVersion, out MaxVersion);
return new SDKCollection(MinVersion, MaxVersion, this);
}
/// <summary>
/// Gets the valid string range of software/flash versions. TryConvertVersionToInt() will need to succeed to make this usable for range checks
/// </summary>
/// <param name="MinVersion">Smallest version allowed, or null if no minmum (in other words, 0 - MaxVersion)</param>
/// <param name="MaxVersion">Largest version allowed (inclusive), or null if no maximum (in other words, MinVersion - infinity)y</param>
protected virtual void GetValidSoftwareVersionRange(out string? MinVersion, out string? MaxVersion)
{
throw new Exception($"This platform's Sdk class ({GetType().Name}) must implement either GetValidSoftwareVersionRange() or GetValidSoftwareVersionRanges(). If this triggers, and GetValidSoftwareVersionRanges() is implemented, that means GetValidSoftwareVersionRange was called directly");
}
protected virtual SoftwareCollection GetValidSoftwareVersions()
{
// if the platform doesn't override this, then it only has one sdk, so put it into the collection as the single software (this is very much the standard behavior)
string? MinVersion, MaxVersion;
GetValidSoftwareVersionRange(out MinVersion, out MaxVersion);
SoftwareCollection SoftwareVersions = new SoftwareCollection(MinVersion, MaxVersion, this);
return SoftwareVersions;
}
/// <summary>
/// Returns the installed SDK version, used to determine if up to date or not (
/// </summary>
/// <returns></returns>
protected virtual string? GetInstalledSDKVersion()
{
if (GetInstalledSDKVersions().FullSdks.Count() == 1)
{
return GetInstalledSDKVersions().FullSdks[0].Current;
}
throw new Exception($"This platform's SDK class {GetType()} did not implement GetInstalledSDKVersion(). That means it has multiple SDKs and you will need use GetAllSDKInfo() or GetInstalledVersion(SDKName)");
}
protected virtual SDKCollection GetInstalledSDKVersions()
{
// if the platform doesn't override this, then it only has one sdk, so put it into the collection as the single sdk (this is very much the standard behavior)
string? Version = GetInstalledSDKVersion();
if (Version != null)
{
return new SDKCollection(Version, this);
}
// if no manual version, then return an empty list of current sdks
return new SDKCollection(this);
}
/// <summary>
/// Returns a platform-specific version string that can be retrieved from anywhere without needing to typecast the SDK object
/// </summary>
/// <param name="VersionType">Descriptor of the version you want</param>
/// <returns>Version string, or empty string if not known/supported</returns>
public virtual string GetPlatformSpecificVersion(string VersionType)
{
return "";
}
/// <summary>
/// Return a list of full SDK versions that are already installed and can be quickly switched to without running any installers.
/// </summary>
/// <returns></returns>
public virtual string[] GetAllInstalledSDKVersions()
{
return new string[] { };
}
/// <summary>
/// Switch to another version of the SDK than what GetInstalledSDKVersion() returns. This will be one of the versions returned from GetAllInstalledSDKVersions()
/// </summary>
/// <param name="Version">String name of the version to switch to</param>
/// <param name="bSwitchForThisProcessOnly">If true, only switch for this process (usually via process environment variable). If false, switch permanently (usually via system-wide environment variable)</param>
/// <returns>True if successful</returns>
public virtual bool SwitchToAlternateSDK(string Version, bool bSwitchForThisProcessOnly)
{
return false;
}
/// <summary>
/// For a platform that doesn't use properly named AutoSDK directories, the directory name may not be convertible to an integer,
/// and IsVersionValid checks could fail when checking AutoSDK version for an exact match. GetMainVersion() would return the
/// proper, integer-convertible version number of the SDK inside of the directory returned by GetAutoSDKDirectoryForMainVersion()
/// </summary>
/// <returns></returns>
public virtual string GetAutoSDKDirectoryForMainVersion()
{
return GetMainVersion();
}
///// <summary>
///// Gets the valid (integer) range of Sdk versions. Must be an integer to easily check a range vs a particular version
///// </summary>
///// <param name="MinVersion">Smallest version allowed</param>
///// <param name="MaxVersion">Largest version allowed (inclusive)</param>
///// <returns>True if the versions are valid, false if the platform is unable to convert its versions into an integer</returns>
//public virtual void GetValidVersionRange(out UInt64 MinVersion, out UInt64 MaxVersion)
//{
// string MinVersionString, MaxVersionString;
// GetValidVersionRange(out MinVersionString, out MaxVersionString);
// // failures to convert here are bad
// if (!TryConvertVersionToInt(MinVersionString, out MinVersion) || !TryConvertVersionToInt(MaxVersionString, out MaxVersion))
// {
// throw new Exception(string.Format("Unable to convert Min and Max valid versions to integers in {0} (Versions are {1} - {2})", GetType().Name, MinVersionString, MaxVersionString));
// }
//}
//public virtual void GetValidSoftwareVersionRange(out UInt64 MinVersion, out UInt64 MaxVersion)
//{
// string? MinVersionString, MaxVersionString;
// GetValidSoftwareVersionRange(out MinVersionString, out MaxVersionString);
// MinVersion = UInt64.MinValue;
// MaxVersion = UInt64.MaxValue - 1; // MaxValue is always bad
// // failures to convert here are bad
// if ((MinVersionString != null && !TryConvertVersionToInt(MinVersionString, out MinVersion)) ||
// (MaxVersionString != null && !TryConvertVersionToInt(MaxVersionString, out MaxVersion)))
// {
// throw new Exception(string.Format("Unable to convert Min and Max valid Software versions to integers in {0} (Versions are {1} - {2})", GetType().Name, MinVersionString, MaxVersionString));
// }
//}
// Let platform override behavior to determine if a version is a valid (useful for non-numeric versions)
protected virtual bool IsVersionValidInternal(string? Version, string? SDKType, SDKDescriptor? VersionInfo)
{
// we could have null if no SDK is installed at all, etc, which is always a failure
if (Version == null)
{
return false;
}
// AutoSDK must match the desired version exactly, since that is the only one we will use
if (string.Compare(SDKType, "AutoSdk", true) == 0)
{
// if integer version checking failed, then we can detect valid autosdk if the version matches the autosdk directory by name
return string.Compare(Version, GetAutoSDKDirectoryForMainVersion(), true) == 0;
}
// look for a range for this hinted type of SDK
VersionInfo = VersionInfo ?? GetSDKVersionForHint(GetValidVersions(), SDKType);
// if we couldn't find the Info, we can't do anything, so fail
if (VersionInfo == null)
{
return false;
}
// convert it to an integer
UInt64 IntVersion;
if (!TryConvertVersionToInt(Version, out IntVersion))
{
return false;
}
// short circuit range check if the Version is the desired version already
if (IntVersion == ConvertVersionToInt(GetMainVersion()))
{
return true;
}
// finally do the numeric comparison
return IntVersion >= VersionInfo.MinInt && IntVersion <= VersionInfo.MaxInt;
}
protected virtual bool IsSoftwareVersionValidInternal(string? Version, string? DeviceType, SDKDescriptor? VersionInfo)
{
// we could have null if no SDK is installed at all, etc, which is always a failure
if (Version == null)
{
return false;
}
// convert it to an integer
UInt64 IntVersion;
if (!TryConvertVersionToInt(Version, out IntVersion))
{
return false;
}
// look for a range for this hinted type of SDK
VersionInfo = VersionInfo ?? GetSDKVersionForHint(GetValidSoftwareVersions(), DeviceType);
// if we couldn't find the Info, we can't do anything, so fail
if (VersionInfo == null)
{
return false;
}
// finally do the numeric comparison
return IntVersion >= VersionInfo.MinInt && IntVersion <= VersionInfo.MaxInt;
}
/// <summary>
/// Only the platform can convert a version string into an integer that is usable for comparison
/// </summary>
/// <param name="StringValue">Version that comes from the installed SDK or a Turnkey manifest or the like</param>
/// <param name="OutValue"></param>
/// <param name="Hint">A platform specific hint that can help guide conversion (usually SDKName or device type)</param>
/// <returns>If the StringValue was able to be be converted to an integer</returns>
public abstract bool TryConvertVersionToInt(string? StringValue, out UInt64 OutValue, string? Hint = null);
/// <summary>
/// Like TryConvertVersionToInt, but will throw an exception on failure
/// </summary>
/// <param name="StringValue">Version that comes from the PlatformSDK class (should not be used with Manifest or other user supplied versions)</param>
/// <param name="Hint">A platform specific hint that can help guide conversion (usually SDKName or device type)</param>
/// <returns>The integer version of StringValue, can be used to compare against a valid range</returns>
public UInt64 ConvertVersionToInt(string? StringValue, string? Hint = null)
{
// quickly handle null input, which is not necessarily an error case
if (StringValue == null)
{
return 0;
}
UInt64 Result;
if (!TryConvertVersionToInt(StringValue, out Result, Hint))
{
throw new Exception($"Unable to convert {GetType()} version {StringValue} to an integer. Likely this version was supplied by code, and is expected to be valid.");
}
return Result;
}
/// <summary>
/// Allow the platform SDK to override the name it will use in AutoSDK, but default to the platform name
/// </summary>
/// <returns>The name of the directory to use inside the AutoSDK system</returns>
public virtual string? GetAutoSDKPlatformName()
{
return PlatformName;
}
#endregion
#region Print SDK Info
private static bool bHasShownTurnkey = false;
private SDKStatus? SDKInfoValidity = null;
public static bool bSuppressSDKWarnings = false;
public virtual SDKStatus PrintSDKInfoAndReturnValidity(LogEventType Verbosity = LogEventType.Console, LogFormatOptions Options = LogFormatOptions.None,
LogEventType ErrorVerbosity = LogEventType.Error, LogFormatOptions ErrorOptions = LogFormatOptions.None)
{
if (SDKInfoValidity != null)
{
return SDKInfoValidity.Value;
}
SDKCollection SDKInfo = GetAllSDKInfo();
// will mark invalid below if needed
SDKInfoValidity = SDKStatus.Valid;
SDKDescriptor? AutoSDKInfo = SDKInfo.AutoSdk;
if (AutoSDKInfo != null && AutoSDKInfo.Validity == SDKStatus.Valid)
{
string PlatformSDKRoot = GetPathToPlatformAutoSDKs();
Log.WriteLine(Verbosity, Options, "{0} using Auto SDK {1} from: {2} 0x{3:X}", PlatformName, AutoSDKInfo.Current, Path.Combine(PlatformSDKRoot, GetAutoSDKDirectoryForMainVersion()), AutoSDKInfo.CurrentInt);
}
else
{
if (SDKInfo.AreAllManualSDKsValid())
{
Log.WriteLine(Verbosity, Options, "{0} Installed SDK(s): {1}", PlatformName, SDKInfo.ToString(true));
}
else
{
SDKInfoValidity = SDKStatus.Invalid;
StringBuilder Msg = new StringBuilder();
Msg.AppendFormat("Unable to find valid SDK(s) for {0}:", PlatformName);
foreach (SDKDescriptor Desc in SDKInfo.Sdks)
{
if (Desc.Validity == SDKStatus.Valid)
{
Msg.Append($" {Desc.Name} is valid ({Desc}");
}
else
{
if (Desc.Current != null)
{
Msg.AppendFormat($" Found {Desc.Name} Version: {Desc.Current}.");
}
Msg.AppendLine($" {Desc.ToString("Required", null, false)}");
}
}
if (!bHasShownTurnkey)
{
Msg.AppendLine(" If your Studio has it set up, you can run this command to find the SDK to install:");
Msg.AppendLine(" RunUAT Turnkey -command=InstallSdk -platform={0} -BestAvailable", PlatformName!);
if ((ErrorOptions & LogFormatOptions.NoConsoleOutput) == LogFormatOptions.None)
{
bHasShownTurnkey = true;
}
}
// Reducing warnings to log to help prevent warnings locally or in Horde about SDKs we might not currently be concerned about
if (bSuppressSDKWarnings)
{
ErrorVerbosity = LogEventType.Log;
}
// always print errors to the screen
Log.WriteLine(ErrorVerbosity, ErrorOptions, Msg.ToString());
}
}
return SDKInfoValidity.Value;
}
#endregion
#region Private/Protected general functionality
// this is the SDK version that was set before activating AutoSDK, since AutoSDK may remove ability to retrieve the Manual SDK version
protected Dictionary<string, string> CachedManualSDKVersions = new Dictionary<string, string>();
private static Dictionary<string, UEBuildPlatformSDK> SDKRegistry = new Dictionary<string, UEBuildPlatformSDK>();
private SDKDescriptor? GetSDKVersionForHint(SDKCollection Collection, string? Hint)
{
// if the hint is found, use it always
SDKDescriptor? SDKDesc = Collection.Sdks.FirstOrDefault(x => string.Compare(x.Name, Hint, true) == 0);
if (SDKDesc != null)
{
return SDKDesc;
}
// only use AUtoSDK if explicitly asked for above, otherwise, remove it and look at what's left
IEnumerable<SDKDescriptor> FullSdks = Collection.FullSdks;
// if there's one with the special name, then use it as it's generic (common case here)
if (FullSdks.Count() == 1 && (FullSdks.First().Name == "Sdk" || FullSdks.First().Name == "Software"))
{
return FullSdks.First();
}
// finally we have to give up
return null;
}
#endregion
// AutoSDKs handling portion
#region protected AutoSDKs Utility
/// <summary>
/// Name of the file that holds currently install SDK version string
/// </summary>
protected const string CurrentlyInstalledSDKStringManifest = "CurrentlyInstalled.txt";
/// <summary>
/// name of the file that holds the last succesfully run SDK setup script version
/// </summary>
protected const string LastRunScriptVersionManifest = "CurrentlyInstalled.Version.txt";
/// <summary>
/// Filename of the script version file in each AutoSDK directory. Changing the contents of this file will force reinstallation of an AutoSDK.
/// </summary>
private const string ScriptVersionFilename = "Version.txt";
/// <summary>
/// Name of the file that holds environment variables of current SDK
/// </summary>
protected const string SDKEnvironmentVarsFile = "OutputEnvVars.txt";
protected const string SDKRootEnvVar = "UE_SDKS_ROOT";
protected const string AutoSetupEnvVar = "AutoSDKSetup";
protected const string ManualSetupEnvVar = "ManualSDKSetup";
private static string GetAutoSDKHostPlatform()
{
if (RuntimePlatform.IsWindows)
{
return "Win64";
}
else if (RuntimePlatform.IsMac)
{
return "Mac";
}
else if (RuntimePlatform.IsLinux)
{
return "Linux";
}
throw new Exception("Unknown host platform!");
}
/// <summary>
/// Whether platform supports switching SDKs during runtime
/// </summary>
/// <returns>true if supports</returns>
protected virtual bool PlatformSupportsAutoSDKs()
{
return false;
}
static private bool bCheckedAutoSDKRootEnvVar = false;
static private bool bAutoSDKSystemEnabled = false;
static private bool HasAutoSDKSystemEnabled()
{
if (!bCheckedAutoSDKRootEnvVar)
{
string? SDKRoot = Environment.GetEnvironmentVariable(SDKRootEnvVar);
if (SDKRoot != null)
{
bAutoSDKSystemEnabled = true;
}
bCheckedAutoSDKRootEnvVar = true;
}
return bAutoSDKSystemEnabled;
}
// Whether AutoSDK setup is safe. AutoSDKs will damage manual installs on some platforms.
protected bool IsAutoSDKSafe()
{
return !IsAutoSDKDestructive() || !HasAnyManualInstall();
}
/// <summary>
/// Gets the version number of the SDK setup script itself. The version in the base should ALWAYS be the master revision from the last refactor.
/// If you need to force a rebuild for a given platform, modify the version file.
/// </summary>
/// <returns>Setup script version</returns>
private string GetRequiredScriptVersionString()
{
const string UnspecifiedVersion = "UnspecifiedScriptVersion";
string VersionFilename = Path.Combine(GetPathToPlatformAutoSDKs(), GetAutoSDKDirectoryForMainVersion(), ScriptVersionFilename);
if (File.Exists(VersionFilename))
{
return File.ReadAllLines(VersionFilename).FirstOrDefault() ?? UnspecifiedVersion;
}
else
{
return UnspecifiedVersion;
}
}
/// <summary>
/// Returns path to platform SDKs
/// </summary>
/// <returns>Valid SDK string</returns>
protected string GetPathToPlatformAutoSDKs()
{
string SDKPath = "";
string? SDKRoot = Environment.GetEnvironmentVariable(SDKRootEnvVar);
if (SDKRoot != null)
{
if (SDKRoot != "")
{
SDKPath = Path.Combine(SDKRoot, "Host" + GetAutoSDKHostPlatform(), GetAutoSDKPlatformName()!);
}
}
return SDKPath;
}
/// <summary>
/// Returns path to platform SDKs
/// </summary>
/// <returns>Valid SDK string</returns>
public static bool TryGetHostPlatformAutoSDKDir([NotNullWhen(true)] out DirectoryReference? OutPlatformDir)
{
string? SDKRoot = Environment.GetEnvironmentVariable(SDKRootEnvVar);
if (String.IsNullOrEmpty(SDKRoot))
{
OutPlatformDir = null;
return false;
}
else
{
OutPlatformDir = DirectoryReference.Combine(new DirectoryReference(SDKRoot), "Host" + GetAutoSDKHostPlatform());
return true;
}
}
/// <summary>
/// Because most ManualSDK determination depends on reading env vars, if this process is spawned by a process that ALREADY set up
/// AutoSDKs then all the SDK env vars will exist, and we will spuriously detect a Manual SDK. (children inherit the environment of the parent process).
/// Therefore we write out an env variable to set in the command file (OutputEnvVars.txt) such that child processes can determine if their manual SDK detection
/// is bogus. Make it platform specific so that platforms can be in different states.
/// </summary>
protected string GetPlatformAutoSDKSetupEnvVar()
{
return GetAutoSDKPlatformName() + AutoSetupEnvVar;
}
protected string GetPlatformManualSDKSetupEnvVar()
{
return GetAutoSDKPlatformName() + ManualSetupEnvVar;
}
/// <summary>
/// Gets currently installed version
/// </summary>
/// <param name="PlatformSDKRoot">absolute path to platform SDK root</param>
/// <param name="OutInstalledSDKVersionString">version string as currently installed</param>
/// <returns>true if was able to read it</returns>
protected bool GetCurrentlyInstalledSDKString(string PlatformSDKRoot, out string OutInstalledSDKVersionString, out string OutInstalledSDKLevel)
{
if (Directory.Exists(PlatformSDKRoot))
{
string VersionFilename = Path.Combine(PlatformSDKRoot, CurrentlyInstalledSDKStringManifest);
if (File.Exists(VersionFilename))
{
using (StreamReader Reader = new StreamReader(VersionFilename))
{
string? Version = Reader.ReadLine();
string? Type = Reader.ReadLine();
string? Level = Reader.ReadLine();
if (string.IsNullOrEmpty(Level))
{
Level = "FULL";
}
// don't allow ManualSDK installs to count as an AutoSDK install version.
if (Type != null && Type == "AutoSDK")
{
if (Version != null)
{
OutInstalledSDKVersionString = Version;
OutInstalledSDKLevel = Level;
return true;
}
}
}
}
}
OutInstalledSDKVersionString = "";
OutInstalledSDKLevel = "";
return false;
}
/// <summary>
/// Gets the version of the last successfully run setup script.
/// </summary>
/// <param name="PlatformSDKRoot">absolute path to platform SDK root</param>
/// <param name="OutLastRunScriptVersion">version string</param>
/// <returns>true if was able to read it</returns>
protected bool GetLastRunScriptVersionString(string PlatformSDKRoot, out string OutLastRunScriptVersion)
{
if (Directory.Exists(PlatformSDKRoot))
{
string VersionFilename = Path.Combine(PlatformSDKRoot, LastRunScriptVersionManifest);
if (File.Exists(VersionFilename))
{
using (StreamReader Reader = new StreamReader(VersionFilename))
{
string? Version = Reader.ReadLine();
if (Version != null)
{
OutLastRunScriptVersion = Version;
return true;
}
}
}
}
OutLastRunScriptVersion = "";
return false;
}
/// <summary>
/// Sets currently installed version
/// </summary>
/// <param name="InstalledSDKVersionString">SDK version string to set</param>
/// <returns>true if was able to set it</returns>
protected bool SetCurrentlyInstalledAutoSDKString(String InstalledSDKVersionString, String InstalledSDKLevelString)
{
String PlatformSDKRoot = GetPathToPlatformAutoSDKs();
if (Directory.Exists(PlatformSDKRoot))
{
string VersionFilename = Path.Combine(PlatformSDKRoot, CurrentlyInstalledSDKStringManifest);
if (File.Exists(VersionFilename))
{
File.Delete(VersionFilename);
}
using (StreamWriter Writer = File.CreateText(VersionFilename))
{
Writer.WriteLine(InstalledSDKVersionString);
Writer.WriteLine("AutoSDK");
Writer.WriteLine(InstalledSDKLevelString);
return true;
}
}
return false;
}
protected void SetupManualSDK()
{
if (PlatformSupportsAutoSDKs() && HasAutoSDKSystemEnabled())
{
String InstalledSDKVersionString = GetAutoSDKDirectoryForMainVersion();
String PlatformSDKRoot = GetPathToPlatformAutoSDKs();
if (!Directory.Exists(PlatformSDKRoot))
{
Directory.CreateDirectory(PlatformSDKRoot);
}
{
string VersionFilename = Path.Combine(PlatformSDKRoot, CurrentlyInstalledSDKStringManifest);
if (File.Exists(VersionFilename))
{
File.Delete(VersionFilename);
}
string EnvVarFile = Path.Combine(PlatformSDKRoot, SDKEnvironmentVarsFile);
if (File.Exists(EnvVarFile))
{
File.Delete(EnvVarFile);
}
using (StreamWriter Writer = File.CreateText(VersionFilename))
{
Writer.WriteLine(InstalledSDKVersionString);
Writer.WriteLine("ManualSDK");
Writer.WriteLine("FULL");
}
}
}
}
protected bool SetLastRunAutoSDKScriptVersion(string LastRunScriptVersion)
{
String PlatformSDKRoot = GetPathToPlatformAutoSDKs();
if (Directory.Exists(PlatformSDKRoot))
{
string VersionFilename = Path.Combine(PlatformSDKRoot, LastRunScriptVersionManifest);
if (File.Exists(VersionFilename))
{
File.Delete(VersionFilename);
}
using (StreamWriter Writer = File.CreateText(VersionFilename))
{
Writer.WriteLine(LastRunScriptVersion);
return true;
}
}
return false;
}
/// <summary>
/// Returns Hook names as needed by the platform
/// (e.g. can be overridden with custom executables or scripts)
/// </summary>
/// <param name="Hook">Hook type</param>
protected virtual string GetHookExecutableName(SDKHookType Hook)
{
if (RuntimePlatform.IsWindows)
{
if (Hook == SDKHookType.Uninstall)
{
return "unsetup.bat";
}
else
{
return "setup.bat";
}
}
else
{
if (Hook == SDKHookType.Uninstall)
{
return "unsetup.sh";
}
else
{
return "setup.sh";
}
}
}
/// <summary>
/// Whether the hook must be run with administrator privileges.
/// </summary>
/// <param name="Hook">Hook for which to check the required privileges.</param>
/// <returns>true if the hook must be run with administrator privileges.</returns>
protected virtual bool DoesHookRequireAdmin(SDKHookType Hook)
{
return true;
}
private void LogAutoSDKHook(object Sender, DataReceivedEventArgs Args)
{
if (Args.Data != null)
{
LogFormatOptions Options = Log.OutputLevel >= LogEventType.Verbose ? LogFormatOptions.None : LogFormatOptions.NoConsoleOutput;
Log.WriteLine(LogEventType.Log, Options, Args.Data);
}
}
static private string[] AutoSDKLevels = new string[]
{
"NONE",
"BUILD",
"PACKAGE",
"RUN",
"FULL",
};
private bool IsSDKLevelAtLeast(string Value, string ComparedTo)
{
int ValueIndex = AutoSDKLevels.FindIndex(x => x == Value.ToUpper());
int ComparedIndex = AutoSDKLevels.FindIndex(x => x == ComparedTo.ToUpper());
if (ValueIndex < 0 || ComparedIndex < 0)
{
throw new Exception($"Passed in a bad value to IsSDKLevelAtLeast: {Value}, {ComparedTo}");
}
return ValueIndex >= ComparedIndex;
}
private string GetAutoSDKLevelForPlatform(string PlatformSDKRoot)
{
// the last component is the platform name
string PlatformName = Path.GetFileName(PlatformSDKRoot).ToUpper();
// parse the envvar
Dictionary<string, string> PlatformSpecificLevels = new Dictionary<string, string>();
string? DetailedSettings = Environment.GetEnvironmentVariable("UE_AUTOSDK_SPECIFIC_LEVELS"); // "Android=PACKAGE;GDk=RuN;PS5=BUILD";
if (!string.IsNullOrEmpty(DetailedSettings) && DetailedSettings.Contains(PlatformName, StringComparison.InvariantCultureIgnoreCase))
{
foreach (string Detail in DetailedSettings.ToUpper().Split(';'))
{
string[] Tokens = Detail.Split('=');
// validate the level string
if (AutoSDKLevels.Contains(Tokens[1]))
{
PlatformSpecificLevels.Add(Tokens[0], Tokens[1]);
}
}
}
string FinalAutoSDKLevel = "FULL";
if (PlatformSpecificLevels.ContainsKey(PlatformName))
{
FinalAutoSDKLevel = PlatformSpecificLevels[PlatformName];
}
else
{
string? DefaultLevel = Environment.GetEnvironmentVariable("UE_AUTOSDK_DEFAULT_LEVEL");
if (!string.IsNullOrEmpty(DefaultLevel) && AutoSDKLevels.Contains(DefaultLevel, StringComparer.InvariantCultureIgnoreCase))
{
FinalAutoSDKLevel = DefaultLevel.ToUpper();
}
}
return FinalAutoSDKLevel;
}
/// <summary>
/// Runs install/uninstall hooks for SDK
/// </summary>
/// <param name="PlatformSDKRoot">absolute path to platform SDK root</param>
/// <param name="SDKVersionString">version string to run for (can be empty!)</param>
/// <param name="Hook">which one of hooks to run</param>
/// <param name="bHookCanBeNonExistent">whether a non-existing hook means failure</param>
/// <returns>true if succeeded</returns>
protected virtual bool RunAutoSDKHooks(string PlatformSDKRoot, string SDKVersionString, string AutoSDKLevel, SDKHookType Hook, bool bHookCanBeNonExistent = true)
{
if (!IsAutoSDKSafe())
{
Logger.LogDebug("{Platform} attempted to run SDK hook which could have damaged manual SDK install!", GetAutoSDKPlatformName());
return false;
}
if (SDKVersionString != "")
{
string SDKDirectory = Path.Combine(PlatformSDKRoot, SDKVersionString);
string HookExe = Path.Combine(SDKDirectory, GetHookExecutableName(Hook));
if (File.Exists(HookExe))
{
Logger.LogDebug("Running {Hook} hook {HookExe}", Hook, HookExe);
// run it
Process HookProcess = new Process();
HookProcess.StartInfo.WorkingDirectory = SDKDirectory;
HookProcess.StartInfo.FileName = HookExe;
HookProcess.StartInfo.Arguments = AutoSDKLevel;
HookProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
bool bHookRequiresAdmin = DoesHookRequireAdmin(Hook);
if (bHookRequiresAdmin)
{
// installers may require administrator access to succeed. so run as an admin.
HookProcess.StartInfo.Verb = "runas";
}
else
{
HookProcess.StartInfo.UseShellExecute = false;
HookProcess.StartInfo.RedirectStandardOutput = true;
HookProcess.StartInfo.RedirectStandardError = true;
HookProcess.OutputDataReceived += LogAutoSDKHook;
HookProcess.ErrorDataReceived += LogAutoSDKHook;
}
//using (ScopedTimer HookTimer = new ScopedTimer("Time to run hook: ", LogEventType.Log))
{
HookProcess.Start();
if (!bHookRequiresAdmin)
{
HookProcess.BeginOutputReadLine();
HookProcess.BeginErrorReadLine();
}
HookProcess.WaitForExit();
}
if (HookProcess.ExitCode != 0)
{
Logger.LogDebug("Hook exited uncleanly (returned {ExitCode}), considering it failed.", HookProcess.ExitCode);
return false;
}
return true;
}
else
{
Logger.LogDebug("File {HookExe} does not exist", HookExe);
}
}
else
{
Logger.LogDebug("Version string is blank for {SdkRoot}. Can't determine {Hook} hook.", PlatformSDKRoot, Hook.ToString());
}
return bHookCanBeNonExistent;
}
/// <summary>
/// Loads environment variables from SDK
/// If any commands are added or removed the handling needs to be duplicated in
/// TargetPlatformManagerModule.cpp
/// </summary>
/// <param name="PlatformSDKRoot">absolute path to platform SDK</param>
/// <returns>true if succeeded</returns>
protected bool SetupEnvironmentFromAutoSDK(string PlatformSDKRoot)
{
string EnvVarFile = Path.Combine(PlatformSDKRoot, SDKEnvironmentVarsFile);
if (File.Exists(EnvVarFile))
{
using (StreamReader Reader = new StreamReader(EnvVarFile))
{
List<string> PathAdds = new List<string>();
List<string> PathRemoves = new List<string>();
List<string> EnvVarNames = new List<string>();
List<string> EnvVarValues = new List<string>();
bool bNeedsToWriteAutoSetupEnvVar = true;
String PlatformSetupEnvVar = GetPlatformAutoSDKSetupEnvVar();
for (; ; )
{
string? VariableString = Reader.ReadLine();
if (VariableString == null)
{
break;
}
string[] Parts = VariableString.Split('=');
if (Parts.Length != 2)
{
Logger.LogDebug("Incorrect environment variable declaration:");
Logger.LogDebug("{VariableString}", VariableString);
return false;
}
if (String.Compare(Parts[0], "strippath", true) == 0)
{
PathRemoves.Add(Parts[1]);
}
else if (String.Compare(Parts[0], "addpath", true) == 0)
{
PathAdds.Add(Parts[1]);
}
else
{
if (String.Compare(Parts[0], PlatformSetupEnvVar) == 0)
{
bNeedsToWriteAutoSetupEnvVar = false;
}
// convenience for setup.bat writers. Trim any accidental whitespace from variable names/values.
EnvVarNames.Add(Parts[0].Trim());
EnvVarValues.Add(Parts[1].Trim());
}
}
// don't actually set anything until we successfully validate and read all values in.
// we don't want to set a few vars, return a failure, and then have a platform try to
// build against a manually installed SDK with half-set env vars.
for (int i = 0; i < EnvVarNames.Count; ++i)
{
string EnvVarName = EnvVarNames[i];
string EnvVarValue = EnvVarValues[i];
Logger.LogDebug("Setting variable '{Name}' to '{Value}'", EnvVarName, EnvVarValue);
Environment.SetEnvironmentVariable(EnvVarName, EnvVarValue);
}
// actually perform the PATH stripping / adding.
String? OrigPathVar = Environment.GetEnvironmentVariable("PATH");
String PathDelimiter = RuntimePlatform.IsWindows ? ";" : ":";
String[] PathVars = { };
if (!String.IsNullOrEmpty(OrigPathVar))
{
PathVars = OrigPathVar.Split(PathDelimiter.ToCharArray());
}
else
{
Logger.LogDebug("Path environment variable is null during AutoSDK");
}
List<String> ModifiedPathVars = new List<string>();
ModifiedPathVars.AddRange(PathVars);
// perform removes first, in case they overlap with any adds.
foreach (String PathRemove in PathRemoves)
{
foreach (String PathVar in PathVars)
{
if (PathVar.IndexOf(PathRemove, StringComparison.OrdinalIgnoreCase) >= 0)
{
Logger.LogDebug("Removing Path: '{Path}'", PathVar);
ModifiedPathVars.Remove(PathVar);
}
}
}
// remove all the of ADDs so that if this function is executed multiple times, the paths will be guaranteed to be in the same order after each run.
// If we did not do this, a 'remove' that matched some, but not all, of our 'adds' would cause the order to change.
foreach (String PathAdd in PathAdds)
{
foreach (String PathVar in PathVars)
{
if (String.Compare(PathAdd, PathVar, true) == 0)
{
Logger.LogDebug("Removing Path: '{Path}'", PathVar);
ModifiedPathVars.Remove(PathVar);
}
}
}
// perform adds, but don't add duplicates
foreach (String PathAdd in PathAdds)
{
if (!ModifiedPathVars.Contains(PathAdd))
{
Logger.LogDebug("Adding Path: '{Path}'", PathAdd);
ModifiedPathVars.Add(PathAdd);
}
}
String ModifiedPath = String.Join(PathDelimiter, ModifiedPathVars);
Environment.SetEnvironmentVariable("PATH", ModifiedPath);
Reader.Close();
// write out environment variable command so any process using this commandfile will mark itself as having had autosdks set up.
// avoids child processes spuriously detecting manualsdks.
if (bNeedsToWriteAutoSetupEnvVar)
{
// write out the manual sdk version since child processes won't be able to detect manual with AutoSDK messing up env vars
using (StreamWriter Writer = File.AppendText(EnvVarFile))
{
Writer.WriteLine("{0}={1}", PlatformSetupEnvVar, "1");
}
// set the variable in the local environment in case this process spawns any others.
Environment.SetEnvironmentVariable(PlatformSetupEnvVar, "1");
}
// make sure we know that we've modified the local environment, invalidating manual installs for this run.
bLocalProcessSetupAutoSDK = true;
// tell any child processes what our manual versions were before setting up autosdk
string ValueToWrite = CachedManualSDKVersions.Count > 0 ? JsonSerializer.Serialize(CachedManualSDKVersions) : "__None";
Environment.SetEnvironmentVariable(GetPlatformManualSDKSetupEnvVar(), ValueToWrite);
return true;
}
}
else
{
Logger.LogDebug("Cannot set up environment for {SdkRoot} because command file {EnvVarFile} does not exist.", PlatformSDKRoot, EnvVarFile);
}
return false;
}
protected void InvalidateCurrentlyInstalledAutoSDK()
{
String PlatformSDKRoot = GetPathToPlatformAutoSDKs();
if (Directory.Exists(PlatformSDKRoot))
{
string SDKFilename = Path.Combine(PlatformSDKRoot, CurrentlyInstalledSDKStringManifest);
if (File.Exists(SDKFilename))
{
File.Delete(SDKFilename);
}
string VersionFilename = Path.Combine(PlatformSDKRoot, LastRunScriptVersionManifest);
if (File.Exists(VersionFilename))
{
File.Delete(VersionFilename);
}
string EnvVarFile = Path.Combine(PlatformSDKRoot, SDKEnvironmentVarsFile);
if (File.Exists(EnvVarFile))
{
File.Delete(EnvVarFile);
}
}
}
/// <summary>
/// Currently installed AutoSDK is written out to a text file in a known location.
/// This function just compares the file's contents with the current requirements.
/// </summary>
public SDKStatus HasRequiredAutoSDKInstalled()
{
if (PlatformSupportsAutoSDKs() && HasAutoSDKSystemEnabled())
{
string AutoSDKRoot = GetPathToPlatformAutoSDKs();
if (AutoSDKRoot != "")
{
string DesiredSDKLevel = GetAutoSDKLevelForPlatform(AutoSDKRoot);
// if the user doesn't want AutoSDK for this platform, then return that it is not installed, even if it actually is
if (string.Compare(DesiredSDKLevel, "NONE", true) == 0)
{
return SDKStatus.Invalid;
}
// check script version so script fixes can be propagated without touching every build machine's CurrentlyInstalled file manually.
string CurrentScriptVersionString;
if (GetLastRunScriptVersionString(AutoSDKRoot, out CurrentScriptVersionString) && CurrentScriptVersionString == GetRequiredScriptVersionString())
{
// check to make sure OutputEnvVars doesn't need regenerating
string EnvVarFile = Path.Combine(AutoSDKRoot, SDKEnvironmentVarsFile);
bool bEnvVarFileExists = File.Exists(EnvVarFile);
string CurrentSDKString;
string CurrentSDKLevel;
if (bEnvVarFileExists && GetCurrentlyInstalledSDKString(AutoSDKRoot, out CurrentSDKString, out CurrentSDKLevel))
{
// match version
if (CurrentSDKString == GetAutoSDKDirectoryForMainVersion())
{
if (IsSDKLevelAtLeast(CurrentSDKLevel, DesiredSDKLevel))
{
return SDKStatus.Valid;
}
}
}
}
}
}
return SDKStatus.Invalid;
}
// This tracks if we have already checked the sdk installation.
private Int32 SDKCheckStatus = -1;
// true if we've ever overridden the process's environment with AutoSDK data. After that, manual installs cannot be considered valid ever again.
private bool bLocalProcessSetupAutoSDK = false;
protected bool HasSetupAutoSDK()
{
return bLocalProcessSetupAutoSDK || HasParentProcessSetupAutoSDK(out _);
}
protected bool HasParentProcessSetupAutoSDK([NotNullWhen(true)] out string? OutAutoSDKSetupValue)
{
String AutoSDKSetupVarName = GetPlatformAutoSDKSetupEnvVar();
OutAutoSDKSetupValue = Environment.GetEnvironmentVariable(AutoSDKSetupVarName);
if (!String.IsNullOrEmpty(OutAutoSDKSetupValue))
{
return true;
}
return false;
}
public SDKStatus HasRequiredManualSDK()
{
// if (HasSetupAutoSDK())
// {
// return SDKStatus.Invalid;
// }
//
// // manual installs are always invalid if we have modified the process's environment for AutoSDKs
return HasRequiredManualSDKInternal();
}
// for platforms with destructive AutoSDK. Report if any manual sdk is installed that may be damaged by an autosdk.
protected virtual bool HasAnyManualInstall()
{
return false;
}
// tells us if the user has a valid manual install.
protected virtual SDKStatus HasRequiredManualSDKInternal()
{
return GetAllSDKInfo().AreAllManualSDKsValid() ? SDKStatus.Valid : SDKStatus.Invalid;
//string? ManualSDKVersion;
//GetInstalledVersions(out ManualSDKVersion, out _);
//return IsVersionValid(ManualSDKVersion, bForAutoSDK:false) ? SDKStatus.Valid : SDKStatus.Invalid;
}
// some platforms will fail if there is a manual install that is the WRONG manual install.
protected virtual bool AllowInvalidManualInstall()
{
return true;
}
// platforms can choose if they prefer a correct the the AutoSDK install over the manual install.
protected virtual bool PreferAutoSDK()
{
return true;
}
// some platforms don't support parallel SDK installs. AutoSDK on these platforms will
// actively damage an existing manual install by overwriting files in it. AutoSDK must NOT
// run any setup if a manual install exists in this case.
protected virtual bool IsAutoSDKDestructive()
{
return false;
}
/// <summary>
/// Runs batch files if necessary to set up required AutoSDK.
/// AutoSDKs are SDKs that have not been setup through a formal installer, but rather come from
/// a source control directory, or other local copy.
/// </summary>
private void SetupAutoSDK()
{
if (IsAutoSDKSafe() && PlatformSupportsAutoSDKs() && HasAutoSDKSystemEnabled())
{
// run installation for autosdk if necessary.
if (HasRequiredAutoSDKInstalled() == SDKStatus.Invalid)
{
string AutoSDKRoot = GetPathToPlatformAutoSDKs();
string DesiredSDKLevel = GetAutoSDKLevelForPlatform(AutoSDKRoot);
// if the user doesn't want AutoSDK for this platform, then do nothing
if (string.Compare(DesiredSDKLevel, "NONE", true) == 0)
{
Logger.LogDebug("Skipping AutoSDK for {PlatformName} because NONE was specified as the desired AutoSDK level", PlatformName);
return;
}
//reset check status so any checking sdk status after the attempted setup will do a real check again.
SDKCheckStatus = -1;
string CurrentSDKString;
string CurrentSDKLevel;
GetCurrentlyInstalledSDKString(AutoSDKRoot, out CurrentSDKString, out CurrentSDKLevel);
// switch over (note that version string can be empty)
if (!RunAutoSDKHooks(AutoSDKRoot, CurrentSDKString, CurrentSDKLevel, SDKHookType.Uninstall))
{
Logger.LogDebug("Failed to uninstall currently installed SDK {SdkVersion}", CurrentSDKString);
InvalidateCurrentlyInstalledAutoSDK();
return;
}
// delete Manifest file to avoid multiple uninstalls
InvalidateCurrentlyInstalledAutoSDK();
if (!RunAutoSDKHooks(AutoSDKRoot, GetAutoSDKDirectoryForMainVersion(), DesiredSDKLevel, SDKHookType.Install, false))
{
Logger.LogDebug("Failed to install required SDK {SdkVersion}. Attemping to uninstall", GetAutoSDKDirectoryForMainVersion());
RunAutoSDKHooks(AutoSDKRoot, GetAutoSDKDirectoryForMainVersion(), DesiredSDKLevel, SDKHookType.Uninstall, false);
return;
}
string EnvVarFile = Path.Combine(AutoSDKRoot, SDKEnvironmentVarsFile);
if (!File.Exists(EnvVarFile))
{
Logger.LogDebug("Installation of required SDK {SdkVersion}. Did not generate Environment file {EnvVarFile}", GetAutoSDKDirectoryForMainVersion(), EnvVarFile);
RunAutoSDKHooks(AutoSDKRoot, GetAutoSDKDirectoryForMainVersion(), DesiredSDKLevel, SDKHookType.Uninstall, false);
return;
}
SetCurrentlyInstalledAutoSDKString(GetAutoSDKDirectoryForMainVersion(), DesiredSDKLevel);
SetLastRunAutoSDKScriptVersion(GetRequiredScriptVersionString());
}
// fixup process environment to match autosdk
SetupEnvironmentFromAutoSDK();
}
}
/// <summary>
/// Allows the platform to optionally returns a path to the internal SDK
/// </summary>
/// <returns>Valid path to the internal SDK, null otherwise</returns>
public virtual string? GetInternalSDKPath()
{
return null;
}
#endregion
#region public AutoSDKs Utility
/// <summary>
/// Enum describing types of hooks a platform SDK can have
/// </summary>
public enum SDKHookType
{
Install,
Uninstall
};
/* Whether or not we should try to automatically switch SDKs when asked to validate the platform's SDK state. */
public static bool bAllowAutoSDKSwitching = true;
public SDKStatus SetupEnvironmentFromAutoSDK()
{
string PlatformSDKRoot = GetPathToPlatformAutoSDKs();
// load environment variables from current SDK
if (!SetupEnvironmentFromAutoSDK(PlatformSDKRoot))
{
Logger.LogDebug("Failed to load environment from required SDK {SdkRoot}", GetAutoSDKDirectoryForMainVersion());
InvalidateCurrentlyInstalledAutoSDK();
return SDKStatus.Invalid;
}
return SDKStatus.Valid;
}
/// <summary>
/// Whether the required external SDKs are installed for this platform.
/// Could be either a manual install or an AutoSDK.
/// </summary>
public SDKStatus HasRequiredSDKsInstalled()
{
// avoid redundant potentially expensive SDK checks.
if (SDKCheckStatus == -1)
{
bool bHasManualSDK = HasRequiredManualSDK() == SDKStatus.Valid;
bool bHasAutoSDK = HasRequiredAutoSDKInstalled() == SDKStatus.Valid;
// Per-Platform implementations can choose how to handle non-Auto SDK detection / handling.
SDKCheckStatus = (bHasManualSDK || bHasAutoSDK) ? 1 : 0;
}
return SDKCheckStatus == 1 ? SDKStatus.Valid : SDKStatus.Invalid;
}
// Arbitrates between manual SDKs and setting up AutoSDK based on program options and platform preferences.
public void ManageAndValidateSDK()
{
// do not modify installed manifests if parent process has already set everything up.
// this avoids problems with determining IsAutoSDKSafe and doing an incorrect invalidate.
if (bAllowAutoSDKSwitching && !HasParentProcessSetupAutoSDK(out _))
{
bool bSetSomeSDK = false;
bool bHasRequiredManualSDK = HasRequiredManualSDK() == SDKStatus.Valid;
if (IsAutoSDKSafe() && (PreferAutoSDK() || !bHasRequiredManualSDK))
{
SetupAutoSDK();
bSetSomeSDK = true;
}
//Setup manual SDK if autoSDK setup was skipped or failed for whatever reason.
if (bHasRequiredManualSDK && (HasRequiredAutoSDKInstalled() != SDKStatus.Valid))
{
SetupManualSDK();
bSetSomeSDK = true;
}
if (!bSetSomeSDK)
{
InvalidateCurrentlyInstalledAutoSDK();
}
}
// print all SDKs to log file (errors will print out later for builds and generateprojectfiles)
PrintSDKInfoAndReturnValidity(LogEventType.Log, LogFormatOptions.NoConsoleOutput, LogEventType.Verbose, LogFormatOptions.NoConsoleOutput);
}
#endregion
}
}