Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.Build/UEBuildPlatformSDK.cs
2025-05-18 13:04:45 +08:00

2352 lines
81 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;
using UnrealBuildBase;
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 ulong MinInt = 0;
public ulong MaxInt = UInt64.MaxValue;
public ulong 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(UEBuildPlatformSDK InPlatformSDK)
{
public virtual string DefaultName => "Sdk";
public virtual bool IsValid(string Version, SDKDescriptor Info)
{
return PlatformSDK.IsVersionValid(Version, Info);
}
public UEBuildPlatformSDK PlatformSDK = InPlatformSDK;
public List<SDKDescriptor> Sdks = [];
public List<SDKDescriptor> FullSdks => Sdks.Where(x => String.Compare(x.Name, "AutoSdk", true) != 0).ToList();
public SDKDescriptor? AutoSdk => Sdks.FirstOrDefault(x => String.Compare(x.Name, "AutoSdk", true) == 0);
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, null, null, this));
}
// convenience for single unnamed Sdk current value
public SDKCollection(string? Current, UEBuildPlatformSDK PlatformSDK)
: this(PlatformSDK)
{
if (Current != null)
{
Sdks.Add(new SDKDescriptor(DefaultName, null, null, Current, null, this));
}
}
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 => "Software";
public override bool IsValid(string Version, SDKDescriptor Info)
{
return PlatformSDK.IsSoftwareVersionValid(Version, Info);
}
}
/// <summary>
/// SDK for a platform
/// </summary>
abstract public class UEBuildPlatformSDK(ILogger InLogger)
{
// Public SDK handling, not specific to AutoSDK
protected readonly ILogger 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>
/// <param name="bIsSdkAllowedOnHost"></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;
// load the SDK config file
if (bIsSdkAllowedOnHost)
{
LoadJsonFile(PlatformName);
}
// 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 = [];
}
else
{
Logger.LogInformation("Processing parent manual sdk: {SDKVersion}", ParentManualSDKVersions);
CachedManualSDKVersions = JsonSerializer.Deserialize<Dictionary<string, string>>(ParentManualSDKVersions) ?? [];
}
}
else
{
// allow the platform to select the most appropriate manually installed SDK if it hasn't already been set up by AutoSDK
bool bHasAutoSDK = PlatformSupportsAutoSDKs() && HasAutoSDKSystemEnabled(); // @todo: PlatformSupportsAutoSDKs & HasAutoSDKSystemEnabled check is not technically needed for manual sdk switching but the editor-side code is heavily tied into this and would require further refactoring first
if ( (bHasAutoSDK || !ManualSDKAutoSwitchRequiresAutoSDK) && !HasParentProcessSetupAutoSDK(out _))
{
if (TrySelectBestManualSDK( out string? SelectedSDKVersion ))
{
Logger.LogInformation("Auto-selected best installed sdk for {PlatformName} - {SDKVersion}", GetAutoSDKPlatformName(), SelectedSDKVersion);
}
}
CachedManualSDKVersions = [];
// 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;
}
public static T? GetSDKForPlatformOrMakeTemp<T>(string PlatformName) where T : UEBuildPlatformSDK
{
UEBuildPlatformSDK? SDK;
SDKRegistry.TryGetValue(PlatformName, out SDK);
// make a temp one if needed, this is not expected to happen often at all
if (SDK == null)
{
if (!TempSDKRegistry.TryGetValue(PlatformName, out SDK))
{
object[] parameter = [Log.Logger];
SDK = (UEBuildPlatformSDK)Activator.CreateInstance(typeof(T), parameter)!;
// by setting this to false, we don't require any of the RequiredVersions to exist, but if they do, they will be read
// this is useful on other platforms that
SDK.bIsSdkAllowedOnHost = false;
SDK.LoadJsonFile(PlatformName);
TempSDKRegistry.Add(PlatformName, SDK);
}
}
return (T?)SDK;
}
/// <summary>
/// Gets the set of all known SDKs
/// </summary>
public static UEBuildPlatformSDK[] AllPlatformSDKObjects => [.. SDKRegistry.Values];
// True if at least one platform has had its version changed from the standard SDK version, which means it may not be compatible with other projects
public static bool bHasAnySDKOverride = false;
// True if this SDK has had its version changed from the standard SDK version, which means it may not be compatible with other projects
public bool bHasSDKOverride = false;
// Contains a list of projects that had per-project SDK overrides, used for validating conflicting SDKs
public List<FileReference> ProjectsThatOverrodeSDK = [];
// 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);
if (bIsSdkAllowedOnHost)
{
// 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)
{
// 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
// if there is only one known SDK named "Software" we allow any DeviceType
IEnumerable<SDKDescriptor> SoftwareSDKs = GetValidSoftwareVersions().Sdks;
bool bAllowAnyDeviceType = DeviceType == null || (SoftwareSDKs.Count() == 1 && SoftwareSDKs.First().Name == "Software");
foreach (SDKDescriptor Desc in SoftwareSDKs)
{
// 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 virtual string GetMainVersion()
{
// by default, look up in SDK config value
return GetRequiredVersionFromConfig("MainVersion");
}
/// <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)
{
// by default, use SDK config file values
MinVersion = GetVersionFromConfig("MinVersion");
MaxVersion = GetVersionFromConfig("MaxVersion");
}
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)
{
MinVersion = GetVersionFromConfig("MinSoftwareVersion");
MaxVersion = GetVersionFromConfig("MaxSoftwareVersion");
}
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 (bIsSdkAllowedOnHost)
{
// 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 GetRequiredVersionFromConfig(VersionType);
}
/// <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 [];
}
/// <summary>
/// Find the highest valid manually-installed SDK, prioritizing the main version if it's available. Requires that the platform SDK implements GetAllInstalledSDKVersions
/// </summary>
/// <returns>True if successful</returns>
protected virtual string? FindBestInstalledSDKVersion()
{
string[] InstalledSDKVersions = GetAllInstalledSDKVersions();
if (InstalledSDKVersions.Length == 0)
{
return null;
}
// see if the main version is available
string MainVersion = GetMainVersion();
if (InstalledSDKVersions.Contains(MainVersion))
{
return MainVersion;
}
// main version is not available - find highest valid version
GetValidVersionRange(out string? MinVersion, out string? MaxVersion);
if (TryConvertVersionToInt(MinVersion, out ulong MinVersionInt, null) && TryConvertVersionToInt(MaxVersion, out ulong MaxVersionInt, null))
{
List<Tuple<ulong,ulong>> BannedSdkVersionRanges = [];
foreach (string SdkVersionPair in GetStringArrayFromConfig("BannedSdkVersions"))
{
string[] Versions = SdkVersionPair.Split("-");
if (Versions.Length > 0 && Versions.Length <= 2)
{
if (TryConvertVersionToInt(Versions.First(), out ulong MinInvalidVersion, null) && TryConvertVersionToInt(Versions.Last(), out ulong MaxInvalidVersion, null))
{
BannedSdkVersionRanges.Add( new( MinInvalidVersion, MaxInvalidVersion));
}
}
}
IGrouping<ulong,string>? ResultGroup = InstalledSDKVersions
.GroupBy( X => TryConvertVersionToInt(X, out ulong Value, null) ? Value : 0 ) // group by integer version
.Where( X => X.Key >= MinVersionInt && X.Key <= MaxVersionInt) // select only valid versions
.OrderByDescending( X => X.Key ) // sort highest version first
.OrderBy(X => BannedSdkVersionRanges.Any(R => X.Key >= R.Item1 && X.Key <= R.Item2)) // put banned versions at the end of the list - they'll be excluded later
.FirstOrDefault();
if (ResultGroup?.FirstOrDefault() != null && BannedSdkVersionRanges.Any(R => ResultGroup.Key >= R.Item1 && ResultGroup.Key <= R.Item2))
{
Logger.LogInformation("The best installed SDK for {Platform} is {Version} but this version is forbidden", PlatformName, ResultGroup.FirstOrDefault());
return null;
}
return ResultGroup?.FirstOrDefault();
}
return null;
}
/// <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>
/// Whether the automatic switching to a locally-installed manual SDK requires AutoSDK to be configured
/// This is typically true unless the editor-side binaries do not depend on a version-specific SDK environment variable or path
/// i.e. the SDK version is compiled into the binary and can be used directly.
/// </summary>
public virtual bool ManualSDKAutoSwitchRequiresAutoSDK => true;
/// <summary>
/// Allows a platform to switch a different version of a manually-installed SDK, if the platform supports side-by-side installations of different versions
/// </summary>
/// <returns>True if successful</returns>
protected virtual bool TryGetEnvironmentForManualSDK(string SelectedSDKVersion, out Dictionary<string, string>? EnvVarValues, out List<string>? PathAdds, out List<string>? PathRemoves)
{
EnvVarValues = null;
PathAdds = null;
PathRemoves = null;
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()
{
// if there is an AutoSDKDirectory property, use it, otherwise, use the MainVersion string
return GetVersionFromConfig("AutoSDKDirectory") ?? 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
ulong IntVersion;
if (!TryConvertVersionToInt(Version, out IntVersion, VersionInfo.Name))
{
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
ulong 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 ulong 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 ulong ConvertVersionToInt(string? StringValue, string? Hint = null)
{
// quickly handle null input, which is not necessarily an error case
if (StringValue == null)
{
return 0;
}
ulong 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>
/// Compare two sdk versions, for Sort() purposes
/// e.g. SdkVersions.Sort( (A,B) => PlatformSDK.SdkVersionsCompare(A,B) );
/// </summary>
/// <param name="StringValueA">First Version to compare</param>
/// <param name="StringValueB">Second Version to compare</param>
/// <param name="Hint">A platform specific hint that can help guide conversion (usually SDKName or device type)</param>
/// <returns>Comparison integer</returns>
public int SdkVersionsCompare(string? StringValueA, string? StringValueB, string? Hint = null)
{
ulong ValueA, ValueB;
TryConvertVersionToInt(StringValueA, out ValueA, Hint);
TryConvertVersionToInt(StringValueB, out ValueB, Hint);
if (ValueA == ValueB)
{
return 0;
}
else if (ValueA < ValueB)
{
return -1;
}
else
{
return 1;
}
}
/// <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 GetVersionFromConfig("AutoSDKPlatform") ?? PlatformName!;
}
#endregion
#region Per-project SDK support
private static List<FileReference> ProjectsToCheckForSDKOverrides = [];
public static void InitializePerProjectSDKVersions(IEnumerable<FileReference> ProjectsToCheckForOverrides)
{
ProjectsToCheckForSDKOverrides = ProjectsToCheckForOverrides.ToList();
}
#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, bool bBriefInvalidSDKWarnings = false)
{
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.AppendLine($"Unable to find valid SDK(s) for {PlatformName}:");
foreach (SDKDescriptor Desc in SDKInfo.Sdks)
{
if (Desc.Validity == SDKStatus.Valid)
{
Msg.AppendLine($" {Desc.Name} is valid ({Desc}");
}
else
{
Msg.Append($" Found {Desc.Name} Version");
if (Desc.Current != null)
{
Msg.Append($"={Desc.Current}");
}
Msg.AppendLine($", {Desc.ToString("Required", null, false)}.");
}
}
if (!bBriefInvalidSDKWarnings && !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 = [];
private static Dictionary<string, UEBuildPlatformSDK> SDKRegistry = [];
// this map holds on to some temporary SDK objects that are generally used once and don't want to stick around, but they could be used multiple times,
// like in MicrosoftPlatofrmSDK.Version.cs, if one of the versions is needed, C# will go construct every version, needing the SDK multiple times
private static Dictionary<string, UEBuildPlatformSDK> TempSDKRegistry = [];
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;
}
// cached SDK info from the SDK.json file
private Dictionary<string, string> ConfigSDKVersions = new(StringComparer.OrdinalIgnoreCase);
private Dictionary<string, string[]> ConfigSDKVersionArrays = new(StringComparer.OrdinalIgnoreCase);
private void LoadJsonFile(string Platform)
{
// fixup for Windows
if (Platform == "Win64")
{
Platform = "Windows";
}
FileReference? MakeConfigFilename(DirectoryReference RootDir, bool bIsRequired)
{
FileReference PlatformExtensionLocation = FileReference.Combine(RootDir, "Platforms", Platform, "Config", $"{Platform}_SDK.json");
if (FileReference.Exists(PlatformExtensionLocation))
{
return PlatformExtensionLocation;
}
FileReference StandardLocation = FileReference.Combine(RootDir, "Config", Platform, $"{Platform}_SDK.json");
if (FileReference.Exists(StandardLocation))
{
return StandardLocation;
}
if (bIsRequired)
{
throw new Exception($"Failed to find required SDK.json for {Platform}. Looked in '{StandardLocation}' and '{PlatformExtensionLocation}'.");
}
return null;
}
// if the SDK isn't allowed on the host, then allow it to not exist
FileReference EngineSDKConfigFile = MakeConfigFilename(Unreal.EngineDirectory, bIsSdkAllowedOnHost)!;
// load the file, along with any chained group file
ProcessJsonFile(EngineSDKConfigFile, ConfigSDKVersions, ConfigSDKVersionArrays);
// copy off the versions to the defaults, so we can check if overridden by the project
Dictionary<string, string> DefaultConfigSDKVersions = new(ConfigSDKVersions);
foreach (string Key in ConfigSDKVersions.Keys)
{
DefaultConfigSDKVersions[Key] = ConfigSDKVersions[Key];
}
// now read overrides into the array
foreach (FileReference ProjectFile in ProjectsToCheckForSDKOverrides)
{
FileReference? ProjectSDKConfigFile = MakeConfigFilename(ProjectFile.Directory, false);
if (ProjectSDKConfigFile != null)
{
// load a project's SDK file if thre is one, into a temp dictionary
Dictionary<string, string> OverrideConfigSDKVersions = new(StringComparer.OrdinalIgnoreCase);
Dictionary<string, string[]> OverrideConfigSDKVersionArrays = new(StringComparer.OrdinalIgnoreCase);
ProcessJsonFile(ProjectSDKConfigFile, OverrideConfigSDKVersions, OverrideConfigSDKVersionArrays);
if (OverrideConfigSDKVersionArrays.Count > 0)
{
throw new Exception($"Overriding version arrays, in project '{ProjectFile.GetFileNameWithoutExtension()}', platform {Platform} is not currently supported");
}
// currently only care about MainVersion in the overrides
string? OverrideMainVersion;
const string MainVersionKey = "MainVersion";
if (OverrideConfigSDKVersions.TryGetValue(MainVersionKey, out OverrideMainVersion))
{
// if different from default, then remmber it, and mark that it's been overridden
if (!OverrideMainVersion.Equals(DefaultConfigSDKVersions[MainVersionKey], StringComparison.OrdinalIgnoreCase))
{
// now check if it was already overridden, in which case we have a conflict we can't resolve, so error
if (ProjectsThatOverrodeSDK.Count > 0 && !OverrideMainVersion.Equals(ConfigSDKVersions[MainVersionKey], StringComparison.OrdinalIgnoreCase))
{
throw new Exception($"Project {ProjectFile.GetFileNameWithoutAnyExtensions()} wants to override {Platform} SDK to {OverrideMainVersion}, but it was already overridden to version {ConfigSDKVersions[MainVersionKey]}");
}
Logger.LogWarning("Project {Project} is overriding {Platform} SDK to {OverrideMainVersion}", ProjectFile.GetFileNameWithoutAnyExtensions(), Platform, OverrideMainVersion);
ConfigSDKVersions[MainVersionKey] = OverrideMainVersion;
UEBuildPlatformSDK.bHasAnySDKOverride = true;
bHasSDKOverride = true;
ProjectsThatOverrodeSDK.Add(ProjectFile);
}
}
}
}
}
static Lazy<JsonSerializerOptions>ProcessJsonFileOptions = new(() => new() {
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip,
});
private void ProcessJsonFile(FileReference SDKConfigFile, Dictionary<string, string> VersionMap, Dictionary<string, string[]> VersionArrayMap)
{
string Contents = FileReference.ReadAllText(SDKConfigFile);
// load the json into a dictionary
Dictionary<string, JsonElement>? LoadedDictionary = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(Contents, ProcessJsonFileOptions.Value);
if (LoadedDictionary == null)
{
throw new Exception($"Failed to parse SDK version file '{SDKConfigFile}'");
}
// look for special key to chain to other - after processing everything else (we only add from the "parent" settings not already in the map)
List<string> Parents = [];
foreach (string Key in LoadedDictionary.Keys)
{
JsonElement Obj = LoadedDictionary[Key];
string? StringValue = Obj.ValueKind == JsonValueKind.String ? Obj.GetString() : null;
string[]? ArrayValue = Obj.ValueKind == JsonValueKind.Array ? Obj.EnumerateArray().Select(x => x.GetString()!).ToArray() : null;
if (StringValue != null)
{
if (Key.Equals("ParentSDKFile", StringComparison.OrdinalIgnoreCase))
{
Parents.Add(StringValue);
}
// add this key if it's not already in the (case-insensitive) dictionary
VersionMap.TryAdd(Key, StringValue);
}
else if (ArrayValue != null)
{
VersionArrayMap.Add(Key, ArrayValue);
}
}
// now load parents, filling in unset properties
foreach (string Parent in Parents)
{
FileReference ParentConfigFile = FileReference.Combine(SDKConfigFile.Directory, Parent);
ProcessJsonFile(ParentConfigFile, VersionMap, VersionArrayMap);
}
}
private string GetHostSpecificVersionName(string VersionName)
{
string HostPlatform = "Win64";
if (OperatingSystem.IsMacOS())
{
HostPlatform = "Mac";
}
else if (OperatingSystem.IsLinux())
{
HostPlatform = "Linux";
}
return $"{VersionName}_{HostPlatform}";
}
public string GetRequiredVersionFromConfig(string VersionName)
{
// when bIsRequired is true, then we know it will return non-null
return GetVersionFromConfig(VersionName, bIsRequired: true)!;
}
public string? GetVersionFromConfig(string VersionName, bool bIsRequired = false)
{
string? Version;
// look up both Version_Host and Version (Host specific version wins)
if (ConfigSDKVersions!.TryGetValue(GetHostSpecificVersionName(VersionName), out Version) || ConfigSDKVersions.TryGetValue(VersionName, out Version))
{
return Version;
}
if (bIsRequired)
{
throw new Exception($"Unable to find required SDK version '{VersionName}' for platform {PlatformName}. Check your SDK.json files");
}
return null;
}
protected VersionNumber GetRequiredVersionNumberFromConfig(string VersionName)
{
// required won't ever return null
return GetVersionNumberFromConfig(VersionName, true)!;
}
public VersionNumber? GetVersionNumberFromConfig(string VersionName, bool bIsRequired = false)
{
string? VersionString = GetVersionFromConfig(VersionName, bIsRequired);
if (VersionString == null)
{
return null;
}
return VersionNumber.Parse(VersionString);
}
private VersionNumberRange? ParseVersionNumberRange(string Range)
{
string[] Versions = Range.Split("-");
if (Versions.Length != 2)
{
return null;
}
return VersionNumberRange.Parse(Versions[0], Versions[1]);
}
public VersionNumberRange? GetRequiredVersionNumberRangeFromConfig(string VersionName, bool bIsRequired = false)
{
// required won't ever return null
return GetVersionNumberRangeFromConfig(VersionName, true)!;
}
public VersionNumberRange? GetVersionNumberRangeFromConfig(string VersionName, bool bIsRequired = false)
{
string? VersionRange;
if (!ConfigSDKVersions!.TryGetValue(GetHostSpecificVersionName(VersionName), out VersionRange) && !ConfigSDKVersions.TryGetValue(VersionName, out VersionRange))
{
if (bIsRequired)
{
throw new Exception($"Unable to find required SDK version range '{VersionName}' for platform {PlatformName}. Check your SDK.json files");
}
return null;
}
VersionNumberRange? Range = ParseVersionNumberRange(VersionRange);
if (Range == null && bIsRequired)
{
throw new Exception($"Unable to parse the version number range for required version '{VersionName}' for platform {PlatformName}. Check your SDK.json files");
}
return Range;
}
public VersionNumberRange[] GetVersionNumberRangeArrayFromConfig(string VersionName)
{
string[]? VersionRanges;
List<VersionNumberRange> Ranges = [];
if (ConfigSDKVersionArrays.TryGetValue(GetHostSpecificVersionName(VersionName), out VersionRanges) || ConfigSDKVersionArrays.TryGetValue(VersionName, out VersionRanges))
{
foreach (string VersionRange in VersionRanges)
{
VersionNumberRange? Range = ParseVersionNumberRange(VersionRange);
if (Range != null)
{
Ranges.Add(Range);
}
}
}
return [.. Ranges];
}
public string[] GetStringArrayFromConfig(string Name)
{
string[]? Results;
if (ConfigSDKVersionArrays.TryGetValue(GetHostSpecificVersionName(Name), out Results) || ConfigSDKVersionArrays.TryGetValue(Name, out Results))
{
return Results;
}
return [];
}
#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 ManualSDKEnvironmentVarsFile = "ManualSDKEnvVars.txt";
protected const string SDKRootEnvVar = "UE_SDKS_ROOT";
protected const string AutoSetupEnvVar = "AutoSDKSetup";
protected const string ManualSetupEnvVar = "ManualSDKSetup";
private static string GetAutoSDKHostPlatform()
{
if (OperatingSystem.IsWindows())
{
return "Win64";
}
else if (OperatingSystem.IsMacOS())
{
return "Mac";
}
else if (OperatingSystem.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 primary 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>
/// <param name="OutInstalledSDKLevel"></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>
/// <param name="InstalledSDKLevelString"></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");
}
}
}
}
public static void ClearManualSDKEnvVarCache()
{
string ManualSDKEnvironmentVarsPath = Path.Combine(Unreal.EngineDirectory.ToString(), "Intermediate", ManualSDKEnvironmentVarsFile);
if (File.Exists(ManualSDKEnvironmentVarsPath))
{
File.Delete(ManualSDKEnvironmentVarsPath);
}
}
private bool TrySelectBestManualSDK(out string? SelectedSDKVersion)
{
// find the best valid manual sdk
SelectedSDKVersion = FindBestInstalledSDKVersion();
if (SelectedSDKVersion == null)
{
return false;
}
// query the platform for environment changes
if (!TryGetEnvironmentForManualSDK(SelectedSDKVersion, out Dictionary<string, string>? EnvVarValues, out List<string>? PathAdds, out List<string>? PathRemoves))
{
return false;
}
// apply environment variables
if (EnvVarValues != null)
{
foreach (KeyValuePair<string,string> EnvVarValue in EnvVarValues)
{
Environment.SetEnvironmentVariable(EnvVarValue.Key, EnvVarValue.Value);
}
}
// apply PATH modifications
if (PathRemoves != null || PathAdds != null)
{
string OrigPathVar = Environment.GetEnvironmentVariable("PATH")!;
IEnumerable<string> PathVars = OrigPathVar.Split( Path.PathSeparator );
if (PathRemoves != null)
{
PathVars = PathVars.Except(PathRemoves, FileUtils.PlatformPathComparer);
}
if (PathAdds != null)
{
PathVars = PathVars.Except(PathAdds, FileUtils.PlatformPathComparer); // remove all of the ADDs so that if this function is executed multiple times, the paths will be guaranteed to be in the same order after each run.
PathVars = PathVars.Union(PathAdds, FileUtils.PlatformPathComparer);
}
string NewPathVar = String.Join( Path.PathSeparator, PathVars );
Environment.SetEnvironmentVariable("PATH", NewPathVar);
}
// write all environment modifications
if ( (EnvVarValues != null && EnvVarValues.Count > 0) || (PathRemoves != null && PathRemoves.Count > 0) || (PathAdds != null && PathAdds.Count > 0) )
{
string ManualSDKEnvironmentVarsPath = Path.Combine(Unreal.EngineDirectory.ToString(), "Intermediate", ManualSDKEnvironmentVarsFile);
Directory.CreateDirectory(Path.GetDirectoryName(ManualSDKEnvironmentVarsPath)!);
using (StreamWriter Writer = File.AppendText(ManualSDKEnvironmentVarsPath))
{
// write environment variables
if (EnvVarValues != null)
{
foreach (KeyValuePair<string,string> EnvVarValue in EnvVarValues)
{
Writer.WriteLine($"{EnvVarValue.Key}={EnvVarValue.Value}");
Logger.LogDebug("Setting variable '{Name}' to '{Value}'", EnvVarValue.Key, EnvVarValue.Value);
}
}
// write PATH modifications
if (PathRemoves != null)
{
foreach (string PathVar in PathRemoves)
{
Writer.WriteLine($"strippath={PathVar}");
Logger.LogDebug("Removing Path: '{Path}'", PathVar);
}
}
if (PathAdds != null)
{
foreach (string PathVar in PathAdds)
{
Writer.WriteLine($"addpath={PathVar}");
Logger.LogDebug("Adding Path: '{Path}'", PathVar);
}
}
}
}
return true;
}
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 (OperatingSystem.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 =
[
"NONE",
"BUILD",
"PACKAGE",
"RUN",
"FULL",
];
private bool IsSDKLevelAtLeast(string Value, string ComparedTo)
{
int ValueIndex = AutoSDKLevels.FindIndex(x => x.Equals(Value, StringComparison.OrdinalIgnoreCase));
int ComparedIndex = AutoSDKLevels.FindIndex(x => x.Equals(ComparedTo, StringComparison.OrdinalIgnoreCase));
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 = [];
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.TryGetValue(PlatformName, out string? value))
{
FinalAutoSDKLevel = value;
}
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="AutoSDKLevel"></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";
//Forcing the old .Net Framework default to prevent processes from failing
HookProcess.StartInfo.UseShellExecute = true;
}
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 = [];
List<string> PathRemoves = [];
List<string> EnvVarNames = [];
List<string> EnvVarValues = [];
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 = OperatingSystem.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 = [.. 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 int 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.Log, LogFormatOptions.NoConsoleOutput, bBriefInvalidSDKWarnings:true);
}
#endregion
}
}