You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1304 lines
47 KiB
C#
1304 lines
47 KiB
C#
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
namespace UnrealBuildTool
|
|
{
|
|
/// <summary>
|
|
/// Enumerates build action types.
|
|
/// </summary>
|
|
public enum ActionType
|
|
{
|
|
BuildProject,
|
|
|
|
Compile,
|
|
|
|
CreateAppBundle,
|
|
|
|
GenerateDebugInfo,
|
|
|
|
Link,
|
|
}
|
|
|
|
/** A build action. */
|
|
public class Action
|
|
{
|
|
/** Unique action identifier */
|
|
public int UniqueId;
|
|
|
|
public List<FileItem> PrerequisiteItems = new List<FileItem>();
|
|
public List<FileItem> ProducedItems = new List<FileItem>();
|
|
public delegate void EventHandler(Action A);
|
|
|
|
/** Total number of actions depending on this one. */
|
|
public int NumTotalDependentActions = 0;
|
|
/** Relative cost of producing items for this action. */
|
|
public long RelativeCost = 0;
|
|
public string WorkingDirectory = null;
|
|
public bool bPrintDebugInfo = false;
|
|
public string CommandPath = null;
|
|
public string CommandArguments = null;
|
|
public string CommandDescription = null;
|
|
public string StatusDescription = "...";
|
|
public string StatusDetailedDescription = "";
|
|
public bool bCanExecuteRemotely = false;
|
|
public bool bIsVCCompiler = false;
|
|
public bool bIsGCCCompiler = false;
|
|
/** Whether the action is using a pre-compiled header to speed it up. */
|
|
public bool bIsUsingPCH = false;
|
|
/** Whether to delete the prerequisite files on completion */
|
|
public bool bShouldDeletePrereqs = false;
|
|
/** Whether the files in ProducedItems should be deleted before executing this action, when the action is outdated */
|
|
public bool bShouldDeleteProducedItems = false;
|
|
/**
|
|
* Whether we should log this action, whether executed locally or remotely. This is useful for actions that take time
|
|
* but invoke tools without any console output.
|
|
*/
|
|
public bool bShouldOutputStatusDescription = true;
|
|
/** True if we should redirect standard input such that input will only come from the builder (which is none) */
|
|
public bool bShouldBlockStandardInput = false;
|
|
/** True if we should redirect standard output such that text will not be logged */
|
|
public bool bShouldBlockStandardOutput = false;
|
|
/** Start time of action, optionally set by executor. */
|
|
public DateTimeOffset StartTime = DateTimeOffset.MinValue;
|
|
/** End time of action, optionally set by executor. */
|
|
public DateTimeOffset EndTime = DateTimeOffset.MinValue;
|
|
/** Optional custom event handler for standard output. */
|
|
public DataReceivedEventHandler OutputEventHandler = null;
|
|
/** Callback used to perform a special action instead of a generic command line */
|
|
public delegate void BlockingActionHandler(Action Action, out int ExitCode, out string Output);
|
|
public BlockingActionHandler ActionHandler = null;
|
|
/** The type of this action (for debugging purposes). */
|
|
public ActionType ActionType;
|
|
|
|
/** True if any libraries produced by this action should be considered 'import libraries' */
|
|
public bool bProducesImportLibrary = false;
|
|
|
|
/** Always-incremented unique id */
|
|
private static int NextUniqueId = 0;
|
|
|
|
public Action(ActionType InActionType)
|
|
{
|
|
ActionType = InActionType;
|
|
|
|
UnrealBuildTool.AllActions.Add(this);
|
|
UniqueId = ++NextUniqueId;
|
|
}
|
|
|
|
/**
|
|
* Compares two actions based on total number of dependent items, descending.
|
|
*
|
|
* @param A Action to compare
|
|
* @param B Action to compare
|
|
*/
|
|
public static int Compare( Action A, Action B )
|
|
{
|
|
// Primary sort criteria is total number of dependent files, up to max depth.
|
|
if( B.NumTotalDependentActions != A.NumTotalDependentActions )
|
|
{
|
|
return Math.Sign( B.NumTotalDependentActions - A.NumTotalDependentActions );
|
|
}
|
|
// Secondary sort criteria is relative cost.
|
|
if( B.RelativeCost != A.RelativeCost )
|
|
{
|
|
return Math.Sign( B.RelativeCost - A.RelativeCost );
|
|
}
|
|
// Tertiary sort criteria is number of pre-requisites.
|
|
else
|
|
{
|
|
return Math.Sign( B.PrerequisiteItems.Count - A.PrerequisiteItems.Count );
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
string ReturnString = "";
|
|
if (CommandPath != null)
|
|
{
|
|
ReturnString += CommandPath + " - ";
|
|
}
|
|
if (CommandArguments != null)
|
|
{
|
|
ReturnString += CommandArguments;
|
|
}
|
|
return ReturnString;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the amount of time that this action is or has been executing in.
|
|
/// </summary>
|
|
public TimeSpan Duration
|
|
{
|
|
get
|
|
{
|
|
if (EndTime == DateTimeOffset.MinValue)
|
|
{
|
|
return DateTimeOffset.Now - StartTime;
|
|
}
|
|
|
|
return EndTime - StartTime;
|
|
}
|
|
}
|
|
};
|
|
|
|
partial class UnrealBuildTool
|
|
{
|
|
public static List<Action> AllActions = new List<Action>();
|
|
|
|
public static void ResetAllActions()
|
|
{
|
|
AllActions = new List<Action>();
|
|
}
|
|
|
|
/** Builds a list of actions that need to be executed to produce the specified output items. */
|
|
static List<Action> GetActionsToExecute(List<FileItem> OutputItems, List<UEBuildTarget> Targets, out Dictionary<UEBuildTarget, List<FileItem>> TargetToOutdatedPrerequisitesMap)
|
|
{
|
|
// @todo fastubt: We really want to be able to generate actions in a separate phase from actually building. For example, we could generate
|
|
// actions at GPF-time, then save them out to be reloaded quickly later when building. It means we would separate C++ include dependencies
|
|
// out from the normal action graph and check outdatedness at build time. The module relationships and link dependencies would all still
|
|
// be in the static part of the action graph that was loaded from disk. You would probably need to re-run GPF after adding or removing source files,
|
|
// but ideally not after changing which headers are included in an existing source file! Maybe we could make adding/removing source files work
|
|
// too, but it affects the Unity/PCH/Linker input, so it would be easier to not deal with this. Still need to run UHT during build phase
|
|
// as well, which means we need to know which files have UObjects for the manifest. This could be cached up front, and kept up to date as
|
|
// we rescan modified source files looking for changed includes. UHT does not require spidering includes.
|
|
|
|
// @todo fastubt: Can we use directory changed notifications or directory timestamps to accelerate C++ file outdatedness checking?
|
|
|
|
// Link producing actions to the items they produce.
|
|
LinkActionsAndItems();
|
|
|
|
DeleteStaleHotReloadDLLs();
|
|
|
|
// Detect cycles in the action graph.
|
|
DetectActionGraphCycles();
|
|
|
|
// Sort action list by "cost" in descending order to improve parallelism.
|
|
SortActionList();
|
|
|
|
// Build a set of all actions needed for this target.
|
|
var ActionsNeededForThisTarget = new Dictionary<Action, bool>();
|
|
|
|
// For now simply treat all object files as the root target.
|
|
foreach (FileItem OutputItem in OutputItems)
|
|
{
|
|
GatherPrerequisiteActions(OutputItem, ref ActionsNeededForThisTarget);
|
|
}
|
|
|
|
// For all targets, build a set of all actions that are outdated.
|
|
var OutdatedActionDictionary = new Dictionary<Action, bool>();
|
|
var HistoryList = new List<ActionHistory>();
|
|
var OpenHistoryFiles = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
|
TargetToOutdatedPrerequisitesMap = new Dictionary<UEBuildTarget,List<FileItem>>();
|
|
foreach (var BuildTarget in Targets)
|
|
{
|
|
var HistoryFilename = ActionHistory.GeneratePathForTarget(BuildTarget);
|
|
if (!OpenHistoryFiles.Contains(HistoryFilename))
|
|
{
|
|
var History = new ActionHistory(HistoryFilename);
|
|
HistoryList.Add(History);
|
|
OpenHistoryFiles.Add(HistoryFilename);
|
|
GatherAllOutdatedActions(BuildTarget, History, ref OutdatedActionDictionary, TargetToOutdatedPrerequisitesMap);
|
|
}
|
|
}
|
|
|
|
// Delete produced items that are outdated.
|
|
DeleteOutdatedProducedItems(OutdatedActionDictionary, BuildConfiguration.bShouldDeleteAllOutdatedProducedItems);
|
|
|
|
// Save the action history.
|
|
// This must happen after deleting outdated produced items to ensure that the action history on disk doesn't have
|
|
// command-lines that don't match the produced items on disk.
|
|
foreach (var TargetHistory in HistoryList)
|
|
{
|
|
TargetHistory.Save();
|
|
}
|
|
|
|
// Create directories for the outdated produced items.
|
|
CreateDirectoriesForProducedItems(OutdatedActionDictionary);
|
|
|
|
// Build a list of actions that are both needed for this target and outdated.
|
|
List<Action> ActionsToExecute = new List<Action>();
|
|
foreach (Action Action in AllActions)
|
|
{
|
|
if (Action.CommandPath != null && ActionsNeededForThisTarget.ContainsKey(Action) && OutdatedActionDictionary[Action])
|
|
{
|
|
ActionsToExecute.Add(Action);
|
|
}
|
|
}
|
|
|
|
return ActionsToExecute;
|
|
}
|
|
|
|
/** Executes a list of actions. */
|
|
static bool ExecuteActions(List<Action> ActionsToExecute, out string ExecutorName)
|
|
{
|
|
bool Result = true;
|
|
bool bUsedXGE = false;
|
|
ExecutorName = "";
|
|
if (ActionsToExecute.Count > 0)
|
|
{
|
|
if (BuildConfiguration.bAllowXGE || BuildConfiguration.bXGEExport)
|
|
{
|
|
XGE.ExecutionResult XGEResult = XGE.ExecutionResult.TasksSucceeded;
|
|
|
|
// Batch up XGE execution by actions with the same output event handler.
|
|
List<Action> ActionBatch = new List<Action>();
|
|
ActionBatch.Add(ActionsToExecute[0]);
|
|
for (int ActionIndex = 1; ActionIndex < ActionsToExecute.Count && XGEResult == XGE.ExecutionResult.TasksSucceeded; ++ActionIndex)
|
|
{
|
|
Action CurrentAction = ActionsToExecute[ActionIndex];
|
|
if (CurrentAction.OutputEventHandler == ActionBatch[0].OutputEventHandler)
|
|
{
|
|
ActionBatch.Add(CurrentAction);
|
|
}
|
|
else
|
|
{
|
|
XGEResult = XGE.ExecuteActions(ActionBatch);
|
|
ActionBatch.Clear();
|
|
ActionBatch.Add(CurrentAction);
|
|
}
|
|
}
|
|
if (ActionBatch.Count > 0 && XGEResult == XGE.ExecutionResult.TasksSucceeded)
|
|
{
|
|
XGEResult = XGE.ExecuteActions(ActionBatch);
|
|
ActionBatch.Clear();
|
|
}
|
|
|
|
if (XGEResult != XGE.ExecutionResult.Unavailable)
|
|
{
|
|
ExecutorName = "XGE";
|
|
Result = (XGEResult == XGE.ExecutionResult.TasksSucceeded);
|
|
// don't do local compilation
|
|
bUsedXGE = true;
|
|
}
|
|
}
|
|
|
|
if (!bUsedXGE && BuildConfiguration.bAllowDistcc)
|
|
{
|
|
ExecutorName = "Distcc";
|
|
Result = Distcc.ExecuteActions (ActionsToExecute);
|
|
// don't do local compilation
|
|
bUsedXGE = true;
|
|
}
|
|
|
|
// If XGE is disallowed or unavailable, execute the commands locally.
|
|
if (!bUsedXGE)
|
|
{
|
|
ExecutorName = "Local";
|
|
Result = LocalExecutor.ExecuteActions(ActionsToExecute);
|
|
}
|
|
|
|
if (bUsedXGE && BuildConfiguration.bXGEExport)
|
|
{
|
|
// we exported xge here, we do not test build products
|
|
}
|
|
else
|
|
{
|
|
// Verify the link outputs were created (seems to happen with Win64 compiles)
|
|
foreach (Action BuildAction in ActionsToExecute)
|
|
{
|
|
if (BuildAction.ActionType == ActionType.Link)
|
|
{
|
|
foreach (FileItem Item in BuildAction.ProducedItems)
|
|
{
|
|
bool bExists;
|
|
if (Item.bIsRemoteFile)
|
|
{
|
|
DateTime UnusedTime;
|
|
long UnusedLength;
|
|
bExists = RPCUtilHelper.GetRemoteFileInfo(Item.AbsolutePath, out UnusedTime, out UnusedLength);
|
|
}
|
|
else
|
|
{
|
|
FileInfo ItemInfo = new FileInfo(Item.AbsolutePath);
|
|
bExists = ItemInfo.Exists;
|
|
}
|
|
if (!bExists)
|
|
{
|
|
throw new BuildException("UBT ERROR: Failed to produce item: " + Item.AbsolutePath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Nothing to execute.
|
|
else
|
|
{
|
|
ExecutorName = "NoActionsToExecute";
|
|
Log.TraceInformation("Target is up to date.");
|
|
}
|
|
|
|
// Perform any cleanup
|
|
foreach (Action Action in ActionsToExecute)
|
|
{
|
|
if (Action.bShouldDeletePrereqs)
|
|
{
|
|
foreach (FileItem FileItem in Action.PrerequisiteItems)
|
|
{
|
|
if (bUsedXGE && BuildConfiguration.bXGEExport)
|
|
{
|
|
throw new BuildException("We are exporting XGE with a request to delete prerequisites; we need a delete prerequisites thing or just roll this into the XGE actions.");
|
|
}
|
|
else
|
|
{
|
|
FileItem.Delete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
/** Links actions with their prerequisite and produced items into an action graph. */
|
|
static void LinkActionsAndItems()
|
|
{
|
|
foreach (Action Action in AllActions)
|
|
{
|
|
foreach (FileItem ProducedItem in Action.ProducedItems)
|
|
{
|
|
ProducedItem.ProducingAction = Action;
|
|
Action.RelativeCost += ProducedItem.RelativeCost;
|
|
}
|
|
}
|
|
}
|
|
static string SplitFilename(string Filename, out string PlatformSuffix, out string ConfigSuffix, out string ProducedItemExtension)
|
|
{
|
|
string WorkingString = Filename;
|
|
ProducedItemExtension = Path.GetExtension(WorkingString);
|
|
if (!WorkingString.EndsWith(ProducedItemExtension))
|
|
{
|
|
throw new BuildException("Bogus extension");
|
|
}
|
|
WorkingString = WorkingString.Substring(0, WorkingString.Length - ProducedItemExtension.Length);
|
|
|
|
ConfigSuffix = "";
|
|
foreach (UnrealTargetConfiguration CurConfig in Enum.GetValues(typeof(UnrealTargetConfiguration)))
|
|
{
|
|
if( CurConfig != UnrealTargetConfiguration.Unknown )
|
|
{
|
|
string Test = "-" + CurConfig;
|
|
if (WorkingString.EndsWith(Test))
|
|
{
|
|
WorkingString = WorkingString.Substring(0, WorkingString.Length - Test.Length);
|
|
ConfigSuffix = Test;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
PlatformSuffix = "";
|
|
foreach (var CurPlatform in Enum.GetValues(typeof(UnrealTargetPlatform)))
|
|
{
|
|
string Test = "-" + CurPlatform;
|
|
if (WorkingString.EndsWith(Test))
|
|
{
|
|
WorkingString = WorkingString.Substring(0, WorkingString.Length - Test.Length);
|
|
PlatformSuffix = Test;
|
|
break;
|
|
}
|
|
}
|
|
return WorkingString;
|
|
}
|
|
/** Finds and deletes stale hot reload DLLs. */
|
|
static void DeleteStaleHotReloadDLLs()
|
|
{
|
|
foreach (Action BuildAction in AllActions)
|
|
{
|
|
if (BuildAction.ActionType == ActionType.Link)
|
|
{
|
|
foreach (FileItem Item in BuildAction.ProducedItems)
|
|
{
|
|
if (Item.bNeedsHotReloadNumbersDLLCleanUp)
|
|
{
|
|
string PlatformSuffix, ConfigSuffix, ProducedItemExtension;
|
|
string Base = SplitFilename(Item.AbsolutePath, out PlatformSuffix, out ConfigSuffix, out ProducedItemExtension);
|
|
String WildCard = Base + "-*" + PlatformSuffix + ConfigSuffix + ProducedItemExtension;
|
|
// Log.TraceInformation("Deleting old hot reload wildcard: \"{0}\".", WildCard);
|
|
// Wildcard search and delete
|
|
string DirectoryToLookIn = Path.GetDirectoryName(WildCard);
|
|
string FileName = Path.GetFileName(WildCard);
|
|
if (Directory.Exists(DirectoryToLookIn))
|
|
{
|
|
// Delete all files within the specified folder
|
|
string[] FilesToDelete = Directory.GetFiles(DirectoryToLookIn, FileName, SearchOption.TopDirectoryOnly);
|
|
foreach (string JunkFile in FilesToDelete)
|
|
{
|
|
|
|
string JunkPlatformSuffix, JunkConfigSuffix, JunkProducedItemExtension;
|
|
SplitFilename(JunkFile, out JunkPlatformSuffix, out JunkConfigSuffix, out JunkProducedItemExtension);
|
|
// now make sure that this file has the same config and platform
|
|
if (JunkPlatformSuffix == PlatformSuffix && JunkConfigSuffix == ConfigSuffix)
|
|
{
|
|
try
|
|
{
|
|
Log.TraceInformation("Deleting old hot reload file: \"{0}\".", JunkFile);
|
|
File.Delete(JunkFile);
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
// Ingore all exceptions
|
|
Log.TraceInformation("Unable to delete old hot reload file: \"{0}\". Error: {0}", JunkFile, Ex.Message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sorts the action list for improved parallelism with local execution.
|
|
*/
|
|
public static void SortActionList()
|
|
{
|
|
// Mapping from action to a list of actions that directly or indirectly depend on it (up to a certain depth).
|
|
Dictionary<Action,HashSet<Action>> ActionToDependentActionsMap = new Dictionary<Action,HashSet<Action>>();
|
|
// Perform multiple passes over all actions to propagate dependencies.
|
|
const int MaxDepth = 5;
|
|
for (int Pass=0; Pass<MaxDepth; Pass++)
|
|
{
|
|
foreach (Action DependendAction in AllActions)
|
|
{
|
|
foreach (FileItem PrerequisiteItem in DependendAction.PrerequisiteItems)
|
|
{
|
|
Action PrerequisiteAction = PrerequisiteItem.ProducingAction;
|
|
if( PrerequisiteAction != null )
|
|
{
|
|
HashSet<Action> DependentActions = null;
|
|
if( ActionToDependentActionsMap.ContainsKey(PrerequisiteAction) )
|
|
{
|
|
DependentActions = ActionToDependentActionsMap[PrerequisiteAction];
|
|
}
|
|
else
|
|
{
|
|
DependentActions = new HashSet<Action>();
|
|
ActionToDependentActionsMap[PrerequisiteAction] = DependentActions;
|
|
}
|
|
// Add dependent action...
|
|
DependentActions.Add( DependendAction );
|
|
// ... and all actions depending on it.
|
|
if( ActionToDependentActionsMap.ContainsKey(DependendAction) )
|
|
{
|
|
DependentActions.UnionWith( ActionToDependentActionsMap[DependendAction] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
// At this point we have a list of dependent actions for each action, up to MaxDepth layers deep.
|
|
foreach (KeyValuePair<Action,HashSet<Action>> ActionMap in ActionToDependentActionsMap)
|
|
{
|
|
ActionMap.Key.NumTotalDependentActions = ActionMap.Value.Count;
|
|
}
|
|
// Sort actions by number of actions depending on them, descending. Secondary sort criteria is file size.
|
|
AllActions.Sort( Action.Compare );
|
|
}
|
|
|
|
/** Checks for cycles in the action graph. */
|
|
static void DetectActionGraphCycles()
|
|
{
|
|
// Starting with actions that only depend on non-produced items, iteratively expand a set of actions that are only dependent on
|
|
// non-cyclical actions.
|
|
Dictionary<Action, bool> ActionIsNonCyclical = new Dictionary<Action, bool>();
|
|
Dictionary<Action, List<Action>> CyclicActions = new Dictionary<Action, List<Action>>();
|
|
while (true)
|
|
{
|
|
bool bFoundNewNonCyclicalAction = false;
|
|
|
|
foreach (Action Action in AllActions)
|
|
{
|
|
if (!ActionIsNonCyclical.ContainsKey(Action))
|
|
{
|
|
// Determine if the action depends on only actions that are already known to be non-cyclical.
|
|
bool bActionOnlyDependsOnNonCyclicalActions = true;
|
|
foreach (FileItem PrerequisiteItem in Action.PrerequisiteItems)
|
|
{
|
|
if (PrerequisiteItem.ProducingAction != null)
|
|
{
|
|
if (!ActionIsNonCyclical.ContainsKey(PrerequisiteItem.ProducingAction))
|
|
{
|
|
bActionOnlyDependsOnNonCyclicalActions = false;
|
|
if (!CyclicActions.ContainsKey(Action))
|
|
{
|
|
CyclicActions.Add(Action, new List<Action>());
|
|
}
|
|
|
|
List<Action> CyclicPrereq = CyclicActions[Action];
|
|
if (!CyclicPrereq.Contains(PrerequisiteItem.ProducingAction))
|
|
{
|
|
CyclicPrereq.Add(PrerequisiteItem.ProducingAction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the action only depends on known non-cyclical actions, then add it to the set of known non-cyclical actions.
|
|
if (bActionOnlyDependsOnNonCyclicalActions)
|
|
{
|
|
ActionIsNonCyclical.Add(Action, true);
|
|
bFoundNewNonCyclicalAction = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If this iteration has visited all actions without finding a new non-cyclical action, then all non-cyclical actions have
|
|
// been found.
|
|
if (!bFoundNewNonCyclicalAction)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If there are any cyclical actions, throw an exception.
|
|
if (ActionIsNonCyclical.Count < AllActions.Count)
|
|
{
|
|
// Describe the cyclical actions.
|
|
string CycleDescription = "";
|
|
foreach (Action Action in AllActions)
|
|
{
|
|
if (!ActionIsNonCyclical.ContainsKey(Action))
|
|
{
|
|
CycleDescription += string.Format("Action #{0}: {1}\n", Action.UniqueId, Action.CommandPath);
|
|
CycleDescription += string.Format("\twith arguments: {0}\n", Action.CommandArguments);
|
|
foreach (FileItem PrerequisiteItem in Action.PrerequisiteItems)
|
|
{
|
|
CycleDescription += string.Format("\tdepends on: {0}\n", PrerequisiteItem.AbsolutePath);
|
|
}
|
|
foreach (FileItem ProducedItem in Action.ProducedItems)
|
|
{
|
|
CycleDescription += string.Format("\tproduces: {0}\n", ProducedItem.AbsolutePath);
|
|
}
|
|
CycleDescription += string.Format("\tDepends on cyclic actions:\n");
|
|
if (CyclicActions.ContainsKey(Action))
|
|
{
|
|
foreach (Action CyclicPrerequisiteAction in CyclicActions[Action])
|
|
{
|
|
if (CyclicPrerequisiteAction.ProducedItems.Count == 1)
|
|
{
|
|
CycleDescription += string.Format("\t\t{0} (produces: {1})\n", CyclicPrerequisiteAction.UniqueId, CyclicPrerequisiteAction.ProducedItems[0].AbsolutePath);
|
|
}
|
|
else
|
|
{
|
|
CycleDescription += string.Format("\t\t{0}\n", CyclicPrerequisiteAction.UniqueId);
|
|
foreach (FileItem CyclicProducedItem in CyclicPrerequisiteAction.ProducedItems)
|
|
{
|
|
CycleDescription += string.Format("\t\t\tproduces: {0}\n", CyclicProducedItem.AbsolutePath);
|
|
}
|
|
}
|
|
}
|
|
CycleDescription += "\n";
|
|
}
|
|
else
|
|
{
|
|
CycleDescription += string.Format("\t\tNone?? Coding error!\n");
|
|
}
|
|
CycleDescription += "\n\n";
|
|
}
|
|
}
|
|
|
|
throw new BuildException("Action graph contains cycle!\n\n{0}", CycleDescription);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines the full set of actions that must be built to produce an item.
|
|
* @param OutputItem - The item to be built.
|
|
* @param PrerequisiteActions - The actions that must be built and the root action are
|
|
*/
|
|
static void GatherPrerequisiteActions(
|
|
FileItem OutputItem,
|
|
ref Dictionary<Action, bool> PrerequisiteActions
|
|
)
|
|
{
|
|
if (OutputItem != null && OutputItem.ProducingAction != null)
|
|
{
|
|
if (!PrerequisiteActions.ContainsKey(OutputItem.ProducingAction))
|
|
{
|
|
PrerequisiteActions.Add(OutputItem.ProducingAction, true);
|
|
foreach (FileItem PrerequisiteItem in OutputItem.ProducingAction.PrerequisiteItems)
|
|
{
|
|
GatherPrerequisiteActions(PrerequisiteItem, ref PrerequisiteActions);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines whether an action is outdated based on the modification times for its prerequisite
|
|
* and produced items.
|
|
* @param RootAction - The action being considered.
|
|
* @param OutdatedActionDictionary -
|
|
* @return true if outdated
|
|
*/
|
|
static public bool IsActionOutdated(UEBuildTarget Target, Action RootAction, ref Dictionary<Action, bool> OutdatedActionDictionary,ActionHistory ActionHistory, Dictionary<UEBuildTarget, List<FileItem>> TargetToOutdatedPrerequisitesMap )
|
|
{
|
|
// Only compute the outdated-ness for actions that don't aren't cached in the outdated action dictionary.
|
|
bool bIsOutdated = false;
|
|
if (!OutdatedActionDictionary.TryGetValue(RootAction, out bIsOutdated))
|
|
{
|
|
// Determine the last time the action was run based on the write times of its produced files.
|
|
string LatestUpdatedProducedItemName = null;
|
|
DateTimeOffset LastExecutionTime = DateTimeOffset.MaxValue;
|
|
foreach (FileItem ProducedItem in RootAction.ProducedItems)
|
|
{
|
|
// Optionally skip the action history check, as this only works for local builds
|
|
if (BuildConfiguration.bUseActionHistory)
|
|
{
|
|
// Check if the command-line of the action previously used to produce the item is outdated.
|
|
string OldProducingCommandLine = "";
|
|
string NewProducingCommandLine = RootAction.CommandPath + " " + RootAction.CommandArguments;
|
|
if (!ActionHistory.GetProducingCommandLine(ProducedItem, out OldProducingCommandLine)
|
|
|| OldProducingCommandLine != NewProducingCommandLine)
|
|
{
|
|
Log.TraceVerbose(
|
|
"{0}: Produced item \"{1}\" was produced by outdated command-line.\nOld command-line: {2}\nNew command-line: {3}",
|
|
RootAction.StatusDescription,
|
|
Path.GetFileName(ProducedItem.AbsolutePath),
|
|
OldProducingCommandLine,
|
|
NewProducingCommandLine
|
|
);
|
|
|
|
bIsOutdated = true;
|
|
|
|
// Update the command-line used to produce this item in the action history.
|
|
ActionHistory.SetProducingCommandLine(ProducedItem, NewProducingCommandLine);
|
|
}
|
|
}
|
|
|
|
// If the produced file doesn't exist or has zero size, consider it outdated. The zero size check is to detect cases
|
|
// where aborting an earlier compile produced invalid zero-sized obj files, but that may cause actions where that's
|
|
// legitimate output to always be considered outdated.
|
|
if (ProducedItem.bExists && (ProducedItem.bIsRemoteFile || ProducedItem.Length > 0 || ProducedItem.IsDirectory))
|
|
{
|
|
// When linking incrementally, don't use LIB, EXP pr PDB files when checking for the oldest produced item,
|
|
// as those files aren't always touched.
|
|
if( BuildConfiguration.bUseIncrementalLinking )
|
|
{
|
|
String ProducedItemExtension = Path.GetExtension( ProducedItem.AbsolutePath ).ToUpperInvariant();
|
|
if( ProducedItemExtension == ".LIB" || ProducedItemExtension == ".EXP" || ProducedItemExtension == ".PDB" )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Use the oldest produced item's time as the last execution time.
|
|
if (ProducedItem.LastWriteTime < LastExecutionTime)
|
|
{
|
|
LastExecutionTime = ProducedItem.LastWriteTime;
|
|
LatestUpdatedProducedItemName = ProducedItem.AbsolutePath;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If any of the produced items doesn't exist, the action is outdated.
|
|
Log.TraceVerbose(
|
|
"{0}: Produced item \"{1}\" doesn't exist.",
|
|
RootAction.StatusDescription,
|
|
Path.GetFileName(ProducedItem.AbsolutePath)
|
|
);
|
|
bIsOutdated = true;
|
|
}
|
|
}
|
|
|
|
Log.WriteLineIf(BuildConfiguration.bLogDetailedActionStats && !String.IsNullOrEmpty( LatestUpdatedProducedItemName ),
|
|
TraceEventType.Verbose, "{0}: Oldest produced item is {1}", RootAction.StatusDescription, LatestUpdatedProducedItemName);
|
|
|
|
if(!bIsOutdated)
|
|
{
|
|
// Check if any of the prerequisite items are produced by outdated actions, or have changed more recently than
|
|
// the oldest produced item.
|
|
foreach (FileItem PrerequisiteItem in RootAction.PrerequisiteItems)
|
|
{
|
|
// Only check for outdated import libraries if we were configured to do so. Often, a changed import library
|
|
// won't affect a dependency unless a public header file was also changed, in which case we would be forced
|
|
// to recompile anyway. This just allows for faster iteration when working on a subsystem in a DLL, as we
|
|
// won't have to wait for dependent targets to be relinked after each change.
|
|
bool bIsImportLibraryFile = false;
|
|
if( PrerequisiteItem.ProducingAction != null && PrerequisiteItem.ProducingAction.bProducesImportLibrary )
|
|
{
|
|
bIsImportLibraryFile = PrerequisiteItem.AbsolutePath.EndsWith( ".LIB", StringComparison.InvariantCultureIgnoreCase );
|
|
}
|
|
if( !bIsImportLibraryFile || !BuildConfiguration.bIgnoreOutdatedImportLibraries )
|
|
{
|
|
// If the prerequisite is produced by an outdated action, then this action is outdated too.
|
|
if( PrerequisiteItem.ProducingAction != null )
|
|
{
|
|
if(IsActionOutdated(Target, PrerequisiteItem.ProducingAction,ref OutdatedActionDictionary,ActionHistory, TargetToOutdatedPrerequisitesMap))
|
|
{
|
|
Log.TraceVerbose(
|
|
"{0}: Prerequisite {1} is produced by outdated action.",
|
|
RootAction.StatusDescription,
|
|
Path.GetFileName(PrerequisiteItem.AbsolutePath)
|
|
);
|
|
bIsOutdated = true;
|
|
}
|
|
}
|
|
|
|
if( PrerequisiteItem.bExists )
|
|
{
|
|
// allow a 1 second slop for network copies
|
|
TimeSpan TimeDifference = PrerequisiteItem.LastWriteTime - LastExecutionTime;
|
|
bool bPrerequisiteItemIsNewerThanLastExecution = TimeDifference.TotalSeconds > 1;
|
|
if (bPrerequisiteItemIsNewerThanLastExecution)
|
|
{
|
|
Log.TraceVerbose(
|
|
"{0}: Prerequisite {1} is newer than the last execution of the action: {2} vs {3}",
|
|
RootAction.StatusDescription,
|
|
Path.GetFileName(PrerequisiteItem.AbsolutePath),
|
|
PrerequisiteItem.LastWriteTime.LocalDateTime,
|
|
LastExecutionTime.LocalDateTime
|
|
);
|
|
bIsOutdated = true;
|
|
}
|
|
}
|
|
|
|
// GatherAllOutdatedActions will ensure all actions are checked for outdated-ness, so we don't need to recurse with
|
|
// all this action's prerequisites once we've determined it's outdated.
|
|
if (bIsOutdated)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For compile actions, we have C++ files that are actually dependent on header files that could have been changed. We only need to
|
|
// know about the set of header files that are included for files that are already determined to be out of date (such as if the file
|
|
// is missing or was modified.) In the case that the file is out of date, we'll perform a deep scan to update our cached set of
|
|
// includes for this file, so that we'll be able to determine whether it is out of date next time very quickly.
|
|
if( BuildConfiguration.bUseExperimentalFastDependencyScan )
|
|
{
|
|
var DeepIncludeScanStartTime = DateTime.UtcNow;
|
|
|
|
// @todo fastubt: we may be scanning more files than we need to here -- indirectly outdated files are bIsOutdated=true by this point (for example basemost includes when deeper includes are dirty)
|
|
if( bIsOutdated && RootAction.ActionType == ActionType.Compile )
|
|
{
|
|
Log.TraceVerbose( "Outdated action: {0}", RootAction.StatusDescription );
|
|
foreach (FileItem PrerequisiteItem in RootAction.PrerequisiteItems)
|
|
{
|
|
if( PrerequisiteItem.CachedCPPEnvironment != null )
|
|
{
|
|
if( !IsCPPFile( PrerequisiteItem ) )
|
|
{
|
|
throw new BuildException( "Was only expecting C++ files to have CachedCPPEnvironments!" );
|
|
}
|
|
Log.TraceVerbose( " -> DEEP include scan: {0}", PrerequisiteItem.AbsolutePath );
|
|
|
|
List<FileItem> OutdatedPrerequisites;
|
|
if( !TargetToOutdatedPrerequisitesMap.TryGetValue( Target, out OutdatedPrerequisites ) )
|
|
{
|
|
OutdatedPrerequisites = new List<FileItem>();
|
|
TargetToOutdatedPrerequisitesMap.Add( Target, OutdatedPrerequisites );
|
|
}
|
|
|
|
OutdatedPrerequisites.Add( PrerequisiteItem );
|
|
}
|
|
else if( IsCPPImplementationFile( PrerequisiteItem ) || IsCPPResourceFile( PrerequisiteItem ) )
|
|
{
|
|
if( PrerequisiteItem.CachedCPPEnvironment == null )
|
|
{
|
|
Log.TraceVerbose( " -> WARNING: No CachedCPPEnvironment: {0}", PrerequisiteItem.AbsolutePath );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( BuildConfiguration.bPrintPerformanceInfo )
|
|
{
|
|
double DeepIncludeScanTime = ( DateTime.UtcNow - DeepIncludeScanStartTime ).TotalSeconds;
|
|
TotalDeepIncludeScanTime += DeepIncludeScanTime;
|
|
}
|
|
}
|
|
|
|
// Cache the outdated-ness of this action.
|
|
OutdatedActionDictionary.Add(RootAction, bIsOutdated);
|
|
}
|
|
|
|
return bIsOutdated;
|
|
}
|
|
|
|
|
|
/**
|
|
* Builds a dictionary containing the actions from AllActions that are outdated by calling
|
|
* IsActionOutdated.
|
|
*/
|
|
static void GatherAllOutdatedActions(UEBuildTarget Target, ActionHistory ActionHistory, ref Dictionary<Action,bool> OutdatedActions, Dictionary<UEBuildTarget, List<FileItem>> TargetToOutdatedPrerequisitesMap )
|
|
{
|
|
foreach (var Action in AllActions)
|
|
{
|
|
IsActionOutdated(Target, Action, ref OutdatedActions, ActionHistory, TargetToOutdatedPrerequisitesMap);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes all the items produced by actions in the provided outdated action dictionary.
|
|
*
|
|
* @param OutdatedActionDictionary Dictionary of outdated actions
|
|
* @param bShouldDeleteAllFiles Whether to delete all files associated with outdated items or just ones required
|
|
*/
|
|
static void DeleteOutdatedProducedItems(Dictionary<Action, bool> OutdatedActionDictionary, bool bShouldDeleteAllFiles)
|
|
{
|
|
foreach (KeyValuePair<Action,bool> OutdatedActionInfo in OutdatedActionDictionary)
|
|
{
|
|
if (OutdatedActionInfo.Value)
|
|
{
|
|
Action OutdatedAction = OutdatedActionInfo.Key;
|
|
foreach (FileItem ProducedItem in OutdatedActionInfo.Key.ProducedItems)
|
|
{
|
|
if( ProducedItem.bExists
|
|
&& ( bShouldDeleteAllFiles
|
|
// Delete PDB files as incremental updates are slower than full ones.
|
|
|| (!BuildConfiguration.bUseIncrementalLinking && ProducedItem.AbsolutePath.EndsWith(".PDB", StringComparison.InvariantCultureIgnoreCase))
|
|
|| OutdatedAction.bShouldDeleteProducedItems) )
|
|
{
|
|
Log.TraceVerbose("Deleting outdated item: {0}", ProducedItem.AbsolutePath);
|
|
ProducedItem.Delete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates directories for all the items produced by actions in the provided outdated action
|
|
* dictionary.
|
|
*/
|
|
static void CreateDirectoriesForProducedItems(Dictionary<Action, bool> OutdatedActionDictionary)
|
|
{
|
|
foreach (KeyValuePair<Action, bool> OutdatedActionInfo in OutdatedActionDictionary)
|
|
{
|
|
if (OutdatedActionInfo.Value)
|
|
{
|
|
foreach (FileItem ProducedItem in OutdatedActionInfo.Key.ProducedItems)
|
|
{
|
|
if (ProducedItem.bIsRemoteFile)
|
|
{
|
|
// we don't need to do this in the SSH mode, the action will have an output file, and it will use that to make the directory while executing the command
|
|
if (RemoteToolChain.bUseRPCUtil)
|
|
{
|
|
try
|
|
{
|
|
RPCUtilHelper.MakeDirectory(Path.GetDirectoryName(ProducedItem.AbsolutePath).Replace("\\", "/"));
|
|
}
|
|
catch (System.Exception Ex)
|
|
{
|
|
throw new BuildException(Ex, "Error while creating remote directory for '{0}'. (Exception: {1})", ProducedItem.AbsolutePath, Ex.Message);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string DirectoryPath = Path.GetDirectoryName(ProducedItem.AbsolutePath);
|
|
if (!Directory.Exists(DirectoryPath))
|
|
{
|
|
Log.TraceVerbose("Creating directory for produced item: {0}", DirectoryPath);
|
|
Directory.CreateDirectory(DirectoryPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Checks if the specified file is a C++ source implementation file (e.g., .cpp)
|
|
/// </summary>
|
|
/// <param name="FileItem">The file to check</param>
|
|
/// <returns>True if this is a C++ source file</returns>
|
|
private static bool IsCPPImplementationFile( FileItem FileItem )
|
|
{
|
|
return( FileItem.AbsolutePath.EndsWith( ".cpp", StringComparison.InvariantCultureIgnoreCase ) ||
|
|
FileItem.AbsolutePath.EndsWith( ".c", StringComparison.InvariantCultureIgnoreCase ) ||
|
|
FileItem.AbsolutePath.EndsWith( ".mm", StringComparison.InvariantCultureIgnoreCase ) );
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Checks if the specified file is a C++ source header file (e.g., .h or .inl)
|
|
/// </summary>
|
|
/// <param name="FileItem">The file to check</param>
|
|
/// <returns>True if this is a C++ source file</returns>
|
|
private static bool IsCPPIncludeFile( FileItem FileItem )
|
|
{
|
|
return( FileItem.AbsolutePath.EndsWith( ".h", StringComparison.InvariantCultureIgnoreCase ) ||
|
|
FileItem.AbsolutePath.EndsWith( ".inl", StringComparison.InvariantCultureIgnoreCase ) );
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Checks if the specified file is a C++ resource file (e.g., .rc)
|
|
/// </summary>
|
|
/// <param name="FileItem">The file to check</param>
|
|
/// <returns>True if this is a C++ source file</returns>
|
|
private static bool IsCPPResourceFile( FileItem FileItem )
|
|
{
|
|
return( FileItem.AbsolutePath.EndsWith( ".rc", StringComparison.InvariantCultureIgnoreCase ) );
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Checks if the specified file is a C++ source file
|
|
/// </summary>
|
|
/// <param name="FileItem">The file to check</param>
|
|
/// <returns>True if this is a C++ source file</returns>
|
|
private static bool IsCPPFile( FileItem FileItem )
|
|
{
|
|
return IsCPPImplementationFile( FileItem ) || IsCPPIncludeFile( FileItem ) || IsCPPResourceFile( FileItem );
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Types of action graph visualizations that we can emit
|
|
/// </summary>
|
|
public enum ActionGraphVisualizationType
|
|
{
|
|
OnlyActions,
|
|
ActionsWithFiles,
|
|
ActionsWithFilesAndHeaders,
|
|
OnlyFilesAndHeaders,
|
|
OnlyCPlusPlusFilesAndHeaders
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Saves the action graph (and include dependency network) to a graph gile
|
|
/// </summary>
|
|
/// <param name="Filename">File name to emit</param>
|
|
/// <param name="Description">Description to be stored in graph metadata</param>
|
|
/// <param name="VisualizationType">Type of graph to create</param>
|
|
/// <param name="Actions">All actions</param>
|
|
/// <param name="IncludeCompileActions">True if we should include compile actions. If disabled, only the static link actions will be shown, which is useful to see module relationships</param>
|
|
public static void SaveActionGraphVisualization( UEBuildTarget Target, string Filename, string Description, ActionGraphVisualizationType VisualizationType, List<Action> Actions, bool IncludeCompileActions = true )
|
|
{
|
|
// True if we should include individual files in the graph network, or false to include only the build actions
|
|
var IncludeFiles = VisualizationType != ActionGraphVisualizationType.OnlyActions;
|
|
var OnlyIncludeCPlusPlusFiles = VisualizationType == ActionGraphVisualizationType.OnlyCPlusPlusFilesAndHeaders;
|
|
|
|
// True if want to show actions in the graph, otherwise we're only showing files
|
|
var IncludeActions = VisualizationType != ActionGraphVisualizationType.OnlyFilesAndHeaders && VisualizationType != ActionGraphVisualizationType.OnlyCPlusPlusFilesAndHeaders;
|
|
|
|
// True if C++ header dependencies should be expanded into the graph, or false to only have .cpp files
|
|
var ExpandCPPHeaderDependencies = IncludeFiles && ( VisualizationType == ActionGraphVisualizationType.ActionsWithFilesAndHeaders || VisualizationType == ActionGraphVisualizationType.OnlyFilesAndHeaders || VisualizationType == ActionGraphVisualizationType.OnlyCPlusPlusFilesAndHeaders );
|
|
|
|
var TimerStartTime = DateTime.UtcNow;
|
|
|
|
var GraphNodes = new List<GraphNode>();
|
|
|
|
var FileToGraphNodeMap = new Dictionary< FileItem, GraphNode >();
|
|
|
|
// Filter our list of actions
|
|
var FilteredActions = new List<Action>();
|
|
{
|
|
for( var ActionIndex = 0; ActionIndex < Actions.Count; ++ActionIndex )
|
|
{
|
|
var Action = Actions[ ActionIndex ];
|
|
|
|
if( !IncludeActions || IncludeCompileActions || ( Action.ActionType != ActionType.Compile ) )
|
|
{
|
|
FilteredActions.Add( Action );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
var FilesToCreateNodesFor = new HashSet<FileItem>();
|
|
for( var ActionIndex = 0; ActionIndex < FilteredActions.Count; ++ActionIndex )
|
|
{
|
|
var Action = FilteredActions[ ActionIndex ];
|
|
|
|
if( IncludeActions )
|
|
{
|
|
var GraphNode = new GraphNode()
|
|
{
|
|
Id = GraphNodes.Count,
|
|
|
|
// Don't bother including "Link" text if we're excluding compile actions
|
|
Label = IncludeCompileActions ? ( Action.ActionType.ToString() + " " + Action.StatusDescription ) : Action.StatusDescription
|
|
};
|
|
|
|
switch( Action.ActionType )
|
|
{
|
|
case ActionType.BuildProject:
|
|
GraphNode.Color = new GraphColor() { R = 0.3f, G = 1.0f, B = 1.0f, A = 1.0f };
|
|
GraphNode.Size = 1.1f;
|
|
break;
|
|
|
|
case ActionType.Compile:
|
|
GraphNode.Color = new GraphColor() { R = 0.3f, G = 1.0f, B = 0.3f, A = 1.0f };
|
|
break;
|
|
|
|
case ActionType.Link:
|
|
GraphNode.Color = new GraphColor() { R = 0.3f, G = 0.3f, B = 1.0f, A = 1.0f };
|
|
GraphNode.Size = 1.2f;
|
|
break;
|
|
}
|
|
|
|
GraphNodes.Add( GraphNode );
|
|
}
|
|
|
|
if( IncludeFiles )
|
|
{
|
|
foreach( var ProducedFileItem in Action.ProducedItems )
|
|
{
|
|
if( !OnlyIncludeCPlusPlusFiles || IsCPPFile( ProducedFileItem ) )
|
|
{
|
|
FilesToCreateNodesFor.Add( ProducedFileItem );
|
|
}
|
|
}
|
|
|
|
foreach( var PrerequisiteFileItem in Action.PrerequisiteItems )
|
|
{
|
|
if( !OnlyIncludeCPlusPlusFiles || IsCPPFile( PrerequisiteFileItem ) )
|
|
{
|
|
FilesToCreateNodesFor.Add( PrerequisiteFileItem );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
var OverriddenPrerequisites = new Dictionary<FileItem, List<FileItem>>();
|
|
|
|
// Determine the average size of all of the C++ source files
|
|
Int64 AverageCPPFileSize;
|
|
{
|
|
Int64 TotalFileSize = 0;
|
|
int CPPFileCount = 0;
|
|
foreach( var FileItem in FilesToCreateNodesFor )
|
|
{
|
|
if( IsCPPFile( FileItem ) )
|
|
{
|
|
++CPPFileCount;
|
|
TotalFileSize += new FileInfo( FileItem.AbsolutePath ).Length;
|
|
}
|
|
}
|
|
|
|
if( CPPFileCount > 0 )
|
|
{
|
|
AverageCPPFileSize = TotalFileSize / CPPFileCount;
|
|
}
|
|
else
|
|
{
|
|
AverageCPPFileSize = 1;
|
|
}
|
|
}
|
|
|
|
var BuildPlatform = UEBuildPlatform.GetBuildPlatform( UnrealTargetPlatform.Win64 );
|
|
|
|
foreach( var FileItem in FilesToCreateNodesFor )
|
|
{
|
|
var FileGraphNode = new GraphNode()
|
|
{
|
|
Id = GraphNodes.Count,
|
|
Label = Path.GetFileName( FileItem.AbsolutePath )
|
|
};
|
|
|
|
if( FileItem.AbsolutePath.EndsWith( ".h", StringComparison.InvariantCultureIgnoreCase ) ||
|
|
FileItem.AbsolutePath.EndsWith( ".inl", StringComparison.InvariantCultureIgnoreCase ) )
|
|
{
|
|
// Header file
|
|
FileGraphNode.Color = new GraphColor() { R = 0.9f, G = 0.2f, B = 0.9f, A = 1.0f };
|
|
}
|
|
else if( FileItem.AbsolutePath.EndsWith( ".cpp", StringComparison.InvariantCultureIgnoreCase ) ||
|
|
FileItem.AbsolutePath.EndsWith( ".c", StringComparison.InvariantCultureIgnoreCase ) ||
|
|
FileItem.AbsolutePath.EndsWith( ".mm", StringComparison.InvariantCultureIgnoreCase ) )
|
|
{
|
|
// C++ file
|
|
FileGraphNode.Color = new GraphColor() { R = 1.0f, G = 1.0f, B = 0.3f, A = 1.0f };
|
|
}
|
|
else
|
|
{
|
|
// Other file
|
|
FileGraphNode.Color = new GraphColor() { R = 0.4f, G = 0.4f, B = 0.1f, A = 1.0f };
|
|
}
|
|
|
|
// Set the size of the file node based on the size of the file on disk
|
|
var bIsCPPFile = IsCPPFile( FileItem );
|
|
if( bIsCPPFile )
|
|
{
|
|
var MinNodeSize = 0.25f;
|
|
var MaxNodeSize = 2.0f;
|
|
var FileSize = new FileInfo( FileItem.AbsolutePath ).Length;
|
|
float FileSizeScale = (float)( (double)FileSize / (double)AverageCPPFileSize );
|
|
|
|
var SourceFileSizeScaleFactor = 0.1f; // How much to make nodes for files bigger or larger based on their difference from the average file's size
|
|
FileGraphNode.Size = Math.Min( Math.Max( 1.0f + SourceFileSizeScaleFactor * FileSizeScale, MinNodeSize ), MaxNodeSize );
|
|
}
|
|
|
|
//@todo: Testing out attribute support. Replace with an attribute that is actually useful!
|
|
//if( FileItem.PrecompiledHeaderIncludeFilename != null )
|
|
//{
|
|
//FileGraphNode.Attributes[ "PCHFile" ] = Path.GetFileNameWithoutExtension( FileItem.PrecompiledHeaderIncludeFilename );
|
|
//}
|
|
|
|
FileToGraphNodeMap[ FileItem ] = FileGraphNode;
|
|
GraphNodes.Add( FileGraphNode );
|
|
|
|
if( ExpandCPPHeaderDependencies && bIsCPPFile )
|
|
{
|
|
bool HasUObjects;
|
|
List<DependencyInclude> DirectlyIncludedFilenames = CPPEnvironment.GetDirectIncludeDependencies( Target, FileItem, BuildPlatform, bOnlyCachedDependencies:false, HasUObjects:out HasUObjects );
|
|
|
|
// Resolve the included file name to an actual file.
|
|
var DirectlyIncludedFiles =
|
|
DirectlyIncludedFilenames
|
|
.Where(DirectlyIncludedFilename => !string.IsNullOrEmpty(DirectlyIncludedFilename.IncludeResolvedName))
|
|
.Select(DirectlyIncludedFilename => DirectlyIncludedFilename.IncludeResolvedName)
|
|
// Skip same include over and over (.inl files)
|
|
.Distinct()
|
|
.Select(FileItem.GetItemByFullPath)
|
|
.ToList();
|
|
|
|
OverriddenPrerequisites[ FileItem ] = DirectlyIncludedFiles;
|
|
}
|
|
}
|
|
|
|
|
|
// Connect everything together
|
|
var GraphEdges = new List<GraphEdge>();
|
|
|
|
if( IncludeActions )
|
|
{
|
|
for( var ActionIndex = 0; ActionIndex < FilteredActions.Count; ++ActionIndex )
|
|
{
|
|
var Action = FilteredActions[ ActionIndex ];
|
|
var ActionGraphNode = GraphNodes[ ActionIndex ];
|
|
|
|
List<FileItem> ActualPrerequisiteItems = Action.PrerequisiteItems;
|
|
if( IncludeFiles && ExpandCPPHeaderDependencies && Action.ActionType == ActionType.Compile )
|
|
{
|
|
// The first prerequisite is always the .cpp file to compile
|
|
var CPPFile = Action.PrerequisiteItems[ 0 ];
|
|
if( !IsCPPFile( CPPFile ) )
|
|
{
|
|
throw new BuildException( "Was expecting a C++ file as the first prerequisite for a Compile action" );
|
|
}
|
|
|
|
ActualPrerequisiteItems = new List<FileItem>();
|
|
ActualPrerequisiteItems.Add( CPPFile );
|
|
}
|
|
|
|
|
|
foreach( var PrerequisiteFileItem in ActualPrerequisiteItems )
|
|
{
|
|
if( IncludeFiles )
|
|
{
|
|
GraphNode PrerequisiteFileGraphNode;
|
|
if( FileToGraphNodeMap.TryGetValue( PrerequisiteFileItem, out PrerequisiteFileGraphNode ) )
|
|
{
|
|
// Connect a file our action is dependent on, to our action itself
|
|
var GraphEdge = new GraphEdge()
|
|
{
|
|
Id = GraphEdges.Count,
|
|
Source = PrerequisiteFileGraphNode,
|
|
Target = ActionGraphNode,
|
|
};
|
|
|
|
GraphEdges.Add( GraphEdge );
|
|
}
|
|
else
|
|
{
|
|
// Not a file we were tracking
|
|
// Console.WriteLine( "Unknown file: " + PrerequisiteFileItem.AbsolutePath );
|
|
}
|
|
}
|
|
else if( PrerequisiteFileItem.ProducingAction != null )
|
|
{
|
|
// Not showing files, so connect the actions together
|
|
var ProducingActionIndex = FilteredActions.IndexOf( PrerequisiteFileItem.ProducingAction );
|
|
if( ProducingActionIndex != -1 )
|
|
{
|
|
var SourceGraphNode = GraphNodes[ ProducingActionIndex ];
|
|
|
|
var GraphEdge = new GraphEdge()
|
|
{
|
|
Id = GraphEdges.Count,
|
|
Source = SourceGraphNode,
|
|
Target = ActionGraphNode,
|
|
};
|
|
|
|
GraphEdges.Add( GraphEdge );
|
|
}
|
|
else
|
|
{
|
|
// Our producer action was filtered out
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach( var ProducedFileItem in Action.ProducedItems )
|
|
{
|
|
if( IncludeFiles )
|
|
{
|
|
if( !OnlyIncludeCPlusPlusFiles || IsCPPFile( ProducedFileItem ) )
|
|
{
|
|
var ProducedFileGraphNode = FileToGraphNodeMap[ ProducedFileItem ];
|
|
|
|
var GraphEdge = new GraphEdge()
|
|
{
|
|
Id = GraphEdges.Count,
|
|
Source = ActionGraphNode,
|
|
Target = ProducedFileGraphNode,
|
|
};
|
|
|
|
GraphEdges.Add( GraphEdge );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( IncludeFiles && ExpandCPPHeaderDependencies )
|
|
{
|
|
// Fill in overridden prerequisites
|
|
foreach( var FileAndPrerequisites in OverriddenPrerequisites )
|
|
{
|
|
var FileItem = FileAndPrerequisites.Key;
|
|
var FilePrerequisites = FileAndPrerequisites.Value;
|
|
|
|
var FileGraphNode = FileToGraphNodeMap[ FileItem ];
|
|
foreach( var PrerequisiteFileItem in FilePrerequisites )
|
|
{
|
|
GraphNode PrerequisiteFileGraphNode;
|
|
if( FileToGraphNodeMap.TryGetValue( PrerequisiteFileItem, out PrerequisiteFileGraphNode ) )
|
|
{
|
|
var GraphEdge = new GraphEdge()
|
|
{
|
|
Id = GraphEdges.Count,
|
|
Source = PrerequisiteFileGraphNode,
|
|
Target = FileGraphNode,
|
|
};
|
|
|
|
GraphEdges.Add( GraphEdge );
|
|
}
|
|
else
|
|
{
|
|
// Some other header that we don't track directly
|
|
//Console.WriteLine( "File not known: " + PrerequisiteFileItem.AbsolutePath );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GraphVisualization.WriteGraphFile( Filename, Description, GraphNodes, GraphEdges );
|
|
|
|
if( BuildConfiguration.bPrintPerformanceInfo )
|
|
{
|
|
var TimerDuration = DateTime.UtcNow - TimerStartTime;
|
|
Log.TraceInformation( "Generating and saving ActionGraph took " + TimerDuration.TotalSeconds + "s" );
|
|
}
|
|
}
|
|
};
|
|
}
|