/// <summary>
        /// Loads a UBTMakefile from disk
        /// </summary>
        /// <param name="MakefilePath">Path to the makefile to load</param>
        /// <param name="ProjectFile">Path to the project file</param>
        /// <param name="ReasonNotLoaded">If the function returns null, this string will contain the reason why</param>
        /// <param name="WorkingSet">Interface to query which source files are in the working set</param>
        /// <returns>The loaded makefile, or null if it failed for some reason.  On failure, the 'ReasonNotLoaded' variable will contain information about why</returns>
        public static UBTMakefile LoadUBTMakefile(FileReference MakefilePath, FileReference ProjectFile, ISourceFileWorkingSet WorkingSet, out string ReasonNotLoaded)
        {
            // Check the directory timestamp on the project files directory.  If the user has generated project files more
            // recently than the UBTMakefile, then we need to consider the file to be out of date
            FileInfo UBTMakefileInfo = new FileInfo(MakefilePath.FullName);

            if (!UBTMakefileInfo.Exists)
            {
                // UBTMakefile doesn't even exist, so we won't bother loading it
                ReasonNotLoaded = "no existing makefile";
                return(null);
            }

            // Check the build version
            FileInfo BuildVersionFileInfo = new FileInfo(BuildVersion.GetDefaultFileName().FullName);

            if (BuildVersionFileInfo.Exists && UBTMakefileInfo.LastWriteTime.CompareTo(BuildVersionFileInfo.LastWriteTime) < 0)
            {
                Log.TraceVerbose("Existing makefile is older than Build.version, ignoring it");
                ReasonNotLoaded = "Build.version is newer";
                return(null);
            }

            // @todo ubtmake: This will only work if the directory timestamp actually changes with every single GPF.  Force delete existing files before creating new ones?  Eh... really we probably just want to delete + create a file in that folder
            //			-> UPDATE: Seems to work OK right now though on Windows platform, maybe due to GUID changes
            // @todo ubtmake: Some platforms may not save any files into this folder.  We should delete + generate a "touch" file to force the directory timestamp to be updated (or just check the timestamp file itself.  We could put it ANYWHERE, actually)

            // Installed Build doesn't need to check engine projects for outdatedness
            if (!UnrealBuildTool.IsEngineInstalled())
            {
                if (DirectoryReference.Exists(ProjectFileGenerator.IntermediateProjectFilesPath))
                {
                    DateTime EngineProjectFilesLastUpdateTime = new FileInfo(ProjectFileGenerator.ProjectTimestampFile).LastWriteTime;
                    if (UBTMakefileInfo.LastWriteTime.CompareTo(EngineProjectFilesLastUpdateTime) < 0)
                    {
                        // Engine project files are newer than UBTMakefile
                        Log.TraceVerbose("Existing makefile is older than generated engine project files, ignoring it");
                        ReasonNotLoaded = "project files are newer";
                        return(null);
                    }
                }
            }

            // Check the game project directory too
            if (ProjectFile != null)
            {
                string   ProjectFilename = ProjectFile.FullName;
                FileInfo ProjectFileInfo = new FileInfo(ProjectFilename);
                if (!ProjectFileInfo.Exists || UBTMakefileInfo.LastWriteTime.CompareTo(ProjectFileInfo.LastWriteTime) < 0)
                {
                    // .uproject file is newer than UBTMakefile
                    Log.TraceVerbose("Makefile is older than .uproject file, ignoring it");
                    ReasonNotLoaded = ".uproject file is newer";
                    return(null);
                }

                DirectoryReference MasterProjectRelativePath        = ProjectFile.Directory;
                string             GameIntermediateProjectFilesPath = Path.Combine(MasterProjectRelativePath.FullName, "Intermediate", "ProjectFiles");
                if (Directory.Exists(GameIntermediateProjectFilesPath))
                {
                    DateTime GameProjectFilesLastUpdateTime = new DirectoryInfo(GameIntermediateProjectFilesPath).LastWriteTime;
                    if (UBTMakefileInfo.LastWriteTime.CompareTo(GameProjectFilesLastUpdateTime) < 0)
                    {
                        // Game project files are newer than UBTMakefile
                        Log.TraceVerbose("Makefile is older than generated game project files, ignoring it");
                        ReasonNotLoaded = "game project files are newer";
                        return(null);
                    }
                }
            }

            // Check to see if UnrealBuildTool.exe was compiled more recently than the UBTMakefile
            DateTime UnrealBuildToolTimestamp = new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime;

            if (UBTMakefileInfo.LastWriteTime.CompareTo(UnrealBuildToolTimestamp) < 0)
            {
                // UnrealBuildTool.exe was compiled more recently than the UBTMakefile
                Log.TraceVerbose("Makefile is older than UnrealBuildTool.exe, ignoring it");
                ReasonNotLoaded = "UnrealBuildTool.exe is newer";
                return(null);
            }

            // Check to see if any BuildConfiguration files have changed since the last build
            List <XmlConfig.InputFile> InputFiles = XmlConfig.FindInputFiles();

            foreach (XmlConfig.InputFile InputFile in InputFiles)
            {
                FileInfo InputFileInfo = new FileInfo(InputFile.Location.FullName);
                if (InputFileInfo.LastWriteTime > UBTMakefileInfo.LastWriteTime)
                {
                    Log.TraceVerbose("Makefile is older than BuildConfiguration.xml, ignoring it");
                    ReasonNotLoaded = "BuildConfiguration.xml is newer";
                    return(null);
                }
            }

            UBTMakefile LoadedUBTMakefile = null;

            try
            {
                DateTime LoadUBTMakefileStartTime = DateTime.UtcNow;

                using (FileStream Stream = new FileStream(UBTMakefileInfo.FullName, FileMode.Open, FileAccess.Read))
                {
                    BinaryFormatter Formatter = new BinaryFormatter();
                    LoadedUBTMakefile = Formatter.Deserialize(Stream) as UBTMakefile;
                }

                if (UnrealBuildTool.bPrintPerformanceInfo)
                {
                    double LoadUBTMakefileTime = (DateTime.UtcNow - LoadUBTMakefileStartTime).TotalSeconds;
                    Log.TraceInformation("LoadUBTMakefile took " + LoadUBTMakefileTime + "s");
                }
            }
            catch (Exception Ex)
            {
                Log.TraceWarning("Failed to read makefile: {0}", Ex.Message);
                ReasonNotLoaded = "couldn't read existing makefile";
                return(null);
            }

            if (!LoadedUBTMakefile.IsValidMakefile())
            {
                Log.TraceWarning("Loaded makefile appears to have invalid contents, ignoring it ({0})", UBTMakefileInfo.FullName);
                ReasonNotLoaded = "existing makefile appears to be invalid";
                return(null);
            }

            // Check if any of the target's Build.cs files are newer than the makefile
            foreach (UEBuildTarget Target in LoadedUBTMakefile.Targets)
            {
                string TargetCsFilename = Target.TargetRulesFile.FullName;
                if (TargetCsFilename != null)
                {
                    FileInfo TargetCsFile        = new FileInfo(TargetCsFilename);
                    bool     bTargetCsFileExists = TargetCsFile.Exists;
                    if (!bTargetCsFileExists || TargetCsFile.LastWriteTime > UBTMakefileInfo.LastWriteTime)
                    {
                        Log.TraceVerbose("{0} has been {1} since makefile was built, ignoring it ({2})", TargetCsFilename, bTargetCsFileExists ? "changed" : "deleted", UBTMakefileInfo.FullName);
                        ReasonNotLoaded = string.Format("changes to target files");
                        return(null);
                    }
                }

                IEnumerable <string> BuildCsFilenames = Target.GetAllModuleBuildCsFilenames();
                foreach (string BuildCsFilename in BuildCsFilenames)
                {
                    if (BuildCsFilename != null)
                    {
                        FileInfo BuildCsFile        = new FileInfo(BuildCsFilename);
                        bool     bBuildCsFileExists = BuildCsFile.Exists;
                        if (!bBuildCsFileExists || BuildCsFile.LastWriteTime > UBTMakefileInfo.LastWriteTime)
                        {
                            Log.TraceVerbose("{0} has been {1} since makefile was built, ignoring it ({2})", BuildCsFilename, bBuildCsFileExists ? "changed" : "deleted", UBTMakefileInfo.FullName);
                            ReasonNotLoaded = string.Format("changes to module files");
                            return(null);
                        }
                    }
                }

                foreach (FlatModuleCsDataType FlatCsModuleData in Target.FlatModuleCsData.Values)
                {
                    if (FlatCsModuleData.BuildCsFilename != null && FlatCsModuleData.ExternalDependencies.Count > 0)
                    {
                        string BaseDir = Path.GetDirectoryName(FlatCsModuleData.BuildCsFilename);
                        foreach (string ExternalDependency in FlatCsModuleData.ExternalDependencies)
                        {
                            FileInfo DependencyFile        = new FileInfo(Path.Combine(BaseDir, ExternalDependency));
                            bool     bDependencyFileExists = DependencyFile.Exists;
                            if (!bDependencyFileExists || DependencyFile.LastWriteTime > UBTMakefileInfo.LastWriteTime)
                            {
                                Log.TraceVerbose("{0} has been {1} since makefile was built, ignoring it ({2})", DependencyFile.FullName, bDependencyFileExists ? "changed" : "deleted", UBTMakefileInfo.FullName);
                                ReasonNotLoaded = string.Format("changes to external dependency");
                                return(null);
                            }
                        }
                    }
                }
            }

            // We do a check to see if any modules' headers have changed which have
            // acquired or lost UHT types.  If so, which should be rare,
            // we'll just invalidate the entire makefile and force it to be rebuilt.
            foreach (UEBuildTarget Target in LoadedUBTMakefile.Targets)
            {
                // Get all H files in processed modules newer than the makefile itself
                HashSet <string> HFilesNewerThanMakefile =
                    new HashSet <string>(
                        Target.FlatModuleCsData
                        .SelectMany(x => x.Value.ModuleSourceFolder != null ? Directory.EnumerateFiles(x.Value.ModuleSourceFolder.FullName, "*.h", SearchOption.AllDirectories) : Enumerable.Empty <string>())
                        .Where(y => Directory.GetLastWriteTimeUtc(y) > UBTMakefileInfo.LastWriteTimeUtc)
                        .OrderBy(z => z).Distinct()
                        );

                // Get all H files in all modules processed in the last makefile build
                HashSet <string> AllUHTHeaders = new HashSet <string>(Target.FlatModuleCsData.Select(x => x.Value).SelectMany(x => x.UHTHeaderNames));

                // Check whether any headers have been deleted. If they have, we need to regenerate the makefile since the module might now be empty. If we don't,
                // and the file has been moved to a different module, we may include stale generated headers.
                foreach (string FileName in AllUHTHeaders)
                {
                    if (!File.Exists(FileName))
                    {
                        Log.TraceVerbose("File processed by UHT was deleted ({0}); invalidating makefile", FileName);
                        ReasonNotLoaded = string.Format("UHT file was deleted");
                        return(null);
                    }
                }

                // Makefile is invalid if:
                // * There are any newer files which contain no UHT data, but were previously in the makefile
                // * There are any newer files contain data which needs processing by UHT, but weren't not previously in the makefile
                foreach (string Filename in HFilesNewerThanMakefile)
                {
                    bool bContainsUHTData = CPPHeaders.DoesFileContainUObjects(Filename);
                    bool bWasProcessed    = AllUHTHeaders.Contains(Filename);
                    if (bContainsUHTData != bWasProcessed)
                    {
                        Log.TraceVerbose("{0} {1} contain UHT types and now {2} , ignoring it ({3})", Filename, bWasProcessed ? "used to" : "didn't", bWasProcessed ? "doesn't" : "does", UBTMakefileInfo.FullName);
                        ReasonNotLoaded = string.Format("new files with reflected types");
                        return(null);
                    }
                }
            }

            // If adaptive unity build is enabled, do a check to see if there are any source files that became part of the
            // working set since the Makefile was created (or, source files were removed from the working set.)  If anything
            // changed, then we'll force a new Makefile to be created so that we have fresh unity build blobs.  We always
            // want to make sure that source files in the working set are excluded from those unity blobs (for fastest possible
            // iteration times.)
            if (LoadedUBTMakefile.bUseAdaptiveUnityBuild)
            {
                // Check if any source files in the working set no longer belong in it
                foreach (FileItem SourceFile in LoadedUBTMakefile.SourceFileWorkingSet)
                {
                    if (!WorkingSet.Contains(SourceFile.Location) && File.GetLastWriteTimeUtc(SourceFile.AbsolutePath) > UBTMakefileInfo.LastWriteTimeUtc)
                    {
                        Log.TraceVerbose("{0} was part of source working set and now is not; invalidating makefile ({1})", SourceFile.AbsolutePath, UBTMakefileInfo.FullName);
                        ReasonNotLoaded = string.Format("working set of source files changed");
                        return(null);
                    }
                }

                // Check if any source files that are eligible for being in the working set have been modified
                foreach (FileItem SourceFile in LoadedUBTMakefile.CandidateSourceFilesForWorkingSet)
                {
                    if (WorkingSet.Contains(SourceFile.Location) && File.GetLastWriteTimeUtc(SourceFile.AbsolutePath) > UBTMakefileInfo.LastWriteTimeUtc)
                    {
                        Log.TraceVerbose("{0} was part of source working set and now is not; invalidating makefile ({1})", SourceFile.AbsolutePath, UBTMakefileInfo.FullName);
                        ReasonNotLoaded = string.Format("working set of source files changed");
                        return(null);
                    }
                }
            }

            ReasonNotLoaded = null;
            return(LoadedUBTMakefile);
        }
        /// <summary>
        /// Saves a UBTMakefile to disk
        /// </summary>
        /// <param name="TargetDescs">List of targets.  Order is not important</param>
        /// <param name="HotReload">The hot reload state</param>
        /// <param name="UBTMakefile">The UBT makefile</param>
        public static void SaveUBTMakefile(List <TargetDescriptor> TargetDescs, EHotReload HotReload, UBTMakefile UBTMakefile)
        {
            if (!UBTMakefile.IsValidMakefile())
            {
                throw new BuildException("Can't save a makefile that has invalid contents.  See UBTMakefile.IsValidMakefile()");
            }

            DateTime TimerStartTime = DateTime.UtcNow;

            FileItem UBTMakefileItem = FileItem.GetItemByFileReference(GetUBTMakefilePath(TargetDescs, HotReload));

            // @todo ubtmake: Optimization: The UBTMakefile saved for game projects is upwards of 9 MB.  We should try to shrink its content if possible
            // @todo ubtmake: Optimization: C# Serialization may be too slow for these big Makefiles.  Loading these files often shows up as the slower part of the assembling phase.

            // Serialize the cache to disk.
            try
            {
                Directory.CreateDirectory(Path.GetDirectoryName(UBTMakefileItem.AbsolutePath));
                using (FileStream Stream = new FileStream(UBTMakefileItem.AbsolutePath, FileMode.Create, FileAccess.Write))
                {
                    BinaryFormatter Formatter = new BinaryFormatter();
                    Formatter.Serialize(Stream, UBTMakefile);
                }
            }
            catch (Exception Ex)
            {
                Log.TraceError("Failed to write makefile: {0}", Ex.Message);
            }

            if (UnrealBuildTool.bPrintPerformanceInfo)
            {
                TimeSpan TimerDuration = DateTime.UtcNow - TimerStartTime;
                Log.TraceInformation("Saving makefile took " + TimerDuration.TotalSeconds + "s");
            }
        }