Announcement

Collapse
No announcement yet.

[UBT]ActionGraph.IsActionOutdated() always return false!

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    [UBT]ActionGraph.IsActionOutdated() always return false!

    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[i].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', '\n' };
    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[i].StartsWith(specialOption))
    {
    int StartIndex = specialOption.Length;
    if (ProcessedTokens[i][StartIndex] == ' ')
    {
    ++StartIndex;
    }
    ParsedCompilerOptions[specialOption] = ProcessedTokens[i].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[i];
    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\n");
    }

    if (envVars.ContainsKey("DXSDK_DIR"))
    {
    AddText("#import DXSDK_DIR\n");
    }

    if (envVars.ContainsKey("DurangoXDK"))
    {
    AddText("#import DurangoXDK\n");
    }

    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}'\n", VCEnv.WindowsSdkDir));

    AddText("Compiler('UE4ResourceCompiler') \n{\n");
    AddText(string.Format("\t.Executable = '{0}'\n", VCEnv.ResourceCompilerPath));
    AddText("\t.CompilerFamily = 'custom'\n");
    AddText("}\n\n");


    AddText("Compiler('UE4Compiler') \n{\n");

    AddText(string.Format("\t.Root = '{0}'\n", VCEnv.CompilerPath.Directory));
    AddText("\t.Executable = '$Root$/cl.exe'\n");
    AddText("\t.ExtraFiles =\n\t{\n");
    AddText("\t\t'$Root$/c1.dll'\n");
    AddText("\t\t'$Root$/c1xx.dll'\n");
    AddText("\t\t'$Root$/c2.dll'\n");

    if (File.Exists(FileReference.Combine(VCEnv.CompilerPath.Directory, "1033/clui.dll").ToString())) //Check English first...
    {
    AddText("\t\t'$Root$/1033/clui.dll'\n");
    }
    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("\t\t'$Root$/{0}/clui.dll'\n", Path.GetFileName(cluiDirectories.First())));
    }
    }
    AddText("\t\t'$Root$/mspdbsrv.exe'\n");
    AddText("\t\t'$Root$/mspdbcore.dll'\n");

    AddText(string.Format("\t\t'$Root$/mspft{0}.dll'\n", platformVersionNumber));
    AddText(string.Format("\t\t'$Root$/msobj{0}.dll'\n", platformVersionNumber));
    AddText(string.Format("\t\t'$Root$/mspdb{0}.dll'\n", platformVersionNumber));

    string RedistFilePath = string.Format("{0}/VC/Auxiliary/Build/Microsoft.VCRedistVersion.default.txt", VCInstallDir.ToString());
    string RedistVersionNumber = File.ReadAllText(RedistFilePath).TrimEnd('\r', '\n');

    if (VCEnv.Compiler == WindowsCompiler.VisualStudio2019)
    {
    AddText(string.Format("\t\t'{0}/VC/Redist/MSVC/{1}/x64/Microsoft.VC142.CRT/msvcp{2}.dll'\n", VCInstallDir.ToString(), RedistVersionNumber, platformVersionNumber));
    AddText(string.Format("\t\t'{0}/VC/Redist/MSVC/{1}/x64/Microsoft.VC142.CRT/vccorlib{2}.dll'\n", 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("\t\t'{0}/VC/Redist/MSVC/{1}/x64/Microsoft.VC141.CRT/msvcp{2}.dll'\n", VCInstallDir.ToString(), RedistVersionNumber, platformVersionNumber));
    AddText(string.Format("\t\t'{0}/VC/Redist/MSVC/{1}/x64/Microsoft.VC141.CRT/vccorlib{2}.dll'\n", VCInstallDir.ToString(), RedistVersionNumber, platformVersionNumber));
    }

    AddText("\t}\n"); //End extra files

    AddText("}\n\n"); //End compiler
    }

    if (envVars.ContainsKey("SCE_ORBIS_SDK_DIR"))
    {
    AddText(string.Format(".SCE_ORBIS_SDK_DIR = '{0}'\n", envVars["SCE_ORBIS_SDK_DIR"]));
    AddText(string.Format(".PS4BasePath = '{0}/host_tools/bin'\n\n", envVars["SCE_ORBIS_SDK_DIR"]));
    AddText("Compiler('UE4PS4Compiler') \n{\n");
    AddText("\t.Executable = '$PS4BasePath$/orbis-clang.exe'\n");
    AddText("\t.ExtraFiles = '$PS4BasePath$/orbis-snarl.exe'\n");
    AddText("}\n\n");
    }

    AddText("Settings \n{\n");

    // Optional cachePath user setting
    if (bEnableCaching && CachePath != "")
    {
    AddText(string.Format("\t.CachePath = '{0}'\n", CachePath));
    }

    //Start Environment
    AddText("\t.Environment = \n\t{\n");
    if (VCEnv != null)
    {
    AddText(string.Format("\t\t"PATH={0}\\Common7\\IDE\\;{1}",\n", VCInstallDir.ToString(), VCToolPath64));
    if (VCEnv.IncludePaths.Count() > 0)
    {
    AddText(string.Format("\t\t"INCLUDE={0}",\n", String.Join(";", VCEnv.IncludePaths.Select(x => x))));
    }

    if (VCEnv.LibraryPaths.Count() > 0)
    {
    AddText(string.Format("\t\t"LIB={0}",\n", String.Join(";", VCEnv.LibraryPaths.Select(x => x))));
    }
    }
    if (envVars.ContainsKey("TMP"))
    AddText(string.Format("\t\t"TMP={0}",\n", envVars["TMP"]));
    if (envVars.ContainsKey("SystemRoot"))
    AddText(string.Format("\t\t"SystemRoot={0}",\n", envVars["SystemRoot"]));
    if (envVars.ContainsKey("INCLUDE"))
    AddText(string.Format("\t\t"INCLUDE={0}",\n", envVars["INCLUDE"]));
    if (envVars.ContainsKey("LIB"))
    AddText(string.Format("\t\t"LIB={0}",\n", envVars["LIB"]));

    AddText("\t}\n"); //End environment
    AddText("}\n\n"); //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}')\n{{\n", ActionIndex));
    AddText(string.Format("\t.Compiler = '{0}' \n", CompilerName));
    AddText(string.Format("\t.CompilerInputFiles = "{0}"\n", InputFile));
    AddText(string.Format("\t.CompilerOutputPath = "{0}"\n", 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("\t.AllowDistribution = false\n"));
    }

    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("\t.CompilerOptions = '"%1" /Fo"%2" /Fp"{0}" /Yu"{1}" {2} '\n", PCHOutputFile, PCHIncludeHeader, OtherCompilerOptions));

    AddText(string.Format("\t.PCHOptions = '"%1" /Fp"%2" /Yc"{0}" {1} /Fo"{2}"'\n", PCHIncludeHeader, OtherCompilerOptions, OutputObjectFileName));
    AddText(string.Format("\t.PCHInputFile = "{0}"\n", InputFile));
    AddText(string.Format("\t.PCHOutputFile = "{0}"\n", 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("\t.CompilerOptions = '"%1" /Fo"%2" /Fp"{0}" /Yu"{1}" /FI"{2}" {3} '\n", PCHOutputFile, PCHIncludeHeader, PCHToForceInclude, OtherCompilerOptions));
    CompilerOutputExtension = ".cpp.obj";
    if (InputFile.EndsWith(".c"))
    {
    CompilerOutputExtension = ".c.obj";
    }
    }
    else
    {
    if (CompilerName == "UE4ResourceCompiler")
    {
    AddText(string.Format("\t.CompilerOptions = '{0} /fo"%2" "%1" '\n", OtherCompilerOptions));
    CompilerOutputExtension = Path.GetExtension(InputFile) + ".res";
    }
    else
    {
    if (IsMSVC())
    {
    AddText(string.Format("\t.CompilerOptions = '{0} /Fo"%2" "%1" '\n", OtherCompilerOptions));
    CompilerOutputExtension = ".cpp.obj";
    if (InputFile.EndsWith(".c"))
    {
    CompilerOutputExtension = ".c.obj";
    }
    }
    else
    {
    AddText(string.Format("\t.CompilerOptions = '{0} -o "%2" "%1" '\n", OtherCompilerOptions));
    CompilerOutputExtension = ".cpp.o";
    }
    }
    }

    AddText(string.Format("\t.CompilerOutputExtension = '{0}' \n", CompilerOutputExtension));

    if (DependencyIndices.Count > 0)
    {
    List<string> DependencyNames = DependencyIndices.ConvertAll(x => string.Format("'Action_{0}'", x));
    AddText(string.Format("\t.PreBuildDependencies = {{ {0} }}\n", string.Join(",", DependencyNames.ToArray())));
    }

    AddText(string.Format("}}\n\n"));
    }

    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[i];
    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}')\n{{\n", ActionIndex));
    AddText(string.Format("\t.Compiler = '{0}'\n", GetCompilerName()));
    if (IsMSVC())
    AddText(string.Format("\t.CompilerOptions = '"%1" /Fo"%2" /c'\n"));
    else
    AddText(string.Format("\t.CompilerOptions = '"%1" -o "%2" -c'\n"));
    AddText(string.Format("\t.CompilerOutputPath = "{0}"\n", Path.GetDirectoryName(OutputFile)));
    AddText(string.Format("\t.Librarian = '{0}' \n", 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("\t.LibrarianOptions = ' /OUT:"%2" /ignore:4042 @"{0}" "%1"' \n", ResponseFilePath));
    else if (IsPS4())
    AddText(string.Format("\t.LibrarianOptions = '"%2" @"%1"' \n", ResponseFilePath));
    else
    AddText(string.Format("\t.LibrarianOptions = '"%2" @"%1" {0}' \n", OtherCompilerOptions));
    }
    else
    {
    if (IsMSVC())
    AddText(string.Format("\t.LibrarianOptions = ' /OUT:"%2" {0} "%1"' \n", OtherCompilerOptions));
    }

    if (DependencyIndices.Count > 0)
    {
    List<string> DependencyNames = DependencyIndices.ConvertAll(x => string.Format("'Action_{0}'", x));

    if (IsPS4())
    AddText(string.Format("\t.LibrarianAdditionalInputs = {{ '{0}' }} \n", ResponseFilePath)); // Hack...Because FastBuild needs at least one Input file
    else if (!string.IsNullOrEmpty(ResponseFilePath))
    AddText(string.Format("\t.LibrarianAdditionalInputs = {{ {0} }} \n", DependencyNames[0])); // Hack...Because FastBuild needs at least one Input file
    else if (IsMSVC())
    AddText(string.Format("\t.LibrarianAdditionalInputs = {{ {0} }} \n", 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("\t.LibrarianAdditionalInputs = {{ '{0}' }} \n", InputFile));
    }

    if (PrebuildDependencies.Count > 0)
    {
    List<string> PrebuildDependencyNames = PrebuildDependencies.ConvertAll(x => string.Format("'Action_{0}'", x));
    AddText(string.Format("\t.PreBuildDependencies = {{ {0} }} \n", string.Join(",", PrebuildDependencyNames.ToArray())));
    }

    AddText(string.Format("\t.LibrarianOutput = '{0}' \n", OutputFile));
    AddText(string.Format("}}\n\n"));
    }
    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')\n{{ \n", ActionIndex));
    AddText(string.Format("\t.Source = '{0}' \n", dummyText));
    AddText(string.Format("\t.Dest = '{0}' \n", dummyText + ".dummy"));
    List<string> DependencyNames = DependencyIndices.ConvertAll(x => string.Format("\t\t'Action_{0}', ;{1}", x, Actions[x].StatusDescription));
    AddText(string.Format("\t.PreBuildDependencies = {{\n{0}\n\t}} \n", string.Join("\n", DependencyNames.ToArray())));
    AddText(string.Format("}}\n\n"));
    }

    AddText(string.Format("Executable('Action_{0}')\n{{ \n", ActionIndex));
    AddText(string.Format("\t.Linker = '{0}' \n", Action.CommandPath));

    if (DependencyIndices.Count == 0)
    {
    AddText(string.Format("\t.Libraries = {{ '{0}' }} \n", ResponseFilePath));
    if (IsMSVC())
    {
    if (TargetPlatform == UnrealTargetPlatform.XboxOne)
    {
    AddText(string.Format("\t.LinkerOptions = '/TLBOUT:"%1" /Out:"%2" @"{0}" {1} ' \n", 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("\t.LinkerOptions = '/TLBOUT:"%1" /ignore:4042 /Out:"%2" @"{0}" ' \n", ResponseFilePath)); // The TLBOUT is a huge bodge to consume the %1.
    }
    }
    else
    AddText(string.Format("\t.LinkerOptions = '{0} -o "%2" @"%1"' \n", OtherCompilerOptions)); // The MQ is a huge bodge to consume the %1.
    }
    else
    {
    AddText(string.Format("\t.Libraries = 'Action_{0}_dummy' \n", ActionIndex));
    if (IsMSVC())
    {
    if (TargetPlatform == UnrealTargetPlatform.XboxOne)
    {
    AddText(string.Format("\t.LinkerOptions = '/TLBOUT:"%1" /Out:"%2" @"{0}" {1} ' \n", ResponseFilePath, OtherCompilerOptions)); // The TLBOUT is a huge bodge to consume the %1.
    }
    else
    {
    AddText(string.Format("\t.LinkerOptions = '/TLBOUT:"%1" /Out:"%2" @"{0}" ' \n", ResponseFilePath)); // The TLBOUT is a huge bodge to consume the %1.
    }
    }
    else
    AddText(string.Format("\t.LinkerOptions = '{0} -o "%2" @"%1"' \n", OtherCompilerOptions)); // The MQ is a huge bodge to consume the %1.
    }

    AddText(string.Format("\t.LinkerOutput = '{0}' \n", OutputFile));
    AddText(string.Format("}}\n\n"));
    }
    }

    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}\n", 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() + "\t" + 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() + "\t" + Action.CommandArguments);
    LocalExecutedIndex.Add(ActionIndex);
    break;

    default:
    Console.WriteLine("Fastbuild is ignoring an unsupported action: " + Action.ActionType.ToString() + "\tActionIndex:" + ActionIndex.ToString());
    break;
    }
    }

    Console.WriteLine("Max FastBuild Action Index: {0}", MaxFastBuildIndex);

    AddText("Alias( 'all' ) \n{\n");
    AddText("\t.Targets = { \n");
    for (int ActionIndex = 0; ActionIndex < InActions.Count; ActionIndex++)
    {
    if (LocalExecutedIndex.Contains(ActionIndex) == false)
    {
    AddText(string.Format("\t\t'Action_{0}'{1}", ActionIndex, ActionIndex != MaxFastBuildIndex ? ",\n" : "\n\t}\n"));
    }
    }
    AddText("}\n");

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


    #2
    This issue is being discussed on git and this code snippet comes directly from that discussion as well. Posting it here without a link splits the discussion making it harder to follow, and takes away credit from people who have been working on it. Please refer to the original issue for further discussion.
    https://github.com/liamkf/Unreal_FASTBuild/issues/39

    Comment


      #3
      https://github.com/Ronsenval/UnrealE...UsingFastBuild.
      Here is one approach to handle issues with dependecy files.
      it is based on https://github.com/liamkf/Unreal_FASTBuild.

      Comment

      Working...
      X