/// <summary> /// Creates a file from a list of strings; each string is placed on a line in the file. /// </summary> /// <param name="TempFileName">Name of response file</param> /// <param name="Lines">List of lines to write to the response file</param> /// <param name="Options"></param> public static FileReference Create(FileReference TempFileName, IEnumerable <string> Lines, CreateOptions Options = CreateOptions.None) { FileInfo TempFileInfo = new FileInfo(TempFileName.FullName); if (TempFileInfo.Exists) { if ((Options & CreateOptions.WriteEvenIfUnchanged) != CreateOptions.WriteEvenIfUnchanged) { string Body = string.Join(Environment.NewLine, Lines); // Reuse the existing response file if it remains unchanged string OriginalBody = File.ReadAllText(TempFileName.FullName); if (string.Equals(OriginalBody, Body, StringComparison.Ordinal)) { return(TempFileName); } } // Delete the existing file if it exists and requires modification TempFileInfo.IsReadOnly = false; TempFileInfo.Delete(); TempFileInfo.Refresh(); } FileItem.CreateIntermediateTextFile(TempFileName, string.Join(Environment.NewLine, Lines)); return(TempFileName); }
public override void FinalizeOutput(ReadOnlyTargetRules Target, TargetMakefile Makefile) { FileReference OutputFile; if (Target.ProjectFile == null) { OutputFile = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Saved", "PVS-Studio", String.Format("{0}.pvslog", Target.Name)); } else { OutputFile = FileReference.Combine(Target.ProjectFile.Directory, "Saved", "PVS-Studio", String.Format("{0}.pvslog", Target.Name)); } List <FileReference> InputFiles = Makefile.OutputItems.Select(x => x.Location).Where(x => x.HasExtension(".pvslog")).ToList(); FileItem InputFileListItem = FileItem.CreateIntermediateTextFile(OutputFile.ChangeExtension(".input"), InputFiles.Select(x => x.FullName)); Action AnalyzeAction = new Action(ActionType.Compile); AnalyzeAction.CommandPath = UnrealBuildTool.GetUBTPath(); AnalyzeAction.CommandArguments = String.Format("-Mode=PVSGather -Input=\"{0}\" -Output=\"{1}\"", InputFileListItem.Location, OutputFile); AnalyzeAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory; AnalyzeAction.PrerequisiteItems.Add(InputFileListItem); AnalyzeAction.PrerequisiteItems.AddRange(Makefile.OutputItems); AnalyzeAction.ProducedItems.Add(FileItem.GetItemByFileReference(OutputFile)); AnalyzeAction.DeleteItems.AddRange(AnalyzeAction.ProducedItems); Makefile.Actions.Add(AnalyzeAction); Makefile.OutputItems.AddRange(AnalyzeAction.ProducedItems); }
public PVSToolChain(ReadOnlyTargetRules Target) { this.Target = Target; Platform = Target.Platform; InnerToolChain = new VCToolChain(Target); AnalyzerFile = FileReference.Combine(new DirectoryReference(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)), "PVS-Studio", "x64", "PVS-Studio.exe"); if (!FileReference.Exists(AnalyzerFile)) { FileReference EngineAnalyzerFile = FileReference.Combine(UnrealBuildTool.RootDirectory, "Engine", "Extras", "ThirdPartyNotUE", "NoRedist", "PVS-Studio", "PVS-Studio.exe"); if (FileReference.Exists(EngineAnalyzerFile)) { AnalyzerFile = EngineAnalyzerFile; } else { throw new BuildException("Unable to find PVS-Studio at {0} or {1}", AnalyzerFile, EngineAnalyzerFile); } } FileReference SettingsPath = FileReference.Combine(new DirectoryReference(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)), "PVS-Studio", "Settings.xml"); if (FileReference.Exists(SettingsPath)) { try { XmlSerializer Serializer = new XmlSerializer(typeof(PVSApplicationSettings)); using (FileStream Stream = new FileStream(SettingsPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { ApplicationSettings = (PVSApplicationSettings)Serializer.Deserialize(Stream); } } catch (Exception Ex) { throw new BuildException(Ex, "Unable to read PVS-Studio settings file from {0}", SettingsPath); } } if (ApplicationSettings != null && !String.IsNullOrEmpty(ApplicationSettings.UserName) && !String.IsNullOrEmpty(ApplicationSettings.SerialNumber)) { LicenseFile = FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "PVS", "PVS-Studio.lic"); FileItem.CreateIntermediateTextFile(LicenseFile, new string[] { ApplicationSettings.UserName, ApplicationSettings.SerialNumber }); } else { FileReference DefaultLicenseFile = AnalyzerFile.ChangeExtension(".lic"); if (FileReference.Exists(DefaultLicenseFile)) { LicenseFile = DefaultLicenseFile; } } }
/// <summary> /// Creates a file from a list of strings; each string is placed on a line in the file. /// </summary> /// <param name="TempFileName">Name of response file</param> /// <param name="Lines">List of lines to write to the response file</param> public static FileReference Create(FileReference TempFileName, List <string> Lines) { FileInfo TempFileInfo = new FileInfo(TempFileName.FullName); // Delete the existing file if it exists if (TempFileInfo.Exists) { TempFileInfo.IsReadOnly = false; TempFileInfo.Delete(); TempFileInfo.Refresh(); } FileItem.CreateIntermediateTextFile(TempFileName, string.Join(Environment.NewLine, Lines)); return(TempFileName); }
public override CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, List <FileItem> InputFiles, DirectoryReference OutputDir, string ModuleName, ActionGraph ActionGraph) { VCEnvironment EnvVars = VCEnvironment.SetEnvironment(CppPlatform, Compiler); // Get the MSVC arguments required to compile all files in this batch List <string> SharedArguments = new List <string>(); SharedArguments.Add("/nologo"); SharedArguments.Add("/P"); // Preprocess SharedArguments.Add("/C"); // Preserve comments when preprocessing SharedArguments.Add("/D PVS_STUDIO"); SharedArguments.Add("/wd4005"); foreach (DirectoryReference IncludePath in CompileEnvironment.IncludePaths.UserIncludePaths) { SharedArguments.Add(String.Format("/I \"{0}\"", IncludePath)); } foreach (DirectoryReference IncludePath in CompileEnvironment.IncludePaths.SystemIncludePaths) { SharedArguments.Add(String.Format("/I \"{0}\"", IncludePath)); } foreach (string Definition in CompileEnvironment.Definitions) { SharedArguments.Add(String.Format("/D \"{0}\"", Definition)); } // Get the path to PVS studio FileReference AnalyzerFile = new FileReference(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "PVS-Studio", "x64", "PVS-Studio.exe")); if (!FileReference.Exists(AnalyzerFile)) { throw new BuildException("Unable to find PVS-Studio at {0}", AnalyzerFile); } CPPOutput Result = new CPPOutput(); foreach (FileItem SourceFile in InputFiles) { // Get the file names for everything we need string BaseFileName = SourceFile.Location.GetFileName(); // Write the response file FileReference PreprocessedFileLocation = FileReference.Combine(OutputDir, BaseFileName + ".i"); List <string> Arguments = new List <string>(SharedArguments); Arguments.Add(String.Format("/Fi\"{0}\"", PreprocessedFileLocation)); // Preprocess to a file Arguments.Add(String.Format("\"{0}\"", SourceFile.AbsolutePath)); FileReference ResponseFileLocation = FileReference.Combine(OutputDir, BaseFileName + ".i.response"); FileItem ResponseFileItem = FileItem.CreateIntermediateTextFile(ResponseFileLocation, String.Join("\n", Arguments)); // Preprocess the source file FileItem PreprocessedFileItem = FileItem.GetItemByFileReference(PreprocessedFileLocation); Action PreprocessAction = ActionGraph.Add(ActionType.Compile); PreprocessAction.CommandPath = EnvVars.CompilerPath.FullName; PreprocessAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName; PreprocessAction.CommandArguments = " @\"" + ResponseFileItem.AbsolutePath + "\""; PreprocessAction.PrerequisiteItems.Add(SourceFile); PreprocessAction.PrerequisiteItems.Add(ResponseFileItem); PreprocessAction.ProducedItems.Add(PreprocessedFileItem); PreprocessAction.bShouldOutputStatusDescription = false; // Write the PVS studio config file StringBuilder ConfigFileContents = new StringBuilder(); ConfigFileContents.AppendFormat("exclude-path={0}\n", EnvVars.VCInstallDir.FullName); if (CppPlatform == CppPlatform.Win64) { ConfigFileContents.Append("platform=x64\n"); } else if (CppPlatform == CppPlatform.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); FileReference ConfigFileLocation = FileReference.Combine(OutputDir, BaseFileName + ".cfg"); FileItem ConfigFileItem = FileItem.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 = ActionGraph.Add(ActionType.Compile); AnalyzeAction.CommandDescription = "Analyzing"; AnalyzeAction.StatusDescription = BaseFileName; AnalyzeAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName; AnalyzeAction.CommandPath = AnalyzerFile.FullName; AnalyzeAction.CommandArguments = String.Format("--cl-params \"{0}\" --source-file \"{1}\" --output-file \"{2}\" --cfg \"{3}\" --analysis-mode 4", PreprocessAction.CommandArguments, SourceFile.AbsolutePath, OutputFileLocation, ConfigFileItem.AbsolutePath); AnalyzeAction.PrerequisiteItems.Add(ConfigFileItem); AnalyzeAction.PrerequisiteItems.Add(PreprocessedFileItem); AnalyzeAction.ProducedItems.Add(OutputFileItem); AnalyzeAction.bShouldDeleteProducedItems = true; // PVS Studio will append by default, so need to delete produced items Result.ObjectFiles.AddRange(AnalyzeAction.ProducedItems); } return(Result); }
public override CPPOutput CompileCPPFiles(UEBuildTarget Target, CPPEnvironment CompileEnvironment, List <FileItem> SourceFiles, string ModuleName) { var EnvVars = VCEnvironment.SetEnvironment(CompileEnvironment.Config.Target.Platform, false); StringBuilder Arguments = new StringBuilder(); AppendCLArguments_Global(CompileEnvironment, EnvVars, Arguments); // Add include paths to the argument list. foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.IncludePaths) { string IncludePathRelative = Utils.CleanDirectorySeparators(Utils.MakePathRelativeTo(IncludePath, Path.Combine(ProjectFileGenerator.RootRelativePath, "Engine/Source")), '/'); Arguments.AppendFormat(" /I \"{0}\"", IncludePathRelative); } foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths) { string IncludePathRelative = Utils.CleanDirectorySeparators(Utils.MakePathRelativeTo(IncludePath, Path.Combine(ProjectFileGenerator.RootRelativePath, "Engine/Source")), '/'); Arguments.AppendFormat(" /I \"{0}\"", IncludePathRelative); } if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled) { // Add .NET framework assembly paths. This is needed so that C++/CLI projects // can reference assemblies with #using, without having to hard code a path in the // .cpp file to the assembly's location. foreach (string AssemblyPath in CompileEnvironment.Config.SystemDotNetAssemblyPaths) { Arguments.AppendFormat(" /AI \"{0}\"", AssemblyPath); } // Add explicit .NET framework assembly references foreach (string AssemblyName in CompileEnvironment.Config.FrameworkAssemblyDependencies) { Arguments.AppendFormat(" /FU \"{0}\"", AssemblyName); } // Add private assembly references foreach (PrivateAssemblyInfo CurAssemblyInfo in CompileEnvironment.PrivateAssemblyDependencies) { Arguments.AppendFormat(" /FU \"{0}\"", CurAssemblyInfo.FileItem.AbsolutePath); } } // Add preprocessor definitions to the argument list. foreach (string Definition in CompileEnvironment.Config.Definitions) { // Escape all quotation marks so that they get properly passed with the command line. var DefinitionArgument = Definition.Contains("\"") ? Definition.Replace("\"", "\\\"") : Definition; Arguments.AppendFormat(" /D\"{0}\"", DefinitionArgument); } var BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(CompileEnvironment.Config.Target.Platform); // Create a compile action for each source file. CPPOutput Result = new CPPOutput(); foreach (FileItem SourceFile in SourceFiles) { Action CompileAction = new Action(ActionType.Compile); CompileAction.CommandDescription = "Compile"; StringBuilder FileArguments = new StringBuilder(); bool bIsPlainCFile = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant() == ".C"; // Add the C++ source file and its included files to the prerequisite item list. AddPrerequisiteSourceFile(Target, BuildPlatform, CompileEnvironment, SourceFile, CompileAction.PrerequisiteItems); // If this is a CLR file then make sure our dependent assemblies are added as prerequisites if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled) { foreach (PrivateAssemblyInfo CurPrivateAssemblyDependency in CompileEnvironment.PrivateAssemblyDependencies) { CompileAction.PrerequisiteItems.Add(CurPrivateAssemblyDependency.FileItem); } } bool bEmitsObjectFile = true; if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create) { // Generate a CPP File that just includes the precompiled header. string PCHCPPFilename = "PCH." + ModuleName + "." + CompileEnvironment.Config.PrecompiledHeaderIncludeFilename.GetFileName() + ".cpp"; FileReference PCHCPPPath = FileReference.Combine(CompileEnvironment.Config.OutputDirectory, PCHCPPFilename); FileItem PCHCPPFile = FileItem.CreateIntermediateTextFile( PCHCPPPath, string.Format("#include \"{0}\"\r\n", CompileEnvironment.Config.PrecompiledHeaderIncludeFilename) ); // Make sure the original source directory the PCH header file existed in is added as an include // path -- it might be a private PCH header and we need to make sure that its found! string OriginalPCHHeaderDirectory = Path.GetDirectoryName(SourceFile.AbsolutePath); FileArguments.AppendFormat(" /I \"{0}\"", OriginalPCHHeaderDirectory); var PrecompiledFileExtension = UEBuildPlatform.GetBuildPlatform(UnrealTargetPlatform.UWP).GetBinaryExtension(UEBuildBinaryType.PrecompiledHeader); // Add the precompiled header file to the produced items list. FileItem PrecompiledHeaderFile = FileItem.GetItemByFileReference( FileReference.Combine( CompileEnvironment.Config.OutputDirectory, Path.GetFileName(SourceFile.AbsolutePath) + PrecompiledFileExtension ) ); CompileAction.ProducedItems.Add(PrecompiledHeaderFile); Result.PrecompiledHeaderFile = PrecompiledHeaderFile; // Add the parameters needed to compile the precompiled header file to the command-line. FileArguments.AppendFormat(" /Yc\"{0}\"", CompileEnvironment.Config.PrecompiledHeaderIncludeFilename); FileArguments.AppendFormat(" /Fp\"{0}\"", PrecompiledHeaderFile.AbsolutePath); // If we're creating a PCH that will be used to compile source files for a library, we need // the compiled modules to retain a reference to PCH's module, so that debugging information // will be included in the library. This is also required to avoid linker warning "LNK4206" // when linking an application that uses this library. if (CompileEnvironment.Config.bIsBuildingLibrary) { // NOTE: The symbol name we use here is arbitrary, and all that matters is that it is // unique per PCH module used in our library string FakeUniquePCHSymbolName = CompileEnvironment.Config.PrecompiledHeaderIncludeFilename.GetFileNameWithoutExtension(); FileArguments.AppendFormat(" /Yl{0}", FakeUniquePCHSymbolName); } FileArguments.AppendFormat(" \"{0}\"", PCHCPPFile.AbsolutePath); CompileAction.StatusDescription = PCHCPPFilename; } else { if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include) { CompileAction.bIsUsingPCH = true; CompileAction.PrerequisiteItems.Add(CompileEnvironment.PrecompiledHeaderFile); FileArguments.AppendFormat(" /Yu\"{0}\"", CompileEnvironment.Config.PCHHeaderNameInCode); FileArguments.AppendFormat(" /Fp\"{0}\"", CompileEnvironment.PrecompiledHeaderFile.AbsolutePath); // Is it unsafe to always force inclusion? Clang is doing it, and .generated.cpp files // won't work otherwise, because they're not located in the context of the module, // so they can't access the module's PCH without an absolute path. //if( CompileEnvironment.Config.bForceIncludePrecompiledHeader ) { // Force include the precompiled header file. This is needed because we may have selected a // precompiled header that is different than the first direct include in the C++ source file, but // we still need to make sure that our precompiled header is the first thing included! FileArguments.AppendFormat(" /FI\"{0}\"", CompileEnvironment.Config.PCHHeaderNameInCode); } } // Add the source file path to the command-line. FileArguments.AppendFormat(" \"{0}\"", SourceFile.AbsolutePath); CompileAction.StatusDescription = Path.GetFileName(SourceFile.AbsolutePath); } if (bEmitsObjectFile) { var ObjectFileExtension = UEBuildPlatform.GetBuildPlatform(UnrealTargetPlatform.UWP).GetBinaryExtension(UEBuildBinaryType.Object); // Add the object file to the produced item list. FileItem ObjectFile = FileItem.GetItemByFileReference( FileReference.Combine( CompileEnvironment.Config.OutputDirectory, Path.GetFileName(SourceFile.AbsolutePath) + ObjectFileExtension ) ); CompileAction.ProducedItems.Add(ObjectFile); Result.ObjectFiles.Add(ObjectFile); FileArguments.AppendFormat(" /Fo\"{0}\"", ObjectFile.AbsolutePath); } // Create PDB files if we were configured to do that. // // Also, when debug info is off and XGE is enabled, force PDBs, otherwise it will try to share // a PDB file, which causes PCH creation to be serial rather than parallel (when debug info is disabled) // --> See https://udn.epicgames.com/lists/showpost.php?id=50619&list=unprog3 if (BuildConfiguration.bUsePDBFiles || (BuildConfiguration.bAllowXGE && !CompileEnvironment.Config.bCreateDebugInfo)) { string PDBFileName; bool bActionProducesPDB = false; // All files using the same PCH are required to share the same PDB that was used when compiling the PCH if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include) { PDBFileName = "PCH." + ModuleName + "." + CompileEnvironment.Config.PrecompiledHeaderIncludeFilename.GetFileName(); } // Files creating a PCH use a PDB per file. else if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create) { PDBFileName = "PCH." + ModuleName + "." + CompileEnvironment.Config.PrecompiledHeaderIncludeFilename.GetFileName(); bActionProducesPDB = true; } // Ungrouped C++ files use a PDB per file. else if (!bIsPlainCFile) { PDBFileName = Path.GetFileName(SourceFile.AbsolutePath); bActionProducesPDB = true; } // Group all plain C files that doesn't use PCH into the same PDB else { PDBFileName = "MiscPlainC"; } // Specify the PDB file that the compiler should write to. FileItem PDBFile = FileItem.GetItemByFileReference( FileReference.Combine( CompileEnvironment.Config.OutputDirectory, PDBFileName + ".pdb" ) ); FileArguments.AppendFormat(" /Fd\"{0}\"", PDBFile.AbsolutePath); // Only use the PDB as an output file if we want PDBs and this particular action is // the one that produces the PDB (as opposed to no debug info, where the above code // is needed, but not the output PDB, or when multiple files share a single PDB, so // only the action that generates it should count it as output directly) if (BuildConfiguration.bUsePDBFiles && bActionProducesPDB) { CompileAction.ProducedItems.Add(PDBFile); Result.DebugDataFiles.Add(PDBFile); } } // Add C or C++ specific compiler arguments. if (bIsPlainCFile) { AppendCLArguments_C(FileArguments); } else { AppendCLArguments_CPP(CompileEnvironment, FileArguments); } CompileAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName; CompileAction.CommandPath = EnvVars.CompilerPath; CompileAction.CommandArguments = Arguments.ToString() + FileArguments.ToString() + CompileEnvironment.Config.AdditionalArguments; if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create) { Log.TraceVerbose("Creating PCH: " + CompileEnvironment.Config.PrecompiledHeaderIncludeFilename); Log.TraceVerbose(" Command: " + CompileAction.CommandArguments); } else { Log.TraceVerbose(" Compiling: " + CompileAction.StatusDescription); Log.TraceVerbose(" Command: " + CompileAction.CommandArguments); } // VC++ always outputs the source file name being compiled, so we don't need to emit this ourselves CompileAction.bShouldOutputStatusDescription = false; // Don't farm out creation of precompiled headers as it is the critical path task. CompileAction.bCanExecuteRemotely = CompileEnvironment.Config.PrecompiledHeaderAction != PrecompiledHeaderAction.Create || BuildConfiguration.bAllowRemotelyCompiledPCHs ; // @todo: XGE has problems remote compiling C++/CLI files that use .NET Framework 4.0 if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled) { CompileAction.bCanExecuteRemotely = false; } } return(Result); }
internal bool ExecuteActions(List <Action> InActions, Dictionary <Action, ActionThread> InActionThreadDictionary) { // Build the script file that will be executed by SN-DBS StreamWriter ScriptFile; string ScriptFilename = Path.Combine(UnrealBuildTool.EngineDirectory.FullName, "Intermediate", "Build", "SNDBS.bat"); FileStream ScriptFileStream = new FileStream(ScriptFilename, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); ScriptFile = new StreamWriter(ScriptFileStream); ScriptFile.AutoFlush = true; int NumScriptedActions = 0; List <Action> LocalActions = new List <Action>(); ActionThread DummyActionThread = new ActionThread(null, 1, 1); bool PrintDebugInfo = false; foreach (Action Action in InActions) { ActionThread ActionProcess = null; bool bFoundActionProcess = InActionThreadDictionary.TryGetValue(Action, out ActionProcess); if (bFoundActionProcess == false) { // Determine whether there are any prerequisites of the action that are outdated. bool bHasOutdatedPrerequisites = false; bool bHasFailedPrerequisites = false; foreach (Action PrerequisiteAction in Action.PrerequisiteActions) { if (InActions.Contains(PrerequisiteAction)) { ActionThread PrerequisiteProcess = null; bool bFoundPrerequisiteProcess = InActionThreadDictionary.TryGetValue(PrerequisiteAction, out PrerequisiteProcess); if (bFoundPrerequisiteProcess == true) { if (PrerequisiteProcess == null) { bHasFailedPrerequisites = true; } else if (PrerequisiteProcess.bComplete == false) { bHasOutdatedPrerequisites = true; } else if (PrerequisiteProcess.ExitCode != 0) { bHasFailedPrerequisites = true; } } else { bHasOutdatedPrerequisites = true; } } } // If there are any failed prerequisites of this action, don't execute it. if (bHasFailedPrerequisites) { // Add a null entry in the dictionary for this action. InActionThreadDictionary.Add(Action, null); } // If there aren't any outdated prerequisites of this action, execute it. else if (!bHasOutdatedPrerequisites) { if (Action.bCanExecuteRemotely == false || Action.bCanExecuteRemotelyWithSNDBS == false) { // Execute locally LocalActions.Add(Action); } else { // Create a dummy force-included file which references PCH files, so that SN-DBS knows they are dependencies. string AdditionalStubIncludes = ""; if (Action.CommandPath.GetFileName().Equals("cl.exe", StringComparison.OrdinalIgnoreCase)) { string ResponseFile = Action.CommandArguments.Replace("\"", "").Replace("@", "").Trim(); StringBuilder WrapperContents = new StringBuilder(); using (StringWriter Writer = new StringWriter(WrapperContents)) { Writer.WriteLine("// PCH dependencies for {0}", ResponseFile); Writer.WriteLine("#if 0"); foreach (FileItem Preqrequisite in Action.PrerequisiteItems) { if (Preqrequisite.AbsolutePath.EndsWith(".pch")) { Writer.WriteLine("#include \"{0}\"", Preqrequisite.AbsolutePath.Replace(".pch", ".obj")); } } Writer.WriteLine("#endif"); } FileItem DummyResponseFileDependency = FileItem.CreateIntermediateTextFile(new FileReference(ResponseFile + ".dummy.h"), WrapperContents.ToString()); AdditionalStubIncludes = string.Format("/FI\"{0}\"", DummyResponseFileDependency); } // Add to script for execution by SN-DBS string NewCommandArguments = "\"" + Action.CommandPath + "\"" + " " + AdditionalStubIncludes + " " + Action.CommandArguments; ScriptFile.WriteLine(NewCommandArguments); InActionThreadDictionary.Add(Action, DummyActionThread); Action.StartTime = Action.EndTime = DateTimeOffset.Now; Log.TraceInformation("[{0}/{1}] {2} {3}", JobNumber, InActions.Count, Action.CommandDescription, Action.StatusDescription); JobNumber++; NumScriptedActions++; PrintDebugInfo |= Action.bPrintDebugInfo; if (Action.DependencyListFile != null && File.Exists(Action.DependencyListFile.AbsolutePath)) { Log.TraceVerbose("Deleting dependency list file {0}", Action.DependencyListFile.AbsolutePath); File.Delete(Action.DependencyListFile.AbsolutePath); } } } } } ScriptFile.Flush(); ScriptFile.Close(); ScriptFile.Dispose(); ScriptFile = null; if (NumScriptedActions > 0) { // Create the process string SCERoot = Environment.GetEnvironmentVariable("SCE_ROOT_DIR"); string SNDBSExecutable = Path.Combine(SCERoot, "Common/SN-DBS/bin/dbsbuild.exe"); DirectoryReference TemplatesDir = DirectoryReference.Combine(UnrealBuildTool.EngineDirectory, "Programs", "UnrealBuildTool", "SndbsTemplates"); string IncludeRewriteRulesArg = String.Format("--include-rewrite-rules \"{0}\"", IncludeRewriteRulesFile.FullName); string VerbosityLevel = PrintDebugInfo ? "-v" : "-q"; ProcessStartInfo PSI = new ProcessStartInfo(SNDBSExecutable, String.Format("{0} -p UE4 -s \"{1}\" -templates \"{2}\" {3}", VerbosityLevel, FileReference.Combine(UnrealBuildTool.EngineDirectory, "Intermediate", "Build", "sndbs.bat").FullName, TemplatesDir.FullName, IncludeRewriteRulesArg)); PSI.RedirectStandardOutput = true; PSI.RedirectStandardError = true; PSI.UseShellExecute = false; PSI.CreateNoWindow = true; PSI.WorkingDirectory = Path.GetFullPath("."); Process NewProcess = new Process(); NewProcess.StartInfo = PSI; NewProcess.OutputDataReceived += new DataReceivedEventHandler(ActionDebugOutput); NewProcess.ErrorDataReceived += new DataReceivedEventHandler(ActionDebugOutput); DateTimeOffset StartTime = DateTimeOffset.Now; NewProcess.Start(); NewProcess.BeginOutputReadLine(); NewProcess.BeginErrorReadLine(); NewProcess.WaitForExit(); TimeSpan Duration; DateTimeOffset EndTime = DateTimeOffset.Now; if (EndTime == DateTimeOffset.MinValue) { Duration = DateTimeOffset.Now - StartTime; } else { Duration = EndTime - StartTime; } DummyActionThread.bComplete = true; int ExitCode = NewProcess.ExitCode; if (ExitCode != 0) { return(false); } } // Execute local tasks if (LocalActions.Count > 0) { return(ExecuteLocalActions(LocalActions, InActionThreadDictionary, InActions.Count)); } return(true); }
/// <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="BaseName">Base name to use for the Unity files</param> /// <returns>The "unity" C++ files.</returns> public static List <FileItem> GenerateUnityCPPs( UEBuildTarget Target, List <FileItem> CPPFiles, CPPEnvironment CompileEnvironment, string BaseName ) { var ToolChain = UEToolChain.GetPlatformToolChain(CompileEnvironment.Config.Target.Platform); var BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(CompileEnvironment.Config.Target.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.Info.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. // // Optimization only makes sense if PCH files are enabled. bool bForceIntoSingleUnityFile = BuildConfiguration.bStressTestUnity || (TotalBytesInCPPFiles < BuildConfiguration.NumIncludedBytesPerUnityCPP * 2 && CompileEnvironment.ShouldUsePCHs()); // Build the list of unity files. List <FileCollection> AllUnityFiles; { var CPPUnityFileBuilder = new UnityFileBuilder(bForceIntoSingleUnityFile ? -1 : BuildConfiguration.NumIncludedBytesPerUnityCPP); foreach (var CPPFile in CPPFiles) { if (!bForceIntoSingleUnityFile && CPPFile.AbsolutePath.Contains(".GeneratedWrapper.")) { CPPUnityFileBuilder.EndCurrentUnityFile(); CPPUnityFileBuilder.AddFile(CPPFile); CPPUnityFileBuilder.EndCurrentUnityFile(); } else { CPPUnityFileBuilder.AddFile(CPPFile); } // Now that the CPPFile is part of this unity file, we will no longer need to treat it like a root level prerequisite for our // dependency cache, as it is now an "indirect include" from the unity file. We'll clear out the compile environment // attached to this file. This prevents us from having to cache all of the indirect includes from these files inside our // dependency cache, which speeds up iterative builds a lot! CPPFile.CachedCPPIncludeInfo = null; } AllUnityFiles = CPPUnityFileBuilder.GetUnityFiles(); } //THIS CHANGE WAS MADE TO FIX THE BUILD IN OUR BRANCH //DO NOT MERGE THIS BACK TO MAIN string PCHHeaderNameInCode = CPPFiles.Count > 0 ? CPPFiles[0].PCHHeaderNameInCode : ""; if (CompileEnvironment.Config.PrecompiledHeaderIncludeFilename != null) { PCHHeaderNameInCode = ToolChain.ConvertPath(CompileEnvironment.Config.PrecompiledHeaderIncludeFilename); // Generated unity .cpp files always include the PCH using an absolute path, so we need to update // our compile environment's PCH header name to use this instead of the text it pulled from the original // C++ source files CompileEnvironment.Config.PCHHeaderNameInCode = PCHHeaderNameInCode; } // 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; var UnityCPPFiles = new List <FileItem>(); foreach (var UnityFile in AllUnityFiles) { ++CurrentUnityFileCount; StringWriter OutputUnityCPPWriter = new StringWriter(); StringWriter OutputUnityCPPWriterExtra = null; // add an extra file for UBT to get the #include dependencies from if (BuildPlatform.RequiresExtraUnityCPPWriter() == true) { OutputUnityCPPWriterExtra = new StringWriter(); } OutputUnityCPPWriter.WriteLine("// This file is automatically generated at compile-time to include some subset of the user-created cpp files."); // Explicitly include the precompiled header first, since Visual C++ expects the first top-level #include to be the header file // that was used to create the PCH. if (CompileEnvironment.Config.PrecompiledHeaderIncludeFilename != null) { OutputUnityCPPWriter.WriteLine("#include \"{0}\"", PCHHeaderNameInCode); if (OutputUnityCPPWriterExtra != null) { OutputUnityCPPWriterExtra.WriteLine("#include \"{0}\"", PCHHeaderNameInCode); } } // Add source files to the unity file foreach (var CPPFile in UnityFile.Files) { OutputUnityCPPWriter.WriteLine("#include \"{0}\"", ToolChain.ConvertPath(CPPFile.AbsolutePath)); if (OutputUnityCPPWriterExtra != null) { OutputUnityCPPWriterExtra.WriteLine("#include \"{0}\"", CPPFile.AbsolutePath); } } // Determine unity file path name string UnityCPPFilePath; if (AllUnityFiles.Count > 1) { UnityCPPFilePath = string.Format("Module.{0}.{1}_of_{2}.cpp", BaseName, CurrentUnityFileCount, AllUnityFiles.Count); } else { UnityCPPFilePath = string.Format("Module.{0}.cpp", BaseName); } UnityCPPFilePath = Path.Combine(CompileEnvironment.Config.OutputDirectory, UnityCPPFilePath); // Write the unity file to the intermediate folder. FileItem UnityCPPFile = FileItem.CreateIntermediateTextFile(UnityCPPFilePath, OutputUnityCPPWriter.ToString()); if (OutputUnityCPPWriterExtra != null) { FileItem.CreateIntermediateTextFile(UnityCPPFilePath + ".ex", OutputUnityCPPWriterExtra.ToString()); } UnityCPPFile.RelativeCost = UnityFile.TotalLength; UnityCPPFile.PCHHeaderNameInCode = PCHHeaderNameInCode; UnityCPPFiles.Add(UnityCPPFile); // Cache information about the unity .cpp dependencies // @todo ubtmake urgent: Fails when building remotely for Mac because unity .cpp has an include for a PCH on the REMOTE machine UEBuildModuleCPP.CachePCHUsageForModuleSourceFile(Target, CompileEnvironment, UnityCPPFile); } return(UnityCPPFiles); }
public override CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, List <FileItem> InputFiles, DirectoryReference OutputDir, string ModuleName, ActionGraph ActionGraph) { // Get the MSVC arguments required to compile all files in this batch List <string> SharedArguments = new List <string>(); SharedArguments.Add("/nologo"); SharedArguments.Add("/P"); // Preprocess SharedArguments.Add("/C"); // Preserve comments when preprocessing SharedArguments.Add("/D PVS_STUDIO"); SharedArguments.Add("/wd4005"); if (EnvVars.Compiler >= WindowsCompiler.VisualStudio2015) { SharedArguments.Add("/D _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS=1"); } foreach (DirectoryReference IncludePath in CompileEnvironment.IncludePaths.UserIncludePaths) { SharedArguments.Add(String.Format("/I \"{0}\"", IncludePath)); } foreach (DirectoryReference IncludePath in CompileEnvironment.IncludePaths.SystemIncludePaths) { SharedArguments.Add(String.Format("/I \"{0}\"", IncludePath)); } foreach (DirectoryReference IncludePath in EnvVars.IncludePaths) { SharedArguments.Add(String.Format("/I \"{0}\"", IncludePath)); } foreach (string Definition in CompileEnvironment.Definitions) { SharedArguments.Add(String.Format("/D \"{0}\"", Definition)); } foreach (FileItem ForceIncludeFile in CompileEnvironment.ForceIncludeFiles) { SharedArguments.Add(String.Format("/FI\"{0}\"", ForceIncludeFile.Location)); } CPPOutput Result = new CPPOutput(); foreach (FileItem SourceFile in InputFiles) { // Get the file names for everything we need string BaseFileName = SourceFile.Location.GetFileName(); // Write the response file FileReference PreprocessedFileLocation = FileReference.Combine(OutputDir, BaseFileName + ".i"); List <string> Arguments = new List <string>(SharedArguments); Arguments.Add(String.Format("/Fi\"{0}\"", PreprocessedFileLocation)); // Preprocess to a file Arguments.Add(String.Format("\"{0}\"", SourceFile.AbsolutePath)); FileReference ResponseFileLocation = FileReference.Combine(OutputDir, BaseFileName + ".i.response"); FileItem ResponseFileItem = FileItem.CreateIntermediateTextFile(ResponseFileLocation, String.Join("\n", Arguments)); // Preprocess the source file FileItem PreprocessedFileItem = FileItem.GetItemByFileReference(PreprocessedFileLocation); Action PreprocessAction = ActionGraph.Add(ActionType.Compile); PreprocessAction.CommandPath = EnvVars.CompilerPath.FullName; PreprocessAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName; PreprocessAction.CommandArguments = " @\"" + ResponseFileItem.AbsolutePath + "\""; PreprocessAction.PrerequisiteItems.AddRange(CompileEnvironment.ForceIncludeFiles); PreprocessAction.PrerequisiteItems.Add(SourceFile); PreprocessAction.PrerequisiteItems.Add(ResponseFileItem); PreprocessAction.ProducedItems.Add(PreprocessedFileItem); PreprocessAction.bShouldOutputStatusDescription = false; // Write the PVS studio config file StringBuilder ConfigFileContents = new StringBuilder(); foreach (DirectoryReference IncludePath in EnvVars.IncludePaths) { ConfigFileContents.AppendFormat("exclude-path={0}\n", IncludePath.FullName); } 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 (CppPlatform == CppPlatform.Win64) { ConfigFileContents.Append("platform=x64\n"); } else if (CppPlatform == CppPlatform.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); FileReference ConfigFileLocation = FileReference.Combine(OutputDir, BaseFileName + ".cfg"); FileItem ConfigFileItem = FileItem.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 = ActionGraph.Add(ActionType.Compile); AnalyzeAction.CommandDescription = "Analyzing"; AnalyzeAction.StatusDescription = BaseFileName; AnalyzeAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName; AnalyzeAction.CommandPath = AnalyzerFile.FullName; AnalyzeAction.CommandArguments = String.Format("--cl-params \"{0}\" --source-file \"{1}\" --output-file \"{2}\" --cfg \"{3}\" --analysis-mode 4", PreprocessAction.CommandArguments, SourceFile.AbsolutePath, OutputFileLocation, ConfigFileItem.AbsolutePath); 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.ProducedItems.Add(OutputFileItem); AnalyzeAction.bShouldDeleteProducedItems = true; // PVS Studio will append by default, so need to delete produced items Result.ObjectFiles.AddRange(AnalyzeAction.ProducedItems); } return(Result); }
public override FileItem LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly, ActionGraph ActionGraph) { FileItem OutputFile; // Make the final javascript file Action LinkAction = ActionGraph.Add(ActionType.Link); LinkAction.CommandDescription = "Link"; // LinkAction.bPrintDebugInfo = true; // ResponseFile lines. List <string> ReponseLines = new List <string>(); LinkAction.bCanExecuteRemotely = false; LinkAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName; LinkAction.CommandPath = HTML5SDKInfo.Python(); LinkAction.CommandArguments = HTML5SDKInfo.EmscriptenCompiler(); // bool bIsBuildingLibrary = LinkEnvironment.bIsBuildingLibrary || bBuildImportLibraryOnly; // ReponseLines.Add( // bIsBuildingLibrary ? // GetLibArguments(LinkEnvironment) : // GetLinkArguments(LinkEnvironment) // ); ReponseLines.Add(GetLinkArguments(LinkEnvironment)); // Add the input files to a response file, and pass the response file on the command-line. foreach (FileItem InputFile in LinkEnvironment.InputFiles) { //System.Console.WriteLine("File {0} ", InputFile.AbsolutePath); ReponseLines.Add(string.Format(" \"{0}\"", InputFile.AbsolutePath)); LinkAction.PrerequisiteItems.Add(InputFile); } if (!LinkEnvironment.bIsBuildingLibrary) { // Make sure ThirdParty libs are at the end. List <string> ThirdParty = (from Lib in LinkEnvironment.AdditionalLibraries where Lib.Contains("ThirdParty") select Lib).ToList(); LinkEnvironment.AdditionalLibraries.RemoveAll(Element => Element.Contains("ThirdParty")); LinkEnvironment.AdditionalLibraries.AddRange(ThirdParty); foreach (string InputFile in LinkEnvironment.AdditionalLibraries) { FileItem Item = FileItem.GetItemByPath(InputFile); if (Item.AbsolutePath.Contains(".lib")) { continue; } if (Item.ToString().EndsWith(".js")) { ReponseLines.Add(string.Format(" --js-library \"{0}\"", Item.AbsolutePath)); } // WARNING: With --pre-js and --post-js, the order in which these directives are passed to // the compiler is very critical, because that dictates the order in which they are appended. // // Set environment variable [ EMCC_DEBUG=1 ] to see the linker order used in packaging. // See GetSharedArguments_Global() above to set this environment variable else if (Item.ToString().EndsWith(".jspre")) { ReponseLines.Add(string.Format(" --pre-js \"{0}\"", Item.AbsolutePath)); } else if (Item.ToString().EndsWith(".jspost")) { ReponseLines.Add(string.Format(" --post-js \"{0}\"", Item.AbsolutePath)); } else { ReponseLines.Add(string.Format(" \"{0}\"", Item.AbsolutePath)); } LinkAction.PrerequisiteItems.Add(Item); } } // make the file we will create OutputFile = FileItem.GetItemByFileReference(LinkEnvironment.OutputFilePath); LinkAction.ProducedItems.Add(OutputFile); ReponseLines.Add(string.Format(" -o \"{0}\"", OutputFile.AbsolutePath)); FileItem OutputBC = FileItem.GetItemByPath(LinkEnvironment.OutputFilePath.FullName.Replace(".js", ".bc").Replace(".html", ".bc")); LinkAction.ProducedItems.Add(OutputBC); ReponseLines.Add(string.Format(" --save-bc \"{0}\"", OutputBC.AbsolutePath)); LinkAction.StatusDescription = Path.GetFileName(OutputFile.AbsolutePath); FileReference ResponseFileName = GetResponseFileName(LinkEnvironment, OutputFile); FileItem ResponseFileItem = FileItem.CreateIntermediateTextFile(ResponseFileName, ReponseLines); LinkAction.CommandArguments += string.Format(" @\"{0}\"", ResponseFileName); LinkAction.PrerequisiteItems.Add(ResponseFileItem); return(OutputFile); }
/// <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="BaseName">Base name to use for the Unity files</param> /// <returns>The "unity" C++ files.</returns> public static List <FileItem> GenerateUnityCPPs( UEToolChain ToolChain, UEBuildTarget Target, List <FileItem> CPPFiles, CPPEnvironment CompileEnvironment, string BaseName ) { List <FileItem> NewCPPFiles = new List <FileItem>(); UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(CompileEnvironment.Config.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.Info.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 = BuildConfiguration.bUseAdaptiveUnityBuild && !BuildConfiguration.bStressTestUnity; // Optimization only makes sense if PCH files are enabled. bool bForceIntoSingleUnityFile = BuildConfiguration.bStressTestUnity || (TotalBytesInCPPFiles < BuildConfiguration.NumIncludedBytesPerUnityCPP * 2 && CompileEnvironment.ShouldUsePCHs()); // 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. if (bUseAdaptiveUnityBuild) { int CandidateWorkingSetSourceFileCount = 0; int WorkingSetSourceFileCount = 0; foreach (FileItem CPPFile in SortedCPPFiles) { // Don't include writable source files into unity blobs if (!CPPFile.Reference.IsUnderDirectory(Target.EngineIntermediateDirectory) && !CPPFile.Reference.IsUnderDirectory(Target.ProjectIntermediateDirectory)) { ++CandidateWorkingSetSourceFileCount; if (UnrealBuildTool.ShouldSourceFileBePartOfWorkingSet(CPPFile.AbsolutePath)) { ++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.) SourceFileWorkingSet.Add(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 : BuildConfiguration.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 && SourceFileWorkingSet.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) { CandidateSourceFilesForWorkingSet.Add(CPPFile); } // Compile this file as part of the unity blob CPPUnityFileBuilder.AddFile(CPPFile); // Now that the CPPFile is part of this unity file, we will no longer need to treat it like a root level prerequisite for our // dependency cache, as it is now an "indirect include" from the unity file. We'll clear out the compile environment // attached to this file. This prevents us from having to cache all of the indirect includes from these files inside our // dependency cache, which speeds up iterative builds a lot! CPPFile.CachedCPPIncludeInfo = null; } } if (AdaptiveUnityBuildInfoString.Length > 0) { Log.TraceInformation(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(); StringWriter OutputUnityCPPWriterExtra = null; // add an extra file for UBT to get the #include dependencies from if (BuildPlatform.RequiresExtraUnityCPPWriter() == true) { OutputUnityCPPWriterExtra = 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}\"", ToolChain.ConvertPath(CPPFile.AbsolutePath)); if (OutputUnityCPPWriterExtra != null) { OutputUnityCPPWriterExtra.WriteLine("#include \"{0}\"", CPPFile.AbsolutePath); } } // 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(CompileEnvironment.Config.OutputDirectory, UnityCPPFileName); // Write the unity file to the intermediate folder. FileItem UnityCPPFile = FileItem.CreateIntermediateTextFile(UnityCPPFilePath, OutputUnityCPPWriter.ToString()); if (OutputUnityCPPWriterExtra != null) { FileItem.CreateIntermediateTextFile(UnityCPPFilePath + ".ex", OutputUnityCPPWriterExtra.ToString()); } UnityCPPFile.RelativeCost = UnityFile.TotalLength; NewCPPFiles.Add(UnityCPPFile); // Cache information about the unity .cpp dependencies // @todo ubtmake urgent: Fails when building remotely for Mac because unity .cpp has an include for a PCH on the REMOTE machine UEBuildModuleCPP.CachePCHUsageForModuleSourceFile(CompileEnvironment, UnityCPPFile); } return(NewCPPFiles); }
public override CPPOutput CompileCPPFiles(UEBuildTarget Target, CPPEnvironment CompileEnvironment, List <FileItem> SourceFiles, string ModuleName) { string Arguments = GetCLArguments_Global(CompileEnvironment); // Add include paths to the argument list. foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.IncludePaths) { Arguments += string.Format(" /I \"{0}\"", IncludePath); } foreach (string IncludePath in CompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths) { Arguments += string.Format(" /I \"{0}\"", IncludePath); } if ((CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled) || (WinRTPlatform.ShouldCompileWinRT() == true)) { // Add .NET framework assembly paths. This is needed so that C++/CLI projects // can reference assemblies with #using, without having to hard code a path in the // .cpp file to the assembly's location. foreach (string AssemblyPath in CompileEnvironment.Config.SystemDotNetAssemblyPaths) { Arguments += string.Format(" /AI \"{0}\"", AssemblyPath); } // Add explicit .NET framework assembly references foreach (string AssemblyName in CompileEnvironment.Config.FrameworkAssemblyDependencies) { Arguments += string.Format(" /FU \"{0}\"", AssemblyName); } // Add private assembly references foreach (PrivateAssemblyInfo CurAssemblyInfo in CompileEnvironment.PrivateAssemblyDependencies) { Arguments += string.Format(" /FU \"{0}\"", CurAssemblyInfo.FileItem.AbsolutePath); } } else { foreach (string AssemblyPath in CompileEnvironment.Config.SystemDotNetAssemblyPaths) { Arguments += string.Format(" /AI \"{0}\"", AssemblyPath); } // Add explicit .NET framework assembly references foreach (string AssemblyName in CompileEnvironment.Config.FrameworkAssemblyDependencies) { Arguments += string.Format(" /FU \"{0}\"", AssemblyName); } } // Add preprocessor definitions to the argument list. foreach (string Definition in CompileEnvironment.Config.Definitions) { Arguments += string.Format(" /D \"{0}\"", Definition); } // Log.TraceInformation("Compile Arguments for {0}:", ModuleName); // Log.TraceInformation(Arguments); var BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(CompileEnvironment.Config.Target.Platform); // Create a compile action for each source file. CPPOutput Result = new CPPOutput(); foreach (FileItem SourceFile in SourceFiles) { Action CompileAction = new Action(ActionType.Compile); string FileArguments = ""; bool bIsPlainCFile = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant() == ".C"; // Add the C++ source file and its included files to the prerequisite item list. AddPrerequisiteSourceFile(Target, BuildPlatform, CompileEnvironment, SourceFile, CompileAction.PrerequisiteItems); // If this is a CLR file then make sure our dependent assemblies are added as prerequisites if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled) { foreach (PrivateAssemblyInfo CurPrivateAssemblyDependency in CompileEnvironment.PrivateAssemblyDependencies) { CompileAction.PrerequisiteItems.Add(CurPrivateAssemblyDependency.FileItem); } } if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create) { // Generate a CPP File that just includes the precompiled header. string PCHCPPFilename = "PCH." + Path.GetFileName(CompileEnvironment.Config.PrecompiledHeaderIncludeFilename) + ".cpp"; string PCHCPPPath = Path.Combine(CompileEnvironment.Config.OutputDirectory, PCHCPPFilename); FileItem PCHCPPFile = FileItem.CreateIntermediateTextFile( PCHCPPPath, string.Format("#include \"{0}\"\r\n", CompileEnvironment.Config.PrecompiledHeaderIncludeFilename) ); // Make sure the original source directory the PCH header file existed in is added as an include // path -- it might be a private PCH header and we need to make sure that its found! string OriginalPCHHeaderDirectory = Path.GetDirectoryName(SourceFile.AbsolutePath); FileArguments += string.Format(" /I \"{0}\"", OriginalPCHHeaderDirectory); var PCHExtension = UEBuildPlatform.BuildPlatformDictionary[UnrealTargetPlatform.WinRT].GetBinaryExtension(UEBuildBinaryType.PrecompiledHeader); // Add the precompiled header file to the produced items list. FileItem PrecompiledHeaderFile = FileItem.GetItemByPath( Path.Combine( CompileEnvironment.Config.OutputDirectory, Path.GetFileName(SourceFile.AbsolutePath) + PCHExtension ) ); CompileAction.ProducedItems.Add(PrecompiledHeaderFile); Result.PrecompiledHeaderFile = PrecompiledHeaderFile; // Add the parameters needed to compile the precompiled header file to the command-line. FileArguments += string.Format(" /Yc\"{0}\"", CompileEnvironment.Config.PrecompiledHeaderIncludeFilename); FileArguments += string.Format(" /Fp\"{0}\"", PrecompiledHeaderFile.AbsolutePath); FileArguments += string.Format(" \"{0}\"", PCHCPPFile.AbsolutePath); // If we're creating a PCH that will be used to compile source files for a library, we need // the compiled modules to retain a reference to PCH's module, so that debugging information // will be included in the library. This is also required to avoid linker warning "LNK4206" // when linking an application that uses this library. if (CompileEnvironment.Config.bIsBuildingLibrary) { // NOTE: The symbol name we use here is arbitrary, and all that matters is that it is // unique per PCH module used in our library string FakeUniquePCHSymbolName = Path.GetFileNameWithoutExtension(CompileEnvironment.Config.PrecompiledHeaderIncludeFilename); FileArguments += string.Format(" /Yl{0}", FakeUniquePCHSymbolName); } CompileAction.StatusDescription = PCHCPPFilename; } else { if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include) { CompileAction.bIsUsingPCH = true; CompileAction.PrerequisiteItems.Add(CompileEnvironment.PrecompiledHeaderFile); FileArguments += string.Format(" /Yu\"{0}\"", CompileEnvironment.Config.PCHHeaderNameInCode); FileArguments += string.Format(" /Fp\"{0}\"", CompileEnvironment.PrecompiledHeaderFile.AbsolutePath); // Is it unsafe to always force inclusion? Clang is doing it, and .generated.cpp files // won't work otherwise, because they're not located in the context of the module, // so they can't access the module's PCH without an absolute path. //if (CompileEnvironment.Config.bForceIncludePrecompiledHeader) { // Force include the precompiled header file. This is needed because we may have selected a // precompiled header that is different than the first direct include in the C++ source file, but // we still need to make sure that our precompiled header is the first thing included! FileArguments += string.Format(" /FI\"{0}\"", CompileEnvironment.Config.PCHHeaderNameInCode); } } // Add the source file path to the command-line. FileArguments += string.Format(" \"{0}\"", SourceFile.AbsolutePath); CompileAction.StatusDescription = Path.GetFileName(SourceFile.AbsolutePath); } var ObjectFileExtension = UEBuildPlatform.BuildPlatformDictionary[UnrealTargetPlatform.WinRT].GetBinaryExtension(UEBuildBinaryType.Object); // Add the object file to the produced item list. FileItem ObjectFile = FileItem.GetItemByPath( Path.Combine( CompileEnvironment.Config.OutputDirectory, Path.GetFileName(SourceFile.AbsolutePath) + ObjectFileExtension ) ); CompileAction.ProducedItems.Add(ObjectFile); Result.ObjectFiles.Add(ObjectFile); FileArguments += string.Format(" /Fo\"{0}\"", ObjectFile.AbsolutePath); // create PDBs per-file when not using debug info, otherwise it will try to share a PDB file, which causes // PCH creation to be serial rather than parallel (when debug info is disabled) // See https://udn.epicgames.com/lists/showpost.php?id=50619&list=unprog3 if (!CompileEnvironment.Config.bCreateDebugInfo || BuildConfiguration.bUsePDBFiles) { string PDBFileName; bool bActionProducesPDB = false; // All files using the same PCH are required to share a PDB. if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include) { PDBFileName = Path.GetFileName(CompileEnvironment.Config.PrecompiledHeaderIncludeFilename); } // Files creating a PCH or ungrouped C++ files use a PDB per file. else if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create || !bIsPlainCFile) { PDBFileName = Path.GetFileName(SourceFile.AbsolutePath); bActionProducesPDB = true; } // Group all plain C files that doesn't use PCH into the same PDB else { PDBFileName = "MiscPlainC"; } // Specify the PDB file that the compiler should write to. FileItem PDBFile = FileItem.GetItemByPath( Path.Combine( CompileEnvironment.Config.OutputDirectory, PDBFileName + ".pdb" ) ); FileArguments += string.Format(" /Fd\"{0}\"", PDBFile.AbsolutePath); // Only use the PDB as an output file if we want PDBs and this particular action is // the one that produces the PDB (as opposed to no debug info, where the above code // is needed, but not the output PDB, or when multiple files share a single PDB, so // only the action that generates it should count it as output directly) if (BuildConfiguration.bUsePDBFiles && bActionProducesPDB) { CompileAction.ProducedItems.Add(PDBFile); Result.DebugDataFiles.Add(PDBFile); } } // Add C or C++ specific compiler arguments. if (bIsPlainCFile) { FileArguments += GetCLArguments_C(); } else { FileArguments += GetCLArguments_CPP(CompileEnvironment); } CompileAction.WorkingDirectory = Path.GetFullPath("."); CompileAction.CommandPath = GetVCToolPath(CompileEnvironment.Config.Target.Platform, CompileEnvironment.Config.Target.Configuration, "cl"); CompileAction.CommandArguments = Arguments + FileArguments + CompileEnvironment.Config.AdditionalArguments; CompileAction.StatusDescription = string.Format("{0}", Path.GetFileName(SourceFile.AbsolutePath)); // Don't farm out creation of precomputed headers as it is the critical path task. CompileAction.bCanExecuteRemotely = CompileEnvironment.Config.PrecompiledHeaderAction != PrecompiledHeaderAction.Create; // @todo: XGE has problems remote compiling C++/CLI files that use .NET Framework 4.0 if (CompileEnvironment.Config.CLRMode == CPPCLRMode.CLREnabled) { CompileAction.bCanExecuteRemotely = false; } } return(Result); }
/// <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="Makefile">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, TargetMakefile Makefile, 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. 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.) Makefile.WorkingSet.Add(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 && Makefile.WorkingSet.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) { Makefile.CandidatesForWorkingSet.Add(CPPFile); } // Compile this file as part of the unity blob CPPUnityFileBuilder.AddFile(CPPFile); } } if (AdaptiveUnityBuildInfoString.Length > 0) { if (Target.bAdaptiveUnityCreatesDedicatedPCH) { AddUniqueDiagnostic(Makefile, "[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) { AddUniqueDiagnostic(Makefile, "[Adaptive unity build] Disabling PCH for excluded files. Set bAdaptiveUnityDisablesPCH to false in BuildConfiguration.xml to change this behavior."); } if (Target.bAdaptiveUnityDisablesOptimizations) { AddUniqueDiagnostic(Makefile, "[Adaptive unity build] Disabling optimizations for excluded files. Set bAdaptiveUnityDisablesOptimizations to false in BuildConfiguration.xml to change this behavior."); } if (Target.bAdaptiveUnityEnablesEditAndContinue) { AddUniqueDiagnostic(Makefile, "[Adaptive unity build] Enabling Edit & Continue for excluded files. Set bAdaptiveUnityEnablesEditAndContinue to false in BuildConfiguration.xml to change this behavior."); } Makefile.Diagnostics.Add(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 = FileItem.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); }
public override CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, List <FileItem> InputFiles, DirectoryReference OutputDir, string ModuleName, List <Action> Actions) { // 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, PreprocessActions); Actions.AddRange(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 = FileItem.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 = new Action(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); 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.ProducedItems.Add(OutputFileItem); AnalyzeAction.DeleteItems.Add(OutputFileItem); // PVS Studio will append by default, so need to delete produced items Actions.Add(AnalyzeAction); Result.ObjectFiles.AddRange(AnalyzeAction.ProducedItems); } return(Result); }