Пример #1
0
        /// <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();
            }
        }
Пример #2
0
 /// <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);
     }
 }
Пример #3
0
        /// <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);
        }
Пример #4
0
        /// <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));
            }
        }
Пример #5
0
        /// <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);
        }
Пример #6
0
        /// <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);
        }
Пример #7
0
        /// <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);
                        }
                    }
                }
            }
        }
Пример #8
0
        /// <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);
        }
Пример #9
0
        /**
         * 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;
        }
Пример #10
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="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);
                        }
                    }
                }
            }
        }
Пример #11
0
        /**
         * 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;
        }
Пример #12
0
        /** 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;
        }
Пример #13
0
        /**
         * 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;
        }
Пример #14
0
		/// <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();
		}
Пример #15
0
        /**
         * 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" );
            }
        }
Пример #16
0
        /** 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;
        }
Пример #17
0
        /// <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());
            }
        }
Пример #18
0
        /// <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);
        }
Пример #19
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);
                        }
                    }
                }
            }
        }