// 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);
}
}
}
}
}