// Copyright Epic Games, Inc. All Rights Reserved. using IncludeTool.Support; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace IncludeTool { /// /// Reference to an output file, in the context of an include stack /// class OutputFileReference { /// /// The file to include /// public readonly OutputFile File; /// /// Fragments which it uniquely provides, which have not been included by a previous file. /// public readonly SourceFragment[] UniqueFragments; /// /// Constructor /// /// The file being included /// Unique fragments supplied by this file public OutputFileReference(OutputFile File, IEnumerable UniqueFragments) { this.File = File; this.UniqueFragments = UniqueFragments.ToArray(); } /// /// Convert to a string for the debugger /// /// Str public override string ToString() { return File.ToString(); } } /// /// An include directive from an output file /// class OutputFileInclude { /// /// The markup index in the source file /// public readonly int MarkupIdx; /// /// The file being included /// public readonly OutputFile TargetFile; /// /// The expanded list of unique output files matching the equivalent input file, which have not already been included by the file containing this #include directive /// public List ExpandedReferences; /// /// The expanded list of output files matching the equivalent input file /// public List FinalFiles = new List(); /// /// Constructor /// /// Index into the PreprocessorMarkup array for the file containing this include /// The file being included /// Whether this include can be removed from the file public OutputFileInclude(int MarkupIdx, OutputFile TargetFile) { this.MarkupIdx = MarkupIdx; this.TargetFile = TargetFile; } /// /// Converts this object to a string for the debugger /// /// String representation of this object public override string ToString() { return String.Format("{0}: {1}", MarkupIdx, TargetFile); } } /// /// Represents an optimized output file /// class OutputFile { /// /// The SourceFile that corresponds to this output file /// public readonly SourceFile InputFile; /// /// The other output files included from this file /// public readonly List Includes; /// /// Fragments which this output file has dependencies on /// public HashSet Dependencies; /// /// Symbols which need explicit forward declarations /// public List ForwardDeclarations; /// /// Fragments which are included through this file /// public HashSet IncludedFragments; /// /// All the files which are included by this file /// public HashList IncludedFiles; /// /// All the files which were originally included by this file /// public HashList OriginalIncludedFiles; /// /// Constructor /// public OutputFile(SourceFile InputFile, List Includes, HashSet Dependencies, List ForwardDeclarations) { this.InputFile = InputFile; this.Includes = Includes; this.Dependencies = Dependencies; this.ForwardDeclarations = ForwardDeclarations; // Build the list of satisfied dependencies IncludedFragments = new HashSet(); IncludedFragments.UnionWith(Includes.SelectMany(x => x.FinalFiles).SelectMany(x => x.IncludedFragments)); IncludedFragments.UnionWith(InputFile.Fragments); // Build the list of all the included files, so other output files that include it can expand it out. IncludedFiles = new HashList(); IncludedFiles.UnionWith(Includes.SelectMany(x => x.FinalFiles).SelectMany(x => x.IncludedFiles)); IncludedFiles.Add(this); // Build the list of all the originally included files, so that we can make files that include it standalone OriginalIncludedFiles = new HashList(); OriginalIncludedFiles.UnionWith(Includes.Select(x => x.TargetFile).Where(x => x != null).SelectMany(x => x.OriginalIncludedFiles)); OriginalIncludedFiles.Add(this); } /// /// Convert the object to a string for debugging purposes /// /// String representation of the object public override string ToString() { return InputFile.ToString(); } } /// /// Class to construct output files from optimized input files /// static class OutputFileBuilder { /// /// Create optimized output files from the given input files /// /// Input files to optimize /// Writer for log messages /// Array of output files public static void PrepareFilesForOutput(IEnumerable CppFiles, Dictionary CppFileToHeaderFile, Dictionary FwdSymbolToInputHeader, bool bMakeStandalone, bool bUseOriginalIncludes, LineBasedTextWriter Log) { // Cache of all the created output files Dictionary OutputFileLookup = new Dictionary(); // Create output files for all the forward declaration headers Dictionary FwdSymbolToHeader = new Dictionary(); foreach(KeyValuePair Pair in FwdSymbolToInputHeader) { List InputFileStack = new List(); InputFileStack.Add(Pair.Value); HashList IncludedFiles = new HashList(); FwdSymbolToHeader[Pair.Key] = FindOrCreateOutputFile(InputFileStack, CppFileToHeaderFile, IncludedFiles, OutputFileLookup, FwdSymbolToHeader, bMakeStandalone, bUseOriginalIncludes, Log); } // Create all the placeholder output files foreach(SourceFile CppFile in CppFiles) { List InputFileStack = new List(); InputFileStack.Add(CppFile); HashList IncludedFiles = new HashList(); FindOrCreateOutputFile(InputFileStack, CppFileToHeaderFile, IncludedFiles, OutputFileLookup, FwdSymbolToHeader, bMakeStandalone, bUseOriginalIncludes, Log); } // Set the results on the source files foreach(SourceFile File in OutputFileLookup.Keys) { OutputFile OutputFile = OutputFileLookup[File]; foreach(OutputFileInclude Include in OutputFile.Includes) { if(Include.MarkupIdx < 0) { File.MissingIncludes.AddRange(Include.FinalFiles.Select(x => x.InputFile)); } else { File.Markup[Include.MarkupIdx].OutputIncludedFiles = Include.FinalFiles.Select(x => x.InputFile).ToList(); } } foreach(Symbol Symbol in OutputFile.ForwardDeclarations) { if(!String.IsNullOrEmpty(Symbol.ForwardDeclaration)) { File.ForwardDeclarations.Add(Symbol.ForwardDeclaration); } } } } /// /// Find or create an output file for a corresponding input file /// /// The input file /// The current include stack /// List of output files /// Mapping from source file to output file /// The new or existing output file static OutputFile FindOrCreateOutputFile(List InputFileStack, Dictionary CppFileToHeaderFile, HashList PreviousFiles, Dictionary OutputFileLookup, Dictionary FwdSymbolToHeader, bool bMakeStandalone, bool bUseOriginalIncludes, LineBasedTextWriter Log) { // Get the file at the top of the stack SourceFile InputFile = InputFileStack[InputFileStack.Count - 1]; // Try to find an existing file OutputFile OutputFile; if(OutputFileLookup.TryGetValue(InputFile, out OutputFile)) { if(OutputFile == null) { throw new Exception("Circular include dependencies are not allowed."); } foreach(OutputFile IncludedFile in OutputFile.OriginalIncludedFiles.Where(x => !PreviousFiles.Contains(x))) { PreviousFiles.Add(IncludedFile); } } else { // Add a placeholder entry in the output file lookup, so we can detect circular include dependencies OutputFileLookup[InputFile] = null; // Duplicate the list of previously included files, so we can construct the List PreviousFilesCopy = new List(PreviousFiles); // Build a list of includes for this file. First include is a placeholder for any missing includes that need to be inserted. List Includes = new List(); if((InputFile.Flags & SourceFileFlags.External) == 0) { for(int MarkupIdx = 0; MarkupIdx < InputFile.Markup.Length; MarkupIdx++) { PreprocessorMarkup Markup = InputFile.Markup[MarkupIdx]; if(Markup.IsActive && Markup.IncludedFile != null && (Markup.IncludedFile.Flags & SourceFileFlags.Inline) == 0 && Markup.IncludedFile.Counterpart == null) { InputFileStack.Add(Markup.IncludedFile); OutputFile IncludeFile = FindOrCreateOutputFile(InputFileStack, CppFileToHeaderFile, PreviousFiles, OutputFileLookup, FwdSymbolToHeader, bMakeStandalone, bUseOriginalIncludes, Log); InputFileStack.RemoveAt(InputFileStack.Count - 1); Includes.Add(new OutputFileInclude(MarkupIdx, IncludeFile)); } } } // Find the matching header file OutputFile HeaderFile = null; if((InputFile.Flags & SourceFileFlags.TranslationUnit) != 0) { SourceFile CandidateHeaderFile; if(CppFileToHeaderFile.TryGetValue(InputFile, out CandidateHeaderFile) && (CandidateHeaderFile.Flags & SourceFileFlags.Standalone) != 0) { OutputFileLookup.TryGetValue(CandidateHeaderFile, out HeaderFile); } } // Create the output file. if((InputFile.Flags & SourceFileFlags.Output) != 0 && !bUseOriginalIncludes) { OutputFile = CreateOptimizedOutputFile(InputFile, HeaderFile, PreviousFilesCopy, Includes, InputFileStack, FwdSymbolToHeader, bMakeStandalone, Log); } else { OutputFile = CreatePassthroughOutputFile(InputFile, Includes, Log); } // Replace the null entry in the output file lookup that we added earlier OutputFileLookup[InputFile] = OutputFile; // Add this file to the list of included files PreviousFiles.Add(OutputFile); // If the output file dependends on something on the stack, make sure it's marked as pinned if((InputFile.Flags & SourceFileFlags.Pinned) == 0) { SourceFragment Dependency = OutputFile.Dependencies.FirstOrDefault(x => InputFileStack.Contains(x.File) && x.File != InputFile); if(Dependency != null) { throw new Exception(String.Format("'{0}' is not marked as pinned, but depends on '{1}' which includes it", InputFile.Location.GetFileName(), Dependency.UniqueName)); } } } return OutputFile; } /// /// Creates an optimized output file /// /// The input file that this output file corresponds to /// The corresponding header file /// List of files parsed before this one /// The active set of includes parsed for this file /// The active include stack /// /// Whether to make this output file standalone /// Writer for log messages public static OutputFile CreateOptimizedOutputFile(SourceFile InputFile, OutputFile HeaderFile, List PreviousFiles, List Includes, List InputFileStack, Dictionary FwdSymbolToHeader, bool bMakeStandalone, LineBasedTextWriter Log) { Debug.Assert(HeaderFile == null || (InputFile.Flags & SourceFileFlags.TranslationUnit) != 0); // Write the state InputFile.LogVerbose("InputFile={0}", InputFile.Location.FullName); InputFile.LogVerbose("InputFile.Flags={0}", InputFile.Flags.ToString()); if(HeaderFile != null) { InputFile.LogVerbose("HeaderFile={0}", HeaderFile.InputFile.Location.FullName); InputFile.LogVerbose("HeaderFile.Flags={0}", HeaderFile.InputFile.Flags.ToString()); } InputFile.LogVerbose(""); for(int Idx = 0; Idx < InputFileStack.Count; Idx++) { InputFile.LogVerbose("InputFileStack[{0}]={1}", Idx, InputFileStack[Idx].Location.FullName); } InputFile.LogVerbose(""); for(int Idx = 0; Idx < PreviousFiles.Count; Idx++) { InputFile.LogVerbose("PreviousFiles[{0}]={1}", Idx, PreviousFiles[Idx].InputFile.Location.FullName); } InputFile.LogVerbose(""); for(int Idx = 0; Idx < Includes.Count; Idx++) { } InputFile.LogVerbose(""); for(int Idx = 0; Idx < Includes.Count; Idx++) { OutputFileInclude Include = Includes[Idx]; InputFile.LogVerbose("Includes[{0}]={1}", Idx, Includes[Idx].TargetFile.InputFile.Location.FullName); foreach(SourceFragment Fragment in Include.FinalFiles.SelectMany(x => x.IncludedFragments)) { InputFile.LogVerbose("Includes[{0}].FinalFiles.IncludedFragments={1}", Idx, Fragment); } } // Traverse through all the included headers, figuring out the first unique include for each file and fragment HashSet VisitedFiles = new HashSet(); HashSet VisitedFragments = new HashSet(); // Go through the standalone headers first OutputFile MonolithicHeader = null; if(HeaderFile == null && (InputFile.Flags & SourceFileFlags.Standalone) != 0 && (InputFile.Flags & SourceFileFlags.External) == 0 && (InputFile.Flags & SourceFileFlags.Aggregate) == 0) { // Insert a dummy include to receive all the inserted headers OutputFileInclude ImplicitInclude = new OutputFileInclude(-1, null); ImplicitInclude.ExpandedReferences = new List(); Includes.Insert(0, ImplicitInclude); // Determine which monolithic header to use IEnumerable PotentialMonolithicHeaders = PreviousFiles.Union(Includes.Select(x => x.TargetFile).Where(x => x != null).SelectMany(x => x.IncludedFiles)); if(InputFile.Module != null && InputFile.Module.PublicDependencyModules.Union(InputFile.Module.PrivateDependencyModules).Any(x => x.Name == "Core")) { MonolithicHeader = PotentialMonolithicHeaders.FirstOrDefault(x => (x.InputFile.Flags & SourceFileFlags.IsCoreMinimal) != 0); } else { MonolithicHeader = PotentialMonolithicHeaders.FirstOrDefault(x => (x.InputFile.Flags & SourceFileFlags.IsCoreTypes) != 0); } // Update the dependencies to treat all the contents of a monolithic header as pinned if (MonolithicHeader != null) { SourceFragment[] UniqueFragments = MonolithicHeader.IncludedFragments.Except(VisitedFragments).ToArray(); ImplicitInclude.ExpandedReferences.Add(new OutputFileReference(MonolithicHeader, UniqueFragments)); VisitedFragments.UnionWith(UniqueFragments); VisitedFiles.Add(MonolithicHeader); } // Insert all the forward declaration headers, but only treat them as supplying the forward declarations themselves. They may happen to include // some utility classes (eg. TSharedPtr), and we don't want to include an unrelated header to satisfy that dependency. foreach(OutputFile FwdHeader in FwdSymbolToHeader.Values) { FindExpandedReferences(FwdHeader, ImplicitInclude.ExpandedReferences, VisitedFiles, VisitedFragments, true); } // Add all the other files if(bMakeStandalone) { foreach (OutputFile PreviousFile in PreviousFiles) { if((InputFile.Flags & SourceFileFlags.Standalone) != 0 && (PreviousFile.InputFile.Flags & SourceFileFlags.Inline) == 0 && (PreviousFile.InputFile.Flags & SourceFileFlags.Pinned) == 0 && VisitedFiles.Add(PreviousFile)) { SourceFragment[] UniqueFragments = PreviousFile.IncludedFragments.Except(VisitedFragments).ToArray(); ImplicitInclude.ExpandedReferences.Add(new OutputFileReference(PreviousFile, UniqueFragments)); VisitedFragments.UnionWith(UniqueFragments); } } } } // Figure out a list of files which are uniquely reachable through each include. Force an include of the matching header as the first thing. OutputFileReference ForcedHeaderFileReference = null; foreach(OutputFileInclude Include in Includes) { if(Include.ExpandedReferences == null) { Include.ExpandedReferences = new List(); if(Include == Includes[0] && HeaderFile != null) { ForcedHeaderFileReference = new OutputFileReference(HeaderFile, HeaderFile.IncludedFragments); Include.ExpandedReferences.Add(ForcedHeaderFileReference); VisitedFragments.UnionWith(HeaderFile.IncludedFragments); } FindExpandedReferences(Include.TargetFile, Include.ExpandedReferences, VisitedFiles, VisitedFragments, true); } } // Find all the symbols which are referenced by this file HashSet FragmentsWithReferencedSymbols = new HashSet(); foreach(SourceFragment Fragment in InputFile.Fragments) { foreach(KeyValuePair ReferencedSymbol in Fragment.ReferencedSymbols) { if(ReferencedSymbol.Value == SymbolReferenceType.RequiresDefinition) { FragmentsWithReferencedSymbols.Add(ReferencedSymbol.Key.Fragment); } } } // Aggregate headers are designed to explicitly include headers from the current module. Expand out a list of them, so they can be included when encountered. HashSet ExplicitIncludes = new HashSet(); if((InputFile.Flags & SourceFileFlags.Aggregate) != 0) { foreach(OutputFileInclude Include in Includes) { ExplicitIncludes.UnionWith(Include.ExpandedReferences.Where(x => x.File.InputFile.Location.IsUnderDirectory(InputFile.Location.Directory)).Select(x => x.File)); } foreach(OutputFileInclude Include in Includes) { ExplicitIncludes.Remove(Include.TargetFile); } } // Create the list of remaining dependencies for this file, and add any forward declarations HashSet Dependencies = new HashSet(); List ForwardDeclarations = new List(); AddForwardDeclarations(InputFile, ForwardDeclarations, Dependencies, FwdSymbolToHeader); // Reduce the list of includes to those that are required. for(int FragmentIdx = InputFile.Fragments.Length - 1, IncludeIdx = Includes.Count - 1; FragmentIdx >= 0; FragmentIdx--) { // Update the dependency lists for this fragment SourceFragment InputFragment = InputFile.Fragments[FragmentIdx]; if(InputFragment.Dependencies != null) { Dependencies.UnionWith(InputFragment.Dependencies); } Dependencies.Remove(InputFragment); // Scan backwards through the list of includes, expanding each include to those which are required int MarkupMin = (FragmentIdx == 0)? -1 : InputFragment.MarkupMin; for(; IncludeIdx >= 0 && Includes[IncludeIdx].MarkupIdx >= MarkupMin; IncludeIdx--) { OutputFileInclude Include = Includes[IncludeIdx]; // Always include the same header for aggregates if((InputFile.Flags & SourceFileFlags.Aggregate) != 0) { Include.FinalFiles.Insert(0, Include.TargetFile); Dependencies.ExceptWith(Include.TargetFile.IncludedFragments); Dependencies.UnionWith(Include.TargetFile.Dependencies); } // Include any indirectly included files for(int Idx = Include.ExpandedReferences.Count - 1; Idx >= 0; Idx--) { // Make sure we haven't already added it above OutputFileReference Reference = Include.ExpandedReferences[Idx]; if(!Include.FinalFiles.Contains(Reference.File)) { if(Dependencies.Any(x => Reference.UniqueFragments.Contains(x)) || (Reference.File.InputFile.Flags & SourceFileFlags.Pinned) != 0 || Reference == ForcedHeaderFileReference || Reference.File == MonolithicHeader || ExplicitIncludes.Contains(Reference.File) || ((InputFile.Flags & SourceFileFlags.Aggregate) != 0 && Reference.File == Include.TargetFile) // Always include the original header for aggregates. They are written explicitly to include certain files. || Reference.UniqueFragments.Any(x => FragmentsWithReferencedSymbols.Contains(x))) { Include.FinalFiles.Insert(0, Reference.File); Dependencies.ExceptWith(Reference.File.IncludedFragments); Dependencies.UnionWith(Reference.File.Dependencies); } } } } } // Remove any includes that are already included by the matching header if(HeaderFile != null) { HashSet HeaderIncludedFiles = new HashSet(HeaderFile.Includes.SelectMany(x => x.FinalFiles)); foreach(OutputFileInclude Include in Includes) { Include.FinalFiles.RemoveAll(x => HeaderIncludedFiles.Contains(x)); } } // Check that all the dependencies have been satisfied if(Dependencies.Count > 0) { // Find those which are completely invalid List InvalidDependencies = Dependencies.Where(x => !InputFileStack.Contains(x.File)).ToList(); if(InvalidDependencies.Count > 0) { Log.WriteLine("warning: {0} does not include {1}{2}; may have missing dependencies.", InputFile, String.Join(", ", InvalidDependencies.Select(x => x.Location.FullName).Take(3)), (InvalidDependencies.Count > 3)? String.Format(" and {0} others", InvalidDependencies.Count - 3) : ""); } Dependencies.ExceptWith(InvalidDependencies); // Otherwise warn about those which were not pinned foreach(SourceFile DependencyFile in Dependencies.Select(x => x.File)) { Log.WriteLine("warning: {0} is included by {1} ({2}), but depends on it and should be marked as pinned.", InputFile, DependencyFile, String.Join(" -> ", InputFileStack.SkipWhile(x => x != DependencyFile).Select(x => x.Location.GetFileName()))); } // Mark it as non-standalone and pinned InputFile.Flags = (InputFile.Flags | SourceFileFlags.Pinned) & ~SourceFileFlags.Standalone; } // Do one more forward pass through all the headers, and remove anything that's included more than once. That can happen if we have a referenced symbol as well as // an explicit include, for example. HashSet FinalIncludes = new HashSet(); foreach(OutputFileInclude Include in Includes) { for(int Idx = 0; Idx < Include.FinalFiles.Count; Idx++) { if(!FinalIncludes.Add(Include.FinalFiles[Idx])) { Include.FinalFiles.RemoveAt(Idx); Idx--; } } } // Create the optimized file OutputFile OptimizedFile = new OutputFile(InputFile, Includes, Dependencies, ForwardDeclarations); // Write the verbose log InputFile.LogVerbose(""); foreach(OutputFile IncludedFile in OptimizedFile.IncludedFiles) { InputFile.LogVerbose("Output: {0}", IncludedFile.InputFile.Location.FullName); } // Return the optimized file return OptimizedFile; } /// /// Creates an output file which represents the same includes as the inpu tfile /// /// The input file that this output file corresponds to /// The active set of includes parsed for this file /// Writer for log messages public static OutputFile CreatePassthroughOutputFile(SourceFile InputFile, List Includes, LineBasedTextWriter Log) { // Write the state InputFile.LogVerbose("InputFile={0}", InputFile.Location.FullName); InputFile.LogVerbose("Duplicate."); // Reduce the list of includes to those that are required. HashSet Dependencies = new HashSet(); for(int FragmentIdx = InputFile.Fragments.Length - 1, IncludeIdx = Includes.Count - 1; FragmentIdx >= 0; FragmentIdx--) { // Update the dependency lists for this fragment SourceFragment InputFragment = InputFile.Fragments[FragmentIdx]; if(InputFragment.Dependencies != null) { Dependencies.UnionWith(InputFragment.Dependencies); } Dependencies.Remove(InputFragment); // Scan backwards through the list of includes, expanding each include to those which are required int MarkupMin = (FragmentIdx == 0)? -1 : InputFragment.MarkupMin; for(; IncludeIdx >= 0 && Includes[IncludeIdx].MarkupIdx >= MarkupMin; IncludeIdx--) { OutputFileInclude Include = Includes[IncludeIdx]; Include.FinalFiles.Add(Include.TargetFile); Dependencies.ExceptWith(Include.TargetFile.IncludedFragments); Dependencies.UnionWith(Include.TargetFile.Dependencies); } } // Create the optimized file return new OutputFile(InputFile, Includes, new HashSet(), new List()); } /// /// Create forward declarations for the given file /// /// The file being optimized /// List of forward declaration lines /// Set of dependencies for the file being optimized /// Map from symbol to header declaring it static void AddForwardDeclarations(SourceFile InputFile, List ForwardDeclarations, HashSet Dependencies, Dictionary FwdSymbolToHeader) { // Find all the files which we have true dependencies on. We can assume that all of these will be included. HashSet IncludedFiles = new HashSet(); foreach(SourceFragment Fragment in InputFile.Fragments) { if(Fragment.Dependencies != null) { IncludedFiles.UnionWith(Fragment.Dependencies.Select(x => x.File)); } } // Find all the symbols to forward declare HashSet FwdSymbols = new HashSet(); foreach(SourceFragment Fragment in InputFile.Fragments) { foreach(Symbol Symbol in Fragment.ReferencedSymbols.Keys) { if(Symbol.Fragment.File == InputFile || !IncludedFiles.Contains(Symbol.Fragment.File)) { FwdSymbols.Add(Symbol); } } } // For each symbol, try to add dependencies on a header which forward declares it, otherwise add the text for it foreach(Symbol FwdSymbol in FwdSymbols) { OutputFile Header; if (FwdSymbolToHeader.TryGetValue(FwdSymbol, out Header)) { Dependencies.UnionWith(Header.InputFile.Fragments); } else { ForwardDeclarations.Add(FwdSymbol); } } } /// /// Build a list of included files which can be expanded out, ignoring those which have already been included /// /// The file to search /// Output list for the uniquely included files from this file /// Files which have already been included /// Fragments which have already been visited static void FindExpandedReferences(OutputFile TargetFile, List ExpandedReferences, HashSet VisitedFiles, HashSet VisitedFragments, bool bIsFirstInclude) { if(TargetFile != null && (TargetFile.InputFile.Flags & SourceFileFlags.Inline) == 0 && VisitedFiles.Add(TargetFile)) { foreach(OutputFileInclude TargetFileInclude in TargetFile.Includes) { FindExpandedReferences(TargetFileInclude.TargetFile, ExpandedReferences, VisitedFiles, VisitedFragments, false); } if((TargetFile.InputFile.Flags & SourceFileFlags.Pinned) == 0 || bIsFirstInclude) { SourceFragment[] UniqueFragments = TargetFile.IncludedFragments.Except(VisitedFragments).ToArray(); ExpandedReferences.Add(new OutputFileReference(TargetFile, UniqueFragments)); VisitedFragments.UnionWith(UniqueFragments); } } } } }