Пример #1
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;
        }
Пример #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(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;
        }