Exemplo n.º 1
0
        public override CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, List <FileItem> InputFiles, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph)
        {
            // Use a subdirectory for PVS output, to avoid clobbering regular build artifacts
            OutputDir = DirectoryReference.Combine(OutputDir, "PVS");

            // Preprocess the source files with the regular toolchain
            CppCompileEnvironment PreprocessCompileEnvironment = new CppCompileEnvironment(CompileEnvironment);

            PreprocessCompileEnvironment.bPreprocessOnly = true;
            PreprocessCompileEnvironment.bEnableUndefinedIdentifierWarnings = false;             // Not sure why THIRD_PARTY_INCLUDES_START doesn't pick this up; the _Pragma appears in the preprocessed output. Perhaps in preprocess-only mode the compiler doesn't respect these?
            PreprocessCompileEnvironment.Definitions.Add("PVS_STUDIO");

            List <Action> PreprocessActions = new List <Action>();
            CPPOutput     Result            = InnerToolChain.CompileCPPFiles(PreprocessCompileEnvironment, InputFiles, OutputDir, ModuleName, new ActionGraphCapture(Graph, PreprocessActions));

            // Run the source files through PVS-Studio
            foreach (Action PreprocessAction in PreprocessActions)
            {
                if (PreprocessAction.ActionType != ActionType.Compile)
                {
                    continue;
                }

                FileItem SourceFileItem = PreprocessAction.PrerequisiteItems.FirstOrDefault(x => x.HasExtension(".c") || x.HasExtension(".cc") || x.HasExtension(".cpp"));
                if (SourceFileItem == null)
                {
                    Log.TraceWarning("Unable to find source file from command: {0} {1}", PreprocessAction.CommandArguments);
                    continue;
                }

                FileItem PreprocessedFileItem = PreprocessAction.ProducedItems.FirstOrDefault(x => x.HasExtension(".i"));
                if (PreprocessedFileItem == null)
                {
                    Log.TraceWarning("Unable to find preprocessed output file from command: {0} {1}", PreprocessAction.CommandArguments);
                    continue;
                }

                // Disable a few warnings that seem to come from the preprocessor not respecting _Pragma
                PreprocessAction.CommandArguments += " /wd4005";                 // macro redefinition
                PreprocessAction.CommandArguments += " /wd4828";                 // file contains a character starting at offset xxxx that is illegal in the current source character set

                // Write the PVS studio config file
                StringBuilder ConfigFileContents = new StringBuilder();
                foreach (DirectoryReference IncludePath in Target.WindowsPlatform.Environment.IncludePaths)
                {
                    ConfigFileContents.AppendFormat("exclude-path={0}\n", IncludePath.FullName);
                }
                if (ApplicationSettings != null && ApplicationSettings.PathMasks != null)
                {
                    foreach (string PathMask in ApplicationSettings.PathMasks)
                    {
                        if (PathMask.Contains(":") || PathMask.Contains("\\") || PathMask.Contains("/"))
                        {
                            if (Path.IsPathRooted(PathMask) && !PathMask.Contains(":"))
                            {
                                ConfigFileContents.AppendFormat("exclude-path=*{0}*\n", PathMask);
                            }
                            else
                            {
                                ConfigFileContents.AppendFormat("exclude-path={0}\n", PathMask);
                            }
                        }
                    }
                }
                if (Platform == UnrealTargetPlatform.Win64)
                {
                    ConfigFileContents.Append("platform=x64\n");
                }
                else if (Platform == UnrealTargetPlatform.Win32)
                {
                    ConfigFileContents.Append("platform=Win32\n");
                }
                else
                {
                    throw new BuildException("PVS-Studio does not support this platform");
                }
                ConfigFileContents.Append("preprocessor=visualcpp\n");
                ConfigFileContents.Append("language=C++\n");
                ConfigFileContents.Append("skip-cl-exe=yes\n");
                ConfigFileContents.AppendFormat("i-file={0}\n", PreprocessedFileItem.Location.FullName);

                string BaseFileName = PreprocessedFileItem.Location.GetFileNameWithoutExtension();

                FileReference ConfigFileLocation = FileReference.Combine(OutputDir, BaseFileName + ".cfg");
                FileItem      ConfigFileItem     = Graph.CreateIntermediateTextFile(ConfigFileLocation, ConfigFileContents.ToString());

                // Run the analzyer on the preprocessed source file
                FileReference OutputFileLocation = FileReference.Combine(OutputDir, BaseFileName + ".pvslog");
                FileItem      OutputFileItem     = FileItem.GetItemByFileReference(OutputFileLocation);

                Action AnalyzeAction = Graph.CreateAction(ActionType.Compile);
                AnalyzeAction.CommandDescription = "Analyzing";
                AnalyzeAction.StatusDescription  = BaseFileName;
                AnalyzeAction.WorkingDirectory   = UnrealBuildTool.EngineSourceDirectory;
                AnalyzeAction.CommandPath        = AnalyzerFile;
                AnalyzeAction.CommandArguments   = String.Format("--cl-params \"{0}\" --source-file \"{1}\" --output-file \"{2}\" --cfg \"{3}\" --analysis-mode {4}", PreprocessAction.CommandArguments, SourceFileItem.AbsolutePath, OutputFileLocation, ConfigFileItem.AbsolutePath, (uint)Settings.ModeFlags);
                if (LicenseFile != null)
                {
                    AnalyzeAction.CommandArguments += String.Format(" --lic-file \"{0}\"", LicenseFile);
                    AnalyzeAction.PrerequisiteItems.Add(FileItem.GetItemByFileReference(LicenseFile));
                }
                AnalyzeAction.PrerequisiteItems.Add(ConfigFileItem);
                AnalyzeAction.PrerequisiteItems.Add(PreprocessedFileItem);
                AnalyzeAction.PrerequisiteItems.AddRange(InputFiles);          // Add the InputFiles as PrerequisiteItems so that in SingleFileCompile mode the PVSAnalyze step is not filtered out
                AnalyzeAction.ProducedItems.Add(OutputFileItem);
                AnalyzeAction.DeleteItems.Add(OutputFileItem);                 // PVS Studio will append by default, so need to delete produced items

                Result.ObjectFiles.AddRange(AnalyzeAction.ProducedItems);
            }
            return(Result);
        }
