// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using EpicGames.Core; using UnrealBuildBase; namespace UnrealBuildTool { /// /// Caches predefined state ("prelude") for compiler versions and options /// static class VCPreludeCache { /// /// Set of options which can influence the prelude state. Any options matching entries in this set will be used to generate a separate prelude file. /// static HashSet FilterArguments = new HashSet(StringComparer.OrdinalIgnoreCase) { "/EH", // Exception handling mode "/GR", // RTTI settings "/MD", // Multithreaded DLL runtime library "/MDd", // Multithreaded debug DLL runtime library "/MT", // Multithreaded static runtime library "/MTd", // Multithreaded debug static runtime library }; /// /// Object used for ensuring only one thread can create items in the cache at once /// static object LockObject = new object(); /// /// File containing a list of macros to be used to generate a prelude file /// static readonly FileReference MacroNamesFile = FileReference.Combine(Unreal.EngineDirectory, "Build", "Windows", "PreludeMacros.txt"); /// /// Base directory for the cache /// static readonly DirectoryReference PreludeCacheDir = DirectoryReference.Combine(Unreal.EngineDirectory, "Saved", "UnrealBuildTool", "Prelude", "MSVC"); /// /// The parsed list of macros /// static List MacroNames = new List(); /// /// Path to the file used to generate prelude headers /// static FileReference? PreludeGeneratorFile; /// /// Cached mapping from compiler filename to its version string /// static Dictionary CompilerExeToVersion = new Dictionary(); /// /// Get the prelude file (or create it) for the given compiler executable and set of options /// /// Path to the compiler executable /// Command line arguments for generating the prelude file /// Path to the prelude file public static FileReference GetPreludeHeader(FileReference Compiler, List Arguments) { // Cache the compiler version string? CompilerVersion; lock (CompilerExeToVersion) { if (!CompilerExeToVersion.TryGetValue(Compiler, out CompilerVersion)) { FileVersionInfo VersionInfo = FileVersionInfo.GetVersionInfo(Compiler.FullName); CompilerVersion = String.Format("{0} {1}", VersionInfo.ProductName, VersionInfo.ProductVersion); } } // Cache the macro list lock (MacroNames) { if (MacroNames.Count == 0) { string[] MacroListLines = FileReference.ReadAllLines(MacroNamesFile); foreach (string MacroListLine in MacroListLines) { string MacroName = MacroListLine.Trim(); if (MacroName.Length > 0 && MacroName[0] != ';') { MacroNames.Add(MacroName); } } } } // Create a digest from the command line string CommandLine = String.Join(" ", Arguments.Where(x => FilterArguments.Contains(x)).OrderBy(x => x)); ContentHash Digest = ContentHash.SHA1(String.Format("{0}\n{1}\n{2}", CompilerVersion, CommandLine, String.Join("\n", MacroNames))); // Get the prelude file FileReference PreludeHeader = FileReference.Combine(PreludeCacheDir, String.Format("{0}.h", Digest)); if (!FileReference.Exists(PreludeHeader)) { lock (LockObject) { if (!FileReference.Exists(PreludeHeader)) { CreatePreludeHeader(PreludeHeader, Compiler, CompilerVersion, CommandLine); } } } return PreludeHeader; } /// /// Creates a prelude header /// /// Path to the file to create /// Path to the compiler /// Version number of the compiler /// Command line arguments used to generate the header static void CreatePreludeHeader(FileReference PreludeHeader, FileReference Compiler, string CompilerVersion, string CommandLine) { // Make sure the cache directory exists DirectoryReference.CreateDirectory(PreludeCacheDir); // Make sure the prelude generator file exists if (PreludeGeneratorFile == null) { PreludeGeneratorFile = FileReference.Combine(PreludeCacheDir, "PreludeGenerator.cpp"); using (StreamWriter Writer = new StreamWriter(PreludeGeneratorFile.FullName)) { Writer.WriteLine("#define STRINGIZE_2(x) #x"); Writer.WriteLine("#define STRINGIZE(x) STRINGIZE_2(x)"); foreach (string MacroName in MacroNames) { Writer.WriteLine(""); Writer.WriteLine("#ifdef {0}", MacroName); Writer.WriteLine("#pragma message(\"#define {0} \" STRINGIZE({0}))", MacroName); Writer.WriteLine("#endif"); } } } // Invoke the compiler and capture the output using (ManagedProcessGroup ProcessGroup = new ManagedProcessGroup()) { string FullCommandLine = String.Format("{0} /nologo /c {1}", CommandLine, Utils.MakePathSafeToUseWithCommandLine(PreludeGeneratorFile.FullName)); using (ManagedProcess Process = new ManagedProcess(ProcessGroup, Compiler.FullName, FullCommandLine, PreludeCacheDir.FullName, null, null, ProcessPriorityClass.Normal)) { // Filter the output FileReference TempPreludeFile = new FileReference(PreludeHeader.FullName + ".temp"); using (StreamWriter Writer = new StreamWriter(TempPreludeFile.FullName)) { Writer.WriteLine("// Compiler: {0}", CompilerVersion); Writer.WriteLine("// Arguments: {0}", CommandLine); Writer.WriteLine(); string? Line; while (Process.TryReadLine(out Line)) { Line = Line.Trim(); if (Line.Length > 0) { if (Line.StartsWith("#define", StringComparison.Ordinal)) { Writer.WriteLine(Line); } else if (Line.Trim() != PreludeGeneratorFile.GetFileName()) { throw new BuildException("Unexpected output from compiler: {0}", Line); } } } } FileReference.Move(TempPreludeFile, PreludeHeader); } } } } }