// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using EpicGames.Core; using Microsoft.Extensions.Logging; namespace UnrealBuildTool { /// /// Generate a clang compile_commands file for a target /// [ToolMode("PrintBuildGraphInfo", ToolModeOptions.XmlConfig | ToolModeOptions.BuildPlatforms | ToolModeOptions.SingleInstance | ToolModeOptions.StartPrefetchingEngine | ToolModeOptions.ShowExecutionTime)] class PrintBuildGraphInfo : ToolMode { /// /// Execute the command /// /// Command line arguments /// Exit code /// public override Task ExecuteAsync(CommandLineArguments Arguments, ILogger Logger) { Arguments.ApplyTo(this); // Create the build configuration object, and read the settings BuildConfiguration BuildConfiguration = new BuildConfiguration(); XmlConfig.ApplyTo(BuildConfiguration); Arguments.ApplyTo(BuildConfiguration); // Parse all the target descriptors List TargetDescriptors = TargetDescriptor.ParseCommandLine(Arguments, BuildConfiguration, Logger); using (ISourceFileWorkingSet WorkingSet = new EmptySourceFileWorkingSet()) { // Find the compile commands for each file in the target Dictionary FileToCommand = new(); foreach (TargetDescriptor TargetDescriptor in TargetDescriptors) { Logger.LogInformation("------------------------"); Logger.LogInformation("Target: {TargetName}", TargetDescriptor.Name); Logger.LogInformation("------------------------"); // Create a makefile for the target UEBuildTarget Target = UEBuildTarget.Create(TargetDescriptor, BuildConfiguration, Logger); List NoUnityModules = new(); List NotOptimizedModules = new(); List NoPCHModules = new(); List PrivatePCHModules = new(); List SharedPCHWithPrivateHeaderModules = new(); // Figure out all the modules referenced by this target. This includes all the modules that are referenced, not just the ones compiled into binaries. CppCompileEnvironment GlobalCompileEnvironment = Target.CreateCompileEnvironmentForProjectFiles(Logger); HashSet Modules = new HashSet(); foreach (UEBuildBinary Binary in Target.Binaries) { CppCompileEnvironment BinaryCompileEnvironment = Binary.CreateBinaryCompileEnvironment(GlobalCompileEnvironment); foreach (UEBuildModule Module in UEBuildModule.StableTopologicalSort(Binary.Modules)) { if (Module is UEBuildModuleCPP ModuleCPP) { ModuleCPP.CreateCompileEnvironmentForIntellisense(Target.Rules, BinaryCompileEnvironment, Logger); } Modules.Add(Module); } } // Gather module info foreach (UEBuildModule Module in Modules) { if (Module.Rules.PCHUsage == ModuleRules.PCHUsageMode.NoPCHs) { NoPCHModules.Add(Module); } else if (Module.Rules.PrivatePCHHeaderFile != null && (Module.Rules.PCHUsage == ModuleRules.PCHUsageMode.NoSharedPCHs || Module.Rules.PCHUsage == ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs)) { PrivatePCHModules.Add(Module); } else if (Module.Rules.PrivatePCHHeaderFile != null && Module.Rules.PCHUsage == ModuleRules.PCHUsageMode.UseSharedPCHs) { SharedPCHWithPrivateHeaderModules.Add(Module); } if (!Module.Rules.bUseUnity) { NoUnityModules.Add(Module); } if (Module.Rules.OptimizeCode == ModuleRules.CodeOptimization.Never) { NotOptimizedModules.Add(Module); } } if (NoUnityModules.Any()) { Logger.LogInformation("Modules not using unity files:"); NoUnityModules.SortBy(Module => Module.Name); foreach (UEBuildModule Module in NoUnityModules) { Logger.LogInformation(" {ModuleName}", Module.Name); } } if (NotOptimizedModules.Any()) { Logger.LogInformation("Modules not optimized:"); NotOptimizedModules.SortBy(Module => Module.Name); foreach (UEBuildModule Module in NotOptimizedModules) { Logger.LogInformation(" {ModuleName}", Module.Name); } } if (SharedPCHWithPrivateHeaderModules.Any()) { Logger.LogInformation("Modules using a shared PCH and private PCH header:"); Logger.LogInformation(" Note these might not compile properly when PCHs are disabled."); SharedPCHWithPrivateHeaderModules.SortBy(Module => Module.Name); foreach (UEBuildModule Module in SharedPCHWithPrivateHeaderModules) { Logger.LogInformation(" {ModuleName}", Module.Name); } } Logger.LogInformation("PCH Usage:"); if (NoPCHModules.Any()) { Logger.LogInformation(" Modules not using PCHs:"); NoPCHModules.SortBy(Module => Module.Name); foreach (UEBuildModule Module in NoPCHModules) { Logger.LogInformation(" {ModuleName}", Module.Name); } } if (PrivatePCHModules.Any()) { Logger.LogInformation(" Modules using private PCHs:"); PrivatePCHModules.SortBy(Module => Module.Name); foreach (UEBuildModule Module in PrivatePCHModules) { Logger.LogInformation(" {ModuleName}", Module.Name); } } Dictionary ModulesUsingSharedPCHs = new(); if (GlobalCompileEnvironment.SharedPCHs.Any()) { Logger.LogInformation(" Shared PCHs:"); List SortedSharedPCHs = new(GlobalCompileEnvironment.SharedPCHs); SortedSharedPCHs.SortBy(SharedPCH => SharedPCH.Module.Name); foreach (PrecompiledHeaderTemplate Template in SortedSharedPCHs) { Logger.LogInformation(" Module {ModuleName} - {TimesUsed} Permutations:", Template.Module.Name, Template.Instances.Count); List SortedInstances = new(Template.Instances); SortedInstances.SortBy(Instance => Instance.HeaderFile.Name); foreach (PrecompiledHeaderInstance Instance in SortedInstances) { Logger.LogInformation(" {InstanceName} - Used by {TimesUsed} modules:", Instance.HeaderFile, Instance.Modules.Count); if (Instance.ParentPCHInstance != null) { Logger.LogInformation(" ParentPCH: {ParentInstanceName}", Instance.ParentPCHInstance.HeaderFile); } List SortedModules = new(Instance.Modules); SortedModules.SortBy(Module => Module.Name); foreach (UEBuildModuleCPP Module in SortedModules) { Logger.LogInformation(" {ModuleName}", Module.Name); ModulesUsingSharedPCHs.Add(Module, Template); } } } } Logger.LogInformation("Circular Dependencies that are not declared."); Logger.LogInformation("NOTE: Anything prefixed with a '!!!' means that the circular dependency could possibly create linker errors."); foreach (UEBuildModule Module in Modules) { List> ModuleDepStacks = new List>(); FindCircularDependencyModules(Module, Module, new HashSet(), true, new Stack(), ModuleDepStacks); if (ModuleDepStacks.Any()) { bool UsesSharedPCH = ModulesUsingSharedPCHs.ContainsKey(Module); Logger.LogInformation(" {ModuleName} - {DepCount}", Module.Name, ModuleDepStacks.Count); foreach (Stack DepModuleStack in ModuleDepStacks) { StringBuilder Dependencies = new StringBuilder(); if (UsesSharedPCH && DepModuleStack.Contains(ModulesUsingSharedPCHs[Module].Module)) { Dependencies.Append("!!! "); } UEBuildModule[] DepModuleArray = DepModuleStack.Reverse().ToArray(); for (int DepModuleIndex = 0; DepModuleIndex < DepModuleArray.Length; DepModuleIndex++) { Dependencies.Append(DepModuleArray[DepModuleIndex].Name); if (DepModuleIndex != DepModuleArray.Length - 1) { Dependencies.Append(" -> "); } } Logger.LogInformation($" {Dependencies}"); } } } } } return Task.FromResult(0); } private void FindCircularDependencyModules(UEBuildModule SearchForBuildModule, UEBuildModule CurrentBuildModule, HashSet IgnoreReferencedModules, bool bIncludePrivateDependencyModules, Stack CurrentStack, List> ModuleDepStacks) { CurrentStack.Push(CurrentBuildModule); if (SearchForBuildModule == CurrentBuildModule && CurrentStack.Count > 1) { ModuleDepStacks.Add(new Stack(CurrentStack.Reverse())); } List AllDependencyModules = new List( ((bIncludePrivateDependencyModules && CurrentBuildModule.PrivateDependencyModules != null) ? CurrentBuildModule.PrivateDependencyModules.Count : 0) + (CurrentBuildModule.PublicDependencyModules != null ? CurrentBuildModule.PublicDependencyModules.Count : 0) + (CurrentBuildModule.PublicIncludePathModules != null ? CurrentBuildModule.PublicIncludePathModules!.Count : 0) ); if (bIncludePrivateDependencyModules && CurrentBuildModule.PrivateDependencyModules != null) { AllDependencyModules.AddRange(CurrentBuildModule.PrivateDependencyModules); } if (CurrentBuildModule.PublicDependencyModules != null) { AllDependencyModules.AddRange(CurrentBuildModule.PublicDependencyModules!); } if (CurrentBuildModule.PublicIncludePathModules != null) { AllDependencyModules.AddRange(CurrentBuildModule.PublicIncludePathModules); } foreach (UEBuildModule DependencyModule in AllDependencyModules) { // Don't follow circular back-references! if (!CurrentBuildModule.HasCircularDependencyOn(DependencyModule)) { if (IgnoreReferencedModules.Add(DependencyModule)) { // Recurse into dependent modules FindCircularDependencyModules(SearchForBuildModule, DependencyModule, new HashSet(IgnoreReferencedModules), false, CurrentStack, ModuleDepStacks); } } } CurrentStack.Pop(); } } }