// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; namespace EpicGames.Core { /// /// SDK installation status /// public enum SDKStatus { /// /// Desired SDK is installed and set up. /// Valid, /// /// Could not find the desired SDK, SDK setup failed, etc. /// Invalid, }; /// /// SDK for a platform /// abstract public class UEBuildPlatformSDK { // Public SDK handling, not specific to AutoSDK public UEBuildPlatformSDK() { } #region Global SDK Registration /// /// Registers the SDK for a given platform (as a string, but equivalent to UnrealTargetPlatform) /// /// SDK object /// Platform name for this SDK 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 if (HasParentProcessSetupAutoSDK(out CachedManualSDKVersion)) { // we pass along __None to indicate the parent didn't have a manual sdk installed if (CachedManualSDKVersion == "__None") { CachedManualSDKVersion = null; } } else { // if there was no parent, get the SDK version before we run AutoSDK to get the manual version CachedManualSDKVersion = GetInstalledSDKVersion(); } } #endregion #region Main Public Interface/Utilties /// /// Retrieves a previously registered SDK for a given platform /// /// String name of the platform (equivalent to UnrealTargetPlatform) /// public static UEBuildPlatformSDK GetSDKForPlatform(string PlatformName) { UEBuildPlatformSDK SDK; SDKRegistry.TryGetValue(PlatformName, out SDK); return SDK; } /// /// Gets the set of all known SDKs /// public static UEBuildPlatformSDK[] AllSDKs { 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 string GetInstalledVersion(out bool bIsAutoSDK) { bIsAutoSDK = HasSetupAutoSDK(); return GetInstalledSDKVersion(); } public string GetInstalledVersion() { return GetInstalledSDKVersion(); } 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, bool bForAutoSDK) { return IsVersionValidInternal(Version, bForAutoSDK); } public virtual bool IsSoftwareVersionValid(string Version) { return IsSoftwareVersionValidInternal(Version); } 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 /// /// Returns the installed SDK version, used to determine if up to date or not ( /// /// public abstract string GetInstalledSDKVersion(); /// /// Return the SDK version that the platform wants to use (AutoSDK dir must match this, full SDKs can be in a valid range) /// /// public abstract string GetMainVersion(); /// /// Returns a platform-specific version string that can be retrieved from anywhere without needing to typecast the SDK object /// /// Descriptor of the version you want /// Version string, or empty string if not known/supported public virtual string GetPlatformSpecificVersion(string VersionType) { return ""; } /// /// Return a list of full SDK versions that are already installed and can be quickly switched to without running any installers. /// /// public virtual string[] GetAllInstalledSDKVersions() { return new string[] { }; } /// /// Switch to another version of the SDK than what GetInstalledSDKVersion() returns. This will be one of the versions returned from GetAllInstalledSDKVersions() /// /// String name of the version to switch to /// If true, only switch for this process (usually via process environment variable). If false, switch permanently (usually via system-wide environment variable) /// True if successful public virtual bool SwitchToAlternateSDK(string Version, bool bSwitchForThisProcessOnly) { return false; } /// /// Gets the valid string range of Sdk versions. TryConvertVersionToInt() will need to succeed to make this usable for range checks /// /// Smallest version allowed /// Largest version allowed (inclusive) public abstract void GetValidVersionRange(out string MinVersion, out string MaxVersion); /// /// Gets the valid string range of software/flash versions. TryConvertVersionToInt() will need to succeed to make this usable for range checks /// /// Smallest version allowed, or null if no minmum (in other words, 0 - MaxVersion) /// Largest version allowed (inclusive), or null if no maximum (in other words, MinVersion - infinity)y public abstract void GetValidSoftwareVersionRange(out string MinVersion, out string MaxVersion); /// /// 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() /// /// public virtual string GetAutoSDKDirectoryForMainVersion() { return GetMainVersion(); } /// /// Gets the valid (integer) range of Sdk versions. Must be an integer to easily check a range vs a particular version /// /// Smallest version allowed /// Largest version allowed (inclusive) /// True if the versions are valid, false if the platform is unable to convert its versions into an integer 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, bool bForAutoSDK) { // 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 (bForAutoSDK) { // 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; } // convert it to an integer UInt64 IntVersion; if (!TryConvertVersionToInt(Version, out IntVersion)) { return false; } UInt64 DesiredVersion; if (!TryConvertVersionToInt(GetMainVersion(), out DesiredVersion)) { return false; } // short circuit range check if the Version is the desired version already if (IntVersion == DesiredVersion) { return true; } // get numeric range UInt64 MinVersion, MaxVersion; GetValidVersionRange(out MinVersion, out MaxVersion); return IntVersion >= MinVersion && IntVersion <= MaxVersion; } protected virtual bool IsSoftwareVersionValidInternal(string Version) { // 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; } // get numeric range UInt64 MinVersion, MaxVersion; GetValidSoftwareVersionRange(out MinVersion, out MaxVersion); return IntVersion >= MinVersion && IntVersion <= MaxVersion; } /// /// Only the platform can convert a version string into an integer that is usable for comparison /// /// Version that comes from the installed SDK or a Turnkey manifest or the like /// The integer version of StringValue, can be used to compare against a valid range /// If the StringValue was able to be be converted to an integer public virtual bool TryConvertVersionToInt(string StringValue, out UInt64 OutValue) { // @todo turnkey make this abstract? OutValue = 0; return false; } /// /// Allow the platform SDK to override the name it will use in AutoSDK, but default to the platform name /// /// The name of the directory to use inside the AutoSDK system public virtual string GetAutoSDKPlatformName() { return PlatformName; } #endregion #region Print SDK Info private static bool bHasShownTurnkey = false; public virtual SDKStatus PrintSDKInfoAndReturnValidity(LogEventType Verbosity = LogEventType.Console, LogFormatOptions Options = LogFormatOptions.None, LogEventType ErrorVerbosity = LogEventType.Error, LogFormatOptions ErrorOptions = LogFormatOptions.None) { string ManualSDKVersion, AutoSDKVersion; GetInstalledVersions(out ManualSDKVersion, out AutoSDKVersion); SDKStatus Validity = SDKStatus.Valid; bool HasValidAutoSDK = HasSetupAutoSDK(); if (HasValidAutoSDK) { string PlatformSDKRoot = GetPathToPlatformAutoSDKs(); UInt64 Ver; if (TryConvertVersionToInt(AutoSDKVersion, out Ver)) { Log.WriteLine(Verbosity, Options, "{0} using Auto SDK from: {1} 0x{2:X}", PlatformName, Path.Combine(PlatformSDKRoot, AutoSDKVersion), Ver); } else { HasValidAutoSDK = false; } } if (HasValidAutoSDK == false) { if (HasRequiredManualSDK() == SDKStatus.Valid) { Log.WriteLine(Verbosity, Options, "{0} using Manual SDK {1}", PlatformName, ManualSDKVersion); } else { Validity = SDKStatus.Invalid; string MinVersionString, MaxVersionString; GetValidVersionRange(out MinVersionString, out MaxVersionString); StringBuilder Msg = new StringBuilder(); Msg.AppendFormat("Unable to find a valid SDK for {0}.", PlatformName); if (ManualSDKVersion != null) { Msg.AppendFormat(" Found Version: {0}.", ManualSDKVersion); } if (MinVersionString != MaxVersionString) { Msg.AppendLine(" Must be between {0} and {1}", MinVersionString, MaxVersionString); } else { Msg.AppendLine(" Must be {0}", MinVersionString); } 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; } } // always print errors to the screen Log.WriteLine(ErrorVerbosity, ErrorOptions, Msg.ToString()); } } return Validity; } #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 string CachedManualSDKVersion; private static Dictionary SDKRegistry = new Dictionary(); #endregion // AutoSDKs handling portion #region protected AutoSDKs Utility /// /// Name of the file that holds currently install SDK version string /// protected const string CurrentlyInstalledSDKStringManifest = "CurrentlyInstalled.txt"; /// /// name of the file that holds the last succesfully run SDK setup script version /// protected const string LastRunScriptVersionManifest = "CurrentlyInstalled.Version.txt"; /// /// Filename of the script version file in each AutoSDK directory. Changing the contents of this file will force reinstallation of an AutoSDK. /// private const string ScriptVersionFilename = "Version.txt"; /// /// Name of the file that holds environment variables of current SDK /// protected const string SDKEnvironmentVarsFile = "OutputEnvVars.txt"; protected const string SDKRootEnvVar = "UE_SDKS_ROOT"; protected const string AutoSetupEnvVar = "AutoSDKSetup"; protected static bool IsWindows() { return Environment.OSVersion.Platform == PlatformID.Win32NT || Environment.OSVersion.Platform == PlatformID.Win32S || Environment.OSVersion.Platform == PlatformID.Win32Windows; } protected static bool IsMac() { // mono tends to return Unix on Mac, so check for a Mac-only file return Environment.OSVersion.Platform == PlatformID.MacOSX || (Environment.OSVersion.Platform == PlatformID.Unix && File.Exists ("/System/Library/CoreServices/SystemVersion.plist")); } private static string GetAutoSDKHostPlatform() { if (IsWindows()) { return "Win64"; } else if (IsMac()) { return "Mac"; } else if (Environment.OSVersion.Platform == PlatformID.Unix) { return "Linux"; } throw new Exception("Unknown host platform!"); } /// /// Whether platform supports switching SDKs during runtime /// /// true if supports 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(); } /// /// 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. /// /// Setup script version 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; } } /// /// Returns path to platform SDKs /// /// Valid SDK string protected string GetPathToPlatformAutoSDKs() { string SDKPath = ""; string SDKRoot = Environment.GetEnvironmentVariable(SDKRootEnvVar); if (SDKRoot != null) { if (SDKRoot != "") { SDKPath = Path.Combine(SDKRoot, "Host" + GetAutoSDKHostPlatform(), GetAutoSDKPlatformName()); } } return SDKPath; } /// /// Returns path to platform SDKs /// /// Valid SDK string public static bool TryGetHostPlatformAutoSDKDir(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; } } /// /// 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. /// protected string GetPlatformAutoSDKSetupEnvVar() { return GetAutoSDKPlatformName() + AutoSetupEnvVar; } /// /// Gets currently installed version /// /// absolute path to platform SDK root /// version string as currently installed /// true if was able to read it protected bool GetCurrentlyInstalledSDKString(string PlatformSDKRoot, out string OutInstalledSDKVersionString) { 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(); // don't allow ManualSDK installs to count as an AutoSDK install version. if (Type != null && Type == "AutoSDK") { if (Version != null) { OutInstalledSDKVersionString = Version; return true; } } } } } OutInstalledSDKVersionString = ""; return false; } /// /// Gets the version of the last successfully run setup script. /// /// absolute path to platform SDK root /// version string /// true if was able to read it 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; } /// /// Sets currently installed version /// /// SDK version string to set /// true if was able to set it protected bool SetCurrentlyInstalledAutoSDKString(String InstalledSDKVersionString) { 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"); 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"); } } } } 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; } /// /// Returns Hook names as needed by the platform /// (e.g. can be overridden with custom executables or scripts) /// /// Hook type protected virtual string GetHookExecutableName(SDKHookType Hook) { if (IsWindows()) { if (Hook == SDKHookType.Uninstall) { return "unsetup.bat"; } else { return "setup.bat"; } } else { if (Hook == SDKHookType.Uninstall) { return "unsetup.sh"; } else { return "setup.sh"; } } } /// /// Whether the hook must be run with administrator privileges. /// /// Hook for which to check the required privileges. /// true if the hook must be run with administrator privileges. 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); } } /// /// Runs install/uninstall hooks for SDK /// /// absolute path to platform SDK root /// version string to run for (can be empty!) /// which one of hooks to run /// whether a non-existing hook means failure /// true if succeeded protected virtual bool RunAutoSDKHooks(string PlatformSDKRoot, string SDKVersionString, SDKHookType Hook, bool bHookCanBeNonExistent = true) { if (!IsAutoSDKSafe()) { Log.TraceLog(GetAutoSDKPlatformName() + " attempted to run SDK hook which could have damaged manual SDK install!"); return false; } if (SDKVersionString != "") { string SDKDirectory = Path.Combine(PlatformSDKRoot, SDKVersionString); string HookExe = Path.Combine(SDKDirectory, GetHookExecutableName(Hook)); if (File.Exists(HookExe)) { Log.TraceLog("Running {0} hook {1}", Hook, HookExe); // run it Process HookProcess = new Process(); HookProcess.StartInfo.WorkingDirectory = SDKDirectory; HookProcess.StartInfo.FileName = HookExe; HookProcess.StartInfo.Arguments = ""; 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) { Log.TraceLog("Hook exited uncleanly (returned {0}), considering it failed.", HookProcess.ExitCode); return false; } return true; } else { Log.TraceLog("File {0} does not exist", HookExe); } } else { Log.TraceLog("Version string is blank for {0}. Can't determine {1} hook.", PlatformSDKRoot, Hook.ToString()); } return bHookCanBeNonExistent; } /// /// Loads environment variables from SDK /// If any commands are added or removed the handling needs to be duplicated in /// TargetPlatformManagerModule.cpp /// /// absolute path to platform SDK /// true if succeeded protected bool SetupEnvironmentFromAutoSDK(string PlatformSDKRoot) { string EnvVarFile = Path.Combine(PlatformSDKRoot, SDKEnvironmentVarsFile); if (File.Exists(EnvVarFile)) { using (StreamReader Reader = new StreamReader(EnvVarFile)) { List PathAdds = new List(); List PathRemoves = new List(); List EnvVarNames = new List(); List EnvVarValues = new List(); bool bNeedsToWriteAutoSetupEnvVar = true; String PlatformSetupEnvVar = GetPlatformAutoSDKSetupEnvVar(); for (; ; ) { string VariableString = Reader.ReadLine(); if (VariableString == null) { break; } string[] Parts = VariableString.Split('='); if (Parts.Length != 2) { Log.TraceLog("Incorrect environment variable declaration:"); Log.TraceLog(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]; Log.TraceVerbose("Setting variable '{0}' to '{1}'", EnvVarName, EnvVarValue); Environment.SetEnvironmentVariable(EnvVarName, EnvVarValue); } // actually perform the PATH stripping / adding. String OrigPathVar = Environment.GetEnvironmentVariable("PATH"); String PathDelimiter = IsWindows() ? ";" : ":"; String[] PathVars = { }; if (!String.IsNullOrEmpty(OrigPathVar)) { PathVars = OrigPathVar.Split(PathDelimiter.ToCharArray()); } else { Log.TraceVerbose("Path environment variable is null during AutoSDK"); } List ModifiedPathVars = new List(); 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) { Log.TraceVerbose("Removing Path: '{0}'", 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) { Log.TraceVerbose("Removing Path: '{0}'", PathVar); ModifiedPathVars.Remove(PathVar); } } } // perform adds, but don't add duplicates foreach (String PathAdd in PathAdds) { if (!ModifiedPathVars.Contains(PathAdd)) { Log.TraceVerbose("Adding Path: '{0}'", 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 version was before setting up autosdk string ValueToWrite = CachedManualSDKVersion != null ? CachedManualSDKVersion : "__None"; Environment.SetEnvironmentVariable(GetPlatformAutoSDKSetupEnvVar(), ValueToWrite); return true; } } else { Log.TraceLog("Cannot set up environment for {1} because command file {2} 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); } } } /// /// 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. /// public SDKStatus HasRequiredAutoSDKInstalled() { if (PlatformSupportsAutoSDKs() && HasAutoSDKSystemEnabled()) { string AutoSDKRoot = GetPathToPlatformAutoSDKs(); if (AutoSDKRoot != "") { // check script version so script fixes can be propagated without touching every build machine's CurrentlyInstalled file manually. bool bScriptVersionMatches = false; string CurrentScriptVersionString; if (GetLastRunScriptVersionString(AutoSDKRoot, out CurrentScriptVersionString) && CurrentScriptVersionString == GetRequiredScriptVersionString()) { bScriptVersionMatches = true; } // check to make sure OutputEnvVars doesn't need regenerating string EnvVarFile = Path.Combine(AutoSDKRoot, SDKEnvironmentVarsFile); bool bEnvVarFileExists = File.Exists(EnvVarFile); string CurrentSDKString; if (bEnvVarFileExists && GetCurrentlyInstalledSDKString(AutoSDKRoot, out CurrentSDKString) && CurrentSDKString == GetAutoSDKDirectoryForMainVersion() && bScriptVersionMatches) { return SDKStatus.Valid; } return SDKStatus.Invalid; } } 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(out string OutAutoSDKSetupValue) { bool bParentProcessSetupAutoSDK = false; String AutoSDKSetupVarName = GetPlatformAutoSDKSetupEnvVar(); OutAutoSDKSetupValue = Environment.GetEnvironmentVariable(AutoSDKSetupVarName); if (!String.IsNullOrEmpty(OutAutoSDKSetupValue)) { bParentProcessSetupAutoSDK = true; } return bParentProcessSetupAutoSDK; } 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() { 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; } /// /// 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. /// private void SetupAutoSDK() { if (IsAutoSDKSafe() && PlatformSupportsAutoSDKs() && HasAutoSDKSystemEnabled()) { // run installation for autosdk if necessary. if (HasRequiredAutoSDKInstalled() == SDKStatus.Invalid) { //reset check status so any checking sdk status after the attempted setup will do a real check again. SDKCheckStatus = -1; string AutoSDKRoot = GetPathToPlatformAutoSDKs(); string CurrentSDKString; GetCurrentlyInstalledSDKString(AutoSDKRoot, out CurrentSDKString); // switch over (note that version string can be empty) if (!RunAutoSDKHooks(AutoSDKRoot, CurrentSDKString, SDKHookType.Uninstall)) { Log.TraceLog("Failed to uninstall currently installed SDK {0}", CurrentSDKString); InvalidateCurrentlyInstalledAutoSDK(); return; } // delete Manifest file to avoid multiple uninstalls InvalidateCurrentlyInstalledAutoSDK(); if (!RunAutoSDKHooks(AutoSDKRoot, GetAutoSDKDirectoryForMainVersion(), SDKHookType.Install, false)) { Log.TraceLog("Failed to install required SDK {0}. Attemping to uninstall", GetAutoSDKDirectoryForMainVersion()); RunAutoSDKHooks(AutoSDKRoot, GetAutoSDKDirectoryForMainVersion(), SDKHookType.Uninstall, false); return; } string EnvVarFile = Path.Combine(AutoSDKRoot, SDKEnvironmentVarsFile); if (!File.Exists(EnvVarFile)) { Log.TraceLog("Installation of required SDK {0}. Did not generate Environment file {1}", GetAutoSDKDirectoryForMainVersion(), EnvVarFile); RunAutoSDKHooks(AutoSDKRoot, GetAutoSDKDirectoryForMainVersion(), SDKHookType.Uninstall, false); return; } SetCurrentlyInstalledAutoSDKString(GetAutoSDKDirectoryForMainVersion()); SetLastRunAutoSDKScriptVersion(GetRequiredScriptVersionString()); } // fixup process environment to match autosdk SetupEnvironmentFromAutoSDK(); } } /// /// Allows the platform to optionally returns a path to the internal SDK /// /// Valid path to the internal SDK, null otherwise public virtual string GetInternalSDKPath() { return null; } #endregion #region public AutoSDKs Utility /// /// Enum describing types of hooks a platform SDK can have /// 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)) { Log.TraceLog("Failed to load environment from required SDK {0}", GetAutoSDKDirectoryForMainVersion()); InvalidateCurrentlyInstalledAutoSDK(); return SDKStatus.Invalid; } return SDKStatus.Valid; } /// /// Whether the required external SDKs are installed for this platform. /// Could be either a manual install or an AutoSDK. /// 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 } }