Exemplo n.º 2
0
        public override CPPOutput CompileISPCFiles(CppCompileEnvironment CompileEnvironment, List <FileItem> InputFiles, DirectoryReference OutputDir, IActionGraphBuilder Graph)
        {
            CPPOutput Result = new CPPOutput();

            if (!CompileEnvironment.bCompileISPC)
            {
                return(Result);
            }

            List <string> CompileTargets = GetISPCCompileTargets(CompileEnvironment.Platform, null);

            foreach (FileItem ISPCFile in InputFiles)
            {
                Action CompileAction = Graph.CreateAction(ActionType.Compile);
                CompileAction.CommandDescription = "Compile";
                CompileAction.WorkingDirectory   = UnrealBuildTool.EngineSourceDirectory;
                CompileAction.CommandPath        = new FileReference(GetISPCHostCompilerPath(BuildHostPlatform.Current.Platform));
                CompileAction.StatusDescription  = Path.GetFileName(ISPCFile.AbsolutePath);

                // Disable remote execution to workaround mismatched case on XGE
                CompileAction.bCanExecuteRemotely = false;

                List <string> Arguments = new List <string>();

                // Add the ISPC file to be compiled.
                Arguments.Add(String.Format(" \"{0}\"", ISPCFile.AbsolutePath));

                List <FileItem> CompiledISPCObjFiles = new List <FileItem>();
                string          TargetString         = "";

                foreach (string Target in CompileTargets)
                {
                    string ObjTarget = Target;

                    if (Target.Contains("-"))
                    {
                        // Remove lane width and gang size from obj file name
                        ObjTarget = Target.Split('-')[0];
                    }

                    FileItem CompiledISPCObjFile;

                    if (CompileTargets.Count > 1)
                    {
                        CompiledISPCObjFile = FileItem.GetItemByFileReference(
                            FileReference.Combine(
                                OutputDir,
                                Path.GetFileName(ISPCFile.AbsolutePath) + "_" + ObjTarget + GetISPCObjectFileSuffix(CompileEnvironment.Platform)
                                )
                            );
                    }
                    else
                    {
                        CompiledISPCObjFile = FileItem.GetItemByFileReference(
                            FileReference.Combine(
                                OutputDir,
                                Path.GetFileName(ISPCFile.AbsolutePath) + GetISPCObjectFileSuffix(CompileEnvironment.Platform)
                                )
                            );
                    }

                    // Add the ISA specific ISPC obj files to the produced item list.
                    CompiledISPCObjFiles.Add(CompiledISPCObjFile);

                    // Build target string. No comma on last
                    if (Target == CompileTargets[CompileTargets.Count - 1])                   // .Last()
                    {
                        TargetString += Target;
                    }
                    else
                    {
                        TargetString += Target + ",";
                    }
                }

                // Add the common ISPC obj file to the produced item list.
                FileItem CompiledISPCObjFileNoISA = FileItem.GetItemByFileReference(
                    FileReference.Combine(
                        OutputDir,
                        Path.GetFileName(ISPCFile.AbsolutePath) + GetISPCObjectFileSuffix(CompileEnvironment.Platform)
                        )
                    );

                CompiledISPCObjFiles.Add(CompiledISPCObjFileNoISA);

                // Add the output ISPC obj file
                Arguments.Add(String.Format("-o \"{0}\"", CompiledISPCObjFileNoISA));

                // Build target triplet
                Arguments.Add(String.Format("--target-os=\"{0}\"", GetISPCOSTarget(CompileEnvironment.Platform)));
                Arguments.Add(String.Format("--arch=\"{0}\"", GetISPCArchTarget(CompileEnvironment.Platform, null)));
                Arguments.Add(String.Format("--target=\"{0}\"", TargetString));

                if (CompileEnvironment.Configuration == CppConfiguration.Debug)
                {
                    Arguments.Add("-g -O0");
                }
                else
                {
                    Arguments.Add("-O2");
                }

                // PIC is needed for modular builds except on Windows
                if ((CompileEnvironment.bIsBuildingDLL ||
                     CompileEnvironment.bIsBuildingLibrary) &&
                    !UEBuildPlatform.IsPlatformInGroup(CompileEnvironment.Platform, UnrealPlatformGroup.Windows))
                {
                    Arguments.Add("--pic");
                }

                // Include paths. Don't use AddIncludePath() here, since it uses the full path and exceeds the max command line length.
                foreach (DirectoryReference IncludePath in CompileEnvironment.UserIncludePaths)
                {
                    Arguments.Add(String.Format("-I\"{0}\"", IncludePath));
                }

                // System include paths.
                foreach (DirectoryReference SystemIncludePath in CompileEnvironment.SystemIncludePaths)
                {
                    Arguments.Add(String.Format("-I\"{0}\"", SystemIncludePath));
                }

                // Preprocessor definitions.
                foreach (string Definition in CompileEnvironment.Definitions)
                {
                    Arguments.Add(String.Format("-D\"{0}\"", Definition));
                }

                // Consume the included header dependency list
                if (CompileEnvironment.bGenerateDependenciesFile)
                {
                    FileItem DependencyListFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, Path.GetFileName(ISPCFile.AbsolutePath) + ".txt"));
                    CompileAction.DependencyListFile = DependencyListFile;
                    CompileAction.PrerequisiteItems.Add(DependencyListFile);
                }

                CompileAction.ProducedItems.AddRange(CompiledISPCObjFiles);
                Result.ObjectFiles.AddRange(CompiledISPCObjFiles);

                FileReference ResponseFileName = new FileReference(CompiledISPCObjFileNoISA.AbsolutePath + ".response");
                FileItem      ResponseFileItem = Graph.CreateIntermediateTextFile(ResponseFileName, Arguments.Select(x => Utils.ExpandVariables(x)));
                CompileAction.CommandArguments = " @\"" + ResponseFileName + "\"";
                CompileAction.PrerequisiteItems.Add(ResponseFileItem);

                // Add the source file and its included files to the prerequisite item list.
                CompileAction.PrerequisiteItems.Add(ISPCFile);

                Log.TraceVerbose("   ISPC Compiling " + CompileAction.StatusDescription + ": \"" + CompileAction.CommandPath + "\"" + CompileAction.CommandArguments);
            }

            return(Result);
        }
