2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Runtime.Serialization.Formatters.Binary ;
namespace UnrealBuildTool
{
2015-09-04 13:48:30 -04:00
[DebuggerDisplay("{IncludeName}")]
2014-03-14 14:13:41 -04:00
public class DependencyInclude
{
/// <summary>
/// These are direct include paths and cannot be resolved to an actual file on disk without using the proper list of include directories for this file's module
/// </summary>
public readonly string IncludeName ;
2015-09-03 08:47:24 -04:00
2014-03-14 14:13:41 -04:00
/// <summary>
2015-09-03 08:47:24 -04:00
/// Whether we've attempted to resolve this include (may be set even if IncludeResolvedNameIfSuccessful = null, in cases where it couldn't be found)
2014-03-14 14:13:41 -04:00
/// </summary>
2015-09-03 08:47:24 -04:00
public bool HasAttemptedResolve ;
/// <summary>
/// This is the fully resolved name, and a bool for whether we've attempted the resolve but failed. We can't really store this globally, but we're going to see how well it works.
/// </summary>
public FileReference IncludeResolvedNameIfSuccessful ;
2014-03-14 14:13:41 -04:00
2015-09-04 13:48:30 -04:00
/// <summary>
/// Public ctor that initializes the include name (the resolved name won't be determined until later)
/// </summary>
/// <param name="InIncludeName"></param>
public DependencyInclude ( string InIncludeName )
2014-03-14 14:13:41 -04:00
{
2015-09-04 13:48:30 -04:00
IncludeName = InIncludeName ;
2014-03-14 14:13:41 -04:00
}
}
/ * *
* Caches include dependency information to speed up preprocessing on subsequent runs .
* /
public class DependencyCache
{
2015-09-04 13:48:30 -04:00
/** The version number for binary serialization */
const int FileVersion = 1 ;
2014-03-14 14:13:41 -04:00
2015-09-04 13:48:30 -04:00
/** The file signature for binary serialization */
const int FileSignature = ( 'D' < < 24 ) | ( 'C' < < 16 ) | FileVersion ;
2014-03-14 14:13:41 -04:00
/** Path to store the cache data to. */
2015-09-04 13:48:30 -04:00
private FileReference BackingFile ;
2014-03-14 14:13:41 -04:00
2015-09-04 13:48:30 -04:00
/** The time the cache was created. Used to invalidate entries. */
public DateTime CreationTimeUtc ;
2014-03-14 14:13:41 -04:00
2015-09-04 13:48:30 -04:00
/** The time the cache was last updated. Stored as the creation date when saved. Not serialized. */
private DateTime UpdateTimeUtc ;
/** Dependency lists, keyed on file's absolute path. */
private Dictionary < FileReference , List < DependencyInclude > > DependencyMap ;
/** A mapping of whether various files exist. Not serialized. */
2015-09-03 08:47:24 -04:00
private Dictionary < FileReference , bool > FileExistsInfo ;
2014-03-14 14:13:41 -04:00
2015-09-04 13:48:30 -04:00
/** Whether the dependency cache is dirty and needs to be saved. Not serialized. */
2014-03-14 14:13:41 -04:00
private bool bIsDirty ;
/ * *
* Creates and deserializes the dependency cache at the passed in location
*
* @param CachePath Name of the cache file to deserialize
* /
2015-09-04 13:48:30 -04:00
public static DependencyCache Create ( FileReference CacheFile )
2014-03-14 14:13:41 -04:00
{
// See whether the cache file exists.
2015-09-04 13:48:30 -04:00
if ( CacheFile . Exists ( ) )
2014-03-14 14:13:41 -04:00
{
if ( BuildConfiguration . bPrintPerformanceInfo )
{
2015-09-04 13:48:30 -04:00
Log . TraceInformation ( "Loading existing IncludeFileCache: " + CacheFile . FullName ) ;
2014-03-14 14:13:41 -04:00
}
var TimerStartTime = DateTime . UtcNow ;
// Deserialize cache from disk if there is one.
2015-09-04 13:48:30 -04:00
DependencyCache Result = Load ( CacheFile ) ;
if ( Result ! = null )
2014-03-14 14:13:41 -04:00
{
// Successfully serialize, create the transient variables and return cache.
2015-09-04 13:48:30 -04:00
Result . UpdateTimeUtc = DateTime . UtcNow ;
2014-03-14 14:13:41 -04:00
var TimerDuration = DateTime . UtcNow - TimerStartTime ;
if ( BuildConfiguration . bPrintPerformanceInfo )
{
Log . TraceInformation ( "Loading IncludeFileCache took " + TimerDuration . TotalSeconds + "s" ) ;
}
Telemetry . SendEvent ( "LoadIncludeDependencyCacheStats.2" ,
"TotalDuration" , TimerDuration . TotalSeconds . ToString ( "0.00" ) ) ;
return Result ;
}
}
// Fall back to a clean cache on error or non-existance.
2015-09-04 13:48:30 -04:00
return new DependencyCache ( CacheFile ) ;
2014-03-14 14:13:41 -04:00
}
/ * *
* Loads the cache from the passed in file .
*
* @param Cache File to deserialize from
* /
2015-09-04 13:48:30 -04:00
public static DependencyCache Load ( FileReference CacheFile )
2014-03-14 14:13:41 -04:00
{
DependencyCache Result = null ;
try
{
2015-09-04 13:48:30 -04:00
string CacheBuildMutexPath = CacheFile . FullName + ".buildmutex" ;
2015-07-27 07:50:47 -04:00
// If the .buildmutex file for the cache is present, it means that something went wrong between loading
// and saving the cache last time (most likely the UBT process being terminated), so we don't want to load
// it.
if ( ! File . Exists ( CacheBuildMutexPath ) )
2014-03-14 14:13:41 -04:00
{
2015-07-27 07:50:47 -04:00
using ( File . Create ( CacheBuildMutexPath ) )
{
}
2015-09-04 13:48:30 -04:00
using ( BinaryReader Reader = new BinaryReader ( new FileStream ( CacheFile . FullName , FileMode . Open , FileAccess . Read ) ) )
2015-07-27 07:50:47 -04:00
{
2015-09-04 13:48:30 -04:00
if ( Reader . ReadInt32 ( ) = = FileSignature )
{
Result = DependencyCache . Deserialize ( Reader ) ;
}
2015-07-27 07:50:47 -04:00
}
2014-03-14 14:13:41 -04:00
}
}
catch ( Exception Ex )
{
2015-09-04 13:48:30 -04:00
Console . Error . WriteLine ( "Failed to read dependency cache: {0}" , Ex . Message ) ;
2014-03-14 14:13:41 -04:00
}
return Result ;
}
2015-09-04 13:48:30 -04:00
/// <summary>
/// Serializes the dependency cache to a binary writer
/// </summary>
/// <param name="Writer">Writer to output to</param>
void Serialize ( BinaryWriter Writer )
{
Writer . Write ( BackingFile ) ;
Writer . Write ( CreationTimeUtc . ToBinary ( ) ) ;
Dictionary < FileReference , int > FileToUniqueId = new Dictionary < FileReference , int > ( ) ;
Writer . Write ( DependencyMap . Count ) ;
foreach ( KeyValuePair < FileReference , List < DependencyInclude > > Pair in DependencyMap )
{
Writer . Write ( Pair . Key ) ;
Writer . Write ( Pair . Value . Count ) ;
foreach ( DependencyInclude Include in Pair . Value )
{
Writer . Write ( Include . IncludeName ) ;
Writer . Write ( Include . HasAttemptedResolve ) ;
Writer . Write ( Include . IncludeResolvedNameIfSuccessful , FileToUniqueId ) ;
}
}
}
/// <summary>
/// Deserialize the dependency cache from a binary reader
/// </summary>
/// <param name="Reader">Reader for the cache data</param>
/// <returns>New dependency cache object</returns>
static DependencyCache Deserialize ( BinaryReader Reader )
{
DependencyCache Cache = new DependencyCache ( Reader . ReadFileReference ( ) ) ;
Cache . CreationTimeUtc = DateTime . FromBinary ( Reader . ReadInt64 ( ) ) ;
int NumEntries = Reader . ReadInt32 ( ) ;
Cache . DependencyMap = new Dictionary < FileReference , List < DependencyInclude > > ( NumEntries ) ;
List < FileReference > UniqueFiles = new List < FileReference > ( ) ;
for ( int Idx = 0 ; Idx < NumEntries ; Idx + + )
{
FileReference File = Reader . ReadFileReference ( ) ;
int NumIncludes = Reader . ReadInt32 ( ) ;
List < DependencyInclude > Includes = new List < DependencyInclude > ( NumIncludes ) ;
for ( int IncludeIdx = 0 ; IncludeIdx < NumIncludes ; IncludeIdx + + )
{
DependencyInclude Include = new DependencyInclude ( Reader . ReadString ( ) ) ;
Include . HasAttemptedResolve = Reader . ReadBoolean ( ) ;
Include . IncludeResolvedNameIfSuccessful = Reader . ReadFileReference ( UniqueFiles ) ;
Includes . Add ( Include ) ;
}
Cache . DependencyMap . Add ( File , Includes ) ;
}
Cache . CreateFileExistsInfo ( ) ;
Cache . ResetUnresolvedDependencies ( ) ;
return Cache ;
}
2014-03-14 14:13:41 -04:00
/ * *
* Constructor
*
* @param Cache File associated with this cache
* /
2015-09-04 13:48:30 -04:00
protected DependencyCache ( FileReference InBackingFile )
2014-03-14 14:13:41 -04:00
{
2015-09-04 13:48:30 -04:00
BackingFile = InBackingFile ;
CreationTimeUtc = DateTime . UtcNow ;
UpdateTimeUtc = DateTime . UtcNow ;
DependencyMap = new Dictionary < FileReference , List < DependencyInclude > > ( ) ;
2014-03-14 14:13:41 -04:00
bIsDirty = false ;
CreateFileExistsInfo ( ) ;
}
/ * *
* Saves the dependency cache to disk using the update time as the creation time .
* /
public void Save ( )
{
// Only save if we've made changes to it since load.
if ( bIsDirty )
{
var TimerStartTime = DateTime . UtcNow ;
// Save update date as new creation date.
2015-09-04 13:48:30 -04:00
CreationTimeUtc = UpdateTimeUtc ;
2014-03-14 14:13:41 -04:00
// Serialize the cache to disk.
try
{
2015-09-04 13:48:30 -04:00
BackingFile . Directory . CreateDirectory ( ) ;
using ( BinaryWriter Writer = new BinaryWriter ( new FileStream ( BackingFile . FullName , FileMode . Create , FileAccess . Write ) ) )
2014-03-14 14:13:41 -04:00
{
2015-09-04 13:48:30 -04:00
Writer . Write ( FileSignature ) ;
Serialize ( Writer ) ;
2014-03-14 14:13:41 -04:00
}
}
catch ( Exception Ex )
{
Console . Error . WriteLine ( "Failed to write dependency cache: {0}" , Ex . Message ) ;
}
if ( BuildConfiguration . bPrintPerformanceInfo )
{
var TimerDuration = DateTime . UtcNow - TimerStartTime ;
Log . TraceInformation ( "Saving IncludeFileCache took " + TimerDuration . TotalSeconds + "s" ) ;
}
}
else
{
if ( BuildConfiguration . bPrintPerformanceInfo )
{
Log . TraceInformation ( "IncludeFileCache did not need to be saved (bIsDirty=false)" ) ;
}
}
2015-07-27 07:50:47 -04:00
2015-09-04 13:48:30 -04:00
FileReference MutexPath = BackingFile + ".buildmutex" ;
2015-09-03 08:47:24 -04:00
if ( MutexPath . Exists ( ) )
2015-07-27 08:55:36 -04:00
{
2015-08-12 12:01:25 -04:00
try
{
2015-09-03 08:47:24 -04:00
MutexPath . Delete ( ) ;
2015-08-12 12:01:25 -04:00
}
catch
{
// We don't care if we couldn't delete this file, as maybe it couldn't have been created in the first place.
}
2015-07-27 08:55:36 -04:00
}
2014-03-14 14:13:41 -04:00
}
/ * *
2015-06-09 11:50:55 -04:00
* Returns the direct dependencies of the specified FileItem if it exists in the cache and they are not stale .
*
* @param File File to try to find dependencies in cache
* @returns Dependency info for File , or null if no dependencies are cached or if the cache is stale .
2014-03-14 14:13:41 -04:00
* /
2015-06-09 11:50:55 -04:00
public List < DependencyInclude > GetCachedDependencyInfo ( FileItem File )
2014-03-14 14:13:41 -04:00
{
// Check whether File is in cache.
2015-09-04 13:48:30 -04:00
List < DependencyInclude > Includes ;
if ( ! DependencyMap . TryGetValue ( File . Reference , out Includes ) )
2014-03-14 14:13:41 -04:00
{
2015-06-09 11:50:55 -04:00
return null ;
2014-03-14 14:13:41 -04:00
}
2015-06-09 11:50:55 -04:00
// File is in cache, now check whether last write time is prior to cache creation time.
2015-09-04 13:48:30 -04:00
if ( File . LastWriteTime . ToUniversalTime ( ) > = CreationTimeUtc )
2015-06-09 11:50:55 -04:00
{
// Remove entry from cache as it's stale.
2015-09-03 08:47:24 -04:00
DependencyMap . Remove ( File . Reference ) ;
2015-06-09 11:50:55 -04:00
bIsDirty = true ;
return null ;
}
// Check if any of the resolved includes is missing
2015-09-04 13:48:30 -04:00
foreach ( var Include in Includes )
2015-06-09 11:50:55 -04:00
{
2015-09-03 08:47:24 -04:00
if ( Include . IncludeResolvedNameIfSuccessful ! = null )
2015-06-09 11:50:55 -04:00
{
bool bIncludeExists = false ;
2015-09-03 08:47:24 -04:00
if ( ! FileExistsInfo . TryGetValue ( Include . IncludeResolvedNameIfSuccessful , out bIncludeExists ) )
2015-06-09 11:50:55 -04:00
{
2015-09-03 08:47:24 -04:00
bIncludeExists = Include . IncludeResolvedNameIfSuccessful . Exists ( ) ;
FileExistsInfo . Add ( Include . IncludeResolvedNameIfSuccessful , bIncludeExists ) ;
2015-06-09 11:50:55 -04:00
}
if ( ! bIncludeExists )
{
// Remove entry from cache as it's stale, as well as the include which no longer exists
2015-09-03 08:47:24 -04:00
DependencyMap . Remove ( Include . IncludeResolvedNameIfSuccessful ) ;
DependencyMap . Remove ( File . Reference ) ;
2015-06-09 11:50:55 -04:00
bIsDirty = true ;
return null ;
}
}
}
// Cached version is up to date, return it.
2015-09-04 13:48:30 -04:00
return Includes ;
2014-03-14 14:13:41 -04:00
}
/ * *
* Update cache with dependencies for the passed in file .
*
* @param File File to update dependencies for
* @param Dependencies List of dependencies to cache for passed in file
2014-07-31 09:34:11 -04:00
* @param HasUObjects True if this file was found to contain UObject classes or types
2014-03-14 14:13:41 -04:00
* /
2015-06-09 11:50:55 -04:00
public void SetDependencyInfo ( FileItem File , List < DependencyInclude > Info )
2014-03-14 14:13:41 -04:00
{
2015-09-04 13:48:30 -04:00
DependencyMap [ File . Reference ] = Info ;
2014-03-14 14:13:41 -04:00
bIsDirty = true ;
}
/// <summary>
/// Creates and pre-allocates a map for storing information about the physical presence of files on disk.
/// </summary>
private void CreateFileExistsInfo ( )
{
// Pre-allocate about 125% of the dependency map count (which amounts to 1.25 unique includes per dependency which is a little more than empirical
// results show but gives us some slack in case something gets added).
2015-09-03 08:47:24 -04:00
FileExistsInfo = new Dictionary < FileReference , bool > ( ( DependencyMap . Count * 5 ) / 4 ) ;
2014-03-14 14:13:41 -04:00
}
/// <summary>
/// Resets unresolved dependency include files so that the compile environment can attempt to re-resolve them.
/// </summary>
public void ResetUnresolvedDependencies ( )
{
foreach ( var Dependency in DependencyMap )
{
2015-09-04 13:48:30 -04:00
foreach ( var Include in Dependency . Value )
2014-03-14 14:13:41 -04:00
{
2015-09-03 08:47:24 -04:00
if ( Include . HasAttemptedResolve & & Include . IncludeResolvedNameIfSuccessful = = null )
2014-03-14 14:13:41 -04:00
{
2015-09-03 08:47:24 -04:00
Include . HasAttemptedResolve = false ;
2014-03-14 14:13:41 -04:00
}
}
}
}
/// <summary>
/// Caches the fully resolved path of the include.
/// TODO: This method should be more tightly coupled with the Resolve step itself so we don't have to reach into the cache externally
/// using internal details like the list index.
/// </summary>
/// <param name="File">The file whose include is being resolved</param>
/// <param name="DirectlyIncludedFileNameIndex">Index in the resolve list to quickly find the include in question in the existing cache.</param>
/// <param name="DirectlyIncludedFileNameFullPath">Full path name of the resolve include.</param>
2015-09-03 08:47:24 -04:00
public void CacheResolvedIncludeFullPath ( FileItem File , int DirectlyIncludedFileNameIndex , FileReference DirectlyIncludedFileNameFullPath )
2014-03-14 14:13:41 -04:00
{
if ( BuildConfiguration . bUseIncludeDependencyResolveCache )
{
2015-09-04 13:48:30 -04:00
var Includes = DependencyMap [ File . Reference ] ;
2014-03-14 14:13:41 -04:00
var IncludeToResolve = Includes [ DirectlyIncludedFileNameIndex ] ;
if ( BuildConfiguration . bTestIncludeDependencyResolveCache )
{
// test whether there are resolve conflicts between modules with different include paths.
2015-09-03 08:47:24 -04:00
if ( IncludeToResolve . HasAttemptedResolve & & IncludeToResolve . IncludeResolvedNameIfSuccessful ! = DirectlyIncludedFileNameFullPath )
2014-03-14 14:13:41 -04:00
{
throw new BuildException ( "Found directly included file that resolved differently in different modules. File ({0}) had previously resolved to ({1}) and now resolves to ({2})." ,
2015-09-03 08:47:24 -04:00
File . AbsolutePath , IncludeToResolve . IncludeResolvedNameIfSuccessful , DirectlyIncludedFileNameFullPath ) ;
2014-03-14 14:13:41 -04:00
}
}
2015-09-03 08:47:24 -04:00
Includes [ DirectlyIncludedFileNameIndex ] . HasAttemptedResolve = true ;
Includes [ DirectlyIncludedFileNameIndex ] . IncludeResolvedNameIfSuccessful = DirectlyIncludedFileNameFullPath ;
if ( DirectlyIncludedFileNameFullPath ! = null )
2014-03-14 14:13:41 -04:00
{
bIsDirty = true ;
}
}
}
/// <summary>
/// Gets the dependency cache path and filename for the specified target.
/// </summary>
/// <param name="Target">Current build target</param>
/// <returns>Cache Path</returns>
2015-09-03 08:47:24 -04:00
public static FileReference GetDependencyCachePathForTarget ( UEBuildTarget Target )
2014-03-14 14:13:41 -04:00
{
2015-09-03 08:47:24 -04:00
DirectoryReference PlatformIntermediatePath ;
2014-03-14 14:13:41 -04:00
if ( UnrealBuildTool . HasUProjectFile ( ) )
{
2015-09-03 08:47:24 -04:00
PlatformIntermediatePath = DirectoryReference . Combine ( UnrealBuildTool . GetUProjectPath ( ) , BuildConfiguration . PlatformIntermediateFolder ) ;
2014-03-14 14:13:41 -04:00
}
2015-09-03 08:47:24 -04:00
else
{
2015-09-03 11:26:53 -04:00
PlatformIntermediatePath = DirectoryReference . Combine ( UnrealBuildTool . EngineDirectory , BuildConfiguration . PlatformIntermediateFolder ) ;
2015-09-03 08:47:24 -04:00
}
return FileReference . Combine ( PlatformIntermediatePath , Target . GetTargetName ( ) , "DependencyCache.bin" ) ;
2014-03-14 14:13:41 -04:00
}
}
}