// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using EpicGames.Core; using Microsoft.Extensions.Logging; using OpenTracing.Util; using UnrealBuildBase; namespace UnrealBuildTool { static class UnrealBuildTool { /// /// Save the application startup time. This can be used as the timestamp for build makefiles, to determine a base time after which any /// modifications should invalidate it. /// public static DateTime StartTimeUtc { get; } = DateTime.UtcNow; /// /// Whether this is a recursive run of of the application /// public static bool IsRecursive = false; /// /// Unique id to track this session /// public static string SessionIdentifier = Guid.NewGuid().ToString("B"); /// /// The mode of this instance /// public static string BuildMode = "BuildMode"; /// /// The result of running the application /// private static CompilationResult ApplicationResult = CompilationResult.Unknown; /// /// The environment at boot time. /// public static System.Collections.IDictionary? InitialEnvironment; /// /// The full name of the Engine/Source directory /// [Obsolete("Replace with Unreal.EngineSourceDirectory")] public static readonly DirectoryReference EngineSourceDirectory = Unreal.EngineSourceDirectory; /// /// Writable engine directory. Uses the user's settings folder for installed builds. /// [Obsolete("Replace with Unreal.WritableEngineDirectory")] public static DirectoryReference WritableEngineDirectory = Unreal.WritableEngineDirectory; /// /// The engine programs directory /// [Obsolete("Replace with Unreal.EngineProgramSavedDirectory")] public static DirectoryReference EngineProgramSavedDirectory = Unreal.EngineProgramSavedDirectory; /// /// The original root directory that was used to compile the installed engine /// Used to remap source code paths when debugging. /// [Obsolete("Deprecated in UE5.6 - use Unreal.OriginalCompilationRootDirectory")] public static DirectoryReference OriginalCompilationRootDirectory => Unreal.OriginalCompilationRootDirectory; /// /// Returns true if UnrealBuildTool is running using an installed project (ie. a mod kit) /// /// True if running using an installed project [Obsolete("Deprecated in UE5.5 - use Unreal.IsProjectInstalled")] public static bool IsProjectInstalled() => Unreal.IsProjectInstalled(); /// /// Gets the installed project file /// /// Location of the installed project file [Obsolete("Deprecated in UE5.5 - use Unreal.GetInstalledProjectFile")] public static FileReference? GetInstalledProjectFile() => Unreal.GetInstalledProjectFile(); /// /// Checks whether the given file is under an installed directory, and should not be overridden /// /// File to test /// True if the file is part of the installed distribution, false otherwise [Obsolete("Deprecated in UE5.5 - use Unreal.IsFileInstalled")] public static bool IsFileInstalled(FileReference File) => Unreal.IsFileInstalled(File); /// /// Gets the absolute path to the UBT assembly. /// /// A string containing the path to the UBT assembly. [Obsolete("Deprecated in UE5.1 - use Unreal.DotnetPath and Unreal.UnrealBuildToolDllPath")] public static FileReference GetUBTPath() => Unreal.UnrealBuildToolPath; /// /// The Unreal remote tool ini directory. This should be valid if compiling using a remote server /// /// The directory path public static string? GetRemoteIniPath() => _RemoteIniPath; /// /// Set the Unreal remote tool ini directory. /// /// remote path public static void SetRemoteIniPath(string Path) => _RemoteIniPath = Path; /// /// The Remote Ini directory. This should always be valid when compiling using a remote server. /// private static string? _RemoteIniPath = null; /// /// Global options for UBT (any modes) /// class GlobalOptions { /// /// User asked for help /// [CommandLine(Prefix = "-Help", Description = "Display this help.")] [CommandLine(Prefix = "-h")] [CommandLine(Prefix = "--help")] public bool bGetHelp = false; /// /// The amount of detail to write to the log /// [CommandLine(Prefix = "-Verbose", Value = "Verbose", Description = "Increase output verbosity")] [CommandLine(Prefix = "-VeryVerbose", Value = "VeryVerbose", Description = "Increase output verbosity more")] public LogEventType LogOutputLevel = LogEventType.Log; /// /// Specifies the path to a log file to write. Note that the default mode (eg. building, generating project files) will create a log file by default if this not specified. /// [CommandLine(Prefix = "-Log", Description = "Specify a log file location instead of the default Engine/Programs/UnrealBuildTool/Log.txt")] public FileReference? LogFileName = null; /// /// Log all attempts to write to the specified file /// [CommandLine(Prefix = "-TraceWrites", Description = "Trace writes requested to the specified file")] public FileReference? TraceWrites = null; /// /// Whether to include timestamps in the log /// [CommandLine(Prefix = "-Timestamps", Description = "Include timestamps in the log")] public bool bLogTimestamps = false; /// /// Whether to format messages in MsBuild format /// [CommandLine(Prefix = "-FromMsBuild", Description = "Format messages for msbuild")] public bool bLogFromMsBuild = false; /// /// Whether or not to suppress warnings of missing SDKs from warnings to LogEventType.Log in UEBuildPlatformSDK.cs /// [CommandLine(Prefix = "-SuppressSDKWarnings", Description = "Missing SDKs error verbosity level will be reduced from warning to log")] public bool bShouldSuppressSDKWarnings = false; /// /// Whether to write progress markup in a format that can be parsed by other programs /// [CommandLine(Prefix = "-Progress", Description = "Write progress messages in a format that can be parsed by other programs")] public bool bWriteProgressMarkup = false; /// /// Whether to ignore the mutex /// [CommandLine(Prefix = "-NoMutex", Description = "Allow more than one instance of the program to run at once")] public bool bNoMutex = false; /// /// Whether to wait for the mutex rather than aborting immediately /// [CommandLine(Prefix = "-WaitMutex", Description = "Wait for another instance to finish and then start, rather than aborting immediately")] public bool bWaitMutex = false; /// /// [CommandLine(Prefix = "-RemoteIni", Description = "Remote tool ini directory")] public string RemoteIni = ""; /// /// The mode to execute /// [CommandLine("-Mode=")] // description handling is special-cased in PrintUsage() [CommandLine("-Clean", Value = "Clean", Description = "Clean build products. Equivalent to -Mode=Clean")] [CommandLine("-ProjectFiles", Value = "GenerateProjectFiles", Description = "Generate project files based on IDE preference. Equivalent to -Mode=GenerateProjectFiles")] [CommandLine("-ProjectFileFormat=", Value = "GenerateProjectFiles", Description = "Generate project files in specified format. May be used multiple times.")] [CommandLine("-Makefile", Value = "GenerateProjectFiles", Description = "Generate Makefile")] [CommandLine("-CMakefile", Value = "GenerateProjectFiles", Description = "Generate project files for CMake")] [CommandLine("-QMakefile", Value = "GenerateProjectFiles", Description = "Generate project files for QMake")] [CommandLine("-KDevelopfile", Value = "GenerateProjectFiles", Description = "Generate project files for KDevelop")] [CommandLine("-CodeliteFiles", Value = "GenerateProjectFiles", Description = "Generate project files for Codelite")] [CommandLine("-XCodeProjectFiles", Value = "GenerateProjectFiles", Description = "Generate project files for XCode")] [CommandLine("-EddieProjectFiles", Value = "GenerateProjectFiles", Description = "Generate project files for Eddie")] [CommandLine("-VSCode", Value = "GenerateProjectFiles", Description = "Generate project files for Visual Studio Code")] [CommandLine("-VSMac", Value = "GenerateProjectFiles", Description = "Generate project files for Visual Studio Mac")] [CommandLine("-CLion", Value = "GenerateProjectFiles", Description = "Generate project files for CLion")] [CommandLine("-Rider", Value = "GenerateProjectFiles", Description = "Generate project files for Rider")] #if __VPROJECT_AVAILABLE__ [CommandLine("-VProject", Value = "GenerateProjectFiles")] #endif public string? Mode = null; // The following Log settings exists in this location because, at the time of writing, EpicGames.Core does // not have access to XmlConfigFileAttribute. /// /// Whether to backup an existing log file, rather than overwriting it. /// [XmlConfigFile(Category = "Log")] public bool bBackupLogFiles = Log.BackupLogFiles; /// /// The number of log file backups to preserve. Older backups will be deleted. /// [XmlConfigFile(Category = "Log")] public int LogFileBackupCount = Log.LogFileBackupCount; /// /// If set and tool execution was successful, then display an unreal build tool script execution timeline summary. /// If unset or the tool execution failed, print the same information silently to the log. /// [XmlConfigFile(Category = "BuildConfiguration")] public bool bShowTimeline = Unreal.IsBuildMachine(); /// /// If set TMP\TEMP will be overidden to this directory, each process will create a unique subdirectory in this folder. /// [XmlConfigFile(Category = "BuildConfiguration")] public string? TempDirectory = null; /// /// If set the application temp directory will be deleted on exit, only when running with a single instance mutex. /// [XmlConfigFile(Category = "BuildConfiguration")] public bool bDeleteTempDirectory = false; /// /// Providers to load opt-in telemetry connection information from ini. If unset, or the provider categories do not contain connection info, no telemetry will be sent. /// [XmlConfigFile(Category = "Telemetry", Name = "Providers")] public string[] TelemetryProviders = Array.Empty(); /// /// Additional command line providers to load opt-in telemetry connection information from ini. /// [CommandLine(Prefix = "-TelemetryProvider", Description = "List of ini providers for telemetry", ListSeparator = '+')] public List CmdTelemetryProviders = new(); /// /// Session identifier for this run of UBT, if unset defaults to a random Guid /// [CommandLine(Prefix = "-Session", Description = "Session identifier for this run of UBT, if unset defaults to a random Guid")] public string? TelemetrySession = null; /// /// Initialize the options with the given command line arguments /// /// public GlobalOptions(CommandLineArguments Arguments) { Arguments.ApplyTo(this); if (!String.IsNullOrEmpty(RemoteIni)) { UnrealBuildTool.SetRemoteIniPath(RemoteIni); } } } /// /// Get all the valid Modes /// /// private static Dictionary GetModes() { Dictionary ModeNameToType = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (Type Type in Assembly.GetExecutingAssembly().GetTypes()) { if (Type.IsClass && !Type.IsAbstract && Type.IsSubclassOf(typeof(ToolMode))) { ToolModeAttribute? Attribute = Type.GetCustomAttribute(); if (Attribute == null) { throw new BuildException("Class '{0}' should have a ToolModeAttribute", Type.Name); } ModeNameToType.Add(Attribute.Name, Type); } } return ModeNameToType; } public static readonly Dictionary ModeNameToType = GetModes(); /// /// Print (incomplete) usage information /// private static void PrintUsage() { Console.WriteLine("Global options:"); int LongestPrefix = 0; foreach (FieldInfo Info in typeof(GlobalOptions).GetFields()) { foreach (CommandLineAttribute Att in Info.GetCustomAttributes()) { if (Att.Prefix != null && Att.Description != null) { LongestPrefix = Att.Prefix.Length > LongestPrefix ? Att.Prefix.Length : LongestPrefix; } } } foreach (FieldInfo Info in typeof(GlobalOptions).GetFields()) { foreach (CommandLineAttribute Att in Info.GetCustomAttributes()) { if (Att.Prefix != null && Att.Description != null) { Console.WriteLine($" {Att.Prefix.PadRight(LongestPrefix)} : {Att.Description}"); } // special case for Mode if (String.Equals(Att.Prefix, "-Mode=")) { Console.WriteLine($" {Att.Prefix!.PadRight(LongestPrefix)} : Select tool mode. One of the following (default tool mode is \"Build\"):"); string Indent = "".PadRight(LongestPrefix + 8); string Line = Indent; IOrderedEnumerable SortedModeNames = ModeNameToType.Keys.ToList().OrderBy(Name => Name); foreach (string ModeName in SortedModeNames.SkipLast(1)) { Line += $"{ModeName}, "; if (Line.Length > 110) { Console.WriteLine(Line); Line = Indent; } } Line += SortedModeNames.Last(); Console.WriteLine(Line); } } } } /// /// Read extra command-line arguments from an environment variable /// Double-quote any argument containing whitespace, as they are split by just that. /// /// Extra arguments private static string[] GetExtraArgsFromEnvVar() { string? extraArgs = Environment.GetEnvironmentVariable("UBT_EXTRA_ARGS"); return String.IsNullOrEmpty(extraArgs) ? Array.Empty() : CommandLineArguments.Split(extraArgs); } /// /// Event handler for the Console.CancelKeyPress event /// /// /// private static async void CancelKeyPressAsync(object? sender, ConsoleCancelEventArgs e) { Console.CancelKeyPress -= CancelKeyPressAsync; Console.WriteLine($"UnrealBuildTool: Ctrl-{(e.SpecialKey == ConsoleSpecialKey.ControlC ? "C" : "Break")} pressed. Exiting..."); // Delay a few seconds to allow for the process to exit normally await Task.Delay(2000); // While the Ctrl-C handler fixes most instances of a zombie process, we still need to // force an exit from the process to handle _all_ cases. Ctrl-C should not be a regular event! // Note: this could be a dotnet (6.0.302) on macOS issue. Recheck with next release if this is still required. Environment.Exit(-1); } /// /// Main entry point. Parses any global options and initializes the logging system, then invokes the appropriate command. /// NB: That the entry point is deliberately NOT async, since we have a single-instance mutex that cannot be disposed from a different thread. /// /// Command line arguments /// Zero on success, non-zero on error private static int Main(string[] ArgumentsArray) { ToolModeOptions ModeOptions = ToolModeOptions.None; FileReference? RunFile = null; DirectoryReference? TempDirectory = null; GlobalSingleInstanceMutex? Mutex = null; JsonTracer? Tracer = null; ILogger Logger = Log.Logger; // When running RunUBT.sh on a Mac we need to install a Ctrl-C handler, or hitting Ctrl-C from a terminal // or from cancelling a build within Xcode, can leave a dotnet process in a zombie state. // By putting this in, the Ctrl-C may not be handled immediately, but it shouldn't leave a blocking zombie process if (OperatingSystem.IsMacOS()) { Console.CancelKeyPress += CancelKeyPressAsync; } bool bShowTimeline = false; try { // Start capturing performance info Timeline.Start(); using ITimelineEvent MainScope = Timeline.ScopeEvent("Main"); Tracer = JsonTracer.TryRegisterAsGlobalTracer(); ArgumentsArray = ArgumentsArray.Concat(GetExtraArgsFromEnvVar()).ToArray(); // Parse the command line arguments CommandLineArguments Arguments = new CommandLineArguments(ArgumentsArray); // Parse the global options GlobalOptions Options = new GlobalOptions(Arguments); if ( // Print usage if there are zero arguments provided ArgumentsArray.Length == 0 // Print usage if the user asks for help || Options.bGetHelp ) { PrintUsage(); return Options.bGetHelp ? 0 : 1; } // Configure the log system Log.OutputLevel = Options.LogOutputLevel; Log.IncludeTimestamps = Options.bLogTimestamps; Log.IncludeProgramNameWithSeverityPrefix = Options.bLogFromMsBuild; // Reducing SDK warning events in the log to LogEventType.Log if (Options.bShouldSuppressSDKWarnings) { UEBuildPlatformSDK.bSuppressSDKWarnings = true; } // Always start capturing logs as early as possible to later copy to a log file if the ToolMode desires it (we have to start capturing before we get the ToolModeOptions below) StartupTraceListener StartupTrace = new StartupTraceListener(); Log.AddTraceListener(StartupTrace); if (Options.TraceWrites != null) { Logger.LogInformation("All attempts to write to \"{TraceWrites}\" via WriteFileIfChanged() will be logged", Options.TraceWrites); Utils.WriteFileIfChangedTrace = Options.TraceWrites; } // Configure the progress writer ProgressWriter.bWriteMarkup = Options.bWriteProgressMarkup; // Ensure we can resolve any external assemblies that are not in the same folder as our assembly. AssemblyUtils.InstallAssemblyResolver(Path.GetDirectoryName(Assembly.GetEntryAssembly()!.GetOriginalLocation())!); // Add the application directory to PATH DirectoryReference.AddDirectoryToPath(Unreal.UnrealBuildToolDllPath.Directory); // Change the working directory to be the Engine/Source folder. We are likely running from Engine/Binaries/DotNET // This is critical to be done early so any code that relies on the current directory being Engine/Source will work. DirectoryReference.CreateDirectory(Unreal.EngineSourceDirectory); DirectoryReference.SetCurrentDirectory(Unreal.EngineSourceDirectory); // Register encodings from Net FW as this is required when using Ionic as we do in multiple toolchains Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // Get the type of the mode to execute, using a fast-path for the build mode. Type? ModeType = typeof(BuildMode); if (Options.Mode != null) { // Try to get the correct mode if (!ModeNameToType.TryGetValue(Options.Mode, out ModeType)) { List ModuleNameList = ModeNameToType.Keys.ToList(); ModuleNameList.Sort(StringComparer.OrdinalIgnoreCase); Logger.LogError("No mode named '{Name}'. Available modes are:\n {ModeList}", Options.Mode, String.Join("\n ", ModuleNameList)); return 1; } } BuildMode = ModeType.Name; // Get the options for which systems have to be initialized for this mode ModeOptions = ModeType.GetCustomAttribute()!.Options; // if we don't care about the trace listener, toss it now if ((ModeOptions & ToolModeOptions.UseStartupTraceListener) == 0) { Log.RemoveTraceListener(StartupTrace); } // Start prefetching the contents of the engine folder if ((ModeOptions & ToolModeOptions.StartPrefetchingEngine) != 0) { using (GlobalTracer.Instance.BuildSpan("FileMetadataPrefetch.QueueEngineDirectory()").StartActive()) { FileMetadataPrefetch.QueueEngineDirectory(); } } // Read the XML configuration files if ((ModeOptions & ToolModeOptions.XmlConfig) != 0) { using (Timeline.ScopeEvent("Apply XmlConfig")) using (GlobalTracer.Instance.BuildSpan("XmlConfig.ReadConfigFiles()").StartActive()) { string XmlConfigMutexName = GlobalSingleInstanceMutex.GetUniqueMutexForPath("UnrealBuildTool_Mutex_XmlConfig", FileReference.FromString(Assembly.GetExecutingAssembly().Location)); using (GlobalSingleInstanceMutex XmlConfigMutex = new GlobalSingleInstanceMutex(XmlConfigMutexName, true)) { FileReference? XmlConfigCache = Arguments.GetFileReferenceOrDefault("-XmlConfigCache=", null); FileReference? ProjectFile; Utils.TryParseProjectFileArgument(Arguments, Logger, out ProjectFile, false); XmlConfig.ReadConfigFiles(XmlConfigCache, ProjectFile?.Directory, Logger); } } XmlConfig.ApplyTo(Options); } Log.BackupLogFiles = Options.bBackupLogFiles; Log.LogFileBackupCount = Options.LogFileBackupCount; bShowTimeline = Options.bShowTimeline; // Add the log writer if requested. When building a target, we'll create the writer for the default log file later. if (Options.LogFileName != null) { Log.AddFileWriter("LogTraceListener", Options.LogFileName); } // Initialize the telemetry service if (!String.IsNullOrEmpty(Options.TelemetrySession)) { IsRecursive = true; SessionIdentifier = Options.TelemetrySession; } TelemetryService.Get().AddTelemetryConfigProviders(Options.TelemetryProviders.Concat(Options.CmdTelemetryProviders)); TelemetryService.Get().AddEndpointsFromConfig(); // Create a UbtRun file try { DirectoryReference RunsDir = DirectoryReference.Combine(Unreal.EngineDirectory, "Intermediate", "UbtRuns"); Directory.CreateDirectory(RunsDir.FullName); string ModuleFileName = Process.GetCurrentProcess().MainModule?.FileName ?? ""; if (!String.IsNullOrEmpty(ModuleFileName)) { ModuleFileName = Path.GetFullPath(ModuleFileName); } FileReference RunFileTemp = FileReference.Combine(RunsDir, $"{Environment.ProcessId}_{ContentHash.MD5(Encoding.UTF8.GetBytes(ModuleFileName.ToUpperInvariant()))}"); File.WriteAllLines(RunFileTemp.FullName, new string[] { ModuleFileName }); RunFile = RunFileTemp; } catch { } // Override the temp directory try { // If the temp directory is already overridden from a parent process, do not override again if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("UnrealBuildTool_TMP"))) { DirectoryReference OverrideTempDirectory = new DirectoryReference(Path.Combine(Path.GetTempPath(), "UnrealBuildTool")); if (Options.TempDirectory != null) { if (Directory.Exists(Options.TempDirectory)) { OverrideTempDirectory = new DirectoryReference(Options.TempDirectory); if (OverrideTempDirectory.GetDirectoryName() != "UnrealBuildTool") { OverrideTempDirectory = DirectoryReference.Combine(OverrideTempDirectory, "UnrealBuildTool"); } } else { Logger.LogWarning("Warning: TempDirectory override '{Override}' does not exist, using '{Temp}'", Options.TempDirectory, OverrideTempDirectory.FullName); } } OverrideTempDirectory = DirectoryReference.Combine(OverrideTempDirectory, ContentHash.MD5(Encoding.UTF8.GetBytes(Unreal.UnrealBuildToolDllPath.FullName)).ToString().Substring(0, 8)); DirectoryReference.CreateDirectory(OverrideTempDirectory); Logger.LogDebug("Setting temp directory to '{Path}'", OverrideTempDirectory); Environment.SetEnvironmentVariable("UnrealBuildTool_TMP", OverrideTempDirectory.FullName); Environment.SetEnvironmentVariable("TMP", OverrideTempDirectory.FullName); Environment.SetEnvironmentVariable("TEMP", OverrideTempDirectory.FullName); // Deleting the directory is only safe in single instance mode, and only if requested if ((ModeOptions & ToolModeOptions.SingleInstance) != 0 && !Options.bNoMutex && Options.bDeleteTempDirectory) { Logger.LogDebug("Temp directory '{Path}' will be deleted on exit", OverrideTempDirectory); TempDirectory = OverrideTempDirectory; } } } catch { } // Acquire a lock for this branch if ((ModeOptions & ToolModeOptions.SingleInstance) != 0 && !Options.bNoMutex) { using (GlobalTracer.Instance.BuildSpan("SingleInstanceMutex.Acquire()").StartActive()) { string MutexName = GlobalSingleInstanceMutex.GetUniqueMutexForPath("UnrealBuildTool_Mutex", FileReference.FromString(Assembly.GetExecutingAssembly().Location)); Mutex = new GlobalSingleInstanceMutex(MutexName, Options.bWaitMutex); } } using (Timeline.ScopeEvent("RegisterPlatforms")) { // Register all the build platforms if ((ModeOptions & ToolModeOptions.BuildPlatforms) != 0) { using (GlobalTracer.Instance.BuildSpan("UEBuildPlatform.RegisterPlatforms()").StartActive()) { UEBuildPlatform.RegisterPlatforms(false, false, ModeType, ArgumentsArray, Logger); } } if ((ModeOptions & ToolModeOptions.BuildPlatformsHostOnly) != 0) { using (GlobalTracer.Instance.BuildSpan("UEBuildPlatform.RegisterPlatforms()").StartActive()) { UEBuildPlatform.RegisterPlatforms(false, true, ModeType, ArgumentsArray, Logger); } } if ((ModeOptions & ToolModeOptions.BuildPlatformsForValidation) != 0) { using (GlobalTracer.Instance.BuildSpan("UEBuildPlatform.RegisterPlatforms()").StartActive()) { UEBuildPlatform.RegisterPlatforms(true, false, ModeType, ArgumentsArray, Logger); } } } // Create the appropriate handler ToolMode Mode = (ToolMode)Activator.CreateInstance(ModeType)!; // Execute the mode MainScope.Finish(); using ITimelineEvent ExecuteScope = Timeline.ScopeEvent(ModeType.Name); int Result = Mode.ExecuteAsync(Arguments, Logger).GetAwaiter().GetResult(); ApplicationResult = (CompilationResult)Result; return Result; } catch (Exception Ex) { Ex.LogException(Logger); // CompilationResultException is used to return a propagate a specific exit code after an error has occurred. ApplicationResult = Ex.GetCompilationResult(); return (int)ApplicationResult; } finally { // Cancel the prefetcher using (GlobalTracer.Instance.BuildSpan("FileMetadataPrefetch.Stop()").StartActive()) { try { FileMetadataPrefetch.Stop(); } catch { } } // Uncomment this to output a file that contains all files that UBT has scanned. // Useful when investigating why UBT takes time. //DirectoryItem.WriteDebugFileWithAllEnumeratedFiles(@"c:\temp\AllFiles.txt"); using (Timeline.ScopeEvent("TelemetryService.FlushEvents")) { if (!IsRecursive) { TelemetryService.Get().RecordEvent(new TelemetryCompletedEvent(ArgumentsArray, StartTimeUtc, ApplicationResult, DateTime.UtcNow)); } // Flush any remaining telemetry events TelemetryService.Get().FlushEvents(); } Utils.LogWriteFileIfChangedActivity(Logger); // Print out all the performance info Timeline.Stop(); bool bShowExecutionTime = ModeOptions.HasFlag(ToolModeOptions.ShowExecutionTime); LogLevel ExecutionLogLevel = bShowExecutionTime ? LogLevel.Information : LogLevel.Debug; LogLevel TimelineLogLevel = bShowExecutionTime && bShowTimeline && ApplicationResult == CompilationResult.Succeeded ? LogLevel.Information : LogLevel.Debug; Timeline.Print(TimeSpan.FromMilliseconds(100.0), TimeSpan.FromMilliseconds(200.0), TimelineLogLevel, Logger); Logger.Log(ExecutionLogLevel, ""); if (ApplicationResult == CompilationResult.Succeeded) { Logger.Log(ExecutionLogLevel, "Result: {ApplicationResult}", ApplicationResult); } else { Logger.Log(ExecutionLogLevel, "Result: Failed ({ApplicationResult})", ApplicationResult); } Logger.Log(ExecutionLogLevel, "Total execution time: {Time:0.00} seconds", Timeline.Elapsed.TotalSeconds); // Make sure we flush the logs however we exit Trace.Close(); // Write any trace logs Tracer?.Flush(); // Delete the ubt run file if (RunFile != null) { try { File.Delete(RunFile.FullName); } catch { } } // Remove the the temp subdirectory. TempDirectory will only be set if running in single instance mode when Options.DeleteTempDirectory is enabled if (TempDirectory != null) { try { DirectoryReference.Delete(TempDirectory, true); } catch { } } // Dispose of the mutex. Must be done last to ensure that another process does not startup and start trying to write to the same log file. Mutex?.Dispose(); } } } }