Пример #1
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<STBuildTarget> Targets, out Dictionary<STBuildTarget, 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<STBuildTarget, 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 (STBuildConfiguration.bSkipLinkingWhenNothingToCompile && !bHasOutdatedNonLinkActions)
            {
                ActionsToExecute.Clear();
            }

            if (BuildConfiguration.bPrintPerformanceInfo)
            {
                var CheckOutdatednessTime = (DateTime.UtcNow - CheckOutdatednessStartTime).TotalSeconds;
                Log.TraceInformation("Checking outdatedness took " + CheckOutdatednessTime + "s");
            }

            return ActionsToExecute;
        }
Пример #2
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(STBuildTarget Target, Action RootAction, ref Dictionary<Action, bool> OutdatedActionDictionary, ActionHistory ActionHistory, Dictionary<STBuildTarget, 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 &&
                        STBuildTool.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 &&
                        STBuildTool.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 = STBuildPlatform.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;
                        STBuildTool.TotalDeepIncludeScanTime += DeepIncludeScanTime;
                    }
                }

                // Cache the outdated-ness of this action.
                OutdatedActionDictionary.Add(RootAction, bIsOutdated);
            }

            return bIsOutdated;
        }
Пример #3
0
        /**
         * Builds a dictionary containing the actions from AllActions that are outdated by calling
         * IsActionOutdated.
         */
        static void GatherAllOutdatedActions(STBuildTarget Target, ActionHistory ActionHistory, ref Dictionary<Action, bool> OutdatedActions, Dictionary<STBuildTarget, 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");
            }
        }