2022-08-10 16:03:37 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Concurrent ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
using EpicGames.Core ;
using Microsoft.Extensions.Logging ;
using OpenTracing.Util ;
using UnrealBuildBase ;
namespace UnrealBuildTool
{
2023-01-06 15:25:38 -05:00
/// <summary>
/// The header unit type markup is used for multiple things:
/// 1. To figure out if a header can be compiled by itself or not. Many includes are included in the middle of other includes and those can never be compiled by themselves
/// This markup is used by both msvc header unit feature and IWYU toolchain
/// 2. Tell if the header even supports being part of a header unit. If it does not support it will also prevent all includes including it from producing a header unit
/// This is how to markup in headers to provide above info:
/// // HEADER_UNIT_SKIP - Here you can write why this file can't be compiled standalone
/// // HEADER_UNIT_UNSUPPORTED - Here you can write why this file can't be part of header units.
/// </summary>
enum HeaderUnitType
{
Valid = 0 ,
Unsupported = 1 ,
2023-06-09 17:55:56 -04:00
Skip = 2 ,
Ignore = 3
2023-01-06 15:25:38 -05:00
}
2022-08-10 16:03:37 +00:00
/// <summary>
/// Caches information about C++ source files; whether they contain reflection markup, what the first included header is, and so on.
/// </summary>
class SourceFileMetadataCache
{
/// <summary>
2023-04-14 16:35:47 -04:00
/// Source(cpp/c) file info
2022-08-10 16:03:37 +00:00
/// </summary>
2023-04-14 16:35:47 -04:00
class SourceFileInfo
2022-08-10 16:03:37 +00:00
{
/// <summary>
/// Last write time of the file when the data was cached
/// </summary>
public long LastWriteTimeUtc ;
/// <summary>
/// Contents of the include directive
/// </summary>
public string? IncludeText ;
2023-04-14 16:35:47 -04:00
/// <summary>
/// List of files this particular file is inlining
/// </summary>
public List < string > InlinedFileNames = new List < string > ( ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
2023-04-14 16:35:47 -04:00
/// Header file info
2022-08-10 16:03:37 +00:00
/// </summary>
2023-04-14 16:35:47 -04:00
class HeaderFileInfo
2022-08-10 16:03:37 +00:00
{
/// <summary>
/// Last write time of the file when the data was cached
/// </summary>
public long LastWriteTimeUtc ;
/// <summary>
/// Whether or not the file contains reflection markup
/// </summary>
public bool bContainsMarkup ;
2023-01-06 15:25:38 -05:00
2023-06-09 17:55:56 -04:00
/// <summary>
/// Whether or not the file has types that use DLL export/import defines
/// </summary>
public bool bUsesAPIDefine ;
2023-01-06 15:25:38 -05:00
/// <summary>
/// List of includes that header contains
/// </summary>
public List < string > ? Includes ;
public HeaderUnitType UnitType ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// The current file version
/// </summary>
2023-06-27 12:59:22 -04:00
public const int CurrentVersion = 8 ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Location of this dependency cache
/// </summary>
FileReference Location ;
/// <summary>
/// Directory for files to cache dependencies for.
/// </summary>
DirectoryReference BaseDirectory ;
/// <summary>
/// The parent cache.
/// </summary>
SourceFileMetadataCache ? Parent ;
/// <summary>
/// Map from file item to source file info
/// </summary>
2023-04-14 16:35:47 -04:00
ConcurrentDictionary < FileItem , SourceFileInfo > FileToSourceFileInfo = new ConcurrentDictionary < FileItem , SourceFileInfo > ( ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Map from file item to header file info
/// </summary>
2023-04-14 16:35:47 -04:00
ConcurrentDictionary < FileItem , HeaderFileInfo > FileToHeaderFileInfo = new ConcurrentDictionary < FileItem , HeaderFileInfo > ( ) ;
2022-08-10 16:03:37 +00:00
2022-09-16 20:28:51 -04:00
/// <summary>
2023-04-14 16:35:47 -04:00
/// Map from file item to source file
2022-08-10 16:03:37 +00:00
/// </summary>
ConcurrentDictionary < FileItem , SourceFile > FileToSourceFile = new ConcurrentDictionary < FileItem , SourceFile > ( ) ;
/// <summary>
/// Whether the cache has been modified and needs to be saved
/// </summary>
bool bModified ;
/// <summary>
/// Logger for output
/// </summary>
ILogger Logger ;
/// <summary>
/// Regex that matches C++ code with UObject declarations which we will need to generated code for.
/// </summary>
2023-01-06 15:25:38 -05:00
static readonly Regex ReflectionMarkupRegex = new Regex ( "^\\s*U(CLASS|STRUCT|ENUM|INTERFACE|DELEGATE)\\b" , RegexOptions . Compiled ) ;
2022-08-10 16:03:37 +00:00
2022-09-16 20:28:51 -04:00
/// <summary>
2022-10-06 20:53:52 -04:00
/// Regex that matches #include UE_INLINE_GENERATED_CPP_BY_NAME(****) statements.
2022-09-16 20:28:51 -04:00
/// </summary>
2022-10-06 20:53:52 -04:00
static readonly Regex InlineReflectionMarkupRegex = new Regex ( "^\\s*#\\s*include\\s*UE_INLINE_GENERATED_CPP_BY_NAME\\((.+)\\)" , RegexOptions . Compiled | RegexOptions . Multiline ) ;
2022-09-16 20:28:51 -04:00
2022-08-10 16:03:37 +00:00
/// <summary>
/// Regex that matches #include statements.
/// </summary>
static readonly Regex IncludeRegex = new Regex ( "^[ \t]*#[ \t]*include[ \t]*[<\"](?<HeaderFile>[^\">]*)[\">]" , RegexOptions . Compiled | RegexOptions . Singleline | RegexOptions . ExplicitCapture ) ;
/// <summary>
/// Regex that matches #import directives in mm files
/// </summary>
static readonly Regex ImportRegex = new Regex ( "^[ \t]*#[ \t]*import[ \t]*[<\"](?<HeaderFile>[^\">]*)[\">]" , RegexOptions . Compiled | RegexOptions . Singleline | RegexOptions . ExplicitCapture ) ;
/// <summary>
/// Static cache of all constructed dependency caches
/// </summary>
2023-04-14 16:35:47 -04:00
static ConcurrentDictionary < FileReference , SourceFileMetadataCache > Caches = new ConcurrentDictionary < FileReference , SourceFileMetadataCache > ( ) ;
2022-08-10 16:03:37 +00:00
/// <summary>
/// Constructs a dependency cache. This method is private; call CppDependencyCache.Create() to create a cache hierarchy for a given project.
/// </summary>
/// <param name="Location">File to store the cache</param>
/// <param name="BaseDir">Base directory for files that this cache should store data for</param>
/// <param name="Parent">The parent cache to use</param>
/// <param name="Logger">Logger for output</param>
private SourceFileMetadataCache ( FileReference Location , DirectoryReference BaseDir , SourceFileMetadataCache ? Parent , ILogger Logger )
{
this . Location = Location ;
2023-05-30 18:59:32 -04:00
BaseDirectory = BaseDir ;
2022-08-10 16:03:37 +00:00
this . Parent = Parent ;
this . Logger = Logger ;
2023-05-30 18:38:07 -04:00
if ( FileReference . Exists ( Location ) )
2022-08-10 16:03:37 +00:00
{
using ( GlobalTracer . Instance . BuildSpan ( "Reading source file metadata cache" ) . StartActive ( ) )
{
Read ( ) ;
}
}
}
/// <summary>
2023-04-14 16:35:47 -04:00
/// Returns a SourceFileInfo struct for a file (and parse the file if not already cached)
/// </summary>
/// <param name="SourceFile">The file to parse</param>
/// <returns>SourceFileInfo for file</returns>
SourceFileInfo GetSourceFileInfo ( FileItem SourceFile )
{
if ( Parent ! = null & & ! SourceFile . Location . IsUnderDirectory ( BaseDirectory ) )
{
return Parent . GetSourceFileInfo ( SourceFile ) ;
}
else
{
2023-05-31 13:37:21 -04:00
Func < FileItem , SourceFileInfo > UpdateSourceFileInfo = ( FileItem SourceFile ) = >
2023-04-14 16:35:47 -04:00
{
2023-05-31 13:37:21 -04:00
SourceFileInfo SourceFileInfo = new SourceFileInfo ( ) ;
string FileText = FileReference . ReadAllText ( SourceFile . Location ) ;
string [ ] FileTextLines = FileText . Split ( '\n' ) ;
2023-04-14 16:35:47 -04:00
SourceFileInfo . LastWriteTimeUtc = SourceFile . LastWriteTimeUtc . Ticks ;
// Inline reflection data
MatchCollection FileMatches = InlineReflectionMarkupRegex . Matches ( FileText ) ;
foreach ( Match Match in FileMatches )
{
SourceFileInfo . InlinedFileNames . Add ( Match . Groups [ 1 ] . Value ) ;
}
SourceFileInfo . IncludeText = ParseFirstInclude ( SourceFile , FileTextLines ) ;
bModified = true ;
return SourceFileInfo ;
} ;
return FileToSourceFileInfo . AddOrUpdate ( SourceFile , _ = >
{
return UpdateSourceFileInfo ( SourceFile ) ;
} ,
( k , v ) = >
{
if ( SourceFile . LastWriteTimeUtc . Ticks > v . LastWriteTimeUtc )
{
return UpdateSourceFileInfo ( SourceFile ) ;
}
return v ;
}
) ;
}
}
/// <summary>
/// Parse the first include directive from a source file
2022-08-10 16:03:37 +00:00
/// </summary>
/// <param name="SourceFile">The source file to parse</param>
2023-04-14 16:35:47 -04:00
/// <param name="FileToSourceFileFileText">The source file contents</param>
/// <returns>The first include directive</returns>
static string? ParseFirstInclude ( FileItem SourceFile , string [ ] FileToSourceFileFileText )
2022-08-10 16:03:37 +00:00
{
2023-04-14 16:35:47 -04:00
bool bMatchImport = SourceFile . HasExtension ( ".m" ) | | SourceFile . HasExtension ( ".mm" ) ;
foreach ( string Line in FileToSourceFileFileText )
2022-08-10 16:03:37 +00:00
{
2023-04-14 16:35:47 -04:00
if ( Line = = null )
2022-08-10 16:03:37 +00:00
{
2023-04-14 16:35:47 -04:00
return null ;
2022-08-10 16:03:37 +00:00
}
2023-04-14 16:35:47 -04:00
Match IncludeMatch = IncludeRegex . Match ( Line ) ;
if ( IncludeMatch . Success )
2022-08-10 16:03:37 +00:00
{
2023-04-14 16:35:47 -04:00
return IncludeMatch . Groups [ 1 ] . Value ;
}
if ( bMatchImport )
{
Match ImportMatch = ImportRegex . Match ( Line ) ;
if ( ImportMatch . Success )
2022-08-10 16:03:37 +00:00
{
2023-06-09 17:55:56 -04:00
return ImportMatch . Groups [ 1 ] . Value ;
2022-08-10 16:03:37 +00:00
}
}
}
2023-04-14 16:35:47 -04:00
return null ;
2022-08-10 16:03:37 +00:00
}
2023-04-14 16:35:47 -04:00
HeaderFileInfo GetHeaderFileInfo ( FileItem HeaderFile )
2022-08-10 16:03:37 +00:00
{
2023-04-14 16:35:47 -04:00
if ( Parent ! = null & & ! HeaderFile . Location . IsUnderDirectory ( BaseDirectory ) )
2022-08-10 16:03:37 +00:00
{
2023-04-14 16:35:47 -04:00
return Parent . GetHeaderFileInfo ( HeaderFile ) ;
2022-08-10 16:03:37 +00:00
}
else
{
2023-05-31 13:37:21 -04:00
Func < FileItem , HeaderFileInfo > UpdateHeaderFileInfo = ( FileItem HeaderFile ) = >
2022-08-10 16:03:37 +00:00
{
2023-05-31 13:37:21 -04:00
HeaderFileInfo HeaderFileInfo = new HeaderFileInfo ( ) ;
string FileText = FileReference . ReadAllText ( HeaderFile . Location ) ;
string [ ] FileTextLines = FileText . Split ( '\n' ) ;
2023-04-14 16:35:47 -04:00
HeaderFileInfo . LastWriteTimeUtc = HeaderFile . LastWriteTimeUtc . Ticks ;
ParseHeader ( HeaderFileInfo , FileTextLines ) ;
2022-08-10 16:03:37 +00:00
bModified = true ;
2023-04-14 16:35:47 -04:00
return HeaderFileInfo ;
} ;
return FileToHeaderFileInfo . AddOrUpdate ( HeaderFile , _ = >
{
return UpdateHeaderFileInfo ( HeaderFile ) ;
} ,
( k , v ) = >
{
if ( HeaderFile . LastWriteTimeUtc . Ticks > v . LastWriteTimeUtc )
{
return UpdateHeaderFileInfo ( HeaderFile ) ;
}
return v ;
2022-08-10 16:03:37 +00:00
}
2023-04-14 16:35:47 -04:00
) ;
2022-08-10 16:03:37 +00:00
}
}
2023-01-06 15:25:38 -05:00
/// <summary>
/// Read entire header file to find markup and includes
/// </summary>
/// <returns>A HeaderInfo struct containing information about header</returns>
2023-04-14 16:35:47 -04:00
private void ParseHeader ( HeaderFileInfo HeaderFileInfo , string [ ] FileText )
2023-01-06 15:25:38 -05:00
{
bool bContainsMarkup = false ;
2023-06-09 17:55:56 -04:00
bool bUsesAPIDefine = false ;
2023-06-27 12:59:22 -04:00
int InsideDeprecationScope = 0 ;
2023-01-06 15:25:38 -05:00
SortedSet < string > Includes = new ( ) ;
2023-04-14 16:35:47 -04:00
foreach ( string Line in FileText )
2023-01-06 15:25:38 -05:00
{
2023-06-09 17:55:56 -04:00
if ( ! bUsesAPIDefine )
2023-01-06 15:25:38 -05:00
{
2023-06-09 17:55:56 -04:00
bUsesAPIDefine = Line . Contains ( "_API" , StringComparison . Ordinal ) ;
2023-01-06 15:25:38 -05:00
}
2023-06-27 12:59:22 -04:00
if ( ! bContainsMarkup )
2023-06-09 17:55:56 -04:00
{
2023-06-27 12:59:22 -04:00
bContainsMarkup = ReflectionMarkupRegex . IsMatch ( Line ) ;
2023-06-09 17:55:56 -04:00
}
2023-06-27 12:59:22 -04:00
ReadOnlySpan < char > LineSpan = Line . AsSpan ( ) . TrimStart ( ) ;
if ( LineSpan . StartsWith ( "#include" ) & & InsideDeprecationScope = = 0 )
2023-01-06 15:25:38 -05:00
{
2023-06-27 12:59:22 -04:00
ReadOnlySpan < char > IncludeSpan = LineSpan . Slice ( "#include" . Length ) . TrimStart ( ) ;
if ( IncludeSpan . IsEmpty )
2023-05-30 18:59:32 -04:00
{
2023-06-27 12:59:22 -04:00
continue ;
2023-05-30 18:59:32 -04:00
}
2023-06-27 12:59:22 -04:00
char EndChar ;
bool TrimQuotation = true ;
if ( IncludeSpan [ 0 ] = = '"' )
2023-05-30 18:59:32 -04:00
{
2023-06-27 12:59:22 -04:00
EndChar = '"' ;
2023-05-30 18:59:32 -04:00
}
2023-06-27 12:59:22 -04:00
else if ( IncludeSpan [ 0 ] = = '<' )
2023-06-09 17:55:56 -04:00
{
2023-06-27 12:59:22 -04:00
EndChar = '>' ;
}
else
{
EndChar = ')' ;
TrimQuotation = false ;
}
if ( TrimQuotation )
{
IncludeSpan = IncludeSpan . Slice ( 1 ) ;
}
if ( IncludeSpan . Contains ( "HEADER_UNIT_IGNORE" , StringComparison . OrdinalIgnoreCase ) )
{
continue ;
}
int EndIndex = IncludeSpan . IndexOf ( EndChar ) ;
if ( EndIndex = = - 1 )
{
continue ;
}
// This will include the ')' at the end
if ( ! TrimQuotation )
{
EndIndex + + ;
}
IncludeSpan = IncludeSpan . Slice ( 0 , EndIndex ) ;
Includes . Add ( IncludeSpan . ToString ( ) ) ;
}
else if ( InsideDeprecationScope ! = 0 )
{
if ( LineSpan . StartsWith ( "#endif" ) )
{
- - InsideDeprecationScope ;
}
else if ( LineSpan . StartsWith ( "#if" ) )
{
+ + InsideDeprecationScope ;
}
}
else if ( LineSpan . StartsWith ( "#if" ) )
{
2024-07-23 19:39:24 -04:00
if ( Line . Contains ( "UE_ENABLE_INCLUDE_ORDER_DEPRECATED_" , StringComparison . CurrentCulture ) )
2023-06-27 12:59:22 -04:00
{
+ + InsideDeprecationScope ;
}
}
2024-03-20 10:38:05 -04:00
else if ( LineSpan . StartsWith ( "UE_DEPRECATED_HEADER" ) )
{
HeaderFileInfo . UnitType = HeaderUnitType . Skip ;
}
2023-06-27 12:59:22 -04:00
else
{
int HeaderUnitIndex = Line . IndexOf ( "HEADER_UNIT_" ) ;
if ( HeaderUnitIndex ! = - 1 )
{
ReadOnlySpan < char > Span = Line . AsSpan ( HeaderUnitIndex + "HEADER_UNIT_" . Length ) ;
if ( Span . StartsWith ( "UNSUPPORTED" ) )
{
HeaderFileInfo . UnitType = HeaderUnitType . Unsupported ;
}
else if ( Span . StartsWith ( "SKIP" ) )
{
HeaderFileInfo . UnitType = HeaderUnitType . Skip ;
}
2023-06-09 17:55:56 -04:00
}
2023-01-06 15:25:38 -05:00
}
}
2023-04-14 16:35:47 -04:00
HeaderFileInfo . bContainsMarkup = bContainsMarkup ;
2023-06-09 17:55:56 -04:00
HeaderFileInfo . bUsesAPIDefine = bUsesAPIDefine ;
2023-04-14 16:35:47 -04:00
HeaderFileInfo . Includes = Includes . ToList ( ) ;
2023-01-06 15:25:38 -05:00
}
2023-04-14 16:35:47 -04:00
/// <summary>
/// Gets the first included file from a source file
/// </summary>
/// <param name="SourceFile">The source file to parse</param>
/// <returns>Text from the first include directive. Null if the file did not contain any include directives.</returns>
public string? GetFirstInclude ( FileItem SourceFile )
{
return GetSourceFileInfo ( SourceFile ) . IncludeText ;
}
2023-01-06 15:25:38 -05:00
2023-04-14 16:35:47 -04:00
/// <summary>
/// Finds or adds a SourceFile class for the given file
/// </summary>
/// <param name="File">File to fetch the source file data for</param>
/// <returns>SourceFile instance corresponding to the given source file</returns>
public SourceFile GetSourceFile ( FileItem File )
{
if ( Parent ! = null & & ! File . Location . IsUnderDirectory ( BaseDirectory ) )
{
return Parent . GetSourceFile ( File ) ;
}
else
{
return FileToSourceFile . AddOrUpdate ( File , _ = >
{
return new SourceFile ( File ) ;
} ,
2023-05-30 18:38:07 -04:00
( k , v ) = >
2023-04-14 16:35:47 -04:00
{
if ( File . LastWriteTimeUtc . Ticks > v . LastWriteTimeUtc )
{
return new SourceFile ( File ) ;
}
return v ;
} ) ;
}
}
2022-09-16 20:28:51 -04:00
/// <summary>
/// Returns a list of inlined generated cpps that this source file contains.
/// </summary>
/// <param name="SourceFile">The source file to parse</param>
/// <returns>List of marked files this source file contains</returns>
public IList < string > GetListOfInlinedGeneratedCppFiles ( FileItem SourceFile )
{
2023-04-14 16:35:47 -04:00
return GetSourceFileInfo ( SourceFile ) . InlinedFileNames ;
2022-09-16 20:28:51 -04:00
}
2023-01-06 15:25:38 -05:00
/// <summary>
2023-04-14 16:35:47 -04:00
/// Determines whether the given file contains reflection markup
2023-01-06 15:25:38 -05:00
/// </summary>
2023-04-14 16:35:47 -04:00
/// <param name="HeaderFile">The source file to parse</param>
/// <returns>True if the file contains reflection markup</returns>
public bool ContainsReflectionMarkup ( FileItem HeaderFile )
2023-01-06 15:25:38 -05:00
{
2023-04-14 16:35:47 -04:00
return GetHeaderFileInfo ( HeaderFile ) . bContainsMarkup ;
2023-01-06 15:25:38 -05:00
}
2023-06-09 17:55:56 -04:00
/// <summary>
/// Determines whether the given file uses the *_API define
/// </summary>
/// <param name="HeaderFile">The source file to parse</param>
/// <returns>True if the file uses the *_API define</returns>
public bool UsesAPIDefine ( FileItem HeaderFile )
{
return GetHeaderFileInfo ( HeaderFile ) . bUsesAPIDefine ;
}
2023-01-06 15:25:38 -05:00
/// <summary>
/// Returns header unit type for a header file (and parse the file if not already cached)
/// </summary>
/// <param name="HeaderFile">The header file to parse</param>
/// <returns>Header unit type</returns>
public HeaderUnitType GetHeaderUnitType ( FileItem HeaderFile )
{
2023-04-14 16:35:47 -04:00
return GetHeaderFileInfo ( HeaderFile ) . UnitType ;
2023-01-06 15:25:38 -05:00
}
/// <summary>
/// Returns all #includes existing inside a header file (and parse the file if not already cached)
/// </summary>
/// <param name="HeaderFile">The header file to parse</param>
/// <returns>List of includes</returns>
public List < string > GetHeaderIncludes ( FileItem HeaderFile )
{
2023-04-14 16:35:47 -04:00
return GetHeaderFileInfo ( HeaderFile ) . Includes ! ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Creates a cache hierarchy for a particular target
/// </summary>
/// <param name="ProjectFile">Project file for the target being built</param>
/// <param name="Logger">Logger for output</param>
/// <returns>Dependency cache hierarchy for the given project</returns>
public static SourceFileMetadataCache CreateHierarchy ( FileReference ? ProjectFile , ILogger Logger )
{
SourceFileMetadataCache ? Cache = null ;
2023-05-30 18:38:07 -04:00
if ( ProjectFile = = null | | ! Unreal . IsEngineInstalled ( ) )
2022-08-10 16:03:37 +00:00
{
FileReference EngineCacheLocation = FileReference . Combine ( Unreal . EngineDirectory , "Intermediate" , "Build" , "SourceFileCache.bin" ) ;
Cache = FindOrAddCache ( EngineCacheLocation , Unreal . EngineDirectory , Cache , Logger ) ;
}
2023-05-30 18:38:07 -04:00
if ( ProjectFile ! = null )
2022-08-10 16:03:37 +00:00
{
FileReference ProjectCacheLocation = FileReference . Combine ( ProjectFile . Directory , "Intermediate" , "Build" , "SourceFileCache.bin" ) ;
Cache = FindOrAddCache ( ProjectCacheLocation , ProjectFile . Directory , Cache , Logger ) ;
}
return Cache ! ;
}
/// <summary>
/// Enumerates all the locations of metadata caches for the given target
/// </summary>
/// <param name="ProjectFile">Project file for the target being built</param>
/// <returns>Dependency cache hierarchy for the given project</returns>
public static IEnumerable < FileReference > GetFilesToClean ( FileReference ? ProjectFile )
{
2023-05-30 18:38:07 -04:00
if ( ProjectFile = = null | | ! Unreal . IsEngineInstalled ( ) )
2022-08-10 16:03:37 +00:00
{
yield return FileReference . Combine ( Unreal . EngineDirectory , "Intermediate" , "Build" , "SourceFileCache.bin" ) ;
}
2023-05-30 18:38:07 -04:00
if ( ProjectFile ! = null )
2022-08-10 16:03:37 +00:00
{
yield return FileReference . Combine ( ProjectFile . Directory , "Intermediate" , "Build" , "SourceFileCache.bin" ) ;
}
}
/// <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>
/// <param name="Logger"></param>
/// <returns>Reference to a dependency cache with the given settings</returns>
static SourceFileMetadataCache FindOrAddCache ( FileReference Location , DirectoryReference BaseDirectory , SourceFileMetadataCache ? Parent , ILogger Logger )
{
2023-04-14 16:35:47 -04:00
SourceFileMetadataCache Cache = Caches . GetOrAdd ( Location , _ = >
2022-08-10 16:03:37 +00:00
{
2024-04-03 12:22:43 -04:00
return new SourceFileMetadataCache ( Location , BaseDirectory , Parent , Logger ) ;
;
2023-04-14 16:35:47 -04:00
} ) ;
Debug . Assert ( Cache . BaseDirectory = = BaseDirectory ) ;
Debug . Assert ( Cache . Parent = = Parent ) ;
return Cache ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Save all the caches that have been modified
/// </summary>
public static void SaveAll ( )
{
2024-04-03 12:22:43 -04:00
Parallel . ForEach ( Caches . Values , Cache = >
{
if ( Cache . bModified )
{
Cache . Write ( ) ;
}
} ) ;
2022-08-10 16:03:37 +00:00
}
/// <summary>
/// Reads data for this dependency cache from disk
/// </summary>
private void Read ( )
{
try
{
2023-05-30 18:38:07 -04:00
using ( BinaryArchiveReader Reader = new BinaryArchiveReader ( Location ) )
2022-08-10 16:03:37 +00:00
{
int Version = Reader . ReadInt ( ) ;
2023-05-30 18:38:07 -04:00
if ( Version ! = CurrentVersion )
2022-08-10 16:03:37 +00:00
{
Logger . LogDebug ( "Unable to read dependency cache from {File}; version {Version} vs current {CurrentVersion}" , Location , Version , CurrentVersion ) ;
return ;
}
int FileToMarkupFlagCount = Reader . ReadInt ( ) ;
2023-05-30 18:38:07 -04:00
for ( int Idx = 0 ; Idx < FileToMarkupFlagCount ; Idx + + )
2022-08-10 16:03:37 +00:00
{
FileItem File = Reader . ReadCompactFileItem ( ) ;
2023-04-14 16:35:47 -04:00
HeaderFileInfo HeaderFileInfo = new HeaderFileInfo ( ) ;
HeaderFileInfo . LastWriteTimeUtc = Reader . ReadLong ( ) ;
HeaderFileInfo . bContainsMarkup = Reader . ReadBool ( ) ;
2023-06-09 17:55:56 -04:00
HeaderFileInfo . bUsesAPIDefine = Reader . ReadBool ( ) ;
2023-04-14 16:35:47 -04:00
HeaderFileInfo . UnitType = ( HeaderUnitType ) Reader . ReadByte ( ) ;
HeaderFileInfo . Includes = Reader . ReadList ( ( ) = > Reader . ReadString ( ) ) ! ;
2022-08-10 16:03:37 +00:00
2023-04-14 16:35:47 -04:00
FileToHeaderFileInfo [ File ] = HeaderFileInfo ;
2022-08-10 16:03:37 +00:00
}
2022-09-16 20:28:51 -04:00
int FileToInlineMarkupFlagCount = Reader . ReadInt ( ) ;
for ( int Idx = 0 ; Idx < FileToInlineMarkupFlagCount ; Idx + + )
{
FileItem File = Reader . ReadCompactFileItem ( ) ;
2023-04-14 16:35:47 -04:00
SourceFileInfo SourceFileInfo = new SourceFileInfo ( ) ;
SourceFileInfo . LastWriteTimeUtc = Reader . ReadLong ( ) ;
SourceFileInfo . IncludeText = Reader . ReadString ( ) ;
SourceFileInfo . InlinedFileNames = Reader . ReadList ( ( ) = > Reader . ReadString ( ) ) ! ;
2022-09-16 20:28:51 -04:00
2023-04-14 16:35:47 -04:00
FileToSourceFileInfo [ File ] = SourceFileInfo ;
2022-09-16 20:28:51 -04:00
}
2022-08-10 16:03:37 +00:00
}
}
2023-05-30 18:38:07 -04:00
catch ( Exception Ex )
2022-08-10 16:03:37 +00:00
{
Logger . LogWarning ( "Unable to read {Location}. See log for additional information." , Location ) ;
Logger . LogDebug ( Ex , "{Ex}" , ExceptionUtils . FormatExceptionDetails ( Ex ) ) ;
}
}
/// <summary>
/// Writes data for this dependency cache to disk
/// </summary>
private void Write ( )
{
DirectoryReference . CreateDirectory ( Location . Directory ) ;
2023-05-30 18:38:07 -04:00
using ( FileStream Stream = File . Open ( Location . FullName , FileMode . Create , FileAccess . Write , FileShare . Read ) )
2022-08-10 16:03:37 +00:00
{
2023-05-30 18:38:07 -04:00
using ( BinaryArchiveWriter Writer = new BinaryArchiveWriter ( Stream ) )
2022-08-10 16:03:37 +00:00
{
Writer . WriteInt ( CurrentVersion ) ;
2023-04-14 16:35:47 -04:00
Writer . WriteInt ( FileToHeaderFileInfo . Count ) ;
2023-05-30 18:38:07 -04:00
foreach ( KeyValuePair < FileItem , HeaderFileInfo > Pair in FileToHeaderFileInfo )
2022-08-10 16:03:37 +00:00
{
Writer . WriteCompactFileItem ( Pair . Key ) ;
Writer . WriteLong ( Pair . Value . LastWriteTimeUtc ) ;
Writer . WriteBool ( Pair . Value . bContainsMarkup ) ;
2023-06-09 17:55:56 -04:00
Writer . WriteBool ( Pair . Value . bUsesAPIDefine ) ;
2023-01-06 15:25:38 -05:00
Writer . WriteByte ( ( byte ) Pair . Value . UnitType ) ;
Writer . WriteList ( Pair . Value . Includes , Item = > Writer . WriteString ( Item ) ) ;
2022-08-10 16:03:37 +00:00
}
2022-09-16 20:28:51 -04:00
2023-04-14 16:35:47 -04:00
Writer . WriteInt ( FileToSourceFileInfo . Count ) ;
foreach ( KeyValuePair < FileItem , SourceFileInfo > Pair in FileToSourceFileInfo )
2022-09-16 20:28:51 -04:00
{
Writer . WriteCompactFileItem ( Pair . Key ) ;
Writer . WriteLong ( Pair . Value . LastWriteTimeUtc ) ;
2023-04-14 16:35:47 -04:00
Writer . WriteString ( Pair . Value . IncludeText ) ;
2022-09-16 20:28:51 -04:00
Writer . WriteList ( Pair . Value . InlinedFileNames , Item = > Writer . WriteString ( Item ) ) ;
}
2022-08-10 16:03:37 +00:00
}
}
bModified = false ;
}
}
}