Exemplo n.º 3
0
        public override CPPOutput GenerateISPCHeaders(CppCompileEnvironment CompileEnvironment, List <FileItem> InputFiles, DirectoryReference OutputDir, IActionGraphBuilder Graph)
        {
            CPPOutput Result = new CPPOutput();

            if (!CompileEnvironment.bCompileISPC)
            {
                return(Result);
            }

            List <string> CompileTargets = GetISPCCompileTargets(CompileEnvironment.Platform, null);

            foreach (FileItem ISPCFile in InputFiles)
            {
                Action CompileAction = Graph.CreateAction(ActionType.Compile);
                CompileAction.CommandDescription = "Compile";
                CompileAction.WorkingDirectory   = UnrealBuildTool.EngineSourceDirectory;
                CompileAction.CommandPath        = new FileReference(GetISPCHostCompilerPath(BuildHostPlatform.Current.Platform));
                CompileAction.StatusDescription  = Path.GetFileName(ISPCFile.AbsolutePath);

                // Disable remote execution to workaround mismatched case on XGE
                CompileAction.bCanExecuteRemotely = false;

                List <string> Arguments = new List <string>();

                // Add the ISPC obj file as a prerequisite of the action.
                CompileAction.CommandArguments = String.Format("\"{0}\" ", ISPCFile.AbsolutePath);

                // Add the ISPC h file to the produced item list.
                FileItem ISPCIncludeHeaderFile = FileItem.GetItemByFileReference(
                    FileReference.Combine(
                        OutputDir,
                        Path.GetFileName(ISPCFile.AbsolutePath) + ".generated.dummy.h"
                        )
                    );

                // Add the ISPC file to be compiled.
                Arguments.Add(String.Format("-h \"{0}\"", ISPCIncludeHeaderFile));

                // Build target string. No comma on last
                string TargetString = "";
                foreach (string Target in CompileTargets)
                {
                    if (Target == CompileTargets[CompileTargets.Count - 1])                   // .Last()
                    {
                        TargetString += Target;
                    }
                    else
                    {
                        TargetString += Target + ",";
                    }
                }

                // Build target triplet
                Arguments.Add(String.Format("--target-os={0}", GetISPCOSTarget(CompileEnvironment.Platform)));
                Arguments.Add(String.Format("--arch={0}", GetISPCArchTarget(CompileEnvironment.Platform, null)));
                Arguments.Add(String.Format("--target={0}", TargetString));

                // PIC is needed for modular builds except on Windows
                if ((CompileEnvironment.bIsBuildingDLL ||
                     CompileEnvironment.bIsBuildingLibrary) &&
                    !UEBuildPlatform.IsPlatformInGroup(CompileEnvironment.Platform, UnrealPlatformGroup.Windows))
                {
                    Arguments.Add("--pic");
                }

                // Include paths. Don't use AddIncludePath() here, since it uses the full path and exceeds the max command line length.
                // Because ISPC response files don't support white space in arguments, paths with white space need to be passed to the command line directly.
                foreach (DirectoryReference IncludePath in CompileEnvironment.UserIncludePaths)
                {
                    Arguments.Add(String.Format("-I\"{0}\"", IncludePath));
                }

                // System include paths.
                foreach (DirectoryReference SystemIncludePath in CompileEnvironment.SystemIncludePaths)
                {
                    Arguments.Add(String.Format("-I\"{0}\"", SystemIncludePath));
                }

                // Generate the included header dependency list
                if (CompileEnvironment.bGenerateDependenciesFile)
                {
                    FileItem DependencyListFile = FileItem.GetItemByFileReference(FileReference.Combine(OutputDir, Path.GetFileName(ISPCFile.AbsolutePath) + ".txt"));
                    Arguments.Add(String.Format("-MMM \"{0}\"", DependencyListFile.AbsolutePath.Replace('\\', '/')));
                    CompileAction.DependencyListFile = DependencyListFile;
                    CompileAction.ProducedItems.Add(DependencyListFile);
                }

                CompileAction.ProducedItems.Add(ISPCIncludeHeaderFile);

                FileReference ResponseFileName = new FileReference(ISPCIncludeHeaderFile.AbsolutePath + ".response");
                FileItem      ResponseFileItem = Graph.CreateIntermediateTextFile(ResponseFileName, Arguments.Select(x => Utils.ExpandVariables(x)));
                CompileAction.CommandArguments += String.Format("@\"{0}\"", ResponseFileName);
                CompileAction.PrerequisiteItems.Add(ResponseFileItem);

                // Add the source file and its included files to the prerequisite item list.
                CompileAction.PrerequisiteItems.Add(ISPCFile);

                FileItem ISPCFinalHeaderFile = FileItem.GetItemByFileReference(
                    FileReference.Combine(
                        OutputDir,
                        Path.GetFileName(ISPCFile.AbsolutePath) + ".generated.h"
                        )
                    );

                // Fix interrupted build issue by copying header after generation completes
                FileReference SourceFile = ISPCIncludeHeaderFile.Location;
                FileReference TargetFile = ISPCFinalHeaderFile.Location;

                FileItem SourceFileItem = FileItem.GetItemByFileReference(SourceFile);
                FileItem TargetFileItem = FileItem.GetItemByFileReference(TargetFile);

                Action CopyAction = Graph.CreateAction(ActionType.BuildProject);
                CopyAction.CommandDescription = "Copy";
                CopyAction.CommandPath        = BuildHostPlatform.Current.Shell;
                if (BuildHostPlatform.Current.ShellType == ShellType.Cmd)
                {
                    CopyAction.CommandArguments = String.Format("/C \"copy /Y \"{0}\" \"{1}\" 1>nul\"", SourceFile, TargetFile);
                }
                else
                {
                    CopyAction.CommandArguments = String.Format("-c 'cp -f \"{0}\" \"{1}\"'", SourceFile.FullName, TargetFile.FullName);
                }
                CopyAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory;
                CopyAction.PrerequisiteItems.Add(SourceFileItem);
                CopyAction.ProducedItems.Add(TargetFileItem);
                CopyAction.StatusDescription              = TargetFileItem.Location.GetFileName();
                CopyAction.bCanExecuteRemotely            = false;
                CopyAction.bShouldOutputStatusDescription = false;

                Result.GeneratedHeaderFiles.Add(TargetFileItem);

                Log.TraceVerbose("   ISPC Generating Header " + CompileAction.StatusDescription + ": \"" + CompileAction.CommandPath + "\"" + CompileAction.CommandArguments);
            }

            return(Result);
        }
