2023-01-10 14:52:00 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
2023-01-31 14:46:47 -05:00
using System.Collections.Concurrent ;
2023-05-30 18:38:07 -04:00
using System.Collections.Generic ;
2023-06-28 15:54:57 -04:00
using System.Diagnostics ;
2023-05-30 18:38:07 -04:00
using System.Diagnostics.CodeAnalysis ;
using System.IO ;
using System.Linq ;
using System.Text ;
using System.Text.Json ;
using System.Threading ;
using System.Threading.Tasks ;
using EpicGames.Core ;
using Microsoft.Extensions.Logging ;
using UnrealBuildBase ;
2023-01-10 14:52:00 -05:00
// IWYUMode is a mode that can be used to clean up includes in source code. It uses the clang based tool include-what-you-use (IWYU) to figure out what is needed in each .h/.cpp file
// and then cleans up accordingly. This mode can be used to clean up unreal as well as plugins and projects on top of unreal.
// Note, IWYU is not perfect. There are still c++ features not supported so even though it will do a good job cleaning up it might require a little bit of hands on but not much.
// When iwyu for some reason removes includes you want to keep, there are ways to anotate the code. Look at the pragma link below.
//
// IWYU Github: https://github.com/include-what-you-use/include-what-you-use
// IWYU Pragmas: https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md
//
// Here are examples of how a commandline could look (using ushell)
//
// 1. Will build lyra editor target using include-what-you-use.exe instead of clang.exe, and preview how iwyu would update module LyraGame and all the modules depending on it
//
// .build target LyraEditor linux development -- -Mode=IWYU -ModuleToUpdate=LyraGame -UpdateDependents
//
// 2. Same as 1 but will check out files from p4 and modify them
//
// .build target LyraEditor linux development -- -Mode=IWYU -ModuleToUpdate=LyraGame -UpdateDependents -Write
//
// 3. Will build and then update all modules that has Lyra/Plugins in its path. Note it will only include modules that are part of LyraEditor target.
//
// .build target LyraEditor linux development -- -Mode=IWYU -PathToUpdate=Lyra/Plugins -Write
//
// 4. Update Niagara plugins private code (not public api) with preview
//
// .build target UnrealEditor linux development -- -Mode=IWYU -PathToUpdate=Engine/Plugins/FX/Niagara -UpdateOnlyPrivate
namespace UnrealBuildTool
{
/// <summary>
/// Representing an #include inside a code file.
/// </summary>
class IWYUIncludeEntry
{
/// <summary>
/// Full path using forward paths, eg: d:/dev/folder/file.h
/// </summary>
public string Full { get ; set ; } = "" ;
/// <summary>
/// Printable path for actual include. Does not include quotes or angle brackets. eg: folder/file.h
/// </summary>
public string Printable { get ; set ; } = "" ;
/// <summary>
/// Reference to file info. Not populated with .iwyu file
/// </summary>
public IWYUInfo ? Resolved ;
/// <summary>
/// Decides if printable should be #include quote or angle bracket
/// </summary>
public bool System { get ; set ; }
2023-01-26 02:25:52 -05:00
/// <summary>
/// If true #include was found inside a declaration such as a namespace or class/struct/function
/// </summary>
public bool InsideDecl { get ; set ; }
2023-01-10 14:52:00 -05:00
}
/// <summary>
/// Comparer for include entries using printable to compare
/// </summary>
class IWYUIncludeEntryPrintableComparer : IEqualityComparer < IWYUIncludeEntry >
{
public bool Equals ( IWYUIncludeEntry ? x , IWYUIncludeEntry ? y )
{
return x ! . Printable = = y ! . Printable ;
}
public int GetHashCode ( [ DisallowNull ] IWYUIncludeEntry obj )
{
throw new NotImplementedException ( ) ;
}
}
/// <summary>
/// Representing a forward declaration inside a code file. eg: class MyClass;
/// </summary>
struct IWYUForwardEntry
{
/// <summary>
/// The string to add to the file
/// </summary>
public string Printable { get ; set ; }
/// <summary>
/// True if forward declaration has already been seen in the file
/// </summary>
public bool Present { get ; set ; }
}
2023-01-13 02:02:01 -05:00
/// <summary>
/// Representing an include that the code file needs that is missing in the include list
/// Note, in cpp files it is listing the includes that are in the matching h file
/// </summary>
struct IWYUMissingInclude
{
/// <summary>
/// Full path using forward paths, eg: d:/dev/folder/file.h
/// </summary>
public string Full { get ; set ; }
}
2023-01-10 14:52:00 -05:00
/// <summary>
/// Representing a file (header or source). Each .iwyu file has a list of these (most of the time only one)
/// </summary>
class IWYUInfo
{
/// <summary>
/// Full path of file using forward paths, eg: d:/dev/folder/file.h
/// </summary>
public string File { get ; set ; } = "" ;
/// <summary>
/// Includes that this file needs. This is what iwyu have decided is used.
/// </summary>
public List < IWYUIncludeEntry > Includes { get ; set ; } = new List < IWYUIncludeEntry > ( ) ;
/// <summary>
/// Forward declarations that this file needs. This is what iwyu have decided is used.
/// </summary>
public List < IWYUForwardEntry > ForwardDeclarations { get ; set ; } = new List < IWYUForwardEntry > ( ) ;
/// <summary>
/// Includes seen in the file. This is how it looked like on disk.
/// </summary>
public List < IWYUIncludeEntry > IncludesSeenInFile { get ; set ; } = new List < IWYUIncludeEntry > ( ) ;
2023-01-13 02:02:01 -05:00
/// <summary>
/// Includes that didnt end up in the Includes list but is needed by the code file
/// </summary>
public List < IWYUMissingInclude > MissingIncludes { get ; set ; } = new List < IWYUMissingInclude > ( ) ;
2023-01-10 14:52:00 -05:00
/// <summary>
/// Transitive includes. This is all the includes that someone gets for free when including this file
/// Note, this is based on what iwyu believes should be in all includes. So it will not look at "seen includes"
/// and only use its generated list.
/// </summary>
public Dictionary < string , string > TransitiveIncludes = new ( ) ;
/// <summary>
/// Transitive forward declarations. Same as transitive includes
/// </summary>
public Dictionary < string , string > TransitiveForwardDeclarations = new ( ) ;
/// <summary>
/// Which .iwyu file that produced this info. Note, it might be null for some special cases (like .generated.h files)
/// </summary>
public IWYUFile ? Source ;
/// <summary>
/// Module that this file belongs to
/// </summary>
public UEBuildModule ? Module ;
2023-01-26 02:25:52 -05:00
/// <summary>
/// Is true if this file was included inside a declaration such as a namespace or class/struct/function
/// </summary>
public bool IncludedInsideDecl ;
2023-02-17 19:39:41 -05:00
/// <summary>
/// Is true if this file ends with .cpp
/// </summary>
public bool IsCpp ;
2023-01-10 14:52:00 -05:00
}
/// <summary>
/// This is what include-what-you-use.exe produces for each build. a .iwyu file that is parsed into these instances
/// </summary>
class IWYUFile
{
/// <summary>
/// The different headers and source files that was covered by this execution of iwyu.
/// </summary>
2023-05-30 18:38:07 -04:00
public List < IWYUInfo > Files { get ; set ; } = new ( ) ;
2023-01-10 14:52:00 -05:00
/// <summary>
/// Name of .iwyu file
/// </summary>
public string? Name ;
}
/// <summary>
/// Profiles different unity sizes and prints out the different size and its timings
/// </summary>
[ToolMode("IWYU", ToolModeOptions.XmlConfig | ToolModeOptions.BuildPlatforms | ToolModeOptions.SingleInstance | ToolModeOptions.StartPrefetchingEngine | ToolModeOptions.ShowExecutionTime)]
class IWYUMode : ToolMode
{
2023-06-28 15:54:57 -04:00
/// <summary>
/// Specifies the file to use for logging.
/// </summary>
[XmlConfigFile(Category = "BuildConfiguration")]
public string? IWYUBaseLogFileName ;
2023-01-10 14:52:00 -05:00
/// <summary>
/// Will check out files from p4 and write to disk
/// </summary>
[CommandLine("-Write")]
public bool bWrite = false ;
/// <summary>
/// Update only includes in cpp files and don't touch headers
/// </summary>
[CommandLine("-UpdateOnlyPrivate")]
public bool bUpdateOnlyPrivate = false ;
/// <summary>
/// Which module to run IWYU on. Will also include all modules depending on this module
/// </summary>
[CommandLine("-ModuleToUpdate")]
public string ModuleToUpdate = "" ;
/// <summary>
/// Which directory to run IWYU on. Will search for modules that has module directory matching this string
/// If no module is found in -PathToUpdate it handles this for individual files instead.
/// Note this can be combined with -ModuleToUpdate to double filter
2023-01-13 02:02:01 -05:00
/// PathToUpdate supports multiple paths using semi colon separation
2023-01-10 14:52:00 -05:00
/// </summary>
[CommandLine("-PathToUpdate")]
public string PathToUpdate = "" ;
2023-01-13 02:02:01 -05:00
/// <summary>
/// Same as PathToUpdate but provide the list of paths in a text file instead.
/// Add all the desired paths on a separate line
/// </summary>
[CommandLine("-PathToUpdateFile")]
public string PathToUpdateFile = "" ;
2023-01-10 14:52:00 -05:00
/// <summary>
/// Also update modules that are depending on the module we are updating
/// </summary>
[CommandLine("-UpdateDependents")]
public bool bUpdateDependents = false ;
/// <summary>
/// Will check out files from p4 and write to disk
/// </summary>
[CommandLine("-NoP4")]
public bool bNoP4 = false ;
/// <summary>
/// Allow files to not add includes if they are transitively included by other includes
/// </summary>
2023-01-26 02:25:52 -05:00
[CommandLine("-NoTransitive")]
2023-01-10 14:52:00 -05:00
public bool bNoTransitiveIncludes = false ;
2023-01-26 02:25:52 -05:00
/// <summary>
2023-01-27 14:11:31 -05:00
/// Will remove headers that are needed but redundant because they are included through other needed includes
2023-01-26 02:25:52 -05:00
/// </summary>
2023-01-27 14:11:31 -05:00
[CommandLine("-RemoveRedundant")]
public bool bRemoveRedundantIncludes = false ;
2023-01-26 02:25:52 -05:00
2023-01-10 14:52:00 -05:00
/// <summary>
/// Will skip compiling before updating. Handle with care, this is dangerous since files might not match .iwyu files
/// </summary>
[CommandLine("-NoCompile")]
public bool bNoCompile = false ;
/// <summary>
/// Will ignore update check that .iwyu is newer than source files.
/// </summary>
[CommandLine("-IgnoreUpToDateCheck")]
public bool bIgnoreUpToDateCheck = false ;
/// <summary>
/// If set, this will keep removed includes in #if/#endif scope at the end of updated file.
/// Applied to non-private headers that are part of the Engine folder.
/// </summary>
[CommandLine("-DeprecateTag")]
2023-01-25 02:42:58 -05:00
private string? HeaderDeprecationTagOverride ;
public string HeaderDeprecationTag
{
get
{
if ( ! String . IsNullOrEmpty ( HeaderDeprecationTagOverride ) )
{
return HeaderDeprecationTagOverride ;
}
return EngineIncludeOrderHelper . GetLatestDeprecationDefine ( ) ;
}
2023-05-30 18:01:50 -04:00
set = > HeaderDeprecationTagOverride = value ;
2023-01-25 02:42:58 -05:00
}
2023-01-10 14:52:00 -05:00
2023-01-26 02:25:52 -05:00
/// <summary>
/// Compare current include structure with how it would look like if iwyu was applied on all files
/// </summary>
[CommandLine("-Compare")]
public bool bCompare = false ;
/// <summary>
/// For Development only - Will write a toc referencing all .iwyu files. Toc can be used by -ReadToc
/// </summary>
[CommandLine("-WriteToc")]
public bool bWriteToc = false ;
/// <summary>
/// For Development only - Will read a toc to find all .iwyu files instead of building the code.
/// </summary>
[CommandLine("-ReadToc")]
public bool bReadToc = false ;
2023-01-12 19:44:53 -05:00
private string? GetModuleToUpdateName ( TargetDescriptor Descriptor )
{
if ( ! String . IsNullOrEmpty ( ModuleToUpdate ) )
{
return ModuleToUpdate ;
}
if ( Descriptor . OnlyModuleNames . Count > 0 )
{
return Descriptor . OnlyModuleNames . First ( ) ;
}
return null ;
}
2023-01-13 15:32:03 -05:00
private string AdjustModulePathForMatching ( string ModulePath )
{
string NewModulePath = ModulePath . Replace ( '\\' , '/' ) ;
if ( ! NewModulePath . EndsWith ( '/' ) )
{
NewModulePath = NewModulePath + '/' ;
}
return NewModulePath ;
}
2023-01-10 14:52:00 -05:00
/// <summary>
/// Execute the command
/// </summary>
/// <param name="Arguments">Command line arguments</param>
/// <returns>Exit code</returns>
/// <param name="Logger"></param>
2023-04-19 21:44:13 -04:00
public override async Task < int > ExecuteAsync ( CommandLineArguments Arguments , ILogger Logger )
2023-01-10 14:52:00 -05:00
{
2023-08-15 19:35:44 -04:00
Arguments . ApplyTo ( this ) ;
2023-01-10 14:52:00 -05:00
Logger . LogInformation ( $"====================================================" ) ;
Logger . LogInformation ( $"Running IWYU. {(bWrite ? "" : " ( Preview mode . Add - Write to write modifications to files ) ")}" ) ;
Logger . LogInformation ( $"====================================================" ) ;
2023-06-28 15:54:57 -04:00
// Fixup the log path if it wasn't overridden by a config file
if ( IWYUBaseLogFileName = = null )
{
IWYUBaseLogFileName = FileReference . Combine ( Unreal . EngineProgramSavedDirectory , "UnrealBuildTool" , "IWYULog.txt" ) . FullName ;
}
// Create the log file, and flush the startup listener to it
if ( ! Arguments . HasOption ( "-NoLog" ) & & ! Log . HasFileWriter ( ) )
{
Log . AddFileWriter ( "DefaultLogTraceListener" , FileReference . FromString ( IWYUBaseLogFileName ) ) ;
}
else
{
IEnumerable < StartupTraceListener > StartupListeners = Trace . Listeners . OfType < StartupTraceListener > ( ) ;
if ( StartupListeners . Any ( ) )
{
Trace . Listeners . Remove ( StartupListeners . First ( ) ) ;
}
}
2023-01-10 14:52:00 -05:00
// Create the build configuration object, and read the settings
CommandLineArguments BuildArguments = Arguments . Append ( new [ ] { "-IWYU" } ) ; // Add in case it is not added (it is needed for iwyu toolchain)
BuildConfiguration BuildConfiguration = new BuildConfiguration ( ) ;
XmlConfig . ApplyTo ( BuildConfiguration ) ;
BuildArguments . ApplyTo ( BuildConfiguration ) ;
BuildConfiguration . MaxNestedPathLength = 220 ; // For now since the path is slightly longer
// Parse all the target descriptors
2023-07-21 12:08:09 -04:00
List < TargetDescriptor > TargetDescriptors = TargetDescriptor . ParseCommandLine ( BuildArguments , BuildConfiguration , Logger ) ;
2023-01-10 14:52:00 -05:00
if ( TargetDescriptors . Count ! = 1 )
{
Logger . LogError ( $"IWYUMode can only handle command lines that produce one target (Cmdline: {Arguments.ToString()})" ) ;
return 0 ;
}
2023-07-21 12:08:09 -04:00
TargetDescriptors [ 0 ] . IntermediateEnvironment = UnrealIntermediateEnvironment . IWYU ;
2023-01-12 19:44:53 -05:00
string? ModuleToUpdateName = GetModuleToUpdateName ( TargetDescriptors [ 0 ] ) ;
2023-05-30 18:59:32 -04:00
if ( ! String . IsNullOrEmpty ( ModuleToUpdateName ) )
2023-01-12 19:44:53 -05:00
{
foreach ( string OnlyModuleName in TargetDescriptors [ 0 ] . OnlyModuleNames )
{
2023-05-30 18:59:32 -04:00
if ( String . Compare ( OnlyModuleName , ModuleToUpdateName , StringComparison . OrdinalIgnoreCase ) ! = 0 )
2023-01-12 19:44:53 -05:00
{
2023-05-30 18:59:32 -04:00
Logger . LogError ( $"ModuleToUpdate '{ModuleToUpdateName}' was not in list of specified modules: {String.Join(" , ", TargetDescriptors[0].OnlyModuleNames)}" ) ;
2023-01-12 19:44:53 -05:00
return - 1 ;
}
}
}
2023-01-13 02:02:01 -05:00
string [ ] PathToUpdateList = Array . Empty < string > ( ) ;
if ( ! String . IsNullOrEmpty ( PathToUpdate ) )
{
PathToUpdateList = PathToUpdate . Split ( ";" ) ;
}
else if ( ! String . IsNullOrEmpty ( PathToUpdateFile ) )
{
PathToUpdateList = File . ReadAllLines ( PathToUpdateFile ) ;
}
if ( PathToUpdateList . Length > 0 )
{
for ( int I = 0 ; I ! = PathToUpdateList . Length ; + + I )
{
PathToUpdateList [ I ] = PathToUpdateList [ I ] . Replace ( '\\' , '/' ) ;
}
}
2023-01-26 02:25:52 -05:00
string TargetName = $"{TargetDescriptors[0].Name}_{TargetDescriptors[0].Configuration}_{TargetDescriptors[0].Platform}" ;
2023-01-10 14:52:00 -05:00
// Calculate file paths filter to figure out which files that IWYU will run on
HashSet < string > ValidPaths = new ( ) ;
2023-01-31 14:46:47 -05:00
Dictionary < string , UEBuildModule > PathToModule = new ( ) ;
Dictionary < string , UEBuildModule > NameToModule = new ( ) ;
2023-01-10 14:52:00 -05:00
{
// Create target to be able to traverse all existing modules
2023-01-26 02:25:52 -05:00
Logger . LogInformation ( $"Creating BuildTarget for {TargetName}..." ) ;
2023-07-21 12:08:09 -04:00
UEBuildTarget Target = UEBuildTarget . Create ( TargetDescriptors [ 0 ] , BuildConfiguration , Logger ) ;
2023-01-10 14:52:00 -05:00
2023-01-26 02:25:52 -05:00
Logger . LogInformation ( $"Calculating file filter for IWYU..." ) ;
2023-01-10 14:52:00 -05:00
UEBuildModule ? UEModuleToUpdate = null ;
// Turn provided module string into UEBuildModule reference
2023-01-12 19:44:53 -05:00
if ( ! String . IsNullOrEmpty ( ModuleToUpdateName ) )
2023-01-10 14:52:00 -05:00
{
2023-01-12 19:44:53 -05:00
UEModuleToUpdate = Target . GetModuleByName ( ModuleToUpdateName ) ;
2023-01-10 14:52:00 -05:00
if ( UEModuleToUpdate = = null )
{
2023-01-12 19:44:53 -05:00
Logger . LogError ( $"Can't find module with name {ModuleToUpdateName}" ) ;
2023-01-10 14:52:00 -05:00
return - 1 ;
}
}
2023-01-26 02:25:52 -05:00
2023-01-13 02:02:01 -05:00
if ( PathToUpdateList . Length = = 0 & & UEModuleToUpdate = = null )
2023-01-10 14:52:00 -05:00
{
Logger . LogError ( $"Need to provide -ModuleToUpdate <modulename> or -PathToUpdate <partofpath> to run IWYU." ) ;
return - 1 ;
}
int ModulesToUpdateCount = 0 ;
int ModulesSkippedCount = 0 ;
// Traverse all modules to figure out which ones should be updated. This is based on -ModuleToUpdate and -PathToUpdate
2023-01-31 14:46:47 -05:00
List < UEBuildModule > ReferencedModules = new ( ) ;
HashSet < UEBuildModule > IgnoreReferencedModules = new ( ) ;
2023-05-31 13:37:21 -04:00
foreach ( UEBuildBinary Binary in Target . Binaries )
2023-01-10 14:52:00 -05:00
{
2023-05-31 13:37:21 -04:00
foreach ( UEBuildModule Module in Binary . Modules )
2023-01-10 14:52:00 -05:00
{
2023-01-31 14:46:47 -05:00
if ( IgnoreReferencedModules . Add ( Module ) )
2023-01-10 14:52:00 -05:00
{
2023-01-31 14:46:47 -05:00
ReferencedModules . Add ( Module ) ;
Module . GetAllDependencyModules ( ReferencedModules , IgnoreReferencedModules , true , false , false ) ;
2023-01-10 14:52:00 -05:00
}
2023-01-31 14:46:47 -05:00
}
}
foreach ( UEBuildModule Module in ReferencedModules )
{
NameToModule . TryAdd ( Module . Name , Module ) ;
2023-05-31 13:37:21 -04:00
foreach ( UEBuildModule Module2 in Module . GetDependencies ( true , true ) )
2023-01-31 14:46:47 -05:00
{
NameToModule . TryAdd ( Module2 . Name , Module2 ) ;
}
}
2023-01-10 14:52:00 -05:00
2023-05-31 13:37:21 -04:00
foreach ( UEBuildModule Module in NameToModule . Values )
2023-01-31 14:46:47 -05:00
{
bool ShouldUpdate = false ;
string ModuleDir = Module . ModuleDirectory . FullName . Replace ( '\\' , '/' ) ;
2023-01-10 14:52:00 -05:00
2023-01-31 14:46:47 -05:00
if ( ! PathToModule . TryAdd ( ModuleDir , Module ) )
{
continue ;
}
foreach ( DirectoryReference AdditionalDir in Module . ModuleDirectories )
{
if ( AdditionalDir ! = Module . ModuleDirectory )
2023-01-10 14:52:00 -05:00
{
2023-01-31 14:46:47 -05:00
PathToModule . TryAdd ( AdditionalDir . FullName . Replace ( '\\' , '/' ) , Module ) ;
2023-01-10 14:52:00 -05:00
}
2023-01-31 14:46:47 -05:00
}
2023-01-10 14:52:00 -05:00
2023-01-31 14:46:47 -05:00
if ( Module . Rules . Type ! = ModuleRules . ModuleType . CPlusPlus )
{
continue ;
}
if ( UEModuleToUpdate ! = null )
{
bool DependsOnModule = Module = = UEModuleToUpdate ;
if ( bUpdateDependents )
2023-01-10 14:52:00 -05:00
{
2023-01-31 14:46:47 -05:00
if ( Module . PublicDependencyModules ! = null )
2023-01-10 14:52:00 -05:00
{
2023-05-31 13:37:21 -04:00
foreach ( UEBuildModule Dependency in Module . PublicDependencyModules )
2023-01-10 14:52:00 -05:00
{
DependsOnModule = DependsOnModule | | Dependency = = UEModuleToUpdate ;
}
2023-01-31 14:46:47 -05:00
}
if ( Module . PrivateDependencyModules ! = null )
{
2023-05-31 13:37:21 -04:00
foreach ( UEBuildModule Dependency in Module . PrivateDependencyModules ! )
2023-01-10 14:52:00 -05:00
{
DependsOnModule = DependsOnModule | | Dependency = = UEModuleToUpdate ;
}
}
2023-01-31 14:46:47 -05:00
}
ShouldUpdate = DependsOnModule ;
}
if ( PathToUpdateList . Length > 0 )
{
bool Match = false ;
for ( int I = 0 ; I ! = PathToUpdateList . Length ; + + I )
{
Match = Match | | ModuleDir . Contains ( PathToUpdateList [ I ] , StringComparison . OrdinalIgnoreCase ) ;
2023-01-10 14:52:00 -05:00
}
2023-01-26 02:25:52 -05:00
2023-01-31 14:46:47 -05:00
if ( Match )
2023-01-10 14:52:00 -05:00
{
2023-01-31 14:46:47 -05:00
if ( UEModuleToUpdate = = null )
2023-01-13 02:02:01 -05:00
{
2023-01-31 14:46:47 -05:00
ShouldUpdate = true ;
2023-01-10 14:52:00 -05:00
}
}
2023-01-31 14:46:47 -05:00
else
2023-01-10 14:52:00 -05:00
{
2023-01-31 14:46:47 -05:00
ShouldUpdate = false ;
2023-01-10 14:52:00 -05:00
}
2023-01-31 14:46:47 -05:00
}
else if ( UEModuleToUpdate = = null )
{
ShouldUpdate = true ;
}
2023-01-10 14:52:00 -05:00
2023-01-31 14:46:47 -05:00
if ( ! ShouldUpdate )
{
continue ;
}
if ( Module . Rules . IWYUSupport = = IWYUSupport . None )
{
+ + ModulesSkippedCount ;
continue ;
}
+ + ModulesToUpdateCount ;
// When adding to ValidPaths, make sure the Module directory ends in a / so we match the exact folder later
ValidPaths . Add ( AdjustModulePathForMatching ( ModuleDir ) ) ;
foreach ( DirectoryReference AdditionalDir in Module . ModuleDirectories )
{
if ( AdditionalDir ! = Module . ModuleDirectory )
2023-01-10 14:52:00 -05:00
{
2023-01-31 14:46:47 -05:00
ValidPaths . Add ( AdjustModulePathForMatching ( AdditionalDir . FullName ) ) ;
2023-01-10 14:52:00 -05:00
}
}
}
2023-01-13 02:02:01 -05:00
if ( ValidPaths . Count = = 0 & & PathToUpdateList . Length > 0 )
2023-01-10 14:52:00 -05:00
{
2023-01-13 02:02:01 -05:00
foreach ( string Path in PathToUpdateList )
{
ValidPaths . Add ( Path ) ;
}
2023-01-10 14:52:00 -05:00
Logger . LogInformation ( $"Will update files matching {PathToUpdate}. Note, path is case sensitive" ) ;
}
else
{
Logger . LogInformation ( $"Will update {ModulesToUpdateCount} module(s) using IWYU. ({ModulesSkippedCount} skipped because of bEnforceIWYU=false)..." ) ;
}
}
Dictionary < string , IWYUInfo > Infos = new ( ) ;
HashSet < string > GeneratedHeaderInfos = new ( ) ;
List < IWYUInfo > GeneratedCppInfos = new ( ) ;
using ( ISourceFileWorkingSet WorkingSet = new EmptySourceFileWorkingSet ( ) )
{
2023-01-26 02:25:52 -05:00
List < string > ? TocContent = null ;
int ReadSuccess = 1 ;
2023-01-10 14:52:00 -05:00
2023-01-26 02:25:52 -05:00
if ( bReadToc )
2023-01-10 14:52:00 -05:00
{
2023-01-26 02:25:52 -05:00
string TocName = TargetName + ".txt" ;
FileReference TocReference = FileReference . Combine ( Unreal . EngineDirectory , "Intermediate" , "IWYU" , TocName ) ;
Logger . LogInformation ( $"Reading TOC from {TocReference.FullName}..." ) ;
if ( FileReference . Exists ( TocReference ) )
2023-01-12 17:29:36 -05:00
{
2023-01-26 02:25:52 -05:00
TocContent = FileReference . ReadAllLines ( TocReference ) . ToList ( ) ;
2023-01-12 17:29:36 -05:00
}
}
2023-01-26 02:25:52 -05:00
if ( TocContent = = null )
2023-01-12 17:29:36 -05:00
{
2023-01-26 02:25:52 -05:00
TargetDescriptor Descriptor = TargetDescriptors . First ( ) ;
// Create the make file that contains all the actions we will use to find .iwyu files.
Logger . LogInformation ( $"Creating MakeFile for target..." ) ;
2023-01-28 01:59:00 -05:00
TargetMakefile Makefile ;
try
{
2023-04-19 21:44:13 -04:00
Makefile = await BuildMode . CreateMakefileAsync ( BuildConfiguration , Descriptor , WorkingSet , Logger ) ;
2023-01-28 01:59:00 -05:00
}
finally
{
SourceFileMetadataCache . SaveAll ( ) ;
}
2023-01-26 02:25:52 -05:00
// Use the make file to build unless -NoCompile is set.
if ( ! bNoCompile )
{
2023-01-28 01:59:00 -05:00
try
{
2023-04-19 21:44:13 -04:00
await BuildMode . BuildAsync ( new TargetMakefile [ ] { Makefile } , new List < TargetDescriptor > ( ) { Descriptor } , BuildConfiguration , BuildOptions . None , null , Logger ) ;
2023-01-28 01:59:00 -05:00
}
finally
{
CppDependencyCache . SaveAll ( ) ;
}
2023-01-26 02:25:52 -05:00
}
HashSet < FileItem > OutputItems = new HashSet < FileItem > ( ) ;
if ( Descriptor . OnlyModuleNames . Count > 0 )
{
foreach ( string OnlyModuleName in Descriptor . OnlyModuleNames )
{
FileItem [ ] ? OutputItemsForModule ;
if ( ! Makefile . ModuleNameToOutputItems . TryGetValue ( OnlyModuleName , out OutputItemsForModule ) )
{
throw new BuildException ( "Unable to find output items for module '{0}'" , OnlyModuleName ) ;
}
OutputItems . UnionWith ( OutputItemsForModule ) ;
}
}
else
{
// Use all the output items from the target
OutputItems . UnionWith ( Makefile . OutputItems ) ;
}
TocContent = new ( ) ;
2023-05-31 13:37:21 -04:00
foreach ( FileItem OutputItem in OutputItems )
2023-01-26 02:25:52 -05:00
{
if ( OutputItem . Name . EndsWith ( ".iwyu" ) )
{
TocContent . Add ( OutputItem . AbsolutePath ) ;
}
}
2023-01-12 17:29:36 -05:00
}
2023-01-10 14:52:00 -05:00
// Time to parse all the .iwyu files generated from iwyu.
// We Do this in parallel since it involves reading a ton of .iwyu files from disk.
2023-01-26 02:25:52 -05:00
Logger . LogInformation ( $"Parsing {TocContent.Count} .iwyu files..." ) ;
Parallel . ForEach ( TocContent , IWYUFilePath = >
2023-01-10 14:52:00 -05:00
{
2023-01-12 17:29:36 -05:00
string? JsonContent = File . ReadAllText ( IWYUFilePath ) ;
2023-01-10 14:52:00 -05:00
if ( JsonContent = = null )
{
2023-01-12 17:29:36 -05:00
Logger . LogError ( $"Failed to read file {IWYUFilePath}" ) ;
2023-01-13 02:02:01 -05:00
Interlocked . Exchange ( ref ReadSuccess , 0 ) ;
2023-01-10 14:52:00 -05:00
return ;
}
try
{
IWYUFile ? IWYUFile = JsonSerializer . Deserialize < IWYUFile > ( JsonContent ) ;
2023-01-12 17:29:36 -05:00
IWYUFile ! . Name = IWYUFilePath ;
2023-01-10 14:52:00 -05:00
// Traverse the cpp/inl/h file entries inside the .iwyu file
2023-05-31 13:37:21 -04:00
foreach ( IWYUInfo Info in IWYUFile ! . Files )
2023-01-10 14:52:00 -05:00
{
Info . Source = IWYUFile ;
2023-02-17 19:39:41 -05:00
Info . IsCpp = Info . File . EndsWith ( ".cpp" ) ;
2023-01-10 14:52:00 -05:00
// We track .gen.cpp in a special list, they need special treatment later
2023-01-26 02:25:52 -05:00
if ( Info . File . Contains ( ".gen.cpp" , StringComparison . Ordinal ) )
2023-01-10 14:52:00 -05:00
{
lock ( GeneratedCppInfos )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
GeneratedCppInfos . Add ( Info ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
continue ;
}
// Ok, time to add file entry to lookup
lock ( Infos )
{
if ( ! Infos . TryAdd ( Info . File , Info ) )
{
// This is a valid scenario when Foo.cpp also registers Foo.h... and then Foo.h registers itself.
2023-05-31 13:37:21 -04:00
IWYUInfo ExistingEntry = Infos [ Info . File ] ;
2023-01-10 14:52:00 -05:00
if ( IWYUFile . Files . Count = = 1 )
{
if ( ExistingEntry . Source ! . Files . Count = = 1 )
{
Logger . LogError ( $"{Info.File} - built twice somehow?" ) ;
return ;
}
else
{
Infos [ Info . File ] = Info ;
}
}
else
{
//bool Equals = Info.Includes.SequenceEqual(ExistingEntry.Includes, new IWYUIncludeEntryPrintableComparer());
//if (!Equals)
//{
// Logger.LogWarning($"{Info.File} - mismatch found in multiple .iwyu-files");
//}
}
}
}
2023-01-26 02:25:52 -05:00
// TODO: Fix bad formatting coming from iwyu
foreach ( IWYUIncludeEntry Entry in Info . IncludesSeenInFile )
{
if ( Entry . Printable [ 0 ] = = '<' )
{
Entry . Printable = Entry . Printable . Substring ( 1 , Entry . Printable . Length - 2 ) ;
Entry . System = true ;
}
}
2023-01-31 14:46:47 -05:00
Info . Module = GetModule ( Info . File , PathToModule ) ;
2023-01-10 14:52:00 -05:00
// Some special logic for headers.
if ( Info . File . EndsWith ( ".h" ) )
{
// We need to add entries for .generated.h. They are not in the iwyu files
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include in Info . Includes )
2023-01-10 14:52:00 -05:00
{
2023-01-31 14:46:47 -05:00
if ( Include . Full . Contains ( "/UHT/" ) )
2023-01-10 14:52:00 -05:00
{
lock ( GeneratedHeaderInfos )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
GeneratedHeaderInfos . Add ( Include . Full ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
break ;
}
2023-01-31 14:46:47 -05:00
else if ( Include . Full . Contains ( "/VNI/" ) )
{
lock ( GeneratedHeaderInfos )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
GeneratedHeaderInfos . Add ( Include . Full ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
}
2023-01-10 14:52:00 -05:00
}
}
}
}
catch ( Exception e )
{
2023-01-12 17:29:36 -05:00
Logger . LogError ( $"Failed to parse json {IWYUFilePath}: {e.Message} - File will be deleted" ) ;
File . Delete ( IWYUFilePath ) ;
2023-01-13 02:02:01 -05:00
Interlocked . Exchange ( ref ReadSuccess , 0 ) ;
2023-01-10 14:52:00 -05:00
return ;
}
} ) ;
2023-01-26 02:25:52 -05:00
// Something went wrong parsing iwyu files.
if ( ReadSuccess = = 0 )
2023-05-30 18:59:32 -04:00
{
2023-01-26 02:25:52 -05:00
return - 1 ;
2023-05-30 18:59:32 -04:00
}
2023-01-26 02:25:52 -05:00
if ( bWriteToc )
{
Logger . LogInformation ( $"Writing TOC that references all .iwyu files " ) ;
DirectoryReference TocDir = DirectoryReference . Combine ( Unreal . EngineDirectory , "Intermediate" , "IWYU" ) ;
DirectoryReference . CreateDirectory ( TocDir ) ;
string TocName = TargetName + ".txt" ;
FileReference TocReference = FileReference . Combine ( TocDir , TocName ) ;
FileReference . WriteAllLines ( TocReference , TocContent ) ;
}
}
2023-01-10 14:52:00 -05:00
2023-02-17 19:39:41 -05:00
KeyValuePair < string , IWYUIncludeEntry ? > GetInfo ( string Include , string Path )
{
string FullPath = DirectoryReference . Combine ( Unreal . EngineDirectory , Path ) . FullName . Replace ( '\\' , '/' ) ;
IWYUInfo ? Out = null ;
if ( ! Infos . TryGetValue ( FullPath , out Out ) )
{
return new ( Include , null ) ;
}
IWYUIncludeEntry Entry = new ( ) ;
Entry . Printable = Include ;
Entry . Full = FullPath ;
Entry . Resolved = Out ;
return new ( Include , Entry ) ;
}
2023-05-31 13:37:21 -04:00
Dictionary < string , IWYUIncludeEntry ? > SpecialIncludes = new Dictionary < string , IWYUIncludeEntry ? > (
2023-02-17 19:39:41 -05:00
new [ ]
{
GetInfo ( "UObject/ObjectMacros.h" , "Source/Runtime/CoreUObject/Public/UObject/ObjectMacros.h" ) ,
GetInfo ( "UObject/ScriptMacros.h" , "Source/Runtime/CoreUObject/Public/UObject/ScriptMacros.h" ) ,
GetInfo ( "VerseInteropUtils.h" , "Restricted/NotForLicensees/Plugins/Solaris/Source/VerseNative/Public/VerseInteropTypes.h" ) ,
GetInfo ( "Containers/ContainersFwd.h" , "Source/Runtime/Core/Public/Containers/ContainersFwd.h" ) ,
GetInfo ( "Misc/OptionalFwd.h" , "Source/Runtime/Core/Public/Misc/OptionalFwd.h" ) ,
}
) ;
2023-05-31 13:37:21 -04:00
Dictionary < string , IWYUIncludeEntry ? > ForwardingHeaders = new Dictionary < string , IWYUIncludeEntry ? > ( )
2023-02-17 19:39:41 -05:00
{
{ "TMap" , SpecialIncludes [ "Containers/ContainersFwd.h" ] } ,
{ "TSet" , SpecialIncludes [ "Containers/ContainersFwd.h" ] } ,
{ "TArray" , SpecialIncludes [ "Containers/ContainersFwd.h" ] } ,
{ "TArrayView" , SpecialIncludes [ "Containers/ContainersFwd.h" ] } ,
{ "TOptional" , SpecialIncludes [ "Misc/OptionalFwd.h" ] } ,
} ;
2023-01-10 14:52:00 -05:00
// Add all .generated.h files as entries in the lookup and explicitly add the includes they have which will never be removed
Logger . LogInformation ( $"Generating infos for .generated.h files..." ) ;
if ( GeneratedHeaderInfos . Count > 0 )
{
2023-06-27 12:07:55 -04:00
IWYUIncludeEntry ? ObjectMacrosInclude = SpecialIncludes [ "UObject/ObjectMacros.h" ] ;
IWYUIncludeEntry ? ScriptMacrosInclude = SpecialIncludes [ "UObject/ScriptMacros.h" ] ;
IWYUIncludeEntry ? VerseInteropUtilsInclude = SpecialIncludes [ "VerseInteropUtils.h" ] ;
2023-01-10 14:52:00 -05:00
2023-05-31 13:37:21 -04:00
foreach ( string Gen in GeneratedHeaderInfos )
2023-01-10 14:52:00 -05:00
{
IWYUInfo GenInfo = new ( ) ;
GenInfo . File = Gen ;
2023-01-31 14:46:47 -05:00
if ( Gen . EndsWith ( ".generated.h" ) )
{
2023-06-27 12:07:55 -04:00
if ( ObjectMacrosInclude ! = null )
{
GenInfo . IncludesSeenInFile . Add ( ObjectMacrosInclude ) ;
GenInfo . Includes . Add ( ObjectMacrosInclude ) ;
}
if ( ScriptMacrosInclude ! = null )
{
GenInfo . IncludesSeenInFile . Add ( ScriptMacrosInclude ) ;
GenInfo . Includes . Add ( ScriptMacrosInclude ) ;
}
2023-01-31 14:46:47 -05:00
}
else
{
2023-06-27 12:07:55 -04:00
if ( VerseInteropUtilsInclude ! = null )
{
GenInfo . IncludesSeenInFile . Add ( VerseInteropUtilsInclude ) ;
GenInfo . Includes . Add ( VerseInteropUtilsInclude ) ;
}
2023-01-31 14:46:47 -05:00
}
2023-01-10 14:52:00 -05:00
Infos . Add ( Gen , GenInfo ) ;
}
}
Logger . LogInformation ( $"Found {Infos.Count} IWYU entries..." ) ;
Logger . LogInformation ( $"Resolving Includes..." ) ;
2023-01-26 02:25:52 -05:00
2023-05-31 13:37:21 -04:00
LinuxPlatformSDK PlatformSDK = new LinuxPlatformSDK ( Logger ) ;
2023-01-26 02:25:52 -05:00
DirectoryReference ? BaseLinuxPath = PlatformSDK . GetBaseLinuxPathForArchitecture ( LinuxPlatform . DefaultHostArchitecture ) ;
string SystemPath = BaseLinuxPath ! . FullName . Replace ( '\\' , '/' ) ;
HashSet < IWYUInfo > IncludedInsideDecl = new ( ) ;
2023-01-31 14:46:47 -05:00
Dictionary < string , IWYUInfo > SkippedHeaders = new ( ) ;
2023-01-26 02:25:52 -05:00
2023-01-10 14:52:00 -05:00
Parallel . ForEach ( Infos . Values , Info = >
{
2023-01-13 02:02:01 -05:00
// If KeepAsIs we transfer all "seen includes" into the include list.
2023-01-10 14:52:00 -05:00
if ( Info . Module ! = null & & Info . Module . Rules . IWYUSupport = = IWYUSupport . KeepAsIs )
{
foreach ( IWYUIncludeEntry Entry in Info . IncludesSeenInFile )
{
// Special hack, we don't want CoreMinimal to be the reason we remove includes transitively
2023-01-26 02:25:52 -05:00
if ( ! Entry . Printable . Contains ( "CoreMinimal.h" , StringComparison . Ordinal ) )
2023-01-10 14:52:00 -05:00
{
Info . Includes . Add ( Entry ) ;
}
}
}
2023-02-17 19:39:41 -05:00
if ( ! Info . IsCpp )
{
// See if we need to replace forward declarations with includes
Info . ForwardDeclarations . RemoveAll ( Entry = >
{
int LastSpace = Entry . Printable . LastIndexOf ( ' ' ) ;
string TypeName = Entry . Printable . Substring ( LastSpace + 1 , Entry . Printable . Length - LastSpace - 2 ) ;
IWYUIncludeEntry ? IncludeEntry ;
ForwardingHeaders . TryGetValue ( TypeName , out IncludeEntry ) ;
if ( IncludeEntry = = null )
{
return false ;
}
Info . Includes . Add ( IncludeEntry ) ;
return true ;
} ) ;
}
2023-01-26 02:25:52 -05:00
// We don't want to mess around with third party includes.. since we can't see the hierarchy we just assumes that they are optimally included
Info . Includes . RemoveAll ( Entry = > Entry . Full . Contains ( "/ThirdParty/" , StringComparison . Ordinal ) | | Entry . Full . StartsWith ( SystemPath ) ) ;
foreach ( IWYUIncludeEntry Entry in Info . IncludesSeenInFile )
{
if ( Entry . Full . Contains ( "/ThirdParty/" , StringComparison . Ordinal ) | | Entry . Full . StartsWith ( SystemPath , StringComparison . Ordinal ) )
{
Info . Includes . Add ( Entry ) ;
}
if ( Entry . InsideDecl & & Infos . TryGetValue ( Entry . Full , out Entry . Resolved ) )
{
lock ( IncludedInsideDecl )
{
IncludedInsideDecl . Add ( Entry . Resolved ) ;
Info . Includes . Add ( Entry ) ;
}
}
}
/*
if (Info.IncludesSeenInFile.Count == 0)
{
if (Info.File.EndsWith(".inl"))
{
Info.Includes.Clear();
Info.ForwardDeclarations.Clear();
}
}
*/
2023-01-13 02:02:01 -05:00
// Definitions.h is automatically added in the reponse file and iwyu sees it as not included
// If there are no includes but we depend on Definitions.h (which is missing) we add Platform.h because it is most likely a <module>_API entry in the file
2023-01-26 02:25:52 -05:00
/*
2023-01-13 02:02:01 -05:00
if (Info.Includes.Count == 0)
{
bool IsUsingDefinitionsH = false;
foreach (var Missing in Info.MissingIncludes)
{
IsUsingDefinitionsH = IsUsingDefinitionsH || Missing.Full.Contains("Definitions.h");
}
if (IsUsingDefinitionsH)
{
Info.Includes.Add(new IWYUIncludeEntry() { Full = PlatformInfo!.File, Printable = "HAL/Platform.h", Resolved = PlatformInfo });
}
}
2023-01-26 02:25:52 -05:00
*/
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include in Info . Includes )
2023-01-10 14:52:00 -05:00
{
if ( Include . Resolved = = null )
2023-01-26 02:25:52 -05:00
{
2023-01-31 14:46:47 -05:00
if ( ! Infos . TryGetValue ( Include . Full , out Include . Resolved ) )
{
if ( ! Include . Full . Contains ( ".gen.cpp" ) & & ! Include . Full . Contains ( "/ThirdParty/" ) & & ! Include . Full . Contains ( "/AutoSDK/" ) & & ! Include . Full . Contains ( "/VNI/" ) )
{
lock ( SkippedHeaders )
{
if ( ! SkippedHeaders . TryGetValue ( Include . Full , out Include . Resolved ) )
{
IWYUInfo NewInfo = new ( ) ;
Include . Resolved = NewInfo ;
NewInfo . File = Include . Full ;
SkippedHeaders . Add ( Include . Full , NewInfo ) ;
}
}
}
}
2023-01-26 02:25:52 -05:00
}
2023-01-10 14:52:00 -05:00
}
} ) ;
2023-01-31 14:46:47 -05:00
int InfosCount = Infos . Count ;
Logger . LogInformation ( $"Parsing included headers not supporting being compiled..." ) ;
ParseSkippedHeaders ( SkippedHeaders , Infos , PathToModule , NameToModule ) ;
Logger . LogInformation ( $"Added {Infos.Count - InfosCount} more read-only IWYU entries..." ) ;
2023-01-26 02:25:52 -05:00
foreach ( IWYUInfo Info in IncludedInsideDecl )
{
if ( Info . IncludesSeenInFile . Count ! = 0 & & ! Info . File . EndsWith ( "ScriptSerialization.h" ) ) // Remove include in ScriptSerialization.h and remove this check
{
Logger . LogWarning ( $"{Info.File} - Included inside declaration in other file but has includes itself." ) ;
}
Info . Includes . Clear ( ) ;
Info . ForwardDeclarations . Clear ( ) ;
}
2023-01-10 14:52:00 -05:00
Logger . LogInformation ( $"Generating transitive include lists and forward declaration lists..." ) ;
Parallel . ForEach ( Infos . Values , Info = >
{
Stack < string > Stack = new ( ) ;
2023-01-26 02:25:52 -05:00
Info . TransitiveIncludes . EnsureCapacity ( 300 ) ;
Info . TransitiveForwardDeclarations . EnsureCapacity ( 300 ) ;
CalculateTransitive ( Info , Info , Stack , Info . TransitiveIncludes , Info . TransitiveForwardDeclarations , false ) ;
2023-01-10 14:52:00 -05:00
} ) ;
// If we have built .gen.cpp it means that it is not inlined in another cpp file
// And we need to promote the includes needed to the header since we can never modify the .gen.cpp file
Logger . LogInformation ( $"Transferring needed includes from .gen.cpp to owning file..." ) ;
Parallel . ForEach ( GeneratedCppInfos , GeneratedCpp = >
{
// First, check which files .gen.cpp will see
HashSet < string > SeenTransitiveIncludes = new ( ) ;
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry SeenInclude in GeneratedCpp . IncludesSeenInFile )
2023-01-10 14:52:00 -05:00
{
2023-01-26 02:25:52 -05:00
SeenTransitiveIncludes . Add ( SeenInclude . Full ) ;
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include in GeneratedCpp . Includes )
2023-01-10 14:52:00 -05:00
{
IWYUInfo ? IncludeInfo ;
if ( SeenInclude . Printable = = Include . Printable & & Infos . TryGetValue ( Include . Full , out IncludeInfo ) )
{
foreach ( string I in IncludeInfo . TransitiveIncludes . Keys )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
SeenTransitiveIncludes . Add ( I ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
break ;
}
}
}
IWYUInfo ? IncluderInfo = null ;
// If there is only one file in .iwyu it means that .gen.cpp was compiled separately
if ( GeneratedCpp . Source ! . Files . Count = = 1 )
{
int NameStart = GeneratedCpp . File . LastIndexOf ( '/' ) ;
string HeaderName = GeneratedCpp . File . Substring ( NameStart + 1 , GeneratedCpp . File . Length - NameStart - ".gen.cpp" . Length ) + "h" ;
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include in GeneratedCpp . Includes )
2023-01-10 14:52:00 -05:00
{
NameStart = Include . Full . LastIndexOf ( '/' ) ;
string IncludeName = Include . Full . Substring ( NameStart + 1 ) ;
if ( HeaderName = = IncludeName )
{
Infos . TryGetValue ( Include . Full , out IncluderInfo ) ;
break ;
}
}
}
else // this .gen.cpp is inlined in a cpp..
{
IncluderInfo = GeneratedCpp . Source . Files . FirstOrDefault ( I = > I . File . Contains ( ".cpp" ) & & ! I . File . Contains ( ".gen" ) ) ;
}
if ( IncluderInfo = = null )
{
return ;
}
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include in GeneratedCpp . Includes )
2023-01-10 14:52:00 -05:00
{
2023-01-26 02:25:52 -05:00
if ( SeenTransitiveIncludes . Contains ( Include . Full ) )
2023-01-10 14:52:00 -05:00
{
continue ;
}
// TODO: Remove UObject check once we've added "IWYU pragma: keep" around the includes in ScriptMacros and ObjectMacros
if ( Include . Full . Contains ( ".generated.h" ) | | Include . Printable . StartsWith ( "UObject/" ) )
{
continue ;
}
2023-01-26 02:25:52 -05:00
if ( ! IncluderInfo ! . TransitiveIncludes . ContainsKey ( Include . Full ) )
2023-01-10 14:52:00 -05:00
{
2023-01-26 02:25:52 -05:00
if ( ! Include . Full . Contains ( "/ThirdParty/" ) & & ! Include . Full . StartsWith ( SystemPath ) )
{
IncluderInfo . Includes . Add ( Include ) ;
}
2023-01-10 14:52:00 -05:00
}
}
} ) ;
2023-01-26 02:25:52 -05:00
if ( bCompare )
{
2023-01-31 14:46:47 -05:00
return CompareFiles ( Infos , ValidPaths , PathToModule , NameToModule , Logger ) ;
2023-01-26 02:25:52 -05:00
}
else
{
return UpdateFiles ( Infos , ValidPaths , Logger ) ;
}
}
2023-01-31 14:46:47 -05:00
static UEBuildModule ? GetModule ( string File , Dictionary < string , UEBuildModule > PathToModule )
{
string FilePath = File ;
while ( true )
{
int LastIndexOfSlash = FilePath . LastIndexOf ( '/' ) ;
if ( LastIndexOfSlash = = - 1 )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
break ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
FilePath = FilePath . Substring ( 0 , LastIndexOfSlash ) ;
UEBuildModule ? Module ;
if ( PathToModule . TryGetValue ( FilePath , out Module ) )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
return Module ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
}
return null ;
}
static string? GetFullName ( string Include , string From , UEBuildModule ? Module , Dictionary < string , UEBuildModule > NameToModule , HashSet < UEBuildModule > ? Visited = null )
{
if ( Module = = null )
{
return null ;
}
2023-05-31 13:37:21 -04:00
foreach ( DirectoryReference ? Dir in Module . PublicIncludePaths . Union ( Module . PrivateIncludePaths ) . Union ( Module . PublicSystemIncludePaths ) )
2023-01-31 14:46:47 -05:00
{
FileReference FileReference = FileReference . Combine ( Dir , Include ) ;
FileItem FileItem = FileItem . GetItemByFileReference ( FileReference ) ;
if ( FileItem . Exists )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
return FileItem . Location . FullName . Replace ( '\\' , '/' ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
}
bool Nested = Visited ! = null ;
if ( ! Nested )
{
FileReference FileReference = FileReference . Combine ( FileReference . FromString ( From ) . Directory , Include ) ;
FileItem FileItem = FileItem . GetItemByFileReference ( FileReference ) ;
if ( FileItem . Exists )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
return FileItem . Location . FullName . Replace ( '\\' , '/' ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
}
2023-05-31 13:37:21 -04:00
foreach ( string? PublicModule in Module . Rules . PublicDependencyModuleNames . Union ( Module . Rules . PublicIncludePathModuleNames ) . Union ( Module . Rules . PrivateIncludePathModuleNames ) . Union ( Module . Rules . PrivateDependencyModuleNames ) )
2023-01-31 14:46:47 -05:00
{
UEBuildModule ? DependencyModule ;
if ( NameToModule . TryGetValue ( PublicModule , out DependencyModule ) )
{
if ( Visited = = null )
{
Visited = new ( ) ;
Visited . Add ( Module ) ;
Visited . Add ( DependencyModule ) ;
}
else if ( ! Visited . Add ( DependencyModule ) )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
continue ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
string? Str = GetFullName ( Include , From , DependencyModule , NameToModule , Visited ) ;
if ( Str ! = null )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
return Str ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
}
}
if ( ! Nested )
{
// This should only show system includes..
//Console.WriteLine($"Can't resolve {Include} from module {Module.Name}");
}
return null ;
}
static void ParseSkippedHeaders ( Dictionary < string , IWYUInfo > SkippedHeaders , Dictionary < string , IWYUInfo > Infos , Dictionary < string , UEBuildModule > PathToModule , Dictionary < string , UEBuildModule > NameToModule )
{
2023-05-31 13:37:21 -04:00
foreach ( KeyValuePair < string , IWYUInfo > Kvp in SkippedHeaders )
2023-01-31 14:46:47 -05:00
{
Infos . Add ( Kvp . Key , Kvp . Value ) ;
}
while ( true )
{
Dictionary < string , IWYUInfo > NewSkippedHeaders = new ( ) ;
Parallel . ForEach ( SkippedHeaders . Values , Info = >
{
Info . Module = GetModule ( Info . File , PathToModule ) ;
bool HasIncludeGuard = false ;
int IfCount = 0 ;
string [ ] Lines = File . ReadAllLines ( Info . File ) ;
foreach ( string Line in Lines )
{
ReadOnlySpan < char > LineSpan = Line . AsSpan ( ) . TrimStart ( ) ;
2023-02-17 19:39:41 -05:00
if ( LineSpan . Length = = 0 | | LineSpan [ 0 ] ! = '#' )
{
continue ;
}
LineSpan = LineSpan . Slice ( 1 ) . TrimStart ( ) ;
if ( LineSpan . StartsWith ( "if" ) )
2023-01-31 14:46:47 -05:00
{
// Include guards are special
if ( ! HasIncludeGuard )
{
2023-02-17 19:39:41 -05:00
if ( LineSpan . StartsWith ( "ifndef " ) & & LineSpan . EndsWith ( "_H" ) )
2023-01-31 14:46:47 -05:00
{
HasIncludeGuard = true ;
continue ;
}
}
+ + IfCount ;
}
2023-02-17 19:39:41 -05:00
else if ( LineSpan . StartsWith ( "endif" ) )
2023-01-31 14:46:47 -05:00
{
- - IfCount ;
}
2023-02-17 19:39:41 -05:00
else if ( LineSpan . StartsWith ( "include" ) )
2023-01-31 14:46:47 -05:00
{
2023-05-31 13:37:21 -04:00
ReadOnlySpan < char > IncludeSpan = LineSpan . Slice ( "include" . Length ) . TrimStart ( ) ;
2023-01-31 14:46:47 -05:00
char LeadingIncludeChar = IncludeSpan [ 0 ] ;
if ( LeadingIncludeChar = = '"' | | LeadingIncludeChar = = '<' )
{
2023-05-31 13:37:21 -04:00
ReadOnlySpan < char > Start = IncludeSpan . Slice ( 1 ) ;
int EndIndex = Start . IndexOf ( LeadingIncludeChar = = '"' ? '"' : '>' ) ;
ReadOnlySpan < char > FileSpan = Start . Slice ( 0 , EndIndex ) ;
string Include = FileSpan . ToString ( ) ;
2023-01-31 14:46:47 -05:00
string? File = GetFullName ( Include , Info . File , Info . Module , NameToModule ) ;
if ( File ! = null )
{
IWYUInfo ? IncludeInfo ;
lock ( Infos )
{
if ( ! Infos . TryGetValue ( File , out IncludeInfo ) )
{
if ( ! SkippedHeaders . TryGetValue ( File , out IncludeInfo ) )
{
IncludeInfo = new ( ) ;
IncludeInfo . File = File ;
NewSkippedHeaders . Add ( File , IncludeInfo ) ;
Infos . Add ( File , IncludeInfo ) ;
}
}
}
if ( IncludeInfo ! = null )
{
IWYUIncludeEntry Entry = new ( ) ;
Entry . Full = File ;
Entry . Printable = Include ;
Entry . System = LeadingIncludeChar = = '<' ;
Entry . Resolved = IncludeInfo ;
Info . IncludesSeenInFile . Add ( Entry ) ;
Info . Includes . Add ( Entry ) ;
}
}
}
}
}
} ) ;
if ( NewSkippedHeaders . Count = = 0 )
{
return ;
}
SkippedHeaders = NewSkippedHeaders ;
}
}
static bool IsValidForUpdate ( IWYUInfo Info , HashSet < string > ValidPaths , bool ObeyModuleRules )
2023-01-26 02:25:52 -05:00
{
if ( Info . Source = = null ) // .generated.h is also in this list, ignore them
{
return false ;
}
if ( ObeyModuleRules & & Info . Module ? . Rules . IWYUSupport ! = IWYUSupport . Full )
{
return false ;
}
// There are some codegen files with this name
if ( Info . File . Contains ( ".gen.h" ) )
{
return false ;
}
// Filter out files
foreach ( string ValidPath in ValidPaths )
{
if ( Info . File . Contains ( ValidPath ) )
{
return true ;
}
}
return false ;
}
int UpdateFiles ( Dictionary < string , IWYUInfo > Infos , HashSet < string > ValidPaths , ILogger Logger )
{
2023-01-10 14:52:00 -05:00
object? ShouldLog = bWrite ? null : new ( ) ;
List < ValueTuple < IWYUInfo , List < string > > > UpdatedFiles = new ( ) ; // <FilePath>, <NewLines>
HashSet < IWYUFile > SkippedFiles = new ( ) ;
int SkippedCount = 0 ;
int OutOfDateCount = 0 ;
Logger . LogInformation ( $"Updating code files (in memory)..." ) ;
uint FilesParseCount = 0 ;
2023-01-13 02:02:01 -05:00
int ProcessSuccess = 1 ;
2023-01-10 14:52:00 -05:00
Parallel . ForEach ( Infos . Values , Info = >
{
2023-01-26 02:25:52 -05:00
if ( ! IsValidForUpdate ( Info , ValidPaths , true ) )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
return ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
2023-02-17 19:39:41 -05:00
bool IsCpp = Info . IsCpp ;
2023-01-10 14:52:00 -05:00
bool IsPrivate = IsCpp | | Info . File . Contains ( "/Private/" ) ;
// If we only want to update private files we early out for non-private headers
if ( ! IsPrivate & & ( bUpdateOnlyPrivate | | ( Info . Module ? . Rules . IWYUSupport = = IWYUSupport . KeepPublicAsIsForNow ) ) )
{
return ;
}
Interlocked . Increment ( ref FilesParseCount ) ;
string MatchingH = "" ;
if ( IsCpp )
{
int LastSlash = Info . File . LastIndexOf ( '/' ) + 1 ;
MatchingH = Info . File . Substring ( LastSlash , Info . File . Length - LastSlash - 4 ) + ".h" ;
}
Dictionary < string , string > TransitivelyIncluded = new ( ) ;
SortedSet < string > CleanedupIncludes = new ( ) ;
SortedSet < string > ForwardDeclarationsToAdd = new ( ) ;
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include in Info . Includes )
2023-01-10 14:52:00 -05:00
{
// We never remove header with name matching cpp
string NameWithoutPath = Include . Printable ;
int LastSlash = NameWithoutPath . LastIndexOf ( "/" ) ;
if ( LastSlash ! = - 1 )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
NameWithoutPath = NameWithoutPath . Substring ( LastSlash + 1 ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
string QuotedPrintable = Include . System ? $"<{Include.Printable}>" : $"\" { Include . Printable } \ "" ;
bool Keep = true ;
if ( Info . File = = Include . Full ) // Sometimes IWYU outputs include to the same file if .gen.cpp is inlined and includes file with slightly different path. just skip those
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
Keep = false ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
else if ( IsCpp & & MatchingH = = NameWithoutPath )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
Keep = true ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
else if ( ! bNoTransitiveIncludes )
{
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include2 in Info . Includes )
2023-01-10 14:52:00 -05:00
{
if ( Include2 . Resolved ! = null & & Include ! = Include2 )
{
2023-01-26 02:25:52 -05:00
string Key = Include . Full ;
2023-01-10 14:52:00 -05:00
string? TransitivePath ;
if ( Include2 . Resolved . TransitiveIncludes ! . TryGetValue ( Key , out TransitivePath ) )
{
if ( ShouldLog ! = null )
2023-05-30 18:59:32 -04:00
{
TransitivelyIncluded . TryAdd ( QuotedPrintable , String . Join ( " -> " , Include2 . Printable , TransitivePath ) ) ;
}
2023-01-10 14:52:00 -05:00
Keep = false ;
break ;
}
}
}
}
if ( Keep )
{
CleanedupIncludes . Add ( QuotedPrintable ) ;
}
}
2023-01-27 14:11:31 -05:00
// We don't remove seen includes that are redundant because they are included through other includes that are needed.
if ( ! bRemoveRedundantIncludes )
2023-01-26 02:25:52 -05:00
{
List < string > ToReadd = new ( ) ;
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Seen in Info . IncludesSeenInFile )
2023-01-26 02:25:52 -05:00
{
string QuotedPrintable = Seen . System ? $"<{Seen.Printable}>" : $"\" { Seen . Printable } \ "" ;
if ( ! CleanedupIncludes . Contains ( QuotedPrintable ) & & Info . TransitiveIncludes . ContainsKey ( Seen . Full ) )
{
CleanedupIncludes . Add ( QuotedPrintable ) ;
}
}
}
// Ignore forward declarations for cpp files. They are very rarely needed and we let the user add them manually instead
if ( ! IsCpp )
2023-01-10 14:52:00 -05:00
{
2023-05-31 13:37:21 -04:00
foreach ( IWYUForwardEntry ForwardDeclaration in Info . ForwardDeclarations )
2023-01-10 14:52:00 -05:00
{
bool Add = ForwardDeclaration . Present = = false ;
if ( ! bNoTransitiveIncludes )
{
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include2 in Info . Includes )
2023-01-10 14:52:00 -05:00
{
if ( Include2 . Resolved ! = null & & Include2 . Resolved . TransitiveForwardDeclarations ! . ContainsKey ( ForwardDeclaration . Printable ) )
{
Add = false ;
break ;
}
}
}
if ( Add )
{
ForwardDeclarationsToAdd . Add ( ForwardDeclaration . Printable ) ;
}
}
}
// Read all lines of the header/source file
string [ ] ExistingLines = File . ReadAllLines ( Info . File ) ;
2023-01-26 02:25:52 -05:00
SortedDictionary < string , string > LinesToRemove = new ( ) ;
2023-01-10 14:52:00 -05:00
SortedSet < string > IncludesToAdd = new ( CleanedupIncludes ) ;
bool HasIncludes = false ;
string? FirstForwardDeclareLine = null ;
HashSet < string > SeenIncludes = new ( ) ;
foreach ( IWYUIncludeEntry SeenInclude in Info . IncludesSeenInFile )
{
2023-01-26 02:25:52 -05:00
if ( SeenInclude . System )
2023-05-30 18:59:32 -04:00
{
2023-01-26 02:25:52 -05:00
SeenIncludes . Add ( $"<{SeenInclude.Printable}>" ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
else
2023-05-30 18:59:32 -04:00
{
2023-01-26 02:25:52 -05:00
SeenIncludes . Add ( $"\" { SeenInclude . Printable } \ "" ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
}
bool ForceKeepScope = false ;
2023-01-13 02:02:01 -05:00
bool ErrorOnMoreIncludes = false ;
2023-01-10 14:52:00 -05:00
int LineIndex = - 1 ;
2023-01-26 02:25:52 -05:00
// This makes sure that we have at least HAL/Platform.h included if the file contains XXX_API
bool Contains_API = false ;
bool LookFor_API = false ;
if ( Info . Includes . Count = = 0 )
{
2023-05-31 13:37:21 -04:00
foreach ( IWYUMissingInclude Missing in Info . MissingIncludes )
2023-01-26 02:25:52 -05:00
{
LookFor_API = LookFor_API | | Missing . Full . Contains ( "Definitions.h" ) ;
}
}
// Traverse all lines in file and figure out which includes that should be added or removed
2023-05-31 13:37:21 -04:00
foreach ( string Line in ExistingLines )
2023-01-10 14:52:00 -05:00
{
+ + LineIndex ;
2023-02-17 19:39:41 -05:00
ReadOnlySpan < char > LineSpan = Line . AsSpan ( ) . Trim ( ) ;
bool StartsWithHash = LineSpan . Length > 0 & & LineSpan [ 0 ] = = '#' ;
if ( StartsWithHash )
2023-05-30 18:59:32 -04:00
{
2023-02-17 19:39:41 -05:00
LineSpan = LineSpan . Slice ( 1 ) . TrimStart ( ) ;
2023-05-30 18:59:32 -04:00
}
2023-02-17 19:39:41 -05:00
if ( ! StartsWithHash | | ! LineSpan . StartsWith ( "include" ) )
2023-01-10 14:52:00 -05:00
{
// Might be forward declaration..
if ( ForwardDeclarationsToAdd . Remove ( Line ) ) // Skip adding the ones that already exists
{
if ( FirstForwardDeclareLine = = null )
{
2023-02-17 19:39:41 -05:00
FirstForwardDeclareLine = Line ;
2023-01-10 14:52:00 -05:00
}
}
if ( Line . Contains ( "IWYU pragma: " ) )
{
if ( Line . Contains ( ": begin_keep" ) )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
ForceKeepScope = true ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
else if ( Line . Contains ( ": end_keep" ) )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
ForceKeepScope = false ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
}
// File is autogenerated by some tool, don't mess with it
if ( Line . Contains ( "AUTO GENERATED CONTENT, DO NOT MODIFY" ) )
{
return ;
}
2023-01-26 02:25:52 -05:00
if ( LookFor_API & & ! Contains_API & & Line . Contains ( "_API" , StringComparison . Ordinal ) )
2023-05-30 18:59:32 -04:00
{
2023-01-26 02:25:52 -05:00
Contains_API = true ;
2023-05-30 18:59:32 -04:00
}
2023-01-26 02:25:52 -05:00
2023-01-10 14:52:00 -05:00
continue ;
}
2023-01-13 02:02:01 -05:00
if ( ErrorOnMoreIncludes )
{
Interlocked . Exchange ( ref ProcessSuccess , 0 ) ;
Logger . LogError ( $"{Info.File} - Found special include using macro and did not expect more includes in this file" ) ;
return ;
}
2023-01-10 14:52:00 -05:00
HasIncludes = true ;
bool ForceKeep = false ;
2023-02-17 19:39:41 -05:00
ReadOnlySpan < char > IncludeSpan = LineSpan . Slice ( "include" . Length ) . TrimStart ( ) ;
2023-01-10 14:52:00 -05:00
char LeadingIncludeChar = IncludeSpan [ 0 ] ;
if ( LeadingIncludeChar ! = '"' & & LeadingIncludeChar ! = '<' )
{
if ( IncludeSpan . IndexOf ( "UE_INLINE_GENERATED_CPP_BY_NAME" ) ! = - 1 )
{
int Open = IncludeSpan . IndexOf ( '(' ) + 1 ;
int Close = IncludeSpan . IndexOf ( ')' ) ;
string ActualInclude = $"\" { IncludeSpan . Slice ( Open , Close - Open ) . ToString ( ) } . gen . cpp \ "" ;
IncludesToAdd . Remove ( ActualInclude ) ;
}
else if ( IncludeSpan . IndexOf ( "COMPILED_PLATFORM_HEADER" ) ! = - 1 )
{
int Open = IncludeSpan . IndexOf ( '(' ) + 1 ;
int Close = IncludeSpan . IndexOf ( ')' ) ;
string ActualInclude = $"\" Linux / Linux { IncludeSpan . Slice ( Open , Close - Open ) . ToString ( ) } \ "" ;
IncludesToAdd . Remove ( ActualInclude ) ;
}
else
{
// TODO: These are includes made through defines. IWYU should probably report these in their original shape
// .. so a #include MY_SPECIAL_INCLUDE is actually reported as MY_SPECIAL_INCLUDE from IWYU instead of what the define expands to
2023-01-13 02:02:01 -05:00
// For now, let's assume that if there is one line in the file that is an include
if ( IncludesToAdd . Count = = 1 )
{
2023-02-17 19:39:41 -05:00
//IncludesToAdd.Clear();
//ErrorOnMoreIncludes = true;
2023-01-13 02:02:01 -05:00
}
2023-01-10 14:52:00 -05:00
}
continue ;
}
else
{
int Index = IncludeSpan . Slice ( 1 ) . IndexOf ( LeadingIncludeChar = = '"' ? '"' : '>' ) ;
if ( Index ! = - 1 )
{
IncludeSpan = IncludeSpan . Slice ( 0 , Index + 2 ) ;
}
2023-01-26 02:25:52 -05:00
if ( Line . Contains ( "IWYU pragma: " , StringComparison . Ordinal ) )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
ForceKeep = true ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
}
string Include = IncludeSpan . ToString ( ) ;
2023-01-26 02:25:52 -05:00
// If Include is not seen it means that it is probably inside a #if/#endif with condition false. These includes we can't touch
2023-01-10 14:52:00 -05:00
if ( ! SeenIncludes . Contains ( Include ) )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
continue ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
if ( ! ForceKeep & & ! ForceKeepScope & & ! CleanedupIncludes . Contains ( Include ) )
{
2023-02-17 19:39:41 -05:00
LinesToRemove . TryAdd ( Line , Include ) ;
2023-01-10 14:52:00 -05:00
}
else
{
IncludesToAdd . Remove ( Include ) ;
}
}
2023-01-26 02:25:52 -05:00
if ( Contains_API & & LookFor_API )
{
//IncludesToAdd.Add(new IWYUIncludeEntry() { Full = PlatformInfo!.File, Printable = "HAL/Platform.h", Resolved = PlatformInfo });
if ( ! LinesToRemove . Remove ( "#include \"HAL/Platform.h\"" ) )
2023-05-30 18:59:32 -04:00
{
2023-01-26 02:25:52 -05:00
IncludesToAdd . Add ( "\"HAL/Platform.h\"" ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-26 02:25:52 -05:00
}
2023-01-10 14:52:00 -05:00
// Nothing has changed! early out of this file
if ( IncludesToAdd . Count = = 0 & & LinesToRemove . Count = = 0 & & ForwardDeclarationsToAdd . Count = = 0 )
{
return ;
}
// If code file last write time is newer than IWYU file this means that iwyu is not up to date and needs to compile before we can apply anything
if ( ! bIgnoreUpToDateCheck )
{
FileInfo IwyuFileInfo = new FileInfo ( Info . Source ! . Name ! ) ;
FileInfo CodeFileInfo = new FileInfo ( Info . File ! ) ;
if ( CodeFileInfo . LastWriteTime > IwyuFileInfo . LastWriteTime )
{
Interlocked . Increment ( ref OutOfDateCount ) ;
return ;
}
}
SortedSet < string > LinesToAdd = new ( ) ;
foreach ( string IncludeToAdd in IncludesToAdd )
{
LinesToAdd . Add ( "#include " + IncludeToAdd ) ;
}
if ( ShouldLog ! = null )
{
lock ( ShouldLog )
{
System . Console . WriteLine ( Info . File ) ;
2023-05-31 13:37:21 -04:00
foreach ( string I in LinesToAdd )
2023-01-10 14:52:00 -05:00
{
System . Console . WriteLine ( " +" + I ) ;
}
2023-05-31 13:37:21 -04:00
foreach ( KeyValuePair < string , string > Pair in LinesToRemove )
2023-01-10 14:52:00 -05:00
{
2023-01-26 02:25:52 -05:00
System . Console . Write ( " -" + Pair . Key ) ;
2023-01-10 14:52:00 -05:00
string? Reason ;
2023-01-26 02:25:52 -05:00
if ( TransitivelyIncluded . TryGetValue ( Pair . Value , out Reason ) )
2023-01-10 14:52:00 -05:00
{
System . Console . Write ( " (Transitively included from " + Reason + ")" ) ;
}
System . Console . WriteLine ( ) ;
}
2023-05-31 13:37:21 -04:00
foreach ( string I in ForwardDeclarationsToAdd )
2023-01-10 14:52:00 -05:00
{
System . Console . WriteLine ( " +" + I ) ;
}
System . Console . WriteLine ( ) ;
}
}
List < string > NewLines = new ( ExistingLines . Length ) ;
SortedSet < String > LinesRemoved = new ( ) ;
if ( ! HasIncludes )
{
LineIndex = 0 ;
foreach ( string OldLine in ExistingLines )
{
NewLines . Add ( OldLine ) ;
+ + LineIndex ;
if ( ! OldLine . TrimStart ( ) . StartsWith ( "#pragma" ) )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
continue ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
NewLines . Add ( "" ) ;
foreach ( string Line in LinesToAdd )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
NewLines . Add ( Line ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
NewLines . AddRange ( ExistingLines . Skip ( LineIndex ) ) ;
break ;
}
}
else
{
// This is a bit of a tricky problem to solve in a generic ways since there are lots of exceptions and hard to make assumptions
// Right now we make the assumption that if there are
// That will be the place where we add/remove our includes.
int ContiguousNonIncludeLineCount = 0 ;
bool IsInFirstIncludeBlock = true ;
int LastSeenIncludeBeforeCode = - 1 ;
int FirstForwardDeclareLineIndex = - 1 ;
foreach ( string OldLine in ExistingLines )
{
2023-02-17 19:39:41 -05:00
ReadOnlySpan < char > OldLineSpan = OldLine . AsSpan ( ) . TrimStart ( ) ;
bool StartsWithHash = OldLineSpan . Length > 0 & & OldLineSpan [ 0 ] = = '#' ;
if ( StartsWithHash )
2023-05-30 18:59:32 -04:00
{
2023-02-17 19:39:41 -05:00
OldLineSpan = OldLineSpan . Slice ( 1 ) . TrimStart ( ) ;
2023-05-30 18:59:32 -04:00
}
2023-02-17 19:39:41 -05:00
bool IsInclude = StartsWithHash & & OldLineSpan . StartsWith ( "include" ) ;
2023-01-10 14:52:00 -05:00
string OldLineTrimmedStart = OldLine . TrimStart ( ) ;
2023-02-17 19:39:41 -05:00
if ( ! IsInclude )
2023-01-10 14:52:00 -05:00
{
if ( IsInFirstIncludeBlock )
{
if ( ! String . IsNullOrEmpty ( OldLineTrimmedStart ) )
{
+ + ContiguousNonIncludeLineCount ;
}
if ( LastSeenIncludeBeforeCode ! = - 1 & & ContiguousNonIncludeLineCount > 10 )
{
IsInFirstIncludeBlock = false ;
}
if ( OldLineTrimmedStart . StartsWith ( "#if" ) )
{
// This logic is a bit shaky but handle the situations where file starts with #if and ends with #endif
if ( LastSeenIncludeBeforeCode ! = - 1 )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
IsInFirstIncludeBlock = false ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
}
// This need to be inside "IsInFirstIncludeBlock" check because some files have forward declares far down in the file
if ( FirstForwardDeclareLine = = OldLine )
{
FirstForwardDeclareLineIndex = NewLines . Count ;
}
}
NewLines . Add ( OldLine ) ;
continue ;
}
ContiguousNonIncludeLineCount = 0 ;
2023-01-26 02:25:52 -05:00
if ( ! LinesToRemove . ContainsKey ( OldLine ) )
2023-01-10 14:52:00 -05:00
{
// If we find #include SOME_DEFINE we assume that should be last and "end" the include block with that
if ( ! OldLineTrimmedStart . Contains ( '\"' ) & & ! OldLineTrimmedStart . Contains ( '<' ) )
{
IsInFirstIncludeBlock = false ;
}
else if ( LinesToAdd . Count ! = 0 & & ( ! IsCpp | | LastSeenIncludeBeforeCode ! = - 1 ) )
{
string LineToAdd = LinesToAdd . First ( ) ;
if ( LineToAdd . CompareTo ( OldLine ) < 0 )
{
NewLines . Add ( LineToAdd ) ;
LinesToAdd . Remove ( LineToAdd ) ;
}
}
NewLines . Add ( OldLine ) ;
}
else
{
FirstForwardDeclareLineIndex = - 1 ; // This should never happen, but just in case, reset since lines have changed
LinesRemoved . Add ( OldLine ) ;
}
if ( IsInFirstIncludeBlock )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
LastSeenIncludeBeforeCode = NewLines . Count - 1 ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
}
if ( LinesToAdd . Count > 0 )
{
int InsertPos = LastSeenIncludeBeforeCode + 1 ;
2023-01-26 02:25:52 -05:00
if ( NewLines [ LastSeenIncludeBeforeCode ] . Contains ( ".generated.h" , StringComparison . Ordinal ) )
2023-01-10 14:52:00 -05:00
{
- - InsertPos ;
}
NewLines . InsertRange ( InsertPos , LinesToAdd ) ;
LastSeenIncludeBeforeCode + = LinesToAdd . Count ;
if ( FirstForwardDeclareLineIndex ! = - 1 & & FirstForwardDeclareLineIndex > LastSeenIncludeBeforeCode )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
FirstForwardDeclareLineIndex + = LinesToAdd . Count ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
}
if ( ForwardDeclarationsToAdd . Count > 0 )
{
int InsertPos ;
if ( FirstForwardDeclareLineIndex = = - 1 )
{
InsertPos = LastSeenIncludeBeforeCode + 1 ;
if ( ! String . IsNullOrEmpty ( NewLines [ InsertPos ] ) )
{
NewLines . Insert ( InsertPos + + , "" ) ;
}
NewLines . Insert ( InsertPos + 1 , "" ) ;
}
else
{
InsertPos = FirstForwardDeclareLineIndex ;
}
NewLines . InsertRange ( InsertPos + 1 , ForwardDeclarationsToAdd ) ;
}
}
// If file is public, in engine and we have a deprecation tag set we will
// add a deprecated include scope at the end of the file (unless scope already exists, then we'll add it inside that)
string EngineDir = Unreal . EngineDirectory . FullName . Replace ( '\\' , '/' ) ;
2023-01-25 02:42:58 -05:00
if ( ! IsPrivate & & Info . File . StartsWith ( EngineDir ) & & ! String . IsNullOrEmpty ( HeaderDeprecationTag ) )
2023-01-10 14:52:00 -05:00
{
2023-01-26 02:25:52 -05:00
Dictionary < string , string > PrintableToFull = new ( ) ;
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Seen in Info . IncludesSeenInFile )
2023-05-30 18:59:32 -04:00
{
2023-01-26 02:25:52 -05:00
PrintableToFull . TryAdd ( Seen . Printable , Seen . Full ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-26 02:25:52 -05:00
2023-01-10 14:52:00 -05:00
// Remove the includes in LinesRemoved
LinesRemoved . RemoveWhere ( Line = >
{
ReadOnlySpan < char > IncludeSpan = Line . AsSpan ( 8 ) . TrimStart ( ) ;
char LeadingIncludeChar = IncludeSpan [ 0 ] ;
if ( LeadingIncludeChar ! = '"' & & LeadingIncludeChar ! = '<' )
{
return false ;
}
int Index = IncludeSpan . Slice ( 1 ) . IndexOf ( LeadingIncludeChar = = '"' ? '"' : '>' ) ;
if ( Index = = - 1 )
{
return false ;
}
IncludeSpan = IncludeSpan . Slice ( 1 , Index ) ;
2023-01-26 02:25:52 -05:00
string? Full ;
if ( ! PrintableToFull . TryGetValue ( IncludeSpan . ToString ( ) , out Full ) )
2023-05-30 18:59:32 -04:00
{
2023-01-26 02:25:52 -05:00
return false ;
2023-05-30 18:59:32 -04:00
}
2023-01-26 02:25:52 -05:00
return Info . TransitiveIncludes . ContainsKey ( Full ) ;
2023-01-10 14:52:00 -05:00
} ) ;
if ( LinesRemoved . Count > 0 )
{
int IndexOfDeprecateScope = - 1 ;
2023-01-25 02:42:58 -05:00
string Match = "#if " + HeaderDeprecationTag ;
2023-01-10 14:52:00 -05:00
for ( int I = NewLines . Count - 1 ; I ! = 0 ; - - I )
{
if ( NewLines [ I ] = = Match )
{
IndexOfDeprecateScope = I + 1 ;
break ;
}
}
if ( IndexOfDeprecateScope = = - 1 )
{
NewLines . Add ( "" ) ;
NewLines . Add ( Match ) ;
IndexOfDeprecateScope = NewLines . Count ;
NewLines . Add ( "#endif" ) ;
}
else
{
// Scan the already added includes to prevent additional adds.
}
NewLines . InsertRange ( IndexOfDeprecateScope , LinesRemoved ) ;
}
}
lock ( UpdatedFiles )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
UpdatedFiles . Add ( new ( Info , NewLines ) ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
} ) ;
2023-01-13 02:02:01 -05:00
// Something went wrong processing code files.
if ( ProcessSuccess = = 0 )
2023-05-30 18:59:32 -04:00
{
2023-01-13 02:02:01 -05:00
return - 1 ;
2023-05-30 18:59:32 -04:00
}
2023-01-13 02:02:01 -05:00
2023-01-10 14:52:00 -05:00
Logger . LogInformation ( $"Parsed {FilesParseCount} and updated {UpdatedFiles.Count} files (Found {OutOfDateCount} .iwyu files out of date)" ) ;
// Wooohoo, all files are up-to-date
if ( UpdatedFiles . Count = = 0 )
{
Logger . LogInformation ( $"All files are up to date!" ) ;
return 0 ;
}
// If we have been logging we can exit now since we don't want to write any files to disk
if ( ShouldLog ! = null )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
return 0 ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
List < System . Diagnostics . Process > P4Processes = new ( ) ;
Action < string > AddP4Process = ( Arguments ) = >
{
System . Diagnostics . Process Process = new System . Diagnostics . Process ( ) ;
System . Diagnostics . ProcessStartInfo StartInfo = new System . Diagnostics . ProcessStartInfo ( ) ;
Process . StartInfo . WindowStyle = System . Diagnostics . ProcessWindowStyle . Hidden ;
Process . StartInfo . CreateNoWindow = true ;
Process . StartInfo . FileName = "p4.exe" ;
Process . StartInfo . Arguments = Arguments ;
Process . Start ( ) ;
P4Processes . Add ( Process ) ;
} ;
Func < bool > WaitForP4 = ( ) = >
{
bool P4Success = true ;
2023-05-31 13:37:21 -04:00
foreach ( System . Diagnostics . Process P4Process in P4Processes )
2023-01-10 14:52:00 -05:00
{
P4Process . WaitForExit ( ) ;
if ( P4Process . ExitCode ! = 0 )
{
P4Success = false ;
Logger . LogError ( $"p4 edit failed - {P4Process.StartInfo.Arguments}" ) ;
}
P4Process . Close ( ) ;
}
P4Processes . Clear ( ) ;
return P4Success ;
} ;
if ( ! bNoP4 )
{
List < IWYUInfo > ReadOnlyFileInfos = new ( ) ;
2023-05-31 13:37:21 -04:00
foreach ( ( IWYUInfo Info , List < string > NewLines ) in UpdatedFiles )
2023-01-10 14:52:00 -05:00
{
if ( new FileInfo ( Info . File ) . IsReadOnly )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
ReadOnlyFileInfos . Add ( Info ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
}
if ( ReadOnlyFileInfos . Count > 0 )
{
// Check out files in batches. This can go quite crazy if there are lots of files.
// Should probably revisit this code to prevent 100s of p4 processes to start at once
2023-01-26 02:25:52 -05:00
Logger . LogInformation ( $"Opening {ReadOnlyFileInfos.Count} files for edit in P4... ({SkippedCount} files skipped)" ) ;
2023-01-10 14:52:00 -05:00
int ShowCount = 8 ;
2023-05-31 13:37:21 -04:00
foreach ( IWYUInfo Info in ReadOnlyFileInfos )
2023-01-10 14:52:00 -05:00
{
Logger . LogInformation ( $" edit {Info.File}" ) ;
if ( - - ShowCount = = 0 )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
break ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
}
2023-01-26 02:25:52 -05:00
if ( ReadOnlyFileInfos . Count > 5 )
2023-05-30 18:59:32 -04:00
{
2023-01-26 02:25:52 -05:00
Logger . LogInformation ( $" ... and {ReadOnlyFileInfos.Count - 5} more." ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
StringBuilder P4Arguments = new ( ) ;
int BatchSize = 10 ;
int BatchCount = 0 ;
int Index = 0 ;
2023-05-31 13:37:21 -04:00
foreach ( IWYUInfo Info in ReadOnlyFileInfos )
2023-01-10 14:52:00 -05:00
{
if ( ! SkippedFiles . Contains ( Info . Source ! ) )
{
P4Arguments . Append ( " \"" ) . Append ( Info . File ) . Append ( '\"' ) ;
+ + BatchCount ;
}
+ + Index ;
2023-01-26 02:25:52 -05:00
if ( BatchCount = = BatchSize | | Index = = ReadOnlyFileInfos . Count )
2023-01-10 14:52:00 -05:00
{
AddP4Process ( $"edit{P4Arguments}" ) ;
P4Arguments . Clear ( ) ;
BatchCount = 0 ;
}
}
// Waiting for edit
if ( ! WaitForP4 ( ) )
{
return - 1 ;
}
}
}
bool WriteSuccess = true ;
Logger . LogInformation ( $"Writing {UpdatedFiles.Count - SkippedCount} files to disk..." ) ;
2023-05-31 13:37:21 -04:00
foreach ( ( IWYUInfo Info , List < string > NewLines ) in UpdatedFiles )
2023-01-10 14:52:00 -05:00
{
if ( SkippedFiles . Contains ( Info . Source ! ) )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
continue ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
try
{
File . WriteAllLines ( Info . File , NewLines ) ;
}
catch ( Exception e )
{
Logger . LogError ( $"Failed to write {Info.File}: {e.Message} - File will be reverted" ) ;
SkippedFiles . Add ( Info . Source ! ) ; // In case other entries from same file is queued
AddP4Process ( $"revert {String.Join(' ', Info.Source!.Files.Select(f => f.File))}" ) ;
WriteSuccess = false ;
}
}
// Waiting for reverts
if ( ! WaitForP4 ( ) )
{
return - 1 ;
2023-01-26 02:25:52 -05:00
}
2023-01-10 14:52:00 -05:00
if ( ! WriteSuccess )
2023-05-30 18:59:32 -04:00
{
2023-01-10 14:52:00 -05:00
return - 1 ;
2023-05-30 18:59:32 -04:00
}
2023-01-10 14:52:00 -05:00
Logger . LogInformation ( $"Done!" ) ;
return 0 ;
}
/// <summary>
/// Calculate all indirect transitive includes for a file. This list contains does not contain itself and will handle circular dependencies
/// </summary>
2023-01-31 14:46:47 -05:00
static void CalculateTransitive ( IWYUInfo Root , IWYUInfo Info , Stack < string > Stack , Dictionary < string , string > TransitiveIncludes , Dictionary < string , string > TransitiveForwardDeclarations , bool UseSeenIncludes )
2023-01-10 14:52:00 -05:00
{
2023-01-26 02:25:52 -05:00
List < IWYUIncludeEntry > Includes = UseSeenIncludes ? Info . IncludesSeenInFile : Info . Includes ;
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include in Includes )
2023-01-10 14:52:00 -05:00
{
2023-01-26 02:25:52 -05:00
string Key = Include . Full ;
2023-01-10 14:52:00 -05:00
if ( TransitiveIncludes . ContainsKey ( Key ) | | Include . Resolved = = Root )
{
continue ;
}
Stack . Push ( Include . Printable ) ;
string TransitivePath = String . Join ( " -> " , Stack . Reverse ( ) ) ;
TransitiveIncludes . Add ( Key , TransitivePath ) ;
if ( Include . Resolved ! = null )
{
2023-01-26 02:25:52 -05:00
CalculateTransitive ( Root , Include . Resolved , Stack , TransitiveIncludes , TransitiveForwardDeclarations , UseSeenIncludes ) ;
2023-01-10 14:52:00 -05:00
}
Stack . Pop ( ) ;
}
2023-05-31 13:37:21 -04:00
foreach ( IWYUForwardEntry ForwardDeclaration in Info . ForwardDeclarations )
2023-01-10 14:52:00 -05:00
{
string Key = ForwardDeclaration . Printable ;
2023-01-26 02:25:52 -05:00
TransitiveForwardDeclarations . TryAdd ( Key , Info . File ) ;
2023-01-10 14:52:00 -05:00
}
}
2023-01-26 02:25:52 -05:00
2023-01-31 14:46:47 -05:00
int CompareFiles ( Dictionary < string , IWYUInfo > Infos , HashSet < string > ValidPaths , Dictionary < string , UEBuildModule > PathToModule , Dictionary < string , UEBuildModule > NameToModule , ILogger Logger )
2023-01-26 02:25:52 -05:00
{
Logger . LogInformation ( $"Comparing code files..." ) ;
2023-01-31 14:46:47 -05:00
Dictionary < string , IWYUInfo > SkippedHeaders = new ( ) ;
2023-01-26 02:25:52 -05:00
2023-01-31 14:46:47 -05:00
Logger . LogInformation ( $"Parsing seen headers not supporting being compiled..." ) ;
2023-01-26 02:25:52 -05:00
Parallel . ForEach ( Infos . Values , Info = >
{
2023-05-31 13:37:21 -04:00
foreach ( IWYUIncludeEntry Include in Info . IncludesSeenInFile )
2023-01-26 02:25:52 -05:00
{
if ( Include . Resolved = = null )
{
if ( ! Infos . TryGetValue ( Include . Full , out Include . Resolved ) )
{
2023-01-31 14:46:47 -05:00
lock ( SkippedHeaders )
2023-01-26 02:25:52 -05:00
{
2023-01-31 14:46:47 -05:00
if ( ! SkippedHeaders . TryGetValue ( Include . Full , out Include . Resolved ) )
{
IWYUInfo NewInfo = new ( ) ;
Include . Resolved = NewInfo ;
NewInfo . File = Include . Full ;
SkippedHeaders . Add ( Include . Full , NewInfo ) ;
}
2023-01-26 02:25:52 -05:00
}
}
}
}
} ) ;
2023-01-31 14:46:47 -05:00
ParseSkippedHeaders ( SkippedHeaders , Infos , PathToModule , NameToModule ) ;
2023-01-26 02:25:52 -05:00
2023-01-31 14:46:47 -05:00
//List<ValueTuple<float, IWYUInfo>> RatioList = new();
2023-01-26 02:25:52 -05:00
2023-01-31 14:46:47 -05:00
ConcurrentDictionary < string , long > AllSeenIncludes = new ( ) ;
2023-01-26 02:25:52 -05:00
2023-05-30 18:38:07 -04:00
List < ValueTuple < IWYUInfo , Dictionary < string , string > > > InfosToCompare = new ( ) ;
2023-01-31 14:46:47 -05:00
Logger . LogInformation ( $"Generating transitive seen include lists..." ) ;
2023-01-26 02:25:52 -05:00
Parallel . ForEach ( Infos . Values , Info = >
{
2023-01-31 14:46:47 -05:00
if ( ! IsValidForUpdate ( Info , ValidPaths , false ) | | Info . File . EndsWith ( ".h" ) )
2023-05-30 18:59:32 -04:00
{
2023-01-26 02:25:52 -05:00
return ;
2023-05-30 18:59:32 -04:00
}
2023-01-26 02:25:52 -05:00
Dictionary < string , string > SeenTransitiveIncludes = new ( ) ;
Stack < string > Stack = new ( ) ;
CalculateTransitive ( Info , Info , Stack , SeenTransitiveIncludes , new Dictionary < string , string > ( ) , true ) ;
2023-01-31 14:46:47 -05:00
lock ( InfosToCompare )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
InfosToCompare . Add ( new ( Info , SeenTransitiveIncludes ) ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
2023-05-31 13:37:21 -04:00
foreach ( KeyValuePair < string , string > Include in SeenTransitiveIncludes . Union ( Info . TransitiveIncludes ) )
2023-01-31 14:46:47 -05:00
{
AllSeenIncludes . TryAdd ( Include . Key , 0 ) ;
}
/*
2023-01-26 02:25:52 -05:00
if (Info.TransitiveIncludes.Count < SeenTransitiveIncludes.Count)
{
float Ratio = (float)Info.TransitiveIncludes.Count / SeenTransitiveIncludes.Count;
lock (RatioList)
RatioList.Add(new(Ratio, Info));
}
2023-01-31 14:46:47 -05:00
if (SeenTransitiveIncludes.Count < Info.TransitiveIncludes.Count)
Console.WriteLine($"{Info.File} - Now: {SeenTransitiveIncludes.Count} Optimized: {Info.TransitiveIncludes.Count}");
*/
2023-01-26 02:25:52 -05:00
} ) ;
2023-05-31 13:37:21 -04:00
Dictionary < string , long > FileToSize = new Dictionary < string , long > ( AllSeenIncludes ) ;
2023-01-31 14:46:47 -05:00
Logger . LogInformation ( $"Reading file sizes..." ) ;
Parallel . ForEach ( AllSeenIncludes , Info = >
{
FileToSize [ Info . Key ] = ( new FileInfo ( Info . Key ) ) . Length ;
} ) ;
Logger . LogInformation ( $"Calculate total amount of bytes included per file..." ) ;
List < ValueTuple < long , IWYUInfo > > ByteSizeDiffList = new ( ) ;
Parallel . ForEach ( InfosToCompare , Kvp = >
{
long OptimizedSize = 0 ;
long SeenSize = 0 ;
2023-05-31 13:37:21 -04:00
foreach ( string Include in Kvp . Item1 . TransitiveIncludes . Keys )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
OptimizedSize + = FileToSize [ Include ] ;
2023-05-30 18:59:32 -04:00
}
2023-05-31 13:37:21 -04:00
foreach ( string? Include in Kvp . Item2 . Keys )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
SeenSize + = FileToSize [ Include ] ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
long Diff = SeenSize - OptimizedSize ;
lock ( ByteSizeDiffList )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
ByteSizeDiffList . Add ( new ( Diff , Kvp . Item1 ) ) ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
} ) ;
ByteSizeDiffList . Sort ( ( a , b ) = >
{
if ( a . Item1 = = b . Item1 )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
return 0 ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
if ( a . Item1 < b . Item1 )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
return 1 ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
return - 1 ;
} ) ;
Func < long , string > PrettySize = ( Size ) = >
{
if ( Size > 1024 * 1024 )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
return ( ( ( float ) Size ) / ( 1024 * 1024 ) ) . ToString ( "0.00" ) + "mb" ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
if ( Size > 1024 )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
return ( ( ( float ) Size ) / ( 1024 ) ) . ToString ( "0.00" ) + "kb" ;
2023-05-30 18:59:32 -04:00
}
2023-01-31 14:46:47 -05:00
return Size + "b" ;
} ;
Logger . LogInformation ( "" ) ;
Logger . LogInformation ( "Top 20 most reduction in bytes parsed by compiler frontend" ) ;
Logger . LogInformation ( "----------------------------------------------------------" ) ;
for ( int I = 0 ; I ! = Math . Min ( 20 , ByteSizeDiffList . Count ) ; + + I )
{
long Saved = ByteSizeDiffList [ I ] . Item1 ;
IWYUInfo File = ByteSizeDiffList [ I ] . Item2 ;
long OptimizedSize = 0 ;
2023-05-31 13:37:21 -04:00
foreach ( string Include in File . TransitiveIncludes . Keys )
2023-05-30 18:59:32 -04:00
{
2023-01-31 14:46:47 -05:00
OptimizedSize + = FileToSize [ Include ] ;
2023-05-30 18:59:32 -04:00
}
2023-05-30 18:38:07 -04:00
float Percent = 100.0f - ( ( ( float ) OptimizedSize ) / ( OptimizedSize + Saved ) * 100.0f ) ;
2023-01-31 14:46:47 -05:00
Logger . LogInformation ( $"{Path.GetFileName(File.File)} {PrettySize(OptimizedSize + Saved)} -> {PrettySize(OptimizedSize)} (Saved {PrettySize(Saved)} or {Percent.ToString(" 0.0 ")}%)" ) ;
}
Logger . LogInformation ( "" ) ;
/*
2023-01-26 02:25:52 -05:00
RatioList.Sort((a, b) =>
{
if (a.Item1 == b.Item1)
return 0;
if (a.Item1 < b.Item1)
return -1;
return 1;
});
2023-01-31 14:46:47 -05:00
*/
2023-01-26 02:25:52 -05:00
return 0 ;
}
2023-01-10 14:52:00 -05:00
}
}