// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using EpicGames.Core; using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace UnrealBuildTool { /// /// A unit of code compilation and linking. /// abstract class UEBuildModule { /// /// The rules for this module /// public readonly ModuleRules Rules; /// /// The directory for this module's object files /// public readonly DirectoryReference IntermediateDirectory; /// /// The directory for this module's generated files that are architecture independent /// public readonly DirectoryReference IntermediateDirectoryNoArch; /// /// The name that uniquely identifies the module. /// public string Name => Rules.Name; /// /// Path to the module directory /// public DirectoryReference ModuleDirectory => Rules.Directory; /// /// Paths to all potential module source directories (with platform extension directories added in) /// public DirectoryReference[] ModuleDirectories; /// /// The name of the .Build.cs file this module was created from, if any /// public FileReference RulesFile => Rules.File; /// /// The binary the module will be linked into for the current target. Only set after UEBuildBinary.BindModules is called. /// public UEBuildBinary Binary = null!; /// /// The name of the _API define for this module /// protected readonly string ModuleApiDefine; /// /// Set of all the public definitions /// public readonly HashSet PublicDefinitions; /// /// Set of all public include paths /// public readonly HashSet PublicIncludePaths; /// /// Set of all internal include paths /// public readonly HashSet InternalIncludePaths; /// /// Nested public include paths which used to be added automatically, but are now only added for modules with bNestedPublicIncludePaths set. /// public readonly HashSet LegacyPublicIncludePaths = new HashSet(); /// /// Parent include paths which used to be added automatically, but are now only added for modules with bLegacyParentIncludePaths set. /// public readonly HashSet LegacyParentIncludePaths = new HashSet(); /// /// Set of all private include paths /// public readonly HashSet PrivateIncludePaths; /// /// Set of all system include paths /// public readonly HashSet PublicSystemIncludePaths; /// /// Set of all additional libraries /// public readonly HashSet PublicLibraries = new HashSet(); /// /// Set of all system libraries /// public readonly HashSet PublicSystemLibraries = new HashSet(); /// /// Set of all public system library paths /// public readonly HashSet PublicSystemLibraryPaths; /// /// Set of additional frameworks /// public readonly HashSet PublicFrameworks; /// /// /// public readonly HashSet PublicWeakFrameworks; /// /// /// protected readonly HashSet PublicAdditionalFrameworks; /// /// /// protected readonly HashSet PublicAdditionalBundleResources; /// /// Names of modules with header files that this module's public interface needs access to. /// public List? PublicIncludePathModules; /// /// Names of modules that this module's public interface depends on. /// public List? PublicDependencyModules; /// /// Names of DLLs that this module should delay load /// public HashSet PublicDelayLoadDLLs; /// /// Names of modules with header files that this module's private implementation needs access to. /// public List? PrivateIncludePathModules; /// /// Names of modules that this module's private implementation depends on. /// public List? PrivateDependencyModules; /// /// Extra modules this module may require at run time /// public List? DynamicallyLoadedModules; /// /// Set of modules that have referenced this module /// protected HashSet? ReferenceStackParentModules; /// /// Set of all allowed restricted folder references /// private readonly HashSet RestrictedFoldersAllowList; /// /// Set of aliased restricted folder references /// public readonly Dictionary AliasRestrictedFolders; /// /// Per-architecture lists of dependencies for linking to ignore (useful when building for multiple architectures, and a lib only is needed for one architecture), it's up to the Toolchain to use this /// public Dictionary> DependenciesToSkipPerArchitecture; /// /// The Verse source code directory associated with this module if any /// public virtual DirectoryReference? VerseDirectory => null; /// /// If this module has Verse code associated with it (convenience function) /// public bool bHasVerse => VerseDirectory != null; /// /// If this module or any of its dependencies has Verse code associated with it /// public bool bDependsOnVerse = false; /// /// Set of debug visualizer paths /// public HashSet NatvisFiles = new(); /// /// Constructor /// /// Rules for this module /// Intermediate directory for this module /// Intermediate directory for this module for files that are architecture independent /// Logger for output public UEBuildModule(ModuleRules Rules, DirectoryReference IntermediateDirectory, DirectoryReference IntermediateDirectoryNoArch, ILogger Logger) { this.Rules = Rules; this.IntermediateDirectory = IntermediateDirectory; this.IntermediateDirectoryNoArch = IntermediateDirectoryNoArch; ModuleApiDefine = Name.ToUpperInvariant() + "_API"; HashSet PublicPreBuildLibraries = HashSetFromOptionalEnumerableStringParameter(Rules.PublicPreBuildLibraries); PublicDefinitions = HashSetFromOptionalEnumerableStringParameter(Rules.PublicDefinitions); PublicIncludePaths = CreateDirectoryHashSet(Rules.PublicIncludePaths); InternalIncludePaths = CreateDirectoryHashSet(Rules.InternalIncludePaths); PublicSystemIncludePaths = CreateDirectoryHashSet(Rules.PublicSystemIncludePaths); PublicSystemLibraryPaths = CreateDirectoryHashSet(Rules.PublicSystemLibraryPaths); HashSet PublicAdditionalLibraries = HashSetFromOptionalEnumerableStringParameter(Rules.PublicAdditionalLibraries.Union(PublicPreBuildLibraries)); PublicSystemLibraries = HashSetFromOptionalEnumerableStringParameter(Rules.PublicSystemLibraries); PublicFrameworks = HashSetFromOptionalEnumerableStringParameter(Rules.PublicFrameworks); PublicWeakFrameworks = HashSetFromOptionalEnumerableStringParameter(Rules.PublicWeakFrameworks); foreach (string LibraryName in PublicAdditionalLibraries) { FileItem Library = FileItem.GetItemByPath(LibraryName); if (Library.Exists) { // if the library path is fully qualified we just add it, this is the preferred method of adding a library FileReference Location = Library.Location; PublicLibraries.Add(Location); } else if (PublicPreBuildLibraries.Contains(LibraryName)) { Logger.LogDebug("Library '{LibraryName}' was not resolvable to a file when used in Module '{Name}'. Be sure to add either a TargetRules.PreBuildSteps entry or a TargetRules.PreBuildTargets entry to assure it is built for your target.", LibraryName, Name); PublicLibraries.Add(Library.Location); } else { // the library path does not seem to be resolvable as is, lets warn about it as dependency checking will not work for it LogWarningOrThrowError(Logger, Rules.Target.DefaultWarningLevel, "Library '{0}' was not resolvable to a file when used in Module '{1}', assuming it is a filename and will search library paths for it. This is slow and dependency checking will not work for it. Please update reference to be fully qualified alternatively use PublicSystemLibraryPaths if you do intended to use this slow path to suppress this warning. ", LibraryName, Name); PublicSystemLibraries.Add(LibraryName); } } PublicAdditionalFrameworks = new HashSet(); if (Rules.PublicAdditionalFrameworks != null) { foreach (ModuleRules.Framework FrameworkRules in Rules.PublicAdditionalFrameworks) { bool bLinkFramework = FrameworkRules.Mode == ModuleRules.Framework.FrameworkMode.Link || FrameworkRules.Mode == ModuleRules.Framework.FrameworkMode.LinkAndCopy; bool bCopyFramework = FrameworkRules.Mode == ModuleRules.Framework.FrameworkMode.Copy || FrameworkRules.Mode == ModuleRules.Framework.FrameworkMode.LinkAndCopy; UEBuildFramework Framework; if (FrameworkRules.IsZipFile()) { // If FrameworkPath ends in .zip, it needs to be extracted Framework = new UEBuildFramework( Name: FrameworkRules.Name, ZipFile: FileReference.Combine(ModuleDirectory, FrameworkRules.Path), OutputDirectory: DirectoryReference.Combine(Unreal.EngineDirectory, "Intermediate", "UnzippedFrameworks", FrameworkRules.Name, Path.GetFileNameWithoutExtension(FrameworkRules.Path)), CopyBundledAssets: FrameworkRules.CopyBundledAssets, bLinkFramework, bCopyFramework, Logger); } else { // Framework on disk Framework = new UEBuildFramework( Name: FrameworkRules.Name, FrameworkDirectory: DirectoryReference.Combine(ModuleDirectory, FrameworkRules.Path), CopyBundledAssets: FrameworkRules.CopyBundledAssets, bLinkFramework, bCopyFramework, Logger); } PublicAdditionalFrameworks.Add(Framework); } } PublicAdditionalBundleResources = Rules.AdditionalBundleResources == null ? new HashSet() : new HashSet(Rules.AdditionalBundleResources.Select(x => new UEBuildBundleResource(x))); PublicDelayLoadDLLs = HashSetFromOptionalEnumerableStringParameter(Rules.PublicDelayLoadDLLs); if (Rules.bUsePrecompiled) { PrivateIncludePaths = new HashSet(); } else { PrivateIncludePaths = CreateDirectoryHashSet(Rules.PrivateIncludePaths); } RestrictedFoldersAllowList = new HashSet(Rules.AllowedRestrictedFolders.Select(x => DirectoryReference.Combine(ModuleDirectory, x))); AliasRestrictedFolders = new Dictionary(Rules.AliasRestrictedFolders); DependenciesToSkipPerArchitecture = Rules.DependenciesToSkipPerArchitecture; // get the module directories from the module ModuleDirectories = Rules.GetAllModuleDirectories(); // Add any additional debug visualizers foreach (string natVisPath in HashSetFromOptionalEnumerableStringParameter(Rules.PublicDebugVisualizerPaths)) { FileItem natVisItem = FileItem.GetItemByPath(natVisPath); if (!natVisItem.HasExtension(".natvis") && !natVisItem.HasExtension(".natstepfilter") && !natVisItem.HasExtension(".natjmc")) { Log.TraceWarningTask(RulesFile, $"Referenced Debug Visualizer '{natVisItem}' is not a .natvis, .natstepfilter, or .natjmc file"); } else if (!natVisItem.Exists) { Log.TraceWarningTask(RulesFile, $"Referenced Debug Visualizer '{natVisItem}' does not exist"); } else { NatvisFiles.Add(natVisItem); } } // Add any debug visualizers found in the module directories foreach (DirectoryItem directory in ModuleDirectories.Select(x => DirectoryItem.GetItemByDirectoryReference(x))) { NatvisFiles.UnionWith(directory.EnumerateFiles().Where(x => x.HasExtension(".natvis") || x.HasExtension(".natstepfilter") || x.HasExtension(".natjmc"))); } } /// /// Log a warning or throw an error message /// /// /// /// /// static void LogWarningOrThrowError(ILogger Logger, WarningLevel Level, string Format, params object[] Args) { if (Level == WarningLevel.Error) { throw new BuildException(Format, Args); } else if (Level == WarningLevel.Warning) { Logger.LogWarning("{Message}", String.Format(Format, Args)); } } /// /// Determines if a file is part of the given module /// /// Path to the file /// True if the file is part of this module public virtual bool ContainsFile(FileReference Location) { return ModuleDirectories.Any(x => Location.IsUnderDirectory(x)); } /// /// Returns a list of this module's dependencies. /// /// An enumerable containing the dependencies of the module. public HashSet GetDependencies(bool bWithIncludePathModules, bool bWithDynamicallyLoadedModules) { HashSet Modules = new HashSet(); Modules.UnionWith(PublicDependencyModules ?? new()); Modules.UnionWith(PrivateDependencyModules ?? new()); if (bWithIncludePathModules) { Modules.UnionWith(PublicIncludePathModules ?? new()); Modules.UnionWith(PrivateIncludePathModules ?? new()); } if (bWithDynamicallyLoadedModules) { Modules.UnionWith(DynamicallyLoadedModules ?? new()); } return Modules; } /// /// Returns a list of this module's frameworks. /// /// A List containing the frameworks this module requires. public List GetPublicFrameworks() { return new List(PublicFrameworks); } /// /// Returns a list of this module's immediate dependencies. /// /// An enumerable containing the dependencies of the module. public IEnumerable GetDirectDependencyModules() { return PublicDependencyModules!.Concat(PrivateDependencyModules!).Concat(DynamicallyLoadedModules!); } /// /// Converts an optional string list parameter to a well-defined hash set. /// protected HashSet CreateDirectoryHashSet(IEnumerable InEnumerableStrings) { HashSet Directories = new HashSet(); if (InEnumerableStrings != null) { foreach (string InputString in InEnumerableStrings) { DirectoryReference Dir = new DirectoryReference(ExpandPathVariables(InputString, null, null)); if (DirectoryLookupCache.DirectoryExists(Dir)) { Directories.Add(Dir); } else { Log.TraceWarningTask(RulesFile, $"Referenced directory '{Dir}' does not exist."); } } } return Directories; } /// /// Converts an optional string list parameter to a well-defined hash set. /// protected HashSet HashSetFromOptionalEnumerableStringParameter(IEnumerable InEnumerableStrings) { return InEnumerableStrings == null ? new HashSet() : new HashSet(InEnumerableStrings.Select(x => ExpandPathVariables(x, null, null))); } /// /// Determines whether this module has a circular dependency on the given module /// public bool HasCircularDependencyOn(UEBuildModule Module) { return Rules.CircularlyReferencedDependentModules.Contains(Module.Name); } /// /// Enumerates additional build products which may be produced by this module. Some platforms (eg. Mac, Linux) can link directly against .so/.dylibs, but they /// are also copied to the output folder by the toolchain. /// /// List to which libraries required by this module are added /// List of bundle resources required by this module public void GatherAdditionalResources(List Libraries, List BundleResources) { Libraries.AddRange(PublicLibraries.Select(x => x.FullName)); Libraries.AddRange(PublicSystemLibraries); BundleResources.AddRange(PublicAdditionalBundleResources); } /// /// Determines the distribution level of a module based on its directory and includes. /// /// The set of additional paths to check, if available /// Map of the restricted folder types to the first found instance public Dictionary FindRestrictedFolderReferences(List RootDirectories) { Dictionary References = new Dictionary(); if (!Rules.bLegalToDistributeObjectCode) { // Find all the directories that this module references HashSet ReferencedDirs = new HashSet(); GetReferencedDirectories(ReferencedDirs); // Remove all the allow listed folders ReferencedDirs.ExceptWith(RestrictedFoldersAllowList); ReferencedDirs.ExceptWith(PublicDependencyModules!.SelectMany(x => x.RestrictedFoldersAllowList)); ReferencedDirs.ExceptWith(PrivateDependencyModules!.SelectMany(x => x.RestrictedFoldersAllowList)); // Add flags for each of them foreach (DirectoryReference ReferencedDir in ReferencedDirs) { // Find the base directory containing this reference DirectoryReference? BaseDir = RootDirectories.FirstOrDefault(x => ReferencedDir.IsUnderDirectory(x)); // @todo platplug does this need to check platform extension engine directories? what are ReferencedDir's here? if (BaseDir == null) { continue; } // Add references to each of the restricted folders List Folders = RestrictedFolders.FindRestrictedFolders(BaseDir, ReferencedDir); foreach (RestrictedFolder Folder in Folders) { if (!References.ContainsKey(Folder)) { References.Add(Folder, ReferencedDir); } } } } return References; } /// /// Finds all the directories that this folder references when building /// /// Set of directories to add to protected virtual void GetReferencedDirectories(HashSet Directories) { Directories.Add(ModuleDirectory); foreach (DirectoryReference PublicIncludePath in PublicIncludePaths) { Directories.Add(PublicIncludePath); } foreach (DirectoryReference InternalIncludePath in InternalIncludePaths) { Directories.Add(InternalIncludePath); } foreach (DirectoryReference PrivateIncludePath in PrivateIncludePaths) { Directories.Add(PrivateIncludePath); } foreach (DirectoryReference PublicSystemIncludePath in PublicSystemIncludePaths) { Directories.Add(PublicSystemIncludePath); } foreach (DirectoryReference PublicSystemLibraryPath in PublicSystemLibraryPaths) { Directories.Add(PublicSystemLibraryPath); } } /// /// Find all the modules which affect the private compile environment. /// /// protected void FindModulesInPrivateCompileEnvironment(Dictionary ModuleToIncludePathsOnlyFlag) { // Add in all the modules that are only in the private compile environment foreach (UEBuildModule PrivateDependencyModule in PrivateDependencyModules!) { PrivateDependencyModule.FindModulesInPublicCompileEnvironment(ModuleToIncludePathsOnlyFlag); } foreach (UEBuildModule PrivateIncludePathModule in PrivateIncludePathModules!) { PrivateIncludePathModule.FindIncludePathModulesInPublicCompileEnvironment(ModuleToIncludePathsOnlyFlag); } // Add the modules in the public compile environment FindModulesInPublicCompileEnvironment(ModuleToIncludePathsOnlyFlag); } /// /// Find all the modules which affect the public compile environment. /// /// protected void FindModulesInPublicCompileEnvironment(Dictionary ModuleToIncludePathsOnlyFlag) { // bool bModuleIncludePathsOnly; if (ModuleToIncludePathsOnlyFlag.TryGetValue(this, out bModuleIncludePathsOnly) && !bModuleIncludePathsOnly) { return; } ModuleToIncludePathsOnlyFlag[this] = false; foreach (UEBuildModule DependencyModule in PublicDependencyModules!) { DependencyModule.FindModulesInPublicCompileEnvironment(ModuleToIncludePathsOnlyFlag); } // Now add an include paths from modules with header files that we need access to, but won't necessarily be importing foreach (UEBuildModule IncludePathModule in PublicIncludePathModules!) { IncludePathModule.FindIncludePathModulesInPublicCompileEnvironment(ModuleToIncludePathsOnlyFlag); } } /// /// Find all the modules which affect the public compile environment. Searches through /// /// protected void FindIncludePathModulesInPublicCompileEnvironment(Dictionary ModuleToIncludePathsOnlyFlag) { if (!ModuleToIncludePathsOnlyFlag.ContainsKey(this)) { // Add this module to the list ModuleToIncludePathsOnlyFlag.Add(this, true); // Include any of its public include path modules in the compile environment too foreach (UEBuildModule IncludePathModule in PublicIncludePathModules!) { IncludePathModule.FindIncludePathModulesInPublicCompileEnvironment(ModuleToIncludePathsOnlyFlag); } } } private void AddIncludePaths(HashSet IncludePaths, HashSet IncludePathsToAdd) { // Need to check whether directories exist to avoid bloating compiler command line with generated code directories IncludePaths.UnionWith(IncludePathsToAdd); } /// /// Add definitions from source to target. This code also reformats defines to match strict rules which does not allow space before and after '=' /// protected void AddDefinitions(List Target, HashSet Source) { StringBuilder Builder = new(); foreach (string Def in Source) { string FixedDef = Def; int IndexOfAssign = Def.IndexOf("="); if (IndexOfAssign != -1) { ReadOnlySpan DefSpan = Def.AsSpan(); ReadOnlySpan Name = DefSpan.Slice(0, IndexOfAssign); ReadOnlySpan NameTrim = Name.Trim(); ReadOnlySpan Value = DefSpan.Slice(IndexOfAssign + 1); ReadOnlySpan ValueTrim = Value.Trim(); if (Name.Length != NameTrim.Length || Value.Length != ValueTrim.Length) { Builder.Clear(); Builder.Append(NameTrim); Builder.Append('='); Builder.Append(ValueTrim); FixedDef = Builder.ToString(); } } Target.Add(FixedDef); } } /// /// Sets up the environment for compiling any module that includes the public interface of this module. /// public virtual void AddModuleToCompileEnvironment( UEBuildModule? SourceModule, UEBuildBinary? SourceBinary, HashSet IncludePaths, HashSet SystemIncludePaths, HashSet ModuleInterfacePaths, List Definitions, List AdditionalFrameworks, List AdditionalPrerequisites, bool bLegacyPublicIncludePaths, bool bLegacyParentIncludePaths ) { // Add this module's public include paths and definitions AddIncludePaths(IncludePaths, PublicIncludePaths); // Add the module's parent directory to the include path, so we can root #includes from generated source files to it. Not recommended (Use BuildSetting.V3 or later) if (bLegacyParentIncludePaths) { AddIncludePaths(IncludePaths, LegacyParentIncludePaths); IncludePaths.Add(ModuleDirectory.ParentDirectory!); } // Add this module's legacy public include paths. Not recommended (Use BuildSetting.V2 or later) if (bLegacyPublicIncludePaths) { AddIncludePaths(IncludePaths, LegacyPublicIncludePaths); } // Add this module's internal include paths, only if the scope contains the same as the SourceModule's scope or if the source module is an engine module if (SourceModule != null && (Rules.Context.Scope.Contains(SourceModule.Rules.Context.Scope) || SourceModule.Rules.bTreatAsEngineModule)) { AddIncludePaths(IncludePaths, InternalIncludePaths); } // Add this module's public system include paths SystemIncludePaths.UnionWith(PublicSystemIncludePaths); // Add this module's public definitions AddDefinitions(Definitions, PublicDefinitions); // Add the import or export declaration for the module if (Rules.Type == ModuleRules.ModuleType.CPlusPlus) { if (Rules.Target.LinkType == TargetLinkType.Monolithic) { if (Rules.Target.bShouldCompileAsDLL && (Rules.Target.bHasExports || Rules.ModuleSymbolVisibility == ModuleRules.SymbolVisibility.VisibileForDll)) { Definitions.Add(ModuleApiDefine + "=DLLEXPORT"); } else { Definitions.Add(ModuleApiDefine + "="); } } else if (Binary == null || (Rules.Target.bMergeModules ? (SourceBinary == null || SourceModule != this) : SourceBinary != Binary)) { Definitions.Add(ModuleApiDefine + "=DLLIMPORT"); } else if (!Binary.bAllowExports || Rules.Target.bMergeModules) { Definitions.Add(ModuleApiDefine + "="); } else { Definitions.Add(ModuleApiDefine + "=DLLEXPORT"); } } // Add the additional frameworks so that the compiler can know about their #include paths AdditionalFrameworks.AddRange(PublicAdditionalFrameworks); // Add any generated type library headers if (Rules.TypeLibraries.Count > 0) { IncludePaths.Add(IntermediateDirectory); foreach (ModuleRules.TypeLibrary TypeLibrary in Rules.TypeLibraries) { AdditionalPrerequisites.Add(FileItem.GetItemByFileReference(FileReference.Combine(IntermediateDirectory, TypeLibrary.Header))); if (!String.IsNullOrEmpty(TypeLibrary.Include)) { AdditionalPrerequisites.Add(FileItem.GetItemByFileReference(FileReference.Combine(IntermediateDirectory, TypeLibrary.Include))); } } } } /// /// Sets up the environment for compiling this module. /// protected virtual void SetupPrivateCompileEnvironment( HashSet IncludePaths, HashSet SystemIncludePaths, HashSet ModuleInterfacePaths, List Definitions, List AdditionalFrameworks, List AdditionalPrerequisites, bool bWithLegacyPublicIncludePaths, bool bWithLegacyParentIncludePaths ) { // Add this module's private include paths and definitions. IncludePaths.UnionWith(PrivateIncludePaths); // Find all the modules that are part of the public compile environment for this module. Dictionary ModuleToIncludePathsOnlyFlag = new Dictionary(); FindModulesInPrivateCompileEnvironment(ModuleToIncludePathsOnlyFlag); // Now set up the compile environment for the modules in the original order that we encountered them foreach (UEBuildModule Module in ModuleToIncludePathsOnlyFlag.Keys) { Module.AddModuleToCompileEnvironment(this, Binary, IncludePaths, SystemIncludePaths, ModuleInterfacePaths, Definitions, AdditionalFrameworks, AdditionalPrerequisites, bWithLegacyPublicIncludePaths, bWithLegacyParentIncludePaths); } } /// /// Expand path variables within the context of this module /// /// Path to expand variables within /// Directory containing the binary that links this module. May be mull. /// Directory containing the output executable. May be null. /// The path with variables expanded public string ExpandPathVariables(string Path, DirectoryReference? BinaryOutputDir, DirectoryReference? TargetOutputDir) { if (Path.StartsWith("$(", StringComparison.Ordinal)) { int StartIdx = 2; for (int EndIdx = StartIdx; EndIdx < Path.Length; EndIdx++) { if (Path[EndIdx] == ')') { if (MatchVariableName(Path, StartIdx, EndIdx, "EngineDir")) { Path = Unreal.EngineDirectory + Path.Substring(EndIdx + 1); } else if (MatchVariableName(Path, StartIdx, EndIdx, "ProjectDir")) { if (Rules.Target.ProjectFile == null) { Path = Unreal.EngineDirectory + Path.Substring(EndIdx + 1); } else { Path = Rules.Target.ProjectFile.Directory + Path.Substring(EndIdx + 1); } } else if (MatchVariableName(Path, StartIdx, EndIdx, "ModuleDir")) { Path = Rules.ModuleDirectory + Path.Substring(EndIdx + 1); } else if (MatchVariableName(Path, StartIdx, EndIdx, "PluginDir")) { Path = Rules.PluginDirectory + Path.Substring(EndIdx + 1); } else if (BinaryOutputDir != null && MatchVariableName(Path, StartIdx, EndIdx, "BinaryOutputDir")) { Path = BinaryOutputDir.FullName + Path.Substring(EndIdx + 1); } else if (TargetOutputDir != null && MatchVariableName(Path, StartIdx, EndIdx, "TargetOutputDir")) { Path = TargetOutputDir.FullName + Path.Substring(EndIdx + 1); } else { string Name = Path.Substring(StartIdx, EndIdx - StartIdx); string? Value = Environment.GetEnvironmentVariable(Name); if (String.IsNullOrEmpty(Value)) { throw new BuildException("Environment variable '{0}' is not defined (referenced by {1})", Name, Rules.File); } Path = Value + Path.Substring(EndIdx + 1); } break; } } } return Path; } /// /// Match a variable name within a path /// /// The path variable /// Start index of the substring to match /// End index of the substring to match /// Variable name to compare against /// True if the variable name matches private bool MatchVariableName(string Path, int StartIdx, int EndIdx, string Name) { return Name.Length == EndIdx - StartIdx && String.Compare(Path, StartIdx, Name, 0, EndIdx - StartIdx) == 0; } /// /// Expand path variables within the context of this module /// /// Path to expand variables within /// Directory containing the binary that links this module. May be mull. /// Directory containing the output executable. May be null. /// The path with variables expanded private IEnumerable ExpandPathVariables(IEnumerable Paths, DirectoryReference? BinaryDir, DirectoryReference? ExeDir) { foreach (string Path in Paths) { yield return ExpandPathVariables(Path, BinaryDir, ExeDir); } } /// /// Sets up the environment for linking any module that includes the public interface of this module. /// protected virtual void SetupPublicLinkEnvironment( UEBuildBinary? SourceBinary, List Libraries, List SystemLibraryPaths, List SystemLibraries, List RuntimeLibraryPaths, List Frameworks, List WeakFrameworks, List AdditionalFrameworks, List AdditionalBundleResources, List DelayLoadDLLs, List BinaryDependencies, Dictionary> DependenciesToSkip, HashSet VisitedModules, DirectoryReference ExeDir, CppRootPaths RootPaths, ILogger Logger ) { // There may be circular dependencies in compile dependencies, so we need to avoid reentrance. if (VisitedModules.Add(this)) { // Add this module's binary to the binary dependencies. if (Binary != null && Binary != SourceBinary && !BinaryDependencies.Contains(Binary)) { BinaryDependencies.Add(Binary); } RootPaths.AddExtraPath(Rules.ExtraRootPath); // If this module belongs to a static library that we are not currently building, recursively add the link environment settings for all of its dependencies too. // Keep doing this until we reach a module that is not part of a static library (or external module, since they have no associated binary). // Static libraries do not contain the symbols for their dependencies, so we need to recursively gather them to be linked into other binary types. bool bIsBuildingAStaticLibrary = (SourceBinary != null && SourceBinary.Type == UEBuildBinaryType.StaticLibrary); bool bIsModuleBinaryAStaticLibrary = (Binary != null && Binary.Type == UEBuildBinaryType.StaticLibrary); if (!bIsBuildingAStaticLibrary && bIsModuleBinaryAStaticLibrary) { // Gather all dependencies and recursively call SetupPublicLinkEnvironmnet List AllDependencyModules = new List(); AllDependencyModules.AddRange(PrivateDependencyModules!); AllDependencyModules.AddRange(PublicDependencyModules!); foreach (UEBuildModule DependencyModule in AllDependencyModules) { bool bIsExternalModule = (DependencyModule as UEBuildModuleExternal != null); bool bIsInStaticLibrary = (DependencyModule.Binary != null && DependencyModule.Binary.Type == UEBuildBinaryType.StaticLibrary); if (bIsExternalModule || bIsInStaticLibrary) { DependencyModule.SetupPublicLinkEnvironment(SourceBinary, Libraries, SystemLibraryPaths, SystemLibraries, RuntimeLibraryPaths, Frameworks, WeakFrameworks, AdditionalFrameworks, AdditionalBundleResources, DelayLoadDLLs, BinaryDependencies, DependenciesToSkip, VisitedModules, ExeDir, RootPaths, Logger); } } } // Add this module's public include library paths and additional libraries. Libraries.AddRange(PublicLibraries); SystemLibraryPaths.AddRange(PublicSystemLibraryPaths); SystemLibraries.AddRange(PublicSystemLibraries); RuntimeLibraryPaths.AddRange(ExpandPathVariables(Rules.PublicRuntimeLibraryPaths, SourceBinary?.OutputDir, ExeDir)); Frameworks.AddRange(PublicFrameworks); WeakFrameworks.AddRange(PublicWeakFrameworks); AdditionalBundleResources.AddRange(PublicAdditionalBundleResources); AdditionalFrameworks.AddRange(PublicAdditionalFrameworks); DelayLoadDLLs.AddRange(PublicDelayLoadDLLs); // merge in to the outgoing dictionary foreach (KeyValuePair> Pair in DependenciesToSkipPerArchitecture) { if (!DependenciesToSkip.ContainsKey(Pair.Key)) { DependenciesToSkip[Pair.Key] = new HashSet(); } DependenciesToSkip[Pair.Key] = DependenciesToSkip[Pair.Key].Union(Pair.Value).ToHashSet(); } } } /// /// Sets up the environment for linking this module. /// public virtual void SetupPrivateLinkEnvironment( UEBuildBinary SourceBinary, LinkEnvironment LinkEnvironment, List BinaryDependencies, HashSet VisitedModules, DirectoryReference ExeDir, ILogger Logger ) { // Add the private rpaths LinkEnvironment.RuntimeLibraryPaths.AddRange(ExpandPathVariables(Rules.PrivateRuntimeLibraryPaths, SourceBinary.OutputDir, ExeDir)); // Allow the module's public dependencies to add library paths and additional libraries to the link environment. SetupPublicLinkEnvironment(SourceBinary, LinkEnvironment.Libraries, LinkEnvironment.SystemLibraryPaths, LinkEnvironment.SystemLibraries, LinkEnvironment.RuntimeLibraryPaths, LinkEnvironment.Frameworks, LinkEnvironment.WeakFrameworks, LinkEnvironment.AdditionalFrameworks, LinkEnvironment.AdditionalBundleResources, LinkEnvironment.DelayLoadDLLs, BinaryDependencies, LinkEnvironment.DependenciesToSkipPerArchitecture, VisitedModules, ExeDir, LinkEnvironment.RootPaths, Logger); // Also allow the module's public and private dependencies to modify the link environment. List AllDependencyModules = new List(); AllDependencyModules.AddRange(PrivateDependencyModules!); AllDependencyModules.AddRange(PublicDependencyModules!); foreach (UEBuildModule DependencyModule in AllDependencyModules) { DependencyModule.SetupPublicLinkEnvironment(SourceBinary, LinkEnvironment.Libraries, LinkEnvironment.SystemLibraryPaths, LinkEnvironment.SystemLibraries, LinkEnvironment.RuntimeLibraryPaths, LinkEnvironment.Frameworks, LinkEnvironment.WeakFrameworks, LinkEnvironment.AdditionalFrameworks, LinkEnvironment.AdditionalBundleResources, LinkEnvironment.DelayLoadDLLs, BinaryDependencies, LinkEnvironment.DependenciesToSkipPerArchitecture, VisitedModules, ExeDir, LinkEnvironment.RootPaths, Logger); } // Add all the additional properties LinkEnvironment.AdditionalProperties.AddRange(Rules.AdditionalPropertiesForReceipt.Inner); // this is a link-time property that needs to be accumulated (if any modules contributing to this module is ignoring, all are ignoring) LinkEnvironment.bIgnoreUnresolvedSymbols |= Rules.bIgnoreUnresolvedSymbols; } /// /// Compiles the module, and returns a list of files output by the compiler. /// public virtual List Compile(ReadOnlyTargetRules Target, UEToolChain ToolChain, CppCompileEnvironment CompileEnvironment, ISourceFileWorkingSet WorkingSet, IActionGraphBuilder Graph, ILogger Logger) { if (Rules.TypeLibraries.Count == 0) { return []; } CppCompileEnvironment ModuleCompileEnvironment = new CppCompileEnvironment(CompileEnvironment); IEnumerable AllIncludePaths = [.. ModuleCompileEnvironment.UserIncludePaths, .. ModuleCompileEnvironment.SystemIncludePaths]; if (Target.ProjectFile != null && (Target.BuildEnvironment != TargetBuildEnvironment.Shared || AllIncludePaths.Any(x => x.IsUnderDirectory(Target.ProjectFile.Directory)))) { ModuleCompileEnvironment.RootPaths[CppRootPathFolder.Project] = Target.ProjectFile.Directory; } ModuleCompileEnvironment.RootPaths.AddExtraPath(Rules.ExtraRootPath); ModuleCompileEnvironment.RootPaths.AddExtraPath(("System32", Environment.SystemDirectory)); // Needed for cmd.exe // Generate type libraries for Windows foreach (ModuleRules.TypeLibrary TypeLibrary in Rules.TypeLibraries) { FileReference OutputFile = FileReference.Combine(IntermediateDirectory, TypeLibrary.Header); FileReference? OutputInclude = TypeLibrary.Include != null ? FileReference.Combine(IntermediateDirectory, TypeLibrary.Include) : null; ToolChain.GenerateTypeLibraryHeader(ModuleCompileEnvironment, TypeLibrary, OutputFile, OutputInclude, Graph); } return []; } public IEnumerable CopyDebuggerVisualizers(UEToolChain ToolChain, IActionGraphBuilder Graph, ILogger Logger) { List Results = new(); foreach (FileItem NatvisSourceFile in NatvisFiles) { FileItem? Item = ToolChain.CopyDebuggerVisualizer(NatvisSourceFile, IntermediateDirectory, Graph); if (Item != null) { Results.Add(Item); } } return Results; } public void LinkDebuggerVisualizers(List OutFiles, UEToolChain ToolChain, ILogger Logger) { foreach (FileItem NatvisSourceFile in NatvisFiles) { FileItem? Item = ToolChain.LinkDebuggerVisualizer(NatvisSourceFile, IntermediateDirectory); if (Item != null) { OutFiles.Add(Item); } } } // Object interface. public override string ToString() { return Name; } /// /// Finds the modules referenced by this module which have not yet been bound to a binary /// /// List of unbound modules public List GetUnboundReferences() { List Modules = new List(); Modules.AddRange(PrivateDependencyModules!.Where(x => x.Binary == null)); Modules.AddRange(PublicDependencyModules!.Where(x => x.Binary == null)); return Modules; } /// /// Gets all of the modules referenced by this module /// /// Hash of all referenced modules with their addition index. /// Hashset used to ignore modules which are already added to the list /// True if dynamically loaded modules (and all of their dependent modules) should be included. /// True if circular dependencies should be processed /// True to return only this module's direct dependencies public void GetAllDependencyModules(List ReferencedModules, HashSet IgnoreReferencedModules, bool bIncludeDynamicallyLoaded, bool bForceCircular, bool bOnlyDirectDependencies) { List AllDependencyModules = new List(PrivateDependencyModules!.Count + PublicDependencyModules!.Count + (bIncludeDynamicallyLoaded ? DynamicallyLoadedModules!.Count : 0)); AllDependencyModules.AddRange(PrivateDependencyModules!); AllDependencyModules.AddRange(PublicDependencyModules!); if (bIncludeDynamicallyLoaded) { AllDependencyModules.AddRange(DynamicallyLoadedModules!); } foreach (UEBuildModule DependencyModule in AllDependencyModules) { // Don't follow circular back-references! if (bForceCircular || !HasCircularDependencyOn(DependencyModule)) { if (IgnoreReferencedModules.Add(DependencyModule)) { if (!bOnlyDirectDependencies) { // Recurse into dependent modules first DependencyModule.GetAllDependencyModules(ReferencedModules, IgnoreReferencedModules, bIncludeDynamicallyLoaded, bForceCircular, bOnlyDirectDependencies); } ReferencedModules.Add(DependencyModule); } } } } /// /// Gathers all the module dependencies a PCH would have /// /// Whether to include private modules. /// True if circular dependencies should be processed public HashSet GetAllDependencyModulesForPCH(bool bIncludePrivateModules, bool bForceCircular) { HashSet ReferencedModules = new HashSet(); HashSet IgnoreReferencedModules = new HashSet { this }; InternalGetAllDependencyModulesForPCH(ReferencedModules, IgnoreReferencedModules, bIncludePrivateModules, bForceCircular); return ReferencedModules; } /// /// Internal function that gathers all the module dependencies a PCH would have /// /// Hash of all referenced modules with their addition index. /// Hashset used to ignore modules which are already added to the list /// Whether to include private modules. /// True if circular dependencies should be processed private void InternalGetAllDependencyModulesForPCH(HashSet ReferencedModules, HashSet IgnoreReferencedModules, bool bIncludePrivateModules, bool bForceCircular) { List AllDependencyModules = new List( ((bIncludePrivateModules && PrivateDependencyModules != null) ? PrivateDependencyModules.Count : 0) + (PublicDependencyModules != null ? PublicDependencyModules.Count : 0) + (PublicIncludePathModules != null ? PublicIncludePathModules!.Count : 0) ); if (bIncludePrivateModules && PrivateDependencyModules != null) { AllDependencyModules.AddRange(PrivateDependencyModules); } if (PublicDependencyModules != null) { AllDependencyModules.AddRange(PublicDependencyModules!); } if (PublicIncludePathModules != null) { AllDependencyModules.AddRange(PublicIncludePathModules); } foreach (UEBuildModule DependencyModule in AllDependencyModules.Distinct()) { // Don't follow circular back-references! if (bForceCircular || !HasCircularDependencyOn(DependencyModule)) { if (IgnoreReferencedModules.Add(DependencyModule)) { // Recurse into dependent modules first DependencyModule.InternalGetAllDependencyModulesForPCH(ReferencedModules, IgnoreReferencedModules, false, bForceCircular); ReferencedModules.Add(DependencyModule); } } } } public delegate UEBuildModule CreateModuleDelegate(string Name, string ReferenceChain); /// /// Public entry point to recursively create a module and all its dependencies /// /// /// /// public void RecursivelyCreateModules(CreateModuleDelegate CreateModule, string ReferenceChain, ILogger Logger) { List ReferenceStack = new List(); RecursivelyCreateModules(CreateModule, ReferenceChain, ReferenceStack, Logger); } /// /// Creates all the modules required for this target /// /// Delegate to create a module with a given name /// Chain of references before reaching this module /// ParentStack of module dependencies that led to this module /// Logger for output protected void RecursivelyCreateModules(CreateModuleDelegate CreateModule, string ReferenceChain, List ReferenceStack, ILogger Logger) { // Name of this reference string ThisRefName = (RulesFile == null) ? Name : RulesFile.GetFileName(); // Set the reference chain for anything referenced by this module string NextReferenceChain = String.Format("{0} -> {1}", ReferenceChain, ThisRefName); // Add us to the reference stack ReferenceStack.Add(this); // Check if this module is invalid for the current target, based on Module attributes if (Rules.CppCompileWarningSettings.ModuleUnsupportedWarningLevel != WarningLevel.Off) { if (!ModuleRules.IsValidForTarget(Rules.GetType(), Rules.Target, out string? InvalidReason)) { if (Rules.CppCompileWarningSettings.ModuleUnsupportedWarningLevel == WarningLevel.Warning) { Logger.LogWarning("Warning: Referenced module '{Module}' not supported '{InvalidReason}' via {ReferenceChain}", ThisRefName, InvalidReason, NextReferenceChain); } else if (Rules.CppCompileWarningSettings.ModuleUnsupportedWarningLevel == WarningLevel.Error) { Logger.LogError("Error: Referenced module '{Module}' not supported '{InvalidReason}' via {ReferenceChain}", ThisRefName, InvalidReason, NextReferenceChain); } } } // Check if this module is invalid for the current target, based on Plugin attributes if (Rules.CppCompileWarningSettings.PluginModuleUnsupportedWarningLevel != WarningLevel.Off && Rules.Plugin != null) { if (!Rules.Plugin.Descriptor.IsModuleCompiledInConfiguration(Rules.Name, Rules.Target.Platform, Rules.Target.Configuration, Rules.Target.Name, Rules.Target.Type, Rules.Target.bBuildDeveloperTools, Rules.Target.bBuildRequiresCookedData, out string? InvalidReason)) { if (Rules.CppCompileWarningSettings.PluginModuleUnsupportedWarningLevel == WarningLevel.Warning) { Logger.LogWarning("Warning: Referenced plugin '{Plugin}' module '{Module}' not supported '{InvalidReason}' via {ReferenceChain}", Rules.Plugin?.File.GetFileName(), ThisRefName, InvalidReason, NextReferenceChain); } else if (Rules.CppCompileWarningSettings.PluginModuleUnsupportedWarningLevel == WarningLevel.Error) { Logger.LogError("Error: Referenced plugin '{Plugin}' module '{Module}' not supported '{InvalidReason}' via {ReferenceChain}", Rules.Plugin?.File.GetFileName(), ThisRefName, InvalidReason, NextReferenceChain); } } } // Create all the referenced modules. This path can be recursive, so we check against PublicDependencyModules to ensure we don't recurse through the // same module twice (it produces better errors if something fails). if (PublicDependencyModules == null) { // Log dependencies if required if (Logger.IsEnabled((LogLevel)LogEventType.VeryVerbose)) { Logger.LogTrace("Module {Name} dependencies:", Name); LogDependencyNameList("Public:", Rules.PublicDependencyModuleNames, Logger); LogDependencyNameList("Private:", Rules.PrivateDependencyModuleNames, Logger); LogDependencyNameList("Dynamic:", Rules.DynamicallyLoadedModuleNames, Logger); LogDependencyNameList("Public Include Paths:", Rules.PublicIncludePathModuleNames, Logger); LogDependencyNameList("Private Include Paths:", Rules.PrivateIncludePathModuleNames, Logger); } ReferenceStackParentModules ??= new(); // Create all the dependency modules - pass through the reference stack so we can check for cycles RecursivelyCreateModulesByName(Rules.PublicDependencyModuleNames, ref PublicDependencyModules, ref bDependsOnVerse, CreateModule, NextReferenceChain, ReferenceStack, Logger); if (Rules.Target.IsTestTarget) { // Move the test runner dependency to last position to give it the opportunity to build its special dependencies such as CoreUObject, ApplicationCore etc. MoveTestsRunnerDependencyToLastPosition(); } RecursivelyCreateModulesByName(Rules.PrivateDependencyModuleNames, ref PrivateDependencyModules, ref bDependsOnVerse, CreateModule, NextReferenceChain, ReferenceStack, Logger); // Recursively create all the public include path modules HashSet PublicIncludePathModuleNames = new(); PublicIncludePathModuleNames.UnionWith(Rules.PublicIncludePathModuleNames!); PublicIncludePathModuleNames.UnionWith(Rules.PublicDependencyModuleNames!); RecursivelyCreateIncludePathModulesByName(PublicIncludePathModuleNames, ref PublicIncludePathModules, ref bDependsOnVerse, CreateModule, NextReferenceChain); // Create the private include path modules RecursivelyCreateIncludePathModulesByName(Rules.PrivateIncludePathModuleNames, ref PrivateIncludePathModules, ref bDependsOnVerse, CreateModule, NextReferenceChain); // Dynamic loads aren't considered a reference chain so start with an empty stack RecursivelyCreateModulesByName(Rules.DynamicallyLoadedModuleNames, ref DynamicallyLoadedModules, ref bDependsOnVerse, CreateModule, NextReferenceChain, new List(), Logger); PublicDependencyModules?.ForEach(x => x.ReferenceStackParentModules?.Add(this)); PrivateDependencyModules?.ForEach(x => x.ReferenceStackParentModules?.Add(this)); } // pop us off the current stack ReferenceStack.RemoveAt(ReferenceStack.Count - 1); } record CircularReference { public List ReferenceStack { get; } public Dictionary GuiltyModules { get; } = new(); public bool IsError => !GuiltyModules.Any(); public CircularReference(List referenceStack) => ReferenceStack = referenceStack; public void AddGuiltyModule(UEBuildModule guiltyModule, UEBuildModule victimModule) => GuiltyModules.TryAdd(guiltyModule, victimModule); } /// /// Public entry point to validate a module /// /// public virtual bool ValidateModule(ILogger logger) { bool anyErrors = false; // Report any [Obsolete("...")] messages from referenced modules. IEnumerable obsoleteAttributes = Rules.GetType().GetCustomAttributes(); if (obsoleteAttributes.Any()) { logger.LogWarning(KnownLogEvents.Validation, "Referenced Module '{Name}' is obsolete: '{Message}'", Name, String.Join(", ", obsoleteAttributes.Select(x => x.Message ?? ""))); } // Check if this module is invalid for the current target, based on Module attributes if (Rules.CppCompileWarningSettings.ModuleUnsupportedWarningLevel == WarningLevel.Error && !ModuleRules.IsValidForTarget(Rules.GetType(), Rules.Target, out string? _)) { anyErrors = true; } if (Rules.CppCompileWarningSettings.PluginModuleUnsupportedWarningLevel == WarningLevel.Error && Rules.Plugin != null) { if (!Rules.Plugin.Descriptor.IsModuleCompiledInConfiguration(Rules.Name, Rules.Target.Platform, Rules.Target.Configuration, Rules.Target.Name, Rules.Target.Type, Rules.Target.bBuildDeveloperTools, Rules.Target.bBuildRequiresCookedData, out string? _)) { anyErrors = true; } } anyErrors = ValidateCircularReferences(logger) || anyErrors; return anyErrors; } /// /// Validate a module for circular references /// /// /// If any circular references are found bool ValidateCircularReferences(ILogger logger) { List circularReferences = []; CheckForCircularReferences(this, [], [], circularReferences); IEnumerable allowed = circularReferences.Where(x => !x.IsError).OrderBy(x => x.ReferenceStack.Count); IEnumerable errors = circularReferences.Where(x => x.IsError).OrderBy(x => x.ReferenceStack.Count); if (errors.Any()) { logger.LogInformation(KnownLogEvents.Validation, "Circular dependency for '{RulesFile}' detected:", RulesFile.MakeRelativeTo(Unreal.EngineDirectory)); foreach (CircularReference circularReference in errors) { logger.LogInformation(KnownLogEvents.Validation, "* {ReferenceStack}", String.Join(" -> ", circularReference.ReferenceStack)); } foreach (FileReference rulesFile in errors.SelectMany(x => x.ReferenceStack).Select(x => x.RulesFile).Distinct()) { logger.LogError(KnownLogEvents.CircularDependency, "Circular dependency in '{Module}' possibly due to '{RulesFile}'", Name, rulesFile); } logger.LogInformation(KnownLogEvents.Validation, "Break this loop by moving dependencies into a separate module or using Private/PublicIncludePathModuleNames to reference declarations"); } if (allowed.Any()) { logger.LogDebug(KnownLogEvents.Validation, "Allowed circular dependency for '{RulesFile}' detected:", RulesFile.MakeRelativeTo(Unreal.EngineDirectory)); foreach (CircularReference circularReference in allowed) { logger.LogDebug(KnownLogEvents.Validation, "* {ReferenceStack} (allowed via {GuiltyModules})", String.Join(" -> ", circularReference.ReferenceStack), String.Join(", ", circularReference.GuiltyModules.Select(x => $"{x.Key} -> {x.Value}"))); } } return errors.Any(); } /// /// Recursively check a module for circular references /// /// The module currently being checked /// Set of modules already processed /// The current parent reference stack (in reverse order) /// List of found circular reference stacks void CheckForCircularReferences(UEBuildModule module, HashSet processed, List parentStack, List circularReferences) { // If this module has no references or has been processed already, nothing to do if (ReferenceStackParentModules == null || !processed.Add(this)) { return; } // Check for a circular reference if (ReferenceStackParentModules.Contains(module)) { List referenceStack = new(parentStack); referenceStack.Add(this); referenceStack.Add(module); referenceStack.Reverse(); CircularReference circularReference = new(referenceStack); for (int i = 0; i < referenceStack.Count - 1; i++) { UEBuildModule referringModule = referenceStack.ElementAt(i); UEBuildModule targetModule = referenceStack.ElementAt(i + 1); // If module has confessed its guilt this is not an error if (referringModule.HasCircularDependencyOn(targetModule)) { circularReference.AddGuiltyModule(referringModule, targetModule); } else if (targetModule.HasCircularDependencyOn(referringModule)) { circularReference.AddGuiltyModule(targetModule, referringModule); } } circularReferences.Add(circularReference); return; } // Push this module to the stack parentStack.Add(this); // Check very module that references this module until all are processed foreach (UEBuildModule ParentModule in ReferenceStackParentModules.OrderBy(x => x.Name)) { ParentModule.CheckForCircularReferences(module, processed, parentStack, circularReferences); } // Pop this module from the stack parentStack.RemoveAt(parentStack.Count - 1); } private void MoveTestsRunnerDependencyToLastPosition() { if (Rules.PrivateDependencyModuleNames.Contains("LowLevelTestsRunner")) { Rules.PrivateDependencyModuleNames.Remove("LowLevelTestsRunner"); Rules.PrivateDependencyModuleNames.Add("LowLevelTestsRunner"); } } private static void LogDependencyNameList(string Title, List DependencyNameList, ILogger logger) { logger.LogTrace(" {Title}", Title); foreach (string name in DependencyNameList) { logger.LogTrace(" {Name}", name); } } private static void RecursivelyCreateModulesByName(List ModuleNames, ref List? Modules, ref bool bDependsOnVerse, CreateModuleDelegate CreateModule, string ReferenceChain, List ReferenceStack, ILogger Logger) { // Check whether the module list is already set. We set this immediately (via the ref) to avoid infinite recursion. if (Modules == null) { Modules = new List(); for (int i = 0; i < ModuleNames.Count; i++) { string ModuleName = ModuleNames[i]; UEBuildModule Module = CreateModule(ModuleName, ReferenceChain); if (!Modules.Contains(Module)) { Module.RecursivelyCreateModules(CreateModule, ReferenceChain, ReferenceStack, Logger); Modules.Add(Module); bDependsOnVerse |= Module.bDependsOnVerse; } } } } private static void RecursivelyCreateIncludePathModulesByName(IEnumerable ModuleNames, ref List? Modules, ref bool bDependsOnVerse, CreateModuleDelegate CreateModule, string ReferenceChain) { // Check whether the module list is already set. We set this immediately (via the ref) to avoid infinite recursion. if (Modules == null) { Modules = new List(); foreach (string ModuleName in ModuleNames) { UEBuildModule Module = CreateModule(ModuleName, ReferenceChain); if (!Modules.Contains(Module)) { // Name of this reference string ModuleRefName = (Module.RulesFile == null) ? Module.Name : Module.RulesFile.GetFileName(); // Set the reference chain for anything referenced by this module string NextReferenceChain = String.Format("{0} -> {1} (public include)", ReferenceChain, ModuleRefName); HashSet PublicIncludePathModuleNames = new(); PublicIncludePathModuleNames.UnionWith(Module.Rules.PublicIncludePathModuleNames); PublicIncludePathModuleNames.UnionWith(Module.Rules.PublicDependencyModuleNames); RecursivelyCreateIncludePathModulesByName(PublicIncludePathModuleNames, ref Module.PublicIncludePathModules, ref Module.bDependsOnVerse, CreateModule, NextReferenceChain); Modules.Add(Module); bDependsOnVerse |= Module.bDependsOnVerse; } } } } /// /// Returns valueless API defines (like MODULE_API) /// public IEnumerable GetEmptyApiMacros() { if (Rules.Type == ModuleRules.ModuleType.CPlusPlus) { return new[] { ModuleApiDefine + "=" }; } return Array.Empty(); } /// /// Write information about this binary to a JSON file /// /// The output directory for the binary containing this module /// The output directory for the target executable /// Writer for this binary's data public virtual void ExportJson(DirectoryReference? BinaryOutputDir, DirectoryReference? TargetOutputDir, JsonWriter Writer) { Writer.WriteValue("Name", Name); Writer.WriteValue("Type", Rules.Type.ToString()); Writer.WriteValue("Directory", ModuleDirectory.FullName); Writer.WriteValue("Rules", RulesFile.FullName); Writer.WriteValue("PCHUsage", Rules.PCHUsage.ToString()); ExportJsonStringArray(Writer, "ForceIncludeModules", Rules.ForceIncludeFiles); if (Rules.PrivatePCHHeaderFile != null) { Writer.WriteValue("PrivatePCH", FileReference.Combine(ModuleDirectory, Rules.PrivatePCHHeaderFile).FullName); } if (Rules.SharedPCHHeaderFile != null) { Writer.WriteValue("SharedPCH", FileReference.Combine(ModuleDirectory, Rules.SharedPCHHeaderFile).FullName); } Writer.WriteValue("ChainSharedPCH", Rules.Target.bChainPCHs); ExportJsonModuleArray(Writer, "PublicDependencyModules", PublicDependencyModules); ExportJsonModuleArray(Writer, "PublicIncludePathModules", PublicIncludePathModules); ExportJsonModuleArray(Writer, "PrivateDependencyModules", PrivateDependencyModules); ExportJsonModuleArray(Writer, "PrivateIncludePathModules", PrivateIncludePathModules); ExportJsonModuleArray(Writer, "DynamicallyLoadedModules", DynamicallyLoadedModules); ExportJsonStringArray(Writer, "PublicSystemIncludePaths", PublicSystemIncludePaths.Select(x => x.FullName)); ExportJsonStringArray(Writer, "PublicIncludePaths", PublicIncludePaths.Select(x => x.FullName)); ExportJsonStringArray(Writer, "InternalIncludePaths", PublicIncludePaths.Select(x => x.FullName)); ExportJsonStringArray(Writer, "PrivateIncludePaths", PrivateIncludePaths.Select(x => x.FullName)); ExportJsonStringArray(Writer, "PublicLibraries", PublicLibraries.Select(x => x.FullName)); ExportJsonStringArray(Writer, "PublicSystemLibraries", PublicSystemLibraries); ExportJsonStringArray(Writer, "PublicSystemLibraryPaths", PublicSystemLibraryPaths.Select(x => x.FullName)); ExportJsonStringArray(Writer, "PublicFrameworks", PublicFrameworks); ExportJsonStringArray(Writer, "PublicWeakFrameworks", PublicWeakFrameworks); ExportJsonStringArray(Writer, "PublicDelayLoadDLLs", PublicDelayLoadDLLs); ExportJsonStringArray(Writer, "PublicDefinitions", PublicDefinitions); Writer.WriteArrayStart("CircularlyReferencedModules"); foreach (string ModuleName in Rules.CircularlyReferencedDependentModules) { Writer.WriteValue(ModuleName); } Writer.WriteArrayEnd(); // Don't add runtime dependencies for modules that aren't being linked in. They may reference BinaryOutputDir, which is invalid. if (Binary != null) { Writer.WriteArrayStart("RuntimeDependencies"); foreach (ModuleRules.RuntimeDependency RuntimeDependency in Rules.RuntimeDependencies.Inner) { Writer.WriteObjectStart(); Writer.WriteValue("Path", ExpandPathVariables(RuntimeDependency.Path, BinaryOutputDir, TargetOutputDir)); if (RuntimeDependency.SourcePath != null) { Writer.WriteValue("SourcePath", ExpandPathVariables(RuntimeDependency.SourcePath, BinaryOutputDir, TargetOutputDir)); } Writer.WriteValue("Type", RuntimeDependency.Type.ToString()); Writer.WriteObjectEnd(); } Writer.WriteArrayEnd(); } } /// /// Write an array of module names to a JSON writer /// /// Writer for the array data /// Name of the array property /// Sequence of modules to write. May be null. void ExportJsonModuleArray(JsonWriter Writer, string ArrayName, IEnumerable? Modules) { Writer.WriteArrayStart(ArrayName); if (Modules != null) { foreach (UEBuildModule Module in Modules) { Writer.WriteValue(Module.Name); } } Writer.WriteArrayEnd(); } /// /// Write an array of strings to a JSON writer /// /// Writer for the array data /// Name of the array property /// Sequence of strings to write. May be null. void ExportJsonStringArray(JsonWriter Writer, string ArrayName, IEnumerable? Strings) { Writer.WriteArrayStart(ArrayName); if (Strings != null) { foreach (string String in Strings) { Writer.WriteValue(String); } } Writer.WriteArrayEnd(); } /// /// Returns a copy of Nodes sorted by dependency. Independent or circularly-dependent nodes should /// remain in their same relative order within the original Nodes sequence. /// /// The list of nodes to sort. public static List StableTopologicalSort(List NodeList) { int NodeCount = NodeList.Count; // For each Node in NodeList, populated with the full circular dependency list from // Node.GetAllDependencyModules() List>> NodeDependencies = new List>>(NodeCount); // Used to populate an element of NodeDependencies HashSet FetchDependencies(int NodeIndex) { HashSet Dependencies = new HashSet(); NodeList[NodeIndex].GetAllDependencyModules(new List(), Dependencies, true, true, false); return Dependencies; } // For each Node in NodeList, populated with the nodes with a lower index in NodeList that Node depends on List>> PrecedingDependents = new List>>(NodeCount); HashSet ComputePrecedingDependents(int NodeIndex) { HashSet Results = new HashSet(); UEBuildModule Node = NodeList[NodeIndex]; HashSet Dependencies = NodeDependencies[NodeIndex].Result; for (int I = 0; I < NodeIndex; ++I) { if (NodeDependencies[I].Result.Contains(Node) && !Dependencies.Contains(NodeList[I])) { Results.Add(NodeList[I]); } } return Results; } for (int I = 0; I < NodeCount; ++I) { int LocalI = I; NodeDependencies.Add(Task.Run(() => FetchDependencies(LocalI))); PrecedingDependents.Add(Task.Run(() => ComputePrecedingDependents(LocalI))); } List Out = new List(NodeCount); // Write the ordered output for (int Index1 = 0; Index1 != NodeCount; ++Index1) { UEBuildModule Node1 = NodeList[Index1]; HashSet NodesThatDependOnNode1 = PrecedingDependents[Index1].Result; Out.Add(Node1); if (NodesThatDependOnNode1.Count == 0) { continue; } for (int Index2 = 0; Index2 != Index1; ++Index2) { UEBuildModule Node2 = Out[Index2]; if (NodesThatDependOnNode1.Contains(Node2)) { // Rotate element at Index1 into position at Index2 for (int Index3 = Index1; Index3 != Index2;) { --Index3; Out[Index3 + 1] = Out[Index3]; } Out[Index2] = Node1; // Break out of this loop, because this iteration must have covered all existing cases // involving the node formerly at position Index1 break; } } } return Out; } }; }