Exemplo n.º 4
0
        /// <summary>
        /// Given a set of C++ files, generates another set of C++ files that #include all the original
        /// files, the goal being to compile the same code in fewer translation units.
        /// The "unity" files are written to the CompileEnvironment's OutputDirectory.
        /// </summary>
        /// <param name="Target">The target we're building</param>
        /// <param name="CPPFiles">The C++ files to #include.</param>
        /// <param name="CompileEnvironment">The environment that is used to compile the C++ files.</param>
        /// <param name="WorkingSet">Interface to query files which belong to the working set</param>
        /// <param name="BaseName">Base name to use for the Unity files</param>
        /// <param name="IntermediateDirectory">Intermediate directory for unity cpp files</param>
        /// <param name="Graph">The makefile being built</param>
        /// <param name="SourceFileToUnityFile">Receives a mapping of source file to unity file</param>
        /// <returns>The "unity" C++ files.</returns>
        public static List <FileItem> GenerateUnityCPPs(
            ReadOnlyTargetRules Target,
            List <FileItem> CPPFiles,
            CppCompileEnvironment CompileEnvironment,
            ISourceFileWorkingSet WorkingSet,
            string BaseName,
            DirectoryReference IntermediateDirectory,
            IActionGraphBuilder Graph,
            Dictionary <FileItem, FileItem> SourceFileToUnityFile
            )
        {
            List <FileItem> NewCPPFiles = new List <FileItem>();

            //UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(CompileEnvironment.Platform);

            // Figure out size of all input files combined. We use this to determine whether to use larger unity threshold or not.
            long TotalBytesInCPPFiles = CPPFiles.Sum(F => F.Length);

            // We have an increased threshold for unity file size if, and only if, all files fit into the same unity file. This
            // is beneficial when dealing with PCH files. The default PCH creation limit is X unity files so if we generate < X
            // this could be fairly slow and we'd rather bump the limit a bit to group them all into the same unity file.


            // When enabled, UnrealBuildTool will try to determine source files that you are actively iteratively changing, and break those files
            // out of their unity blobs so that you can compile them as individual translation units, much faster than recompiling the entire
            // unity blob each time.
            bool bUseAdaptiveUnityBuild = Target.bUseAdaptiveUnityBuild && !Target.bStressTestUnity;

            // Optimization only makes sense if PCH files are enabled.
            bool bForceIntoSingleUnityFile = Target.bStressTestUnity || (TotalBytesInCPPFiles < Target.NumIncludedBytesPerUnityCPP * 2 && Target.bUsePCHFiles);

            // Build the list of unity files.
            List <FileCollection> AllUnityFiles;
            {
                // Sort the incoming file paths alphabetically, so there will be consistency in unity blobs across multiple machines.
                // Note that we're relying on this not only sorting files within each directory, but also the directories
                // themselves, so the whole list of file paths is the same across computers.
                List <FileItem> SortedCPPFiles = CPPFiles.GetRange(0, CPPFiles.Count);
                {
                    // Case-insensitive file path compare, because you never know what is going on with local file systems
                    Comparison <FileItem> FileItemComparer = (FileA, FileB) => { return(FileA.AbsolutePath.ToLowerInvariant().CompareTo(FileB.AbsolutePath.ToLowerInvariant())); };
                    SortedCPPFiles.Sort(FileItemComparer);
                }


                // Figure out whether we REALLY want to use adaptive unity for this module.  If nearly every file in the module appears in the working
                // set, we'll just go ahead and let unity build do its thing.
                HashSet <FileItem> FilesInWorkingSet = new HashSet <FileItem>();
                if (bUseAdaptiveUnityBuild)
                {
                    int CandidateWorkingSetSourceFileCount = 0;
                    int WorkingSetSourceFileCount          = 0;
                    foreach (FileItem CPPFile in SortedCPPFiles)
                    {
                        ++CandidateWorkingSetSourceFileCount;

                        // Don't include writable source files into unity blobs
                        if (WorkingSet.Contains(CPPFile))
                        {
                            ++WorkingSetSourceFileCount;

                            // Mark this file as part of the working set.  This will be saved into the UBT Makefile so that
                            // the assembler can automatically invalidate the Makefile when the working set changes (allowing this
                            // code to run again, to build up new unity blobs.)
                            FilesInWorkingSet.Add(CPPFile);
                            Graph.AddFileToWorkingSet(CPPFile);
                        }
                    }

                    if (WorkingSetSourceFileCount >= CandidateWorkingSetSourceFileCount)
                    {
                        // Every single file in the module appears in the working set, so don't bother using adaptive unity for this
                        // module.  Otherwise it would make full builds really slow.
                        bUseAdaptiveUnityBuild = false;
                    }
                }

                UnityFileBuilder CPPUnityFileBuilder          = new UnityFileBuilder(bForceIntoSingleUnityFile ? -1 : Target.NumIncludedBytesPerUnityCPP);
                StringBuilder    AdaptiveUnityBuildInfoString = new StringBuilder();
                foreach (FileItem CPPFile in SortedCPPFiles)
                {
                    if (!bForceIntoSingleUnityFile && CPPFile.AbsolutePath.IndexOf(".GeneratedWrapper.", StringComparison.InvariantCultureIgnoreCase) != -1)
                    {
                        NewCPPFiles.Add(CPPFile);
                    }

                    // When adaptive unity is enabled, go ahead and exclude any source files that we're actively working with
                    if (bUseAdaptiveUnityBuild && FilesInWorkingSet.Contains(CPPFile))
                    {
                        // Just compile this file normally, not as part of the unity blob
                        NewCPPFiles.Add(CPPFile);

                        // Let the unity file builder know about the file, so that we can retain the existing size of the unity blobs.
                        // This won't actually make the source file part of the unity blob, but it will keep track of how big the
                        // file is so that other existing unity blobs from the same module won't be invalidated.  This prevents much
                        // longer compile times the first time you build after your working file set changes.
                        CPPUnityFileBuilder.AddVirtualFile(CPPFile);

                        string CPPFileName = Path.GetFileName(CPPFile.AbsolutePath);
                        if (AdaptiveUnityBuildInfoString.Length == 0)
                        {
                            AdaptiveUnityBuildInfoString.Append(String.Format("[Adaptive unity build] Excluded from {0} unity file: {1}", BaseName, CPPFileName));
                        }
                        else
                        {
                            AdaptiveUnityBuildInfoString.Append(", " + CPPFileName);
                        }
                    }
                    else
                    {
                        // If adaptive unity build is enabled for this module, add this source file to the set that will invalidate the makefile
                        if (bUseAdaptiveUnityBuild)
                        {
                            Graph.AddCandidateForWorkingSet(CPPFile);
                        }

                        // Compile this file as part of the unity blob
                        CPPUnityFileBuilder.AddFile(CPPFile);
                    }
                }

                if (AdaptiveUnityBuildInfoString.Length > 0)
                {
                    if (Target.bAdaptiveUnityCreatesDedicatedPCH)
                    {
                        Graph.AddDiagnostic("[Adaptive unity build] Creating dedicated PCH for each excluded file. Set bAdaptiveUnityCreatesDedicatedPCH to false in BuildConfiguration.xml to change this behavior.");
                    }
                    else if (Target.bAdaptiveUnityDisablesPCH)
                    {
                        Graph.AddDiagnostic("[Adaptive unity build] Disabling PCH for excluded files. Set bAdaptiveUnityDisablesPCH to false in BuildConfiguration.xml to change this behavior.");
                    }

                    if (Target.bAdaptiveUnityDisablesOptimizations)
                    {
                        Graph.AddDiagnostic("[Adaptive unity build] Disabling optimizations for excluded files. Set bAdaptiveUnityDisablesOptimizations to false in BuildConfiguration.xml to change this behavior.");
                    }
                    if (Target.bAdaptiveUnityEnablesEditAndContinue)
                    {
                        Graph.AddDiagnostic("[Adaptive unity build] Enabling Edit & Continue for excluded files. Set bAdaptiveUnityEnablesEditAndContinue to false in BuildConfiguration.xml to change this behavior.");
                    }

                    Graph.AddDiagnostic(AdaptiveUnityBuildInfoString.ToString());
                }

                AllUnityFiles = CPPUnityFileBuilder.GetUnityFiles();
            }

            // Create a set of CPP files that combine smaller CPP files into larger compilation units, along with the corresponding
            // actions to compile them.
            int CurrentUnityFileCount = 0;

            foreach (FileCollection UnityFile in AllUnityFiles)
            {
                ++CurrentUnityFileCount;

                StringWriter OutputUnityCPPWriter = new StringWriter();

                OutputUnityCPPWriter.WriteLine("// This file is automatically generated at compile-time to include some subset of the user-created cpp files.");

                // Add source files to the unity file
                foreach (FileItem CPPFile in UnityFile.Files)
                {
                    OutputUnityCPPWriter.WriteLine("#include \"{0}\"", CPPFile.AbsolutePath.Replace('\\', '/'));
                }

                // Determine unity file path name
                string UnityCPPFileName;
                if (AllUnityFiles.Count > 1)
                {
                    UnityCPPFileName = string.Format("{0}{1}.{2}_of_{3}.cpp", ModulePrefix, BaseName, CurrentUnityFileCount, AllUnityFiles.Count);
                }
                else
                {
                    UnityCPPFileName = string.Format("{0}{1}.cpp", ModulePrefix, BaseName);
                }
                FileReference UnityCPPFilePath = FileReference.Combine(IntermediateDirectory, UnityCPPFileName);

                // Write the unity file to the intermediate folder.
                FileItem UnityCPPFile = Graph.CreateIntermediateTextFile(UnityCPPFilePath, OutputUnityCPPWriter.ToString());
                NewCPPFiles.Add(UnityCPPFile);

                // Store the mapping of source files to unity files in the makefile
                foreach (FileItem SourceFile in UnityFile.Files)
                {
                    SourceFileToUnityFile[SourceFile] = UnityCPPFile;
                }
                foreach (FileItem SourceFile in UnityFile.VirtualFiles)
                {
                    SourceFileToUnityFile[SourceFile] = UnityCPPFile;
                }
            }

            return(NewCPPFiles);
        }
 /// <summary>
 /// Creates a text file with the given contents.  If the contents of the text file aren't changed, it won't write the new contents to
 /// the file to avoid causing an action to be considered outdated.
 /// </summary>
 /// <param name="Graph">The action graph</param>
 /// <param name="AbsolutePath">Path to the intermediate file to create</param>
 /// <param name="Contents">Contents of the new file</param>
 /// <returns>File item for the newly created file</returns>
 public static FileItem CreateIntermediateTextFile(this IActionGraphBuilder Graph, FileReference AbsolutePath, IEnumerable <string> Contents)
 {
     return(Graph.CreateIntermediateTextFile(AbsolutePath, string.Join(Environment.NewLine, Contents)));
 }
 /// <inheritdoc/>
 public virtual FileItem CreateIntermediateTextFile(FileReference Location, string Contents)
 {
     return(Inner.CreateIntermediateTextFile(Location, Contents));
 }