/// <summary> /// Constructor /// </summary> /// <param name="Location">File to store this history in</param> /// <param name="BaseDirectory">Base directory for files to track</param> /// <param name="Parent">The parent action history</param> public ActionHistory(FileReference Location, DirectoryReference BaseDirectory, ActionHistory Parent) { this.Location = Location; this.BaseDirectory = BaseDirectory; this.Parent = Parent; if (FileReference.Exists(Location)) { Load(); } }
/// <summary> /// Reads a cache from the given location, or creates it with the given settings /// </summary> /// <param name="Location">File to store the cache</param> /// <param name="BaseDirectory">Base directory for files that this cache should store data for</param> /// <param name="Parent">The parent cache to use</param> /// <returns>Reference to a dependency cache with the given settings</returns> static ActionHistory FindOrAddHistory(FileReference Location, DirectoryReference BaseDirectory, ActionHistory Parent) { lock (LoadedFiles) { ActionHistory History; if (LoadedFiles.TryGetValue(Location, out History)) { Debug.Assert(History.BaseDirectory == BaseDirectory); Debug.Assert(History.Parent == Parent); } else { History = new ActionHistory(Location, BaseDirectory, Parent); LoadedFiles.Add(Location, History); } return(History); } }
/// <summary> /// Creates a hierarchy of action history stores for a particular target /// </summary> /// <param name="ProjectFile">Project file for the target being built</param> /// <param name="TargetName">Name of the target</param> /// <param name="Platform">Platform being built</param> /// <param name="TargetType">The target type</param> /// <param name="Architecture">The target architecture</param> /// <returns>Dependency cache hierarchy for the given project</returns> public static ActionHistory CreateHierarchy(FileReference ProjectFile, string TargetName, UnrealTargetPlatform Platform, TargetType TargetType, string Architecture) { ActionHistory History = null; if (ProjectFile == null || !UnrealBuildTool.IsEngineInstalled()) { FileReference EngineCacheLocation = GetEngineLocation(TargetName, Platform, TargetType, Architecture); History = FindOrAddHistory(EngineCacheLocation, UnrealBuildTool.EngineDirectory, History); } if (ProjectFile != null) { FileReference ProjectCacheLocation = GetProjectLocation(ProjectFile, TargetName, Platform, Architecture); History = FindOrAddHistory(ProjectCacheLocation, ProjectFile.Directory, History); } return(History); }
/// <summary> /// Builds a dictionary containing the actions from AllActions that are outdated by calling /// IsActionOutdated. /// </summary> static void GatherAllOutdatedActions(List <Action> Actions, ActionHistory ActionHistory, Dictionary <Action, bool> OutdatedActions, CppDependencyCache CppDependencies, bool bIgnoreOutdatedImportLibraries) { using (Timeline.ScopeEvent("Prefetching include dependencies")) { List <FileItem> Dependencies = new List <FileItem>(); foreach (Action Action in Actions) { if (Action.DependencyListFile != null) { Dependencies.Add(Action.DependencyListFile); } } Parallel.ForEach(Dependencies, File => { List <FileItem> Temp; CppDependencies.TryGetDependencies(File, out Temp); }); } using (Timeline.ScopeEvent("Cache outdated actions")) { Parallel.ForEach(Actions, Action => IsActionOutdated(Action, OutdatedActions, ActionHistory, CppDependencies, bIgnoreOutdatedImportLibraries)); } }
/// <summary> /// Builds a list of actions that need to be executed to produce the specified output items. /// </summary> public static HashSet <Action> GetActionsToExecute(List <Action> Actions, List <Action> PrerequisiteActions, CppDependencyCache CppDependencies, ActionHistory History, bool bIgnoreOutdatedImportLibraries) { ITimelineEvent GetActionsToExecuteTimer = Timeline.ScopeEvent("ActionGraph.GetActionsToExecute()"); // Build a set of all actions needed for this target. Dictionary <Action, bool> IsActionOutdatedMap = new Dictionary <Action, bool>(); foreach (Action Action in PrerequisiteActions) { IsActionOutdatedMap.Add(Action, true); } // For all targets, build a set of all actions that are outdated. Dictionary <Action, bool> OutdatedActionDictionary = new Dictionary <Action, bool>(); GatherAllOutdatedActions(Actions, History, OutdatedActionDictionary, CppDependencies, bIgnoreOutdatedImportLibraries); // Build a list of actions that are both needed for this target and outdated. HashSet <Action> ActionsToExecute = new HashSet <Action>(Actions.Where(Action => Action.CommandPath != null && IsActionOutdatedMap.ContainsKey(Action) && OutdatedActionDictionary[Action])); GetActionsToExecuteTimer.Finish(); return(ActionsToExecute); }
/// <summary> /// Determines whether an action is outdated based on the modification times for its prerequisite /// and produced items. /// </summary> /// <param name="RootAction">- The action being considered.</param> /// <param name="OutdatedActionDictionary">-</param> /// <param name="ActionHistory"></param> /// <param name="CppDependencies"></param> /// <param name="bIgnoreOutdatedImportLibraries"></param> /// <returns>true if outdated</returns> public static bool IsActionOutdated(Action RootAction, Dictionary <Action, bool> OutdatedActionDictionary, ActionHistory ActionHistory, CppDependencyCache CppDependencies, bool bIgnoreOutdatedImportLibraries) { // Only compute the outdated-ness for actions that don't aren't cached in the outdated action dictionary. bool bIsOutdated = false; lock (OutdatedActionDictionary) { if (OutdatedActionDictionary.TryGetValue(RootAction, out bIsOutdated)) { return(bIsOutdated); } } // Determine the last time the action was run based on the write times of its produced files. string LatestUpdatedProducedItemName = null; DateTimeOffset LastExecutionTimeUtc = DateTimeOffset.MaxValue; foreach (FileItem ProducedItem in RootAction.ProducedItems) { // Check if the command-line of the action previously used to produce the item is outdated. string NewProducingCommandLine = RootAction.CommandPath.FullName + " " + RootAction.CommandArguments; if (ActionHistory.UpdateProducingCommandLine(ProducedItem, NewProducingCommandLine)) { if (ProducedItem.Exists) { Log.TraceLog( "{0}: Produced item \"{1}\" was produced by outdated command-line.\n New command-line: {2}", RootAction.StatusDescription, Path.GetFileName(ProducedItem.AbsolutePath), NewProducingCommandLine ); } bIsOutdated = true; } // 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.Exists && (RootAction.ActionType != ActionType.Compile || ProducedItem.Length > 0 || (!ProducedItem.Location.HasExtension(".obj") && !ProducedItem.Location.HasExtension(".o")))) { // Use the oldest produced item's time as the last execution time. if (ProducedItem.LastWriteTimeUtc < LastExecutionTimeUtc) { LastExecutionTimeUtc = ProducedItem.LastWriteTimeUtc; LatestUpdatedProducedItemName = ProducedItem.AbsolutePath; } } else { // If any of the produced items doesn't exist, the action is outdated. Log.TraceLog( "{0}: Produced item \"{1}\" doesn't exist.", RootAction.StatusDescription, Path.GetFileName(ProducedItem.AbsolutePath) ); bIsOutdated = true; } } // Check if any of the prerequisite actions are out of date if (!bIsOutdated) { foreach (Action PrerequisiteAction in RootAction.PrerequisiteActions) { if (IsActionOutdated(PrerequisiteAction, OutdatedActionDictionary, ActionHistory, CppDependencies, bIgnoreOutdatedImportLibraries)) { // 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. if (!bIgnoreOutdatedImportLibraries || !IsImportLibraryDependency(RootAction, PrerequisiteAction)) { Log.TraceLog("{0}: Prerequisite {1} is produced by outdated action.", RootAction.StatusDescription, PrerequisiteAction.StatusDescription); bIsOutdated = true; break; } } } } // Check if any prerequisite item has a newer timestamp than the last execution time of this action if (!bIsOutdated) { foreach (FileItem PrerequisiteItem in RootAction.PrerequisiteItems) { if (PrerequisiteItem.Exists) { // allow a 1 second slop for network copies TimeSpan TimeDifference = PrerequisiteItem.LastWriteTimeUtc - LastExecutionTimeUtc; bool bPrerequisiteItemIsNewerThanLastExecution = TimeDifference.TotalSeconds > 1; if (bPrerequisiteItemIsNewerThanLastExecution) { // Need to check for import libraries here too if (!bIgnoreOutdatedImportLibraries || !IsImportLibraryDependency(RootAction, PrerequisiteItem)) { Log.TraceLog("{0}: Prerequisite {1} is newer than the last execution of the action: {2} vs {3}", RootAction.StatusDescription, Path.GetFileName(PrerequisiteItem.AbsolutePath), PrerequisiteItem.LastWriteTimeUtc.ToLocalTime(), LastExecutionTimeUtc.LocalDateTime); bIsOutdated = true; break; } } } } } // Check the dependency list if (!bIsOutdated && RootAction.DependencyListFile != null) { List <FileItem> DependencyFiles; if (!CppDependencies.TryGetDependencies(RootAction.DependencyListFile, out DependencyFiles)) { Log.TraceLog("{0}: Missing dependency list file \"{1}\"", RootAction.StatusDescription, RootAction.DependencyListFile); bIsOutdated = true; } else { foreach (FileItem DependencyFile in DependencyFiles) { if (!DependencyFile.Exists || DependencyFile.LastWriteTimeUtc > LastExecutionTimeUtc) { Log.TraceLog( "{0}: Dependency {1} is newer than the last execution of the action: {2} vs {3}", RootAction.StatusDescription, Path.GetFileName(DependencyFile.AbsolutePath), DependencyFile.LastWriteTimeUtc.ToLocalTime(), LastExecutionTimeUtc.LocalDateTime ); bIsOutdated = true; break; } } } } // Cache the outdated-ness of this action. lock (OutdatedActionDictionary) { if (!OutdatedActionDictionary.ContainsKey(RootAction)) { OutdatedActionDictionary.Add(RootAction, bIsOutdated); } } return(bIsOutdated); }
/// <summary> /// Build a list of targets with a given set of makefiles. /// </summary> /// <param name="Makefiles">Makefiles created with CreateMakefiles</param> /// <param name="TargetDescriptors">Target descriptors</param> /// <param name="BuildConfiguration">Current build configuration</param> /// <param name="WorkingSet">The source file working set</param> /// <param name="Options">Additional options for the build</param> /// <param name="WriteOutdatedActionsFile">Files to write the list of outdated actions to (rather than building them)</param> /// <returns>Result from the compilation</returns> static void Build(TargetMakefile[] Makefiles, List <TargetDescriptor> TargetDescriptors, BuildConfiguration BuildConfiguration, ISourceFileWorkingSet WorkingSet, BuildOptions Options, FileReference WriteOutdatedActionsFile) { // Export the actions for each target for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { TargetDescriptor TargetDescriptor = TargetDescriptors[TargetIdx]; foreach (FileReference WriteActionFile in TargetDescriptor.WriteActionFiles) { Log.TraceInformation("Writing actions to {0}", WriteActionFile); ActionGraph.ExportJson(Makefiles[TargetIdx].Actions, WriteActionFile); } } // Execute the build if ((Options & BuildOptions.SkipBuild) == 0) { // Make sure that none of the actions conflict with any other (producing output files differently, etc...) ActionGraph.CheckForConflicts(Makefiles.SelectMany(x => x.Actions)); // Check we don't exceed the nominal max path length using (Timeline.ScopeEvent("ActionGraph.CheckPathLengths")) { ActionGraph.CheckPathLengths(BuildConfiguration, Makefiles.SelectMany(x => x.Actions)); } // Clean up any previous hot reload runs, and reapply the current state if it's already active for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { HotReload.Setup(TargetDescriptors[TargetIdx], Makefiles[TargetIdx], BuildConfiguration); } // Merge the action graphs together List <Action> MergedActions; if (TargetDescriptors.Count == 1) { MergedActions = new List <Action>(Makefiles[0].Actions); } else { MergedActions = MergeActionGraphs(TargetDescriptors, Makefiles); } // Gather all the prerequisite actions that are part of the targets HashSet <FileItem> MergedOutputItems = new HashSet <FileItem>(); for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { GatherOutputItems(TargetDescriptors[TargetIdx], Makefiles[TargetIdx], MergedOutputItems); } // Link all the actions together ActionGraph.Link(MergedActions); // Get all the actions that are prerequisites for these targets. This forms the list of actions that we want executed. List <Action> PrerequisiteActions = ActionGraph.GatherPrerequisiteActions(MergedActions, MergedOutputItems); // Create the action history ActionHistory History = new ActionHistory(); for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { using (Timeline.ScopeEvent("Reading action history")) { TargetDescriptor TargetDescriptor = TargetDescriptors[TargetIdx]; if (TargetDescriptor.ProjectFile != null) { History.Mount(TargetDescriptor.ProjectFile.Directory); } } } // Figure out which actions need to be built Dictionary <Action, bool> ActionToOutdatedFlag = new Dictionary <Action, bool>(); for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { TargetDescriptor TargetDescriptor = TargetDescriptors[TargetIdx]; // Create the dependencies cache CppDependencyCache CppDependencies; using (Timeline.ScopeEvent("Reading dependency cache")) { CppDependencies = CppDependencyCache.CreateHierarchy(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, TargetDescriptor.Configuration, Makefiles[TargetIdx].TargetType, TargetDescriptor.Architecture); } // Plan the actions to execute for the build. For single file compiles, always rebuild the source file regardless of whether it's out of date. if (TargetDescriptor.SpecificFilesToCompile.Count == 0) { ActionGraph.GatherAllOutdatedActions(PrerequisiteActions, History, ActionToOutdatedFlag, CppDependencies, BuildConfiguration.bIgnoreOutdatedImportLibraries); } else { foreach (FileReference SpecificFile in TargetDescriptor.SpecificFilesToCompile) { foreach (Action PrerequisiteAction in PrerequisiteActions.Where(x => x.PrerequisiteItems.Any(y => y.Location == SpecificFile))) { ActionToOutdatedFlag[PrerequisiteAction] = true; } } } } // Link the action graph again to sort it List <Action> MergedActionsToExecute = ActionToOutdatedFlag.Where(x => x.Value).Select(x => x.Key).ToList(); ActionGraph.Link(MergedActionsToExecute); // Allow hot reload to override the actions int HotReloadTargetIdx = -1; for (int Idx = 0; Idx < TargetDescriptors.Count; Idx++) { if (TargetDescriptors[Idx].HotReloadMode != HotReloadMode.Disabled) { if (HotReloadTargetIdx != -1) { throw new BuildException("Unable to perform hot reload with multiple targets."); } else { MergedActionsToExecute = HotReload.PatchActionsForTarget(BuildConfiguration, TargetDescriptors[Idx], Makefiles[Idx], PrerequisiteActions, MergedActionsToExecute); } HotReloadTargetIdx = Idx; } } // Make sure we're not modifying any engine files if ((Options & BuildOptions.NoEngineChanges) != 0) { List <FileItem> EngineChanges = MergedActionsToExecute.SelectMany(x => x.ProducedItems).Where(x => x.Location.IsUnderDirectory(UnrealBuildTool.EngineDirectory)).Distinct().OrderBy(x => x.FullName).ToList(); if (EngineChanges.Count > 0) { StringBuilder Result = new StringBuilder("Building would modify the following engine files:\n"); foreach (FileItem EngineChange in EngineChanges) { Result.AppendFormat("\n{0}", EngineChange.FullName); } Result.Append("\n\nPlease rebuild from an IDE instead."); Log.TraceError("{0}", Result.ToString()); throw new CompilationResultException(CompilationResult.FailedDueToEngineChange); } } // Make sure the appropriate executor is selected foreach (TargetDescriptor TargetDescriptor in TargetDescriptors) { UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(TargetDescriptor.Platform); BuildConfiguration.bAllowXGE &= BuildPlatform.CanUseXGE(); BuildConfiguration.bAllowDistcc &= BuildPlatform.CanUseDistcc(); BuildConfiguration.bAllowSNDBS &= BuildPlatform.CanUseSNDBS(); } // Delete produced items that are outdated. ActionGraph.DeleteOutdatedProducedItems(MergedActionsToExecute); // Save all the action histories now that files have been removed. We have to do this after deleting produced items to ensure that any // items created during the build don't have the wrong command line. History.Save(); // Create directories for the outdated produced items. ActionGraph.CreateDirectoriesForProducedItems(MergedActionsToExecute); // Execute the actions if ((Options & BuildOptions.XGEExport) != 0) { OutputToolchainInfo(TargetDescriptors, Makefiles); // Just export to an XML file using (Timeline.ScopeEvent("XGE.ExportActions()")) { XGE.ExportActions(MergedActionsToExecute); } } else if (WriteOutdatedActionsFile != null) { OutputToolchainInfo(TargetDescriptors, Makefiles); // Write actions to an output file using (Timeline.ScopeEvent("ActionGraph.WriteActions")) { ActionGraph.ExportJson(MergedActionsToExecute, WriteOutdatedActionsFile); } } else { // Execute the actions if (MergedActionsToExecute.Count == 0) { if (TargetDescriptors.Any(x => !x.bQuiet)) { Log.TraceInformation((TargetDescriptors.Count == 1)? "Target is up to date" : "Targets are up to date"); } } else { if (TargetDescriptors.Any(x => !x.bQuiet)) { Log.TraceInformation("Building {0}...", StringUtils.FormatList(TargetDescriptors.Select(x => x.Name).Distinct())); } OutputToolchainInfo(TargetDescriptors, Makefiles); using (Timeline.ScopeEvent("ActionGraph.ExecuteActions()")) { ActionGraph.ExecuteActions(BuildConfiguration, MergedActionsToExecute); } } // Run the deployment steps foreach (TargetMakefile Makefile in Makefiles) { if (Makefile.bDeployAfterCompile) { TargetReceipt Receipt = TargetReceipt.Read(Makefile.ReceiptFile); Log.TraceInformation("Deploying {0} {1} {2}...", Receipt.TargetName, Receipt.Platform, Receipt.Configuration); UEBuildPlatform.GetBuildPlatform(Receipt.Platform).Deploy(Receipt); } } } } }
/// <summary> /// Determine what needs to be built for a target /// </summary> /// <param name="BuildConfiguration">The build configuration</param> /// <param name="TargetDescriptor">Target being built</param> /// <param name="Makefile">Makefile generated for this target</param> /// <returns>Set of actions to execute</returns> static HashSet <Action> GetActionsForTarget(BuildConfiguration BuildConfiguration, TargetDescriptor TargetDescriptor, TargetMakefile Makefile) { // Create the action graph ActionGraph.Link(Makefile.Actions); // Get the hot-reload mode HotReloadMode HotReloadMode = TargetDescriptor.HotReloadMode; if (HotReloadMode == HotReloadMode.Default) { if (TargetDescriptor.HotReloadModuleNameToSuffix.Count > 0 && TargetDescriptor.ForeignPlugin == null) { HotReloadMode = HotReloadMode.FromEditor; } else if (BuildConfiguration.bAllowHotReloadFromIDE && HotReload.ShouldDoHotReloadFromIDE(BuildConfiguration, TargetDescriptor)) { HotReloadMode = HotReloadMode.FromIDE; } else { HotReloadMode = HotReloadMode.Disabled; } } // Get the root prerequisite actions List <Action> PrerequisiteActions = GatherPrerequisiteActions(TargetDescriptor, Makefile); // Get the path to the hot reload state file for this target FileReference HotReloadStateFile = global::UnrealBuildTool.HotReloadState.GetLocation(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, TargetDescriptor.Configuration, TargetDescriptor.Architecture); // Apply the previous hot reload state HotReloadState HotReloadState = null; if (HotReloadMode == HotReloadMode.Disabled) { // Make sure we're not doing a partial build from the editor (eg. compiling a new plugin) if (TargetDescriptor.ForeignPlugin == null && TargetDescriptor.SingleFileToCompile == null) { // Delete the previous state file HotReload.DeleteTemporaryFiles(HotReloadStateFile); } } else { // Read the previous state file and apply it to the action graph if (FileReference.Exists(HotReloadStateFile)) { HotReloadState = HotReloadState.Load(HotReloadStateFile); } else { HotReloadState = new HotReloadState(); } // Apply the old state to the makefile HotReload.ApplyState(HotReloadState, Makefile); // If we want a specific suffix on any modules, apply that now. We'll track the outputs later, but the suffix has to be forced (and is always out of date if it doesn't exist). HotReload.PatchActionGraphWithNames(PrerequisiteActions, TargetDescriptor.HotReloadModuleNameToSuffix, Makefile); // Re-link the action graph ActionGraph.Link(PrerequisiteActions); } // Create the dependencies cache CppDependencyCache CppDependencies; using (Timeline.ScopeEvent("Reading dependency cache")) { CppDependencies = CppDependencyCache.CreateHierarchy(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, TargetDescriptor.Configuration, Makefile.TargetType); } // Create the action history ActionHistory History; using (Timeline.ScopeEvent("Reading action history")) { History = ActionHistory.CreateHierarchy(TargetDescriptor.ProjectFile, TargetDescriptor.Name, TargetDescriptor.Platform, Makefile.TargetType); } // Plan the actions to execute for the build. For single file compiles, always rebuild the source file regardless of whether it's out of date. HashSet <Action> TargetActionsToExecute; if (TargetDescriptor.SingleFileToCompile == null) { TargetActionsToExecute = ActionGraph.GetActionsToExecute(Makefile.Actions, PrerequisiteActions, CppDependencies, History, BuildConfiguration.bIgnoreOutdatedImportLibraries); } else { TargetActionsToExecute = new HashSet <Action>(PrerequisiteActions); } // Additional processing for hot reload if (HotReloadMode == HotReloadMode.LiveCoding) { // Filter the prerequisite actions down to just the compile actions, then recompute all the actions to execute PrerequisiteActions = new List <Action>(TargetActionsToExecute.Where(x => x.ActionType == ActionType.Compile)); TargetActionsToExecute = ActionGraph.GetActionsToExecute(Makefile.Actions, PrerequisiteActions, CppDependencies, History, BuildConfiguration.bIgnoreOutdatedImportLibraries); } else if (HotReloadMode == HotReloadMode.FromEditor || HotReloadMode == HotReloadMode.FromIDE) { // Patch action history for hot reload when running in assembler mode. In assembler mode, the suffix on the output file will be // the same for every invocation on that makefile, but we need a new suffix each time. // For all the hot-reloadable modules that may need a unique suffix appended, build a mapping from output item to all the output items in that module. We can't // apply a suffix to one without applying a suffix to all of them. Dictionary <FileItem, FileItem[]> HotReloadItemToDependentItems = new Dictionary <FileItem, FileItem[]>(); foreach (string HotReloadModuleName in Makefile.HotReloadModuleNames) { int ModuleSuffix; if (!TargetDescriptor.HotReloadModuleNameToSuffix.TryGetValue(HotReloadModuleName, out ModuleSuffix) || ModuleSuffix == -1) { FileItem[] ModuleOutputItems; if (Makefile.ModuleNameToOutputItems.TryGetValue(HotReloadModuleName, out ModuleOutputItems)) { foreach (FileItem ModuleOutputItem in ModuleOutputItems) { HotReloadItemToDependentItems[ModuleOutputItem] = ModuleOutputItems; } } } } // Expand the list of actions to execute to include everything that references any files with a new suffix. Unlike a regular build, we can't ignore // dependencies on import libraries under the assumption that a header would change if the API changes, because the dependency will be on a different DLL. HashSet <FileItem> FilesRequiringSuffix = new HashSet <FileItem>(TargetActionsToExecute.SelectMany(x => x.ProducedItems).Where(x => HotReloadItemToDependentItems.ContainsKey(x))); for (int LastNumFilesWithNewSuffix = 0; FilesRequiringSuffix.Count > LastNumFilesWithNewSuffix;) { LastNumFilesWithNewSuffix = FilesRequiringSuffix.Count; foreach (Action PrerequisiteAction in PrerequisiteActions) { if (!TargetActionsToExecute.Contains(PrerequisiteAction)) { foreach (FileItem ProducedItem in PrerequisiteAction.ProducedItems) { FileItem[] DependentItems; if (HotReloadItemToDependentItems.TryGetValue(ProducedItem, out DependentItems)) { TargetActionsToExecute.Add(PrerequisiteAction); FilesRequiringSuffix.UnionWith(DependentItems); } } } } } // Build a list of file mappings Dictionary <FileReference, FileReference> OldLocationToNewLocation = new Dictionary <FileReference, FileReference>(); foreach (FileItem FileRequiringSuffix in FilesRequiringSuffix) { FileReference OldLocation = FileRequiringSuffix.Location; FileReference NewLocation = HotReload.ReplaceSuffix(OldLocation, HotReloadState.NextSuffix); OldLocationToNewLocation[OldLocation] = NewLocation; } // Update the action graph with these new paths HotReload.PatchActionGraph(PrerequisiteActions, OldLocationToNewLocation); // Get a new list of actions to execute now that the graph has been modified TargetActionsToExecute = ActionGraph.GetActionsToExecute(Makefile.Actions, PrerequisiteActions, CppDependencies, History, BuildConfiguration.bIgnoreOutdatedImportLibraries); // Build a mapping of all file items to their original Dictionary <FileReference, FileReference> HotReloadFileToOriginalFile = new Dictionary <FileReference, FileReference>(); foreach (KeyValuePair <FileReference, FileReference> Pair in HotReloadState.OriginalFileToHotReloadFile) { HotReloadFileToOriginalFile[Pair.Value] = Pair.Key; } foreach (KeyValuePair <FileReference, FileReference> Pair in OldLocationToNewLocation) { FileReference OriginalLocation; if (!HotReloadFileToOriginalFile.TryGetValue(Pair.Key, out OriginalLocation)) { OriginalLocation = Pair.Key; } HotReloadFileToOriginalFile[Pair.Value] = OriginalLocation; } // Now filter out all the hot reload files and update the state foreach (Action Action in TargetActionsToExecute) { foreach (FileItem ProducedItem in Action.ProducedItems) { FileReference OriginalLocation; if (HotReloadFileToOriginalFile.TryGetValue(ProducedItem.Location, out OriginalLocation)) { HotReloadState.OriginalFileToHotReloadFile[OriginalLocation] = ProducedItem.Location; HotReloadState.TemporaryFiles.Add(ProducedItem.Location); } } } // Increment the suffix for the next iteration if (TargetActionsToExecute.Count > 0) { HotReloadState.NextSuffix++; } // Save the new state HotReloadState.Save(HotReloadStateFile); // Prevent this target from deploying Makefile.bDeployAfterCompile = false; } return(TargetActionsToExecute); }
/** * 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 */ public static 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) || !String.Equals(OldProducingCommandLine, NewProducingCommandLine, StringComparison.InvariantCultureIgnoreCase)) { 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); bool bFindCPPIncludePrerequisites = false; if( RootAction.ActionType == ActionType.Compile ) { // Outdated targets don't need their headers scanned yet, because presumably they would already be out of dated based on already-cached // includes before getting this far. However, if we find them to be outdated after processing includes, we'll do a deep scan later // on and cache all of the includes so that we have them for a quick outdatedness check the next run. if( !bIsOutdated && BuildConfiguration.bUseExperimentalFastBuildIteration && UnrealBuildTool.IsAssemblingBuild && RootAction.ActionType == ActionType.Compile ) { bFindCPPIncludePrerequisites = true; } // Were we asked to force an update of our cached includes BEFORE we try to build? This may be needed if our cache can no longer // be trusted and we need to fill it with perfectly valid data (even if we're in assembler only mode) if( BuildConfiguration.bUseExperimentalFastDependencyScan && UnrealBuildTool.bNeedsFullCPPIncludeRescan ) { bFindCPPIncludePrerequisites = true; } } if( bFindCPPIncludePrerequisites ) { // Scan this file for included headers that may be out of date. Note that it's OK if we break out early because we found // the action to be outdated. For outdated actions, we kick off a separate include scan in a background thread later on to // catch all of the other includes and form an exhaustive set. foreach (FileItem PrerequisiteItem in RootAction.PrerequisiteItems) { // @todo ubtmake: Make sure we are catching RC files here too. Anything that the toolchain would have tried it on. Logic should match the CACHING stuff below if( PrerequisiteItem.CachedCPPIncludeInfo != null ) { var BuildPlatform = UEBuildPlatform.GetBuildPlatform( Target.GetTargetInfo().Platform ); var IncludedFileList = CPPEnvironment.FindAndCacheAllIncludedFiles( Target, PrerequisiteItem, BuildPlatform, PrerequisiteItem.CachedCPPIncludeInfo, bOnlyCachedDependencies:BuildConfiguration.bUseExperimentalFastDependencyScan ); foreach( var IncludedFile in IncludedFileList ) // @todo fastubt: @todo ubtmake: Optimization: This is "retesting" a lot of the same files over and over in a single run (common indirect includes) { if( IncludedFile.bExists ) { // allow a 1 second slop for network copies TimeSpan TimeDifference = IncludedFile.LastWriteTime - LastExecutionTime; bool bPrerequisiteItemIsNewerThanLastExecution = TimeDifference.TotalSeconds > 1; if (bPrerequisiteItemIsNewerThanLastExecution) { Log.TraceVerbose( "{0}: Included file {1} is newer than the last execution of the action: {2} vs {3}", RootAction.StatusDescription, Path.GetFileName(IncludedFile.AbsolutePath), IncludedFile.LastWriteTime.LocalDateTime, LastExecutionTime.LocalDateTime ); bIsOutdated = true; // Don't bother checking every single include if we've found one that is out of date break; } } } } if( bIsOutdated ) { break; } } } 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 ) // @todo fastubt: Does this work with RC files? See above too. { Log.TraceVerbose( "Outdated action: {0}", RootAction.StatusDescription ); foreach (FileItem PrerequisiteItem in RootAction.PrerequisiteItems) { if( PrerequisiteItem.CachedCPPIncludeInfo != 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.CachedCPPIncludeInfo == null ) { Log.TraceVerbose( " -> WARNING: No CachedCPPEnvironment: {0}", PrerequisiteItem.AbsolutePath ); } } } } if( BuildConfiguration.bPrintPerformanceInfo ) { double DeepIncludeScanTime = ( DateTime.UtcNow - DeepIncludeScanStartTime ).TotalSeconds; UnrealBuildTool.TotalDeepIncludeScanTime += DeepIncludeScanTime; } } // Cache the outdated-ness of this action. OutdatedActionDictionary.Add(RootAction, bIsOutdated); } return bIsOutdated; }
/// <summary> /// Build a list of targets /// </summary> /// <param name="TargetDescriptors">Target descriptors</param> /// <param name="BuildConfiguration">Current build configuration</param> /// <param name="WorkingSet">The source file working set</param> /// <param name="Options">Additional options for the build</param> /// <param name="WriteOutdatedActionsFile">Files to write the list of outdated actions to (rather than building them)</param> /// <returns>Result from the compilation</returns> public static void Build(List <TargetDescriptor> TargetDescriptors, BuildConfiguration BuildConfiguration, ISourceFileWorkingSet WorkingSet, BuildOptions Options, FileReference WriteOutdatedActionsFile) { // Create a makefile for each target TargetMakefile[] Makefiles = new TargetMakefile[TargetDescriptors.Count]; for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { Makefiles[TargetIdx] = CreateMakefile(BuildConfiguration, TargetDescriptors[TargetIdx], WorkingSet); } // Export the actions for each target for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { TargetDescriptor TargetDescriptor = TargetDescriptors[TargetIdx]; foreach (FileReference WriteActionFile in TargetDescriptor.WriteActionFiles) { Log.TraceInformation("Writing actions to {0}", WriteActionFile); ActionGraph.ExportJson(Makefiles[TargetIdx].Actions, WriteActionFile); } } // Execute the build if ((Options & BuildOptions.SkipBuild) == 0) { // Make sure that none of the actions conflict with any other (producing output files differently, etc...) ActionGraph.CheckForConflicts(Makefiles.SelectMany(x => x.Actions)); // Check we don't exceed the nominal max path length using (Timeline.ScopeEvent("ActionGraph.CheckPathLengths")) { ActionGraph.CheckPathLengths(BuildConfiguration, Makefiles.SelectMany(x => x.Actions)); } // Find all the actions to be executed HashSet <Action>[] ActionsToExecute = new HashSet <Action> [TargetDescriptors.Count]; for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { ActionsToExecute[TargetIdx] = GetActionsForTarget(BuildConfiguration, TargetDescriptors[TargetIdx], Makefiles[TargetIdx]); } // If there are multiple targets being built, merge the actions together List <Action> MergedActionsToExecute; if (TargetDescriptors.Count == 1) { MergedActionsToExecute = new List <Action>(ActionsToExecute[0]); } else { MergedActionsToExecute = MergeActionGraphs(TargetDescriptors, ActionsToExecute); } // Link all the actions together ActionGraph.Link(MergedActionsToExecute); // Make sure we're not modifying any engine files if ((Options & BuildOptions.NoEngineChanges) != 0) { List <FileItem> EngineChanges = MergedActionsToExecute.SelectMany(x => x.ProducedItems).Where(x => x.Location.IsUnderDirectory(UnrealBuildTool.EngineDirectory)).Distinct().OrderBy(x => x.FullName).ToList(); if (EngineChanges.Count > 0) { StringBuilder Result = new StringBuilder("Building would modify the following engine files:\n"); foreach (FileItem EngineChange in EngineChanges) { Result.AppendFormat("\n{0}", EngineChange.FullName); } Result.Append("\n\nPlease rebuild from an IDE instead."); Log.TraceError("{0}", Result.ToString()); throw new CompilationResultException(CompilationResult.FailedDueToEngineChange); } } // Make sure the appropriate executor is selected foreach (TargetDescriptor TargetDescriptor in TargetDescriptors) { UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(TargetDescriptor.Platform); BuildConfiguration.bAllowXGE &= BuildPlatform.CanUseXGE(); BuildConfiguration.bAllowDistcc &= BuildPlatform.CanUseDistcc(); BuildConfiguration.bAllowSNDBS &= BuildPlatform.CanUseSNDBS(); } // Delete produced items that are outdated. ActionGraph.DeleteOutdatedProducedItems(MergedActionsToExecute); // Save all the action histories now that files have been removed. We have to do this after deleting produced items to ensure that any // items created during the build don't have the wrong command line. ActionHistory.SaveAll(); // Create directories for the outdated produced items. ActionGraph.CreateDirectoriesForProducedItems(MergedActionsToExecute); // Execute the actions if ((Options & BuildOptions.XGEExport) != 0) { OutputToolchainInfo(TargetDescriptors, Makefiles); // Just export to an XML file using (Timeline.ScopeEvent("XGE.ExportActions()")) { XGE.ExportActions(MergedActionsToExecute); } } else if (WriteOutdatedActionsFile != null) { OutputToolchainInfo(TargetDescriptors, Makefiles); // Write actions to an output file using (Timeline.ScopeEvent("ActionGraph.WriteActions")) { ActionGraph.ExportJson(MergedActionsToExecute, WriteOutdatedActionsFile); } } else { // Execute the actions if (MergedActionsToExecute.Count == 0) { if (TargetDescriptors.Any(x => !x.bQuiet)) { Log.TraceInformation((TargetDescriptors.Count == 1)? "Target is up to date" : "Targets are up to date"); } } else { if (TargetDescriptors.Any(x => !x.bQuiet)) { Log.TraceInformation("Building {0}...", StringUtils.FormatList(TargetDescriptors.Select(x => x.Name).Distinct())); } OutputToolchainInfo(TargetDescriptors, Makefiles); using (Timeline.ScopeEvent("ActionGraph.ExecuteActions()")) { ActionGraph.ExecuteActions(BuildConfiguration, MergedActionsToExecute); } } // Run the deployment steps foreach (TargetMakefile Makefile in Makefiles) { if (Makefile.bDeployAfterCompile) { TargetReceipt Receipt = TargetReceipt.Read(Makefile.ReceiptFile); Log.TraceInformation("Deploying {0} {1} {2}...", Receipt.TargetName, Receipt.Platform, Receipt.Configuration); UEBuildPlatform.GetBuildPlatform(Receipt.Platform).Deploy(Receipt); } } } } }
/** * 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 */ public static bool IsActionOutdated(Action RootAction, ref Dictionary<Action, bool> OutdatedActionDictionary,ActionHistory ActionHistory) { // 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.\r\nOld command-line: {2}\r\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 (ProducedItem.bIsRemoteFile) { DateTime LastWriteTime; long UnusedLength; ProducedItem.bExists = RPCUtilHelper.GetRemoteFileInfo(ProducedItem.AbsolutePath, out LastWriteTime, out UnusedLength); ProducedItem.LastWriteTime = LastWriteTime; } // 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(PrerequisiteItem.ProducingAction,ref OutdatedActionDictionary,ActionHistory)) { 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; } } } } // Cache the outdated-ness of this action. OutdatedActionDictionary.Add(RootAction, bIsOutdated); } return bIsOutdated; }
/** 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) { // 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); 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); var OutdatedActionsForTarget = GatherAllOutdatedActions(History); foreach (var OutdatedAction in OutdatedActionsForTarget) { OutdatedActionDictionary.Add(OutdatedAction.Key, OutdatedAction.Value); } } } // 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 (ActionsNeededForThisTarget.ContainsKey(Action) && OutdatedActionDictionary[Action] && Action.CommandPath != null) { ActionsToExecute.Add(Action); } } return ActionsToExecute; }
/** * Builds a dictionary containing the actions from AllActions that are outdated by calling * IsActionOutdated. */ static Dictionary<Action, bool> GatherAllOutdatedActions(ActionHistory ActionHistory) { var OutdatedActionDictionary = new Dictionary<Action, bool>(); foreach (var Action in AllActions) { IsActionOutdated(Action, ref OutdatedActionDictionary,ActionHistory); } return OutdatedActionDictionary; }
/// <summary> /// Builds a list of actions that need to be executed to produce the specified output items. /// </summary> public static List<Action> GetActionsToExecute(Action[] PrerequisiteActions, List<UEBuildTarget> Targets, out Dictionary<UEBuildTarget, List<FileItem>> TargetToOutdatedPrerequisitesMap) { DateTime CheckOutdatednessStartTime = DateTime.UtcNow; // Build a set of all actions needed for this target. Dictionary<Action, bool> IsActionOutdatedMap = new Dictionary<Action, bool>(); foreach (Action Action in PrerequisiteActions) { IsActionOutdatedMap.Add(Action, true); } // For all targets, build a set of all actions that are outdated. Dictionary<Action, bool> OutdatedActionDictionary = new Dictionary<Action, bool>(); List<ActionHistory> HistoryList = new List<ActionHistory>(); HashSet<FileReference> OpenHistoryFiles = new HashSet<FileReference>(); TargetToOutdatedPrerequisitesMap = new Dictionary<UEBuildTarget, List<FileItem>>(); foreach (UEBuildTarget BuildTarget in Targets) // @todo ubtmake: Optimization: Ideally we don't even need to know about targets for ubtmake -- everything would come from the files { FileReference HistoryFilename = ActionHistory.GeneratePathForTarget(BuildTarget); if (!OpenHistoryFiles.Contains(HistoryFilename)) // @todo ubtmake: Optimization: We should be able to move the command-line outdatedness and build product deletion over to the 'gather' phase, as the command-lines won't change between assembler runs { ActionHistory History = new ActionHistory(HistoryFilename.FullName); HistoryList.Add(History); OpenHistoryFiles.Add(HistoryFilename); GatherAllOutdatedActions(BuildTarget, History, ref OutdatedActionDictionary, TargetToOutdatedPrerequisitesMap); } } // If we're only compiling a single file, we should always compile and should never link. if (!string.IsNullOrEmpty(BuildConfiguration.SingleFileToCompile)) { // Never do anything but compile the target file AllActions.RemoveAll(x => x.ActionType != ActionType.Compile); // Check all of the leftover compilation actions for the one we want... that one is always outdated. FileItem SingleFileToCompile = FileItem.GetExistingItemByPath(BuildConfiguration.SingleFileToCompile); foreach (Action Action in AllActions) { bool bIsSingleFileAction = Action.PrerequisiteItems.Contains(SingleFileToCompile); OutdatedActionDictionary[Action] = bIsSingleFileAction; } // Don't save the history of a single file compilation. HistoryList.Clear(); } // 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 (ActionHistory 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. HashSet<Action> ActionsToExecute = AllActions.Where(Action => Action.CommandPath != null && IsActionOutdatedMap.ContainsKey(Action) && OutdatedActionDictionary[Action]).ToHashSet(); // Remove link actions if asked to if (UEBuildConfiguration.bSkipLinkingWhenNothingToCompile) { // Get all items produced by a compile action HashSet<FileItem> ProducedItems = ActionsToExecute.Where(Action => Action.ActionType == ActionType.Compile).SelectMany(x => x.ProducedItems).ToHashSet(); // Get all link actions which have no out-of-date prerequisites HashSet<Action> UnlinkedActions = ActionsToExecute.Where(Action => Action.ActionType == ActionType.Link && !ProducedItems.Overlaps(Action.PrerequisiteItems)).ToHashSet(); // Don't regard an action as unlinked if there is an associated 'failed.hotreload' file. UnlinkedActions.RemoveWhere(Action => Action.ProducedItems.Any(Item => File.Exists(Path.Combine(Path.GetDirectoryName(Item.AbsolutePath), "failed.hotreload")))); HashSet<Action> UnlinkedActionsWithFailedHotreload = ActionsToExecute.Where(Action => Action.ActionType == ActionType.Link && !ProducedItems.Overlaps(Action.PrerequisiteItems)).ToHashSet(); // Remove unlinked items ActionsToExecute.ExceptWith(UnlinkedActions); // Re-add unlinked items which produce things which are dependencies of other actions for (;;) { // Get all prerequisite items of a link action HashSet<Action> PrerequisiteLinkActions = ActionsToExecute.Where(Action => Action.ActionType == ActionType.Link).SelectMany(x => x.PrerequisiteItems).Select(Item => Item.ProducingAction).ToHashSet(); // Find all unlinked actions that need readding HashSet<Action> UnlinkedActionsToReadd = UnlinkedActions.Where(Action => PrerequisiteLinkActions.Contains(Action)).ToHashSet(); if (UnlinkedActionsToReadd.Count == 0) { break; } ActionsToExecute.UnionWith(UnlinkedActionsToReadd); UnlinkedActions.ExceptWith(UnlinkedActionsToReadd); // Break early if there are no more unlinked actions to readd if (UnlinkedActions.Count == 0) { break; } } // Remove actions that are wholly dependent on unlinked actions ActionsToExecute = ActionsToExecute.Where(Action => Action.PrerequisiteItems.Count == 0 || !Action.PrerequisiteItems.Select(Item => Item.ProducingAction).ToHashSet().IsSubsetOf(UnlinkedActions)).ToHashSet(); } if (BuildConfiguration.bPrintPerformanceInfo) { double CheckOutdatednessTime = (DateTime.UtcNow - CheckOutdatednessStartTime).TotalSeconds; Log.TraceInformation("Checking outdatedness took " + CheckOutdatednessTime + "s"); } return ActionsToExecute.ToList(); }
/** * 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 ) { var CheckOutdatednessStartTime = DateTime.UtcNow; foreach (var Action in AllActions) { IsActionOutdated(Target, Action, ref OutdatedActions, ActionHistory, TargetToOutdatedPrerequisitesMap); } if( BuildConfiguration.bPrintPerformanceInfo ) { var CheckOutdatednessTime = (DateTime.UtcNow - CheckOutdatednessStartTime).TotalSeconds; Log.TraceInformation( "Checking actions for " + Target.GetTargetName() + " took " + CheckOutdatednessTime + "s" ); } }
/** Builds a list of actions that need to be executed to produce the specified output items. */ public static List<Action> GetActionsToExecute(Action[] PrerequisiteActions, List<UEBuildTarget> Targets, out Dictionary<UEBuildTarget, List<FileItem>> TargetToOutdatedPrerequisitesMap) { var CheckOutdatednessStartTime = DateTime.UtcNow; // Build a set of all actions needed for this target. var IsActionOutdatedMap = new Dictionary<Action, bool>(); foreach (var Action in PrerequisiteActions) { IsActionOutdatedMap.Add( Action, true ); } // 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) // @todo ubtmake: Optimization: Ideally we don't even need to know about targets for ubtmake -- everything would come from the files { var HistoryFilename = ActionHistory.GeneratePathForTarget(BuildTarget); if (!OpenHistoryFiles.Contains(HistoryFilename)) // @todo ubtmake: Optimization: We should be able to move the command-line outdatedness and build product deletion over to the 'gather' phase, as the command-lines won't change between assembler runs { 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>(); bool bHasOutdatedNonLinkActions = false; foreach (Action Action in AllActions) { if (Action.CommandPath != null && IsActionOutdatedMap.ContainsKey(Action) && OutdatedActionDictionary[Action]) { ActionsToExecute.Add(Action); if (Action.ActionType != ActionType.Link) { bHasOutdatedNonLinkActions = true; } } } // Remove link actions if asked to if (UEBuildConfiguration.bSkipLinkingWhenNothingToCompile && !bHasOutdatedNonLinkActions) { ActionsToExecute.Clear(); } if( BuildConfiguration.bPrintPerformanceInfo ) { var CheckOutdatednessTime = (DateTime.UtcNow - CheckOutdatednessStartTime).TotalSeconds; Log.TraceInformation( "Checking outdatedness took " + CheckOutdatednessTime + "s" ); } return ActionsToExecute; }
/// <summary> /// Builds a list of actions that need to be executed to produce the specified output items. /// </summary> public static List <Action> GetActionsToExecute(List <Action> Actions, CppDependencyCache CppDependencies, ActionHistory History, bool bIgnoreOutdatedImportLibraries) { using (Timeline.ScopeEvent("ActionGraph.GetActionsToExecute()")) { // For all targets, build a set of all actions that are outdated. Dictionary <Action, bool> OutdatedActionDictionary = new Dictionary <Action, bool>(); GatherAllOutdatedActions(Actions, History, OutdatedActionDictionary, CppDependencies, bIgnoreOutdatedImportLibraries); // Build a list of actions that are both needed for this target and outdated. return(Actions.Where(Action => Action.CommandPath != null && OutdatedActionDictionary[Action]).ToList()); } }
/// <summary> /// Main entry point /// </summary> /// <param name="Arguments">Command-line arguments</param> /// <returns>One of the values of ECompilationResult</returns> public override int Execute(CommandLineArguments Arguments) { Arguments.ApplyTo(this); // Create the build configuration object, and read the settings BuildConfiguration BuildConfiguration = new BuildConfiguration(); XmlConfig.ApplyTo(BuildConfiguration); Arguments.ApplyTo(BuildConfiguration); // Parse all the targets being built List <TargetDescriptor> TargetDescriptors = TargetDescriptor.ParseCommandLine(Arguments, BuildConfiguration.bUsePrecompiled, bSkipRulesCompile); if (TargetDescriptors.Count == 0) { throw new BuildException("No targets specified to clean"); } // Also add implicit descriptors for cleaning UnrealBuildTool if (!BuildConfiguration.bDoNotBuildUHT) { const string UnrealHeaderToolTarget = "UnrealHeaderTool"; // Get a list of project files to clean UHT for List <FileReference> ProjectFiles = new List <FileReference>(); foreach (TargetDescriptor TargetDesc in TargetDescriptors) { if (TargetDesc.Name != UnrealHeaderToolTarget && !RemoteMac.HandlesTargetPlatform(TargetDesc.Platform)) { if (ProjectFiles.Count == 0) { ProjectFiles.Add(null); } if (TargetDesc.ProjectFile != null && !ProjectFiles.Contains(TargetDesc.ProjectFile)) { ProjectFiles.Add(TargetDesc.ProjectFile); } } } // Add descriptors for cleaning UHT with all these projects if (ProjectFiles.Count > 0) { UnrealTargetConfiguration Configuration = BuildConfiguration.bForceDebugUnrealHeaderTool ? UnrealTargetConfiguration.Debug : UnrealTargetConfiguration.Development; string Architecture = UEBuildPlatform.GetBuildPlatform(BuildHostPlatform.Current.Platform).GetDefaultArchitecture(null); foreach (FileReference ProjectFile in ProjectFiles) { TargetDescriptors.Add(new TargetDescriptor(ProjectFile, UnrealHeaderToolTarget, BuildHostPlatform.Current.Platform, Configuration, Architecture, null)); } } } // Output the list of targets that we're cleaning Log.TraceInformation("Cleaning {0} binaries...", StringUtils.FormatList(TargetDescriptors.Select(x => x.Name).Distinct())); // Loop through all the targets, and clean them all HashSet <FileReference> FilesToDelete = new HashSet <FileReference>(); HashSet <DirectoryReference> DirectoriesToDelete = new HashSet <DirectoryReference>(); foreach (TargetDescriptor TargetDescriptor in TargetDescriptors) { // Create the rules assembly RulesAssembly RulesAssembly = RulesCompiler.CreateTargetRulesAssembly(TargetDescriptor.ProjectFile, TargetDescriptor.Name, bSkipRulesCompile, BuildConfiguration.bUsePrecompiled, TargetDescriptor.ForeignPlugin); // Create the rules object ReadOnlyTargetRules Target = new ReadOnlyTargetRules(RulesAssembly.CreateTargetRules(TargetDescriptor.Name, TargetDescriptor.Platform, TargetDescriptor.Configuration, TargetDescriptor.Architecture, TargetDescriptor.ProjectFile, TargetDescriptor.AdditionalArguments)); // Find the base folders that can contain binaries List <DirectoryReference> BaseDirs = new List <DirectoryReference>(); BaseDirs.Add(UnrealBuildTool.EngineDirectory); BaseDirs.Add(UnrealBuildTool.EnterpriseDirectory); foreach (FileReference Plugin in Plugins.EnumeratePlugins(Target.ProjectFile)) { BaseDirs.Add(Plugin.Directory); } if (Target.ProjectFile != null) { BaseDirs.Add(Target.ProjectFile.Directory); } // If we're running a precompiled build, remove anything under the engine folder BaseDirs.RemoveAll(x => RulesAssembly.IsReadOnly(x)); // Get all the names which can prefix build products List <string> NamePrefixes = new List <string>(); if (Target.Type != TargetType.Program) { NamePrefixes.Add(UEBuildTarget.GetAppNameForTargetType(Target.Type)); } NamePrefixes.Add(Target.Name); // Get the suffixes for this configuration List <string> NameSuffixes = new List <string>(); if (Target.Configuration == Target.UndecoratedConfiguration) { NameSuffixes.Add(""); } NameSuffixes.Add(String.Format("-{0}-{1}", Target.Platform.ToString(), Target.Configuration.ToString())); if (!String.IsNullOrEmpty(Target.Architecture)) { NameSuffixes.AddRange(NameSuffixes.ToArray().Select(x => x + Target.Architecture)); } // Add all the makefiles and caches to be deleted FilesToDelete.Add(TargetMakefile.GetLocation(Target.ProjectFile, Target.Name, Target.Platform, Target.Configuration)); FilesToDelete.UnionWith(SourceFileMetadataCache.GetFilesToClean(Target.ProjectFile)); FilesToDelete.UnionWith(ActionHistory.GetFilesToClean(Target.ProjectFile, Target.Name, Target.Platform, Target.Type)); // Add all the intermediate folders to be deleted foreach (DirectoryReference BaseDir in BaseDirs) { foreach (string NamePrefix in NamePrefixes) { DirectoryReference GeneratedCodeDir = DirectoryReference.Combine(BaseDir, "Intermediate", "Build", Target.Platform.ToString(), NamePrefix, "Inc"); if (DirectoryReference.Exists(GeneratedCodeDir)) { DirectoriesToDelete.Add(GeneratedCodeDir); } DirectoryReference IntermediateDir = DirectoryReference.Combine(BaseDir, "Intermediate", "Build", Target.Platform.ToString(), NamePrefix, Target.Configuration.ToString()); if (DirectoryReference.Exists(IntermediateDir)) { DirectoriesToDelete.Add(IntermediateDir); } } } // List of additional files and directories to clean, specified by the target platform List <FileReference> AdditionalFilesToDelete = new List <FileReference>(); List <DirectoryReference> AdditionalDirectoriesToDelete = new List <DirectoryReference>(); // Add all the build products from this target string[] NamePrefixesArray = NamePrefixes.Distinct().ToArray(); string[] NameSuffixesArray = NameSuffixes.Distinct().ToArray(); foreach (DirectoryReference BaseDir in BaseDirs) { DirectoryReference BinariesDir = DirectoryReference.Combine(BaseDir, "Binaries", Target.Platform.ToString()); if (DirectoryReference.Exists(BinariesDir)) { UEBuildPlatform.GetBuildPlatform(Target.Platform).FindBuildProductsToClean(BinariesDir, NamePrefixesArray, NameSuffixesArray, AdditionalFilesToDelete, AdditionalDirectoriesToDelete); } } // Get all the additional intermediate folders created by this platform UEBuildPlatform.GetBuildPlatform(Target.Platform).FindAdditionalBuildProductsToClean(Target, AdditionalFilesToDelete, AdditionalDirectoriesToDelete); // Add the platform's files and directories to the main list FilesToDelete.UnionWith(AdditionalFilesToDelete); DirectoriesToDelete.UnionWith(AdditionalDirectoriesToDelete); } // Delete all the directories, then all the files. By sorting the list of directories before we delete them, we avoid spamming the log if a parent directory is deleted first. foreach (DirectoryReference DirectoryToDelete in DirectoriesToDelete.OrderBy(x => x.FullName)) { if (DirectoryReference.Exists(DirectoryToDelete)) { Log.TraceVerbose(" Deleting {0}{1}...", DirectoryToDelete, Path.DirectorySeparatorChar); try { DirectoryReference.Delete(DirectoryToDelete, true); } catch (Exception Ex) { throw new BuildException(Ex, "Unable to delete {0} ({1})", DirectoryToDelete, Ex.Message.TrimEnd()); } } } foreach (FileReference FileToDelete in FilesToDelete.OrderBy(x => x.FullName)) { if (FileReference.Exists(FileToDelete)) { Log.TraceVerbose(" Deleting " + FileToDelete); try { FileReference.Delete(FileToDelete); } catch (Exception Ex) { throw new BuildException(Ex, "Unable to delete {0} ({1})", FileToDelete, Ex.Message.TrimEnd()); } } } // Also clean all the remote targets for (int Idx = 0; Idx < TargetDescriptors.Count; Idx++) { TargetDescriptor TargetDescriptor = TargetDescriptors[Idx]; if (RemoteMac.HandlesTargetPlatform(TargetDescriptor.Platform)) { RemoteMac RemoteMac = new RemoteMac(TargetDescriptor.ProjectFile); RemoteMac.Clean(TargetDescriptor); } } return(0); }
/// <summary> /// Build a list of targets /// </summary> /// <param name="TargetDescriptors">Target descriptors</param> /// <param name="BuildConfiguration">Current build configuration</param> /// <param name="WorkingSet">The source file working set</param> /// <param name="Options">Additional options for the build</param> /// <param name="LiveCodingManifest">Path to write the live coding manifest to</param> /// <param name="WriteOutdatedActionsFile">Files to write the list of outdated actions to (rather than building them)</param> /// <returns>Result from the compilation</returns> public static void Build(List <TargetDescriptor> TargetDescriptors, BuildConfiguration BuildConfiguration, ISourceFileWorkingSet WorkingSet, BuildOptions Options, FileReference LiveCodingManifest, FileReference WriteOutdatedActionsFile) { // Create a makefile for each target TargetMakefile[] Makefiles = new TargetMakefile[TargetDescriptors.Count]; for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { Makefiles[TargetIdx] = CreateMakefile(BuildConfiguration, TargetDescriptors[TargetIdx], WorkingSet); } // Output the Live Coding manifest if (LiveCodingManifest != null) { List <Action> AllActions = Makefiles.SelectMany(x => x.Actions).ToList(); HotReload.WriteLiveCodingManifest(LiveCodingManifest, AllActions); } // Export the actions for each target for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { TargetDescriptor TargetDescriptor = TargetDescriptors[TargetIdx]; foreach (FileReference WriteActionFile in TargetDescriptor.WriteActionFiles) { Log.TraceInformation("Writing actions to {0}", WriteActionFile); ActionGraph.ExportJson(Makefiles[TargetIdx].Actions, WriteActionFile); } } // Execute the build if ((Options & BuildOptions.SkipBuild) == 0) { // Make sure that none of the actions conflict with any other (producing output files differently, etc...) ActionGraph.CheckForConflicts(Makefiles.SelectMany(x => x.Actions)); // Find all the actions to be executed HashSet <Action>[] ActionsToExecute = new HashSet <Action> [TargetDescriptors.Count]; for (int TargetIdx = 0; TargetIdx < TargetDescriptors.Count; TargetIdx++) { ActionsToExecute[TargetIdx] = GetActionsForTarget(BuildConfiguration, TargetDescriptors[TargetIdx], Makefiles[TargetIdx]); } // If there are multiple targets being built, merge the actions together List <Action> MergedActionsToExecute; if (TargetDescriptors.Count == 1) { MergedActionsToExecute = new List <Action>(ActionsToExecute[0]); } else { MergedActionsToExecute = MergeActionGraphs(TargetDescriptors, ActionsToExecute); } // Link all the actions together ActionGraph.Link(MergedActionsToExecute); // Make sure the appropriate executor is selected foreach (TargetDescriptor TargetDescriptor in TargetDescriptors) { UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(TargetDescriptor.Platform); BuildConfiguration.bAllowXGE &= BuildPlatform.CanUseXGE(); BuildConfiguration.bAllowDistcc &= BuildPlatform.CanUseDistcc(); BuildConfiguration.bAllowSNDBS &= BuildPlatform.CanUseSNDBS(); } // Delete produced items that are outdated. ActionGraph.DeleteOutdatedProducedItems(MergedActionsToExecute); // Save all the action histories now that files have been removed. We have to do this after deleting produced items to ensure that any // items created during the build don't have the wrong command line. ActionHistory.SaveAll(); // Create directories for the outdated produced items. ActionGraph.CreateDirectoriesForProducedItems(MergedActionsToExecute); // Execute the actions if ((Options & BuildOptions.XGEExport) != 0) { // Just export to an XML file using (Timeline.ScopeEvent("XGE.ExportActions()")) { XGE.ExportActions(MergedActionsToExecute); } } else if (WriteOutdatedActionsFile != null) { // Write actions to an output file using (Timeline.ScopeEvent("ActionGraph.WriteActions")) { ActionGraph.ExportJson(MergedActionsToExecute, WriteOutdatedActionsFile); } } else { // Execute the actions if (MergedActionsToExecute.Count == 0) { if ((Options & BuildOptions.Quiet) == 0) { Log.TraceInformation((TargetDescriptors.Count == 1)? "Target is up to date" : "Targets are up to date"); } } else { if ((Options & BuildOptions.Quiet) != 0) { Log.TraceInformation("Building {0}...", StringUtils.FormatList(TargetDescriptors.Select(x => x.Name).Distinct())); } OutputToolchainInfo(TargetDescriptors, Makefiles); using (Timeline.ScopeEvent("ActionGraph.ExecuteActions()")) { ActionGraph.ExecuteActions(BuildConfiguration, MergedActionsToExecute); } } // Run the deployment steps foreach (TargetMakefile Makefile in Makefiles) { if (Makefile.bDeployAfterCompile) { TargetReceipt Receipt = TargetReceipt.Read(Makefile.ReceiptFile); Log.TraceInformation("Deploying {0} {1} {2}...", Receipt.TargetName, Receipt.Platform, Receipt.Configuration); UEBuildPlatform.GetBuildPlatform(Receipt.Platform).Deploy(Receipt); } } } } }