Hi,
I am building UE4 use FastBuild. after build Engine. each time I debug my game from VS2019, UBT detected Engine is outdated, full building occurs, because *.h.txt, *.cpp.txt does not exist! How can I avoid full rebuilding?
where is the code generated *.h.txt and *.cpp.txt file?
// Copyright 2018 Yassine Riahi and Liam Flookes. Provided under a MIT License, see license file on github.
// Used to generate a fastbuild .bff file from UnrealBuildTool to allow caching and distributed builds.
// Tested with Windows 10, Visual Studio 2015/2017, Unreal Engine 4.19.1, FastBuild v0.95
// Durango is fully supported (Compiles with VS2015).
// Orbis will likely require some changes.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Linq;
using Tools.DotNETCommon;
namespace UnrealBuildTool
{
class FASTBuild : ActionExecutor
{
/---- Configurable User settings ----/
// Used to specify a non-standard location for the FBuild.exe, for example if you have not added it to your PATH environment variable.
public static string FBuildExePathOverride = @“C:\FASTBuild\FBuild.exe”;
// Controls network build distribution
private static bool bEnableDistribution = true;
// Controls whether to use caching at all. CachePath and CacheMode are only relevant if this is enabled.
private static bool bEnableCaching = true;
// Location of the shared cache, it could be a local or network path (i.e: @"\DESKTOP-BEAST\FASTBuildCache").
// Only relevant if bEnableCaching is true;
private static string CachePath = “”;
public enum eCacheMode
{
ReadWrite, // This machine will both read and write to the cache
ReadOnly, // This machine will only read from the cache, use for developer machines when you have centralized build machines
WriteOnly, // This machine will only write from the cache, use for build machines when you have centralized build machines
}
// Cache access mode
// Only relevant if bEnableCaching is true;
private eCacheMode CacheMode = eCacheMode.ReadWrite;
/--------------------------------------/
public override string Name
{
get { return “FASTBuild”; }
}
public static bool IsAvailable()
{
if (bEnableCaching && CachePath.Length < 2)
{
CachePath = Environment.GetEnvironmentVariable(“FASTBUILD_CACHE_PATH”);
}
if (FBuildExePathOverride != “”)
{
return File.Exists(FBuildExePathOverride);
}
// Get the name of the FASTBuild executable.
string fbuild = “fbuild”;
if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64)
{
fbuild = “fbuild.exe”;
}
// Search the path for it
string PathVariable = Environment.GetEnvironmentVariable(“PATH”);
foreach (string SearchPath in PathVariable.Split(Path.PathSeparator))
{
try
{
string PotentialPath = Path.Combine(SearchPath, fbuild);
if (File.Exists(PotentialPath))
{
return true;
}
}
catch (ArgumentException)
{
// PATH variable may contain illegal characters; just ignore them.
}
}
return false;
}
private HashSet<string> ForceLocalCompileModules = new HashSet<string>()
{“Module.ProxyLODMeshReduction”,
“GoogleVRController”};
public UnrealTargetPlatform TargetPlatform = UnrealTargetPlatform.Win64;
List<int> LocalExecutedIndex = new List<int>();
private void DetectBuildType(List<Action> Actions)
{
foreach (Action action in Actions)
{
if (action.ActionType != ActionType.Compile && action.ActionType != ActionType.Link)
continue;
else if (action.CommandArguments.Contains("Intermediate\\Build\\XboxOne"))
{
TargetPlatform = UnrealTargetPlatform.XboxOne;
return;
}
else if (action.CommandPath.FullName.Contains("Windows")) //Not a great test.
{
TargetPlatform = UnrealTargetPlatform.Win64;
return;
}
}
}
private bool IsMSVC() { return TargetPlatform == UnrealTargetPlatform.Win64 || TargetPlatform == UnrealTargetPlatform.XboxOne; }
private bool IsPS4() { return TargetPlatform == UnrealTargetPlatform.PS4; }
private string GetCompilerName()
{
switch (TargetPlatform)
{
default:
case UnrealTargetPlatform.XboxOne:
case UnrealTargetPlatform.Win64: return “UE4Compiler”;
//case UnrealTargetPlatform.PS4: return “UE4PS4Compiler”;
}
}
//Run FASTBuild on the list of actions. Relies on fbuild.exe being in the path.
public override bool ExecuteActions(List<Action> Actions, bool bLogDetailedActionStats)
{
bool FASTBuildResult = true;
if (Actions.Count > 0)
{
DetectBuildType(Actions);
string FASTBuildFilePath = Path.Combine(UnrealBuildTool.EngineDirectory.FullName, "Intermediate", "Build", "fbuild.bff");
List<Action> LocalExecutorActions = new List<Action>();
if (CreateBffFile(Actions, FASTBuildFilePath, LocalExecutorActions))
{
FASTBuildResult = ExecuteBffFile(FASTBuildFilePath);
if (FASTBuildResult)
{
LocalExecutor localExecutor = new LocalExecutor();
FASTBuildResult = localExecutor.ExecuteActions(LocalExecutorActions, bLogDetailedActionStats);
}
}
else
{
FASTBuildResult = false;
}
}
return FASTBuildResult;
}
private void AddText(string StringToWrite)
{
byte] Info = new System.Text.UTF8Encoding(true).GetBytes(StringToWrite);
bffOutputFileStream.Write(Info, 0, Info.Length);
}
private Dictionary<string, string> ParseCommandLineOptions(string CompilerCommandLine, string] SpecialOptions, bool SaveResponseFile = false, bool SkipInputFile = false)
{
Dictionary<string, string> ParsedCompilerOptions = new Dictionary<string, string>();
string] CmdLineTokens = CompilerCommandLine.Trim().Split(’ ');
List<string> ProcessedTokens = new List<string>();
string ResponseFilePath = “”;
int RespIndex = -1;
for (int i = 0; i < CmdLineTokens.Length; ++i)
{
if (CmdLineTokens*.StartsWith("@""))
{
RespIndex = i;
}
}
if (RespIndex >= 0) //Response files are in 4.13 by default. Changing VCToolChain to not do this is probably better.
{
string responseCommandline = CmdLineTokens[RespIndex];
ResponseFilePath = responseCommandline.Substring(2, responseCommandline.Length - 3); // bit of a bodge to get the @"response.txt" path...
if (ResponseFilePath.EndsWith("dep.response"))
{
Console.WriteLine("Error!!!!!!!!");
}
try
{
string ResponseFileText = File.ReadAllText(ResponseFilePath);
char] Separators = { '\r', '
’ };
if (File.Exists(ResponseFilePath))
{
ProcessedTokens.AddRange(ResponseFileText.Split(Separators, StringSplitOptions.RemoveEmptyEntries)); //Certainly not ideal
}
}
catch (Exception e)
{
Console.WriteLine("Looks like a response file in: " + CompilerCommandLine + ", but we could not load it! " + e.Message);
ResponseFilePath = "";
}
}
//Processed tokens should now have ‘whole’ tokens, so now we look for any specified special options
foreach (string specialOption in SpecialOptions)
{
for (int i = 0; i < ProcessedTokens.Count; ++i)
{
if (ProcessedTokens*.StartsWith(specialOption))
{
int StartIndex = specialOption.Length;
if (ProcessedTokens*[StartIndex] == ’ ')
{
++StartIndex;
}
ParsedCompilerOptions[specialOption] = ProcessedTokens*.Substring(StartIndex);
ProcessedTokens.RemoveAt(i);
break;
}
}
}
//The search for the input file… we take the first non-argument we can find
if (!SkipInputFile)
{
for (int i = 0; i < ProcessedTokens.Count; ++i)
{
string Token = ProcessedTokens*;
if (Token.Length == 0)
{
continue;
}
if (Token.StartsWith("/I") || Token.StartsWith("/l") || Token.StartsWith("/D") || Token.StartsWith("-D") || Token.StartsWith("-x") || Token.StartsWith("-include")) // Skip tokens with values, I for cpp includes, l for resource compiler includes
{
//++i;
continue;
}
else if (!Token.StartsWith("/") && !Token.StartsWith("-") && !Token.StartsWith(""-"))
{
ParsedCompilerOptions"InputFile"] = Token;
ProcessedTokens.RemoveAt(i);
--i;
break;
}
}
}
ParsedCompilerOptions"OtherOptions"] = string.Join(" ", ProcessedTokens) + " ";
if (SaveResponseFile && !string.IsNullOrEmpty(ResponseFilePath))
{
ParsedCompilerOptions"@"] = ResponseFilePath;
}
return ParsedCompilerOptions;
}
private string GetOptionValue(Dictionary<string, string> OptionsDictionary, string Key, Action Action, bool ProblemIfNotFound = false)
{
string Value = string.Empty;
if (OptionsDictionary.TryGetValue(Key, out Value))
{
return Value.Trim(new Char] { ‘"’ });
}
if (ProblemIfNotFound)
{
Console.WriteLine("We failed to find " + Key + “, which may be a problem.”);
Console.WriteLine("Action.CommandArguments: " + Action.CommandArguments);
}
return Value;
}
public string GetRegistryValue(string keyName, string valueName, object defaultValue)
{
object returnValue = (string)Microsoft.Win32.Registry.GetValue(“HKEY_LOCAL_MACHINE\SOFTWARE” + keyName, valueName, defaultValue);
if (returnValue != null)
return returnValue.ToString();
returnValue = Microsoft.Win32.Registry.GetValue(“HKEY_CURRENT_USER\SOFTWARE” + keyName, valueName, defaultValue);
if (returnValue != null)
return returnValue.ToString();
returnValue = (string)Microsoft.Win32.Registry.GetValue(“HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node” + keyName, valueName, defaultValue);
if (returnValue != null)
return returnValue.ToString();
returnValue = Microsoft.Win32.Registry.GetValue(“HKEY_CURRENT_USER\SOFTWARE\Wow6432Node” + keyName, valueName, defaultValue);
if (returnValue != null)
return returnValue.ToString();
return defaultValue.ToString();
}
private void WriteEnvironmentSetup()
{
DirectoryReference VCInstallDir = null;
string VCToolPath64 = “”;
VCEnvironment VCEnv = null;
try
{
// This may fail if the caller emptied PATH; we try to ignore the problem since
// it probably means we are building for another platform.
if (TargetPlatform == UnrealTargetPlatform.Win64)
{
VCEnv = VCEnvironment.Create(WindowsPlatform.GetDefaultCompiler(null), CppPlatform.Win64, null, null);
}
else if (TargetPlatform == UnrealTargetPlatform.XboxOne)
{
// If you have XboxOne source access, uncommenting the line below will be better for selecting the appropriate version of the compiler.
// Translate the XboxOne compiler to the right Windows compiler to set the VC environment vars correctly…
WindowsCompiler windowsCompiler = WindowsCompiler.VisualStudio2017;
VCEnv = VCEnvironment.Create(windowsCompiler, CppPlatform.Win64, null, null);
}
}
catch (Exception)
{
Console.WriteLine(“Failed to get Visual Studio environment.”);
}
// Copy environment into a case-insensitive dictionary for easier key lookups
Dictionary<string, string> envVars = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables())
{
envVars(string)entry.Key] = (string)entry.Value;
}
if (envVars.ContainsKey(“CommonProgramFiles”))
{
AddText("#import CommonProgramFiles
");
}
if (envVars.ContainsKey(“DXSDK_DIR”))
{
AddText("#import DXSDK_DIR
");
}
if (envVars.ContainsKey(“DurangoXDK”))
{
AddText("#import DurangoXDK
");
}
if (VCEnv != null)
{
string platformVersionNumber = “VSVersionUnknown”;
switch (VCEnv.Compiler)
{
case WindowsCompiler.VisualStudio2019:
platformVersionNumber = "140";
break;
case WindowsCompiler.VisualStudio2017:
// For now we are working with the 140 version, might need to change to 141 or 150 depending on the version of the Toolchain you chose
// to install
platformVersionNumber = "140";
break;
default:
string exceptionString = "Error: Unsupported Visual Studio Version.";
Console.WriteLine(exceptionString);
throw new BuildException(exceptionString);
}
if (!WindowsPlatform.TryGetVSInstallDir(WindowsPlatform.GetDefaultCompiler(null), out VCInstallDir))
{
string exceptionString = "Error: Cannot locate Visual Studio Installation.";
Console.WriteLine(exceptionString);
throw new BuildException(exceptionString);
}
VCToolPath64 = VCEnv.CompilerPath.ToString();//VCEnvironment.GetVCToolPath64(WindowsPlatform.GetDefaultCompiler(null), VCEnv.ToolChainDir).ToString();
string debugVCToolPath64 = VCEnv.CompilerPath.Directory.ToString();
AddText(string.Format(".WindowsSDKBasePath = '{0}'
", VCEnv.WindowsSdkDir));
AddText("Compiler('UE4ResourceCompiler')
{
“);
AddText(string.Format(” .Executable = ‘{0}’
“, VCEnv.ResourceCompilerPath));
AddText(” .CompilerFamily = ‘custom’
“);
AddText(”}
");
AddText("Compiler('UE4Compiler')
{
");
AddText(string.Format(" .Root = '{0}'
“, VCEnv.CompilerPath.Directory));
AddText(” .Executable = ‘$Root$/cl.exe’
“);
AddText(” .ExtraFiles =
{
“);
AddText(” ‘$Root$/c1.dll’
“);
AddText(” ‘$Root$/c1xx.dll’
“);
AddText(” ‘$Root$/c2.dll’
");
if (File.Exists(FileReference.Combine(VCEnv.CompilerPath.Directory, "1033/clui.dll").ToString())) //Check English first...
{
AddText(" '$Root$/1033/clui.dll'
“);
}
else
{
var numericDirectories = Directory.GetDirectories(VCToolPath64).Where(d => Path.GetFileName(d).All(char.IsDigit));
var cluiDirectories = numericDirectories.Where(d => Directory.GetFiles(d, “clui.dll”).Any());
if (cluiDirectories.Any())
{
AddText(string.Format(” ‘$Root$/{0}/clui.dll’
“, Path.GetFileName(cluiDirectories.First())));
}
}
AddText(” ‘$Root$/mspdbsrv.exe’
“);
AddText(” ‘$Root$/mspdbcore.dll’
");
AddText(string.Format(" '$Root$/mspft{0}.dll'
“, platformVersionNumber));
AddText(string.Format(” ‘$Root$/msobj{0}.dll’
“, platformVersionNumber));
AddText(string.Format(” ‘$Root$/mspdb{0}.dll’
", platformVersionNumber));
string RedistFilePath = string.Format("{0}/VC/Auxiliary/Build/Microsoft.VCRedistVersion.default.txt", VCInstallDir.ToString());
string RedistVersionNumber = File.ReadAllText(RedistFilePath).TrimEnd('\r', '
');
if (VCEnv.Compiler == WindowsCompiler.VisualStudio2019)
{
AddText(string.Format(" '{0}/VC/Redist/MSVC/{1}/x64/Microsoft.VC142.CRT/msvcp{2}.dll'
“, VCInstallDir.ToString(), RedistVersionNumber, platformVersionNumber));
AddText(string.Format(” ‘{0}/VC/Redist/MSVC/{1}/x64/Microsoft.VC142.CRT/vccorlib{2}.dll’
“, VCInstallDir.ToString(), RedistVersionNumber, platformVersionNumber));
}
else
{
//VS 2017 is really confusing in terms of version numbers and paths so these values might need to be modified depending on what version of the tool chain you
// chose to install.
AddText(string.Format(” ‘{0}/VC/Redist/MSVC/{1}/x64/Microsoft.VC141.CRT/msvcp{2}.dll’
“, VCInstallDir.ToString(), RedistVersionNumber, platformVersionNumber));
AddText(string.Format(” ‘{0}/VC/Redist/MSVC/{1}/x64/Microsoft.VC141.CRT/vccorlib{2}.dll’
", VCInstallDir.ToString(), RedistVersionNumber, platformVersionNumber));
}
AddText(" }
"); //End extra files
AddText("}
"); //End compiler
}
if (envVars.ContainsKey(“SCE_ORBIS_SDK_DIR”))
{
AddText(string.Format(".SCE_ORBIS_SDK_DIR = ‘{0}’
“, envVars"SCE_ORBIS_SDK_DIR”]));
AddText(string.Format(".PS4BasePath = ‘{0}/host_tools/bin’
“, envVars"SCE_ORBIS_SDK_DIR”]));
AddText("Compiler(‘UE4PS4Compiler’)
{
“);
AddText(” .Executable = ‘$PS4BasePath$/orbis-clang.exe’
“);
AddText(” .ExtraFiles = ‘$PS4BasePath$/orbis-snarl.exe’
“);
AddText(”}
");
}
AddText("Settings
{
");
// Optional cachePath user setting
if (bEnableCaching && CachePath != “”)
{
AddText(string.Format(" .CachePath = ‘{0}’
", CachePath));
}
//Start Environment
AddText(" .Environment =
{
“);
if (VCEnv != null)
{
AddText(string.Format(” “PATH={0}\Common7\IDE\;{1}”,
“, VCInstallDir.ToString(), VCToolPath64));
if (VCEnv.IncludePaths.Count() > 0)
{
AddText(string.Format(” “INCLUDE={0}”,
“, String.Join(”;", VCEnv.IncludePaths.Select(x => x))));
}
if (VCEnv.LibraryPaths.Count() > 0)
{
AddText(string.Format(" "LIB={0}",
“, String.Join(”;", VCEnv.LibraryPaths.Select(x => x))));
}
}
if (envVars.ContainsKey(“TMP”))
AddText(string.Format(" “TMP={0}”,
“, envVars"TMP”]));
if (envVars.ContainsKey(“SystemRoot”))
AddText(string.Format(" “SystemRoot={0}”,
“, envVars"SystemRoot”]));
if (envVars.ContainsKey(“INCLUDE”))
AddText(string.Format(" “INCLUDE={0}”,
“, envVars"INCLUDE”]));
if (envVars.ContainsKey(“LIB”))
AddText(string.Format(" “LIB={0}”,
“, envVars"LIB”]));
AddText(" }
“); //End environment
AddText(”}
"); //End Settings
}
private void AddCompileAction(Action Action, int ActionIndex, List<int> DependencyIndices)
{
string CompilerName = GetCompilerName();
if (Action.CommandPath.FullName.Contains(“rc.exe”))
{
CompilerName = “UE4ResourceCompiler”;
}
string] SpecialCompilerOptions = { “/Fo”, “/fo”, “/Yc”, “/Yu”, “/Fp”, “-o” };
var ParsedCompilerOptions = ParseCommandLineOptions(Action.CommandArguments, SpecialCompilerOptions);
string OutputObjectFileName = GetOptionValue(ParsedCompilerOptions, IsMSVC() ? “/Fo” : “-o”, Action, ProblemIfNotFound: !IsMSVC());
if (IsMSVC() && string.IsNullOrEmpty(OutputObjectFileName)) // Didn’t find /Fo, try /fo
{
OutputObjectFileName = GetOptionValue(ParsedCompilerOptions, “/fo”, Action, ProblemIfNotFound: true);
}
if (string.IsNullOrEmpty(OutputObjectFileName)) //No /Fo or /fo, we’re probably in trouble.
{
Console.WriteLine(“We have no OutputObjectFileName. Bailing.”);
return;
}
string IntermediatePath = Path.GetDirectoryName(OutputObjectFileName);
if (string.IsNullOrEmpty(IntermediatePath))
{
Console.WriteLine(“We have no IntermediatePath. Bailing.”);
Console.WriteLine("Our Action.CommandArguments were: " + Action.CommandArguments);
return;
}
string InputFile = GetOptionValue(ParsedCompilerOptions, “InputFile”, Action, ProblemIfNotFound: true);
if (string.IsNullOrEmpty(InputFile))
{
Console.WriteLine(“We have no InputFile. Bailing.”);
return;
}
AddText(string.Format("ObjectList(‘Action_{0}’)
{{
“, ActionIndex));
AddText(string.Format(” .Compiler = ‘{0}’
“, CompilerName));
AddText(string.Format(” .CompilerInputFiles = “{0}”
“, InputFile));
AddText(string.Format(” .CompilerOutputPath = “{0}”
", IntermediatePath));
bool bSkipDistribution = false;
foreach (var it in ForceLocalCompileModules)
{
if (Path.GetFullPath(InputFile).Contains(it))
{
bSkipDistribution = true;
break;
}
}
if (!Action.bCanExecuteRemotely || !Action.bCanExecuteRemotelyWithSNDBS || bSkipDistribution)
{
AddText(string.Format(" .AllowDistribution = false
"));
}
string OtherCompilerOptions = GetOptionValue(ParsedCompilerOptions, “OtherOptions”, Action);
string CompilerOutputExtension = “.unset”;
if (ParsedCompilerOptions.ContainsKey("/Yc")) //Create PCH
{
string PCHIncludeHeader = GetOptionValue(ParsedCompilerOptions, “/Yc”, Action, ProblemIfNotFound: true);
string PCHOutputFile = GetOptionValue(ParsedCompilerOptions, “/Fp”, Action, ProblemIfNotFound: true);
AddText(string.Format(" .CompilerOptions = '"%1" /Fo"%2" /Fp"{0}" /Yu"{1}" {2} '
", PCHOutputFile, PCHIncludeHeader, OtherCompilerOptions));
AddText(string.Format(" .PCHOptions = '"%1" /Fp"%2" /Yc"{0}" {1} /Fo"{2}"'
“, PCHIncludeHeader, OtherCompilerOptions, OutputObjectFileName));
AddText(string.Format(” .PCHInputFile = “{0}”
“, InputFile));
AddText(string.Format(” .PCHOutputFile = “{0}”
“, PCHOutputFile));
CompilerOutputExtension = “.obj”;
}
else if (ParsedCompilerOptions.ContainsKey(”/Yu")) //Use PCH
{
string PCHIncludeHeader = GetOptionValue(ParsedCompilerOptions, “/Yu”, Action, ProblemIfNotFound: true);
string PCHOutputFile = GetOptionValue(ParsedCompilerOptions, “/Fp”, Action, ProblemIfNotFound: true);
string PCHToForceInclude = PCHOutputFile.Replace(".pch", “”);
AddText(string.Format(" .CompilerOptions = '"%1" /Fo"%2" /Fp"{0}" /Yu"{1}" /FI"{2}" {3} ’
“, PCHOutputFile, PCHIncludeHeader, PCHToForceInclude, OtherCompilerOptions));
CompilerOutputExtension = “.cpp.obj”;
if (InputFile.EndsWith(”.c"))
{
CompilerOutputExtension = “.c.obj”;
}
}
else
{
if (CompilerName == “UE4ResourceCompiler”)
{
AddText(string.Format(" .CompilerOptions = '{0} /fo"%2" “%1” ’
“, OtherCompilerOptions));
CompilerOutputExtension = Path.GetExtension(InputFile) + “.res”;
}
else
{
if (IsMSVC())
{
AddText(string.Format(” .CompilerOptions = '{0} /Fo"%2" “%1” ’
“, OtherCompilerOptions));
CompilerOutputExtension = “.cpp.obj”;
if (InputFile.EndsWith(”.c"))
{
CompilerOutputExtension = “.c.obj”;
}
}
else
{
AddText(string.Format(" .CompilerOptions = '{0} -o “%2” “%1” ’
", OtherCompilerOptions));
CompilerOutputExtension = “.cpp.o”;
}
}
}
AddText(string.Format(" .CompilerOutputExtension = ‘{0}’
", CompilerOutputExtension));
if (DependencyIndices.Count > 0)
{
List<string> DependencyNames = DependencyIndices.ConvertAll(x => string.Format("‘Action_{0}’", x));
AddText(string.Format(" .PreBuildDependencies = {{ {0} }}
“, string.Join(”,", DependencyNames.ToArray())));
}
AddText(string.Format("}}
"));
}
private void AddLinkAction(List<Action> Actions, int ActionIndex, List<int> DependencyIndices)
{
Action Action = Actions[ActionIndex];
string] SpecialLinkerOptions = { “/OUT:”, “@”, “-o” };
var ParsedLinkerOptions = ParseCommandLineOptions(Action.CommandArguments, SpecialLinkerOptions, SaveResponseFile: true, SkipInputFile: Action.CommandPath.FullName.Contains(“orbis-clang”));
string OutputFile = “”;
if (IsMSVC())
{
OutputFile = GetOptionValue(ParsedLinkerOptions, “/OUT:”, Action, ProblemIfNotFound: true);
}
if (string.IsNullOrEmpty(OutputFile))
{
Console.WriteLine(“Failed to find output file. Bailing.”);
return;
}
string ResponseFilePath = GetOptionValue(ParsedLinkerOptions, “@”, Action);
string OtherCompilerOptions = GetOptionValue(ParsedLinkerOptions, “OtherOptions”, Action);
List<int> PrebuildDependencies = new List<int>();
if (Action.CommandPath.FullName.EndsWith(“lib.exe”) || Action.CommandPath.FullName.Contains(“orbis-snarl”))
{
if (DependencyIndices.Count > 0)
{
for (int i = 0; i < DependencyIndices.Count; ++i) //Don’t specify pch or resource files, they have the wrong name and the response file will have them anyways.
{
int depIndex = DependencyIndices*;
foreach (FileItem item in Actions[depIndex].ProducedItems)
{
if (item.ToString().EndsWith(".pch") || item.ToString().Contains(".res"))
{
DependencyIndices.RemoveAt(i);
i–;
PrebuildDependencies.Add(depIndex);
break;
}
}
}
}
AddText(string.Format("Library('Action_{0}')
{{
“, ActionIndex));
AddText(string.Format(” .Compiler = ‘{0}’
“, GetCompilerName()));
if (IsMSVC())
AddText(string.Format(” .CompilerOptions = ‘"%1" /Fo"%2" /c’
“));
else
AddText(string.Format(” .CompilerOptions = ‘"%1" -o “%2” -c’
“));
AddText(string.Format(” .CompilerOutputPath = “{0}”
“, Path.GetDirectoryName(OutputFile)));
AddText(string.Format(” .Librarian = ‘{0}’
", Action.CommandPath));
if (!string.IsNullOrEmpty(ResponseFilePath))
{
if (IsMSVC())
// /ignore:4042 to turn off the linker warning about the output option being present twice (command-line + rsp file)
AddText(string.Format(" .LibrarianOptions = ' /OUT:"%2" /ignore:4042 @"{0}" "%1"'
“, ResponseFilePath));
else if (IsPS4())
AddText(string.Format(” .LibrarianOptions = ‘"%2" @"%1"’
“, ResponseFilePath));
else
AddText(string.Format(” .LibrarianOptions = ‘"%2" @"%1" {0}’
“, OtherCompilerOptions));
}
else
{
if (IsMSVC())
AddText(string.Format(” .LibrarianOptions = ’ /OUT:"%2" {0} “%1”’
", OtherCompilerOptions));
}
if (DependencyIndices.Count > 0)
{
List<string> DependencyNames = DependencyIndices.ConvertAll(x => string.Format("'Action_{0}'", x));
if (IsPS4())
AddText(string.Format(" .LibrarianAdditionalInputs = {{ '{0}' }}
“, ResponseFilePath)); // Hack…Because FastBuild needs at least one Input file
else if (!string.IsNullOrEmpty(ResponseFilePath))
AddText(string.Format(” .LibrarianAdditionalInputs = {{ {0} }}
“, DependencyNames[0])); // Hack…Because FastBuild needs at least one Input file
else if (IsMSVC())
AddText(string.Format(” .LibrarianAdditionalInputs = {{ {0} }}
“, string.Join(”,", DependencyNames.ToArray())));
PrebuildDependencies.AddRange(DependencyIndices);
}
else
{
string InputFile = GetOptionValue(ParsedLinkerOptions, "InputFile", Action, ProblemIfNotFound: true);
if (InputFile != null && InputFile.Length > 0)
AddText(string.Format(" .LibrarianAdditionalInputs = {{ '{0}' }}
", InputFile));
}
if (PrebuildDependencies.Count > 0)
{
List<string> PrebuildDependencyNames = PrebuildDependencies.ConvertAll(x => string.Format("'Action_{0}'", x));
AddText(string.Format(" .PreBuildDependencies = {{ {0} }}
“, string.Join(”,", PrebuildDependencyNames.ToArray())));
}
AddText(string.Format(" .LibrarianOutput = '{0}'
“, OutputFile));
AddText(string.Format(”}}
“));
}
else if (Action.CommandPath.FullName.EndsWith(“link.exe”) || Action.CommandPath.FullName.Contains(“orbis-clang”))
{
if (DependencyIndices.Count > 0) //Insert a dummy node to make sure all of the dependencies are finished.
//If FASTBuild supports PreBuildDependencies on the Executable action we can remove this.
{
string dummyText = string.IsNullOrEmpty(ResponseFilePath) ? GetOptionValue(ParsedLinkerOptions, “InputFile”, Action) : ResponseFilePath;
File.SetLastAccessTimeUtc(dummyText, DateTime.UtcNow);
AddText(string.Format(“Copy(‘Action_{0}dummy’)
{{
“, ActionIndex));
AddText(string.Format(” .Source = ‘{0}’
“, dummyText));
AddText(string.Format(” .Dest = ‘{0}’
“, dummyText + “.dummy”));
List<string> DependencyNames = DependencyIndices.ConvertAll(x => string.Format(” 'Action{0}’, ;{1}”, x, Actions[x].StatusDescription));
AddText(string.Format(” .PreBuildDependencies = {{
{0}
}}
“, string.Join(”
“, DependencyNames.ToArray())));
AddText(string.Format(”}}
"));
}
AddText(string.Format("Executable('Action_{0}')
{{
“, ActionIndex));
AddText(string.Format(” .Linker = ‘{0}’
", Action.CommandPath));
if (DependencyIndices.Count == 0)
{
AddText(string.Format(" .Libraries = {{ '{0}' }}
“, ResponseFilePath));
if (IsMSVC())
{
if (TargetPlatform == UnrealTargetPlatform.XboxOne)
{
AddText(string.Format(” .LinkerOptions = '/TLBOUT:"%1" /Out:"%2" @"{0}" {1} ’
“, ResponseFilePath, OtherCompilerOptions)); // The TLBOUT is a huge bodge to consume the %1.
}
else
{
// /ignore:4042 to turn off the linker warning about the output option being present twice (command-line + rsp file)
AddText(string.Format(” .LinkerOptions = '/TLBOUT:"%1" /ignore:4042 /Out:"%2" @"{0}" ’
“, ResponseFilePath)); // The TLBOUT is a huge bodge to consume the %1.
}
}
else
AddText(string.Format(” .LinkerOptions = ‘{0} -o “%2” @"%1"’
“, OtherCompilerOptions)); // The MQ is a huge bodge to consume the %1.
}
else
{
AddText(string.Format(” .Libraries = ‘Action_{0}_dummy’
“, ActionIndex));
if (IsMSVC())
{
if (TargetPlatform == UnrealTargetPlatform.XboxOne)
{
AddText(string.Format(” .LinkerOptions = '/TLBOUT:"%1" /Out:"%2" @"{0}" {1} ’
“, ResponseFilePath, OtherCompilerOptions)); // The TLBOUT is a huge bodge to consume the %1.
}
else
{
AddText(string.Format(” .LinkerOptions = '/TLBOUT:"%1" /Out:"%2" @"{0}" ’
“, ResponseFilePath)); // The TLBOUT is a huge bodge to consume the %1.
}
}
else
AddText(string.Format(” .LinkerOptions = ‘{0} -o “%2” @"%1"’
", OtherCompilerOptions)); // The MQ is a huge bodge to consume the %1.
}
AddText(string.Format(" .LinkerOutput = '{0}'
“, OutputFile));
AddText(string.Format(”}}
"));
}
}
private FileStream bffOutputFileStream = null;
private bool CreateBffFile(List<Action> InActions, string BffFilePath, List<Action> LocalExecutorActions)
{
List<Action> Actions = InActions;
try
{
bffOutputFileStream = new FileStream(BffFilePath, FileMode.Create, FileAccess.Write);
WriteEnvironmentSetup(); //Compiler, environment variables and base paths
int MaxFastBuildIndex = 0;
for (int ActionIndex = 0; ActionIndex < Actions.Count; ActionIndex++)
{
Action Action = Actions[ActionIndex];
// Resolve dependencies
List<int> DependencyIndices = new List<int>();
foreach (Action RequiredAction in Action.PrerequisiteActions)
{
int ProducingActionIndex = Actions.IndexOf(RequiredAction);
if (ProducingActionIndex >= 0)
{
DependencyIndices.Add(ProducingActionIndex);
}
}
AddText(string.Format("// "{0}" {1}
", Action.CommandPath, Action.CommandArguments));
switch (Action.ActionType)
{
case ActionType.Compile:
if (Action.CommandPath.FullName.EndsWith(“rc.exe”))
{
if(Action.PrerequisiteActions.Count < 1)
{
//Compile PCLaunch.rc.res
LocalExecutor localExecutor = new LocalExecutor();
bool ResourceResult = localExecutor.ExecuteActions(new List<Action>() { Action }, true);
LocalExecutedIndex.Add(ActionIndex);
if (ResourceResult == false)
{
Console.WriteLine(“ResourceResult Error!!!”);
}
}
else
{
LocalExecutorActions.Add(Action);
Console.WriteLine(“Action will be executed on local:” + ActionIndex.ToString() + " " + Action.CommandArguments);
LocalExecutedIndex.Add(ActionIndex);
}
}
else
{
AddCompileAction(Action, ActionIndex, DependencyIndices);
MaxFastBuildIndex = ActionIndex;
}
break;
case ActionType.Link:
AddLinkAction(Actions, ActionIndex, DependencyIndices);
MaxFastBuildIndex = ActionIndex;
break;
case ActionType.BuildProject:
case ActionType.PostBuildStep:
case ActionType.WriteMetadata:
LocalExecutorActions.Add(Action);
Console.WriteLine("Action will be executed on local:" + ActionIndex.ToString() + " " + Action.CommandArguments);
LocalExecutedIndex.Add(ActionIndex);
break;
default:
Console.WriteLine("Fastbuild is ignoring an unsupported action: " + Action.ActionType.ToString() + " ActionIndex:" + ActionIndex.ToString());
break;
}
}
Console.WriteLine("Max FastBuild Action Index: {0}", MaxFastBuildIndex);
AddText("Alias( 'all' )
{
“);
AddText(” .Targets = {
“);
for (int ActionIndex = 0; ActionIndex < InActions.Count; ActionIndex++)
{
if (LocalExecutedIndex.Contains(ActionIndex) == false)
{
AddText(string.Format(” ‘Action_{0}’{1}", ActionIndex, ActionIndex != MaxFastBuildIndex ? ",
" : "
}
“));
}
}
AddText(”}
");
bffOutputFileStream.Close();
}
catch (Exception e)
{
Console.WriteLine("Exception while creating bff file: " + e.ToString());
return false;
}
return true;
}
private bool ExecuteBffFile(string BffFilePath)
{
string cacheArgument = “”;
if (bEnableCaching)
{
switch (CacheMode)
{
case eCacheMode.ReadOnly:
cacheArgument = “-cacheread”;
break;
case eCacheMode.WriteOnly:
cacheArgument = “-cachewrite”;
break;
case eCacheMode.ReadWrite:
cacheArgument = “-cache”;
break;
}
}
string distArgument = bEnableDistribution ? “-dist” : “”;
//Interesting flags for FASTBuild: -nostoponerror, -verbose, -monitor (if FASTBuild Monitor Visual Studio Extension is installed!)
// Yassine: The -clean is to bypass the FastBuild internal dependencies checks (cached in the fdb) as it could create some conflicts with UBT.
// Basically we want FB to stupidly compile what UBT tells it to.
string FBCommandLine = string.Format("-monitor -summary {0} {1} -ide -clean -config “{2}”", distArgument, cacheArgument, BffFilePath);
ProcessStartInfo FBStartInfo = new ProcessStartInfo(string.IsNullOrEmpty(FBuildExePathOverride) ? “fbuild” : FBuildExePathOverride, FBCommandLine);
FBStartInfo.UseShellExecute = false;
FBStartInfo.WorkingDirectory = Path.Combine(UnrealBuildTool.EngineDirectory.MakeRelativeTo(DirectoryReference.GetCurrentDirectory()), “Source”);
try
{
Process FBProcess = new Process();
FBProcess.StartInfo = FBStartInfo;
FBStartInfo.RedirectStandardError = true;
FBStartInfo.RedirectStandardOutput = true;
FBProcess.EnableRaisingEvents = true;
DataReceivedEventHandler OutputEventHandler = (Sender, Args) =>
{
if (Args.Data != null)
Console.WriteLine(Args.Data);
};
FBProcess.OutputDataReceived += OutputEventHandler;
FBProcess.ErrorDataReceived += OutputEventHandler;
FBProcess.Start();
FBProcess.BeginOutputReadLine();
FBProcess.BeginErrorReadLine();
FBProcess.WaitForExit();
return FBProcess.ExitCode == 0;
}
catch (Exception e)
{
Console.WriteLine(“Exception launching fbuild process. Is it in your path?” + e.ToString());
return false;
}
}
}
}