// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #if FEATURE_FILE_TRACKER using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; #nullable disable namespace Microsoft.Build.Utilities { /// /// Enumeration to express the type of executable being wrapped by Tracker.exe. /// public enum ExecutableType { /// /// 32-bit native executable. /// Native32Bit = 0, /// /// 64-bit native executable. /// Native64Bit = 1, /// /// A managed executable without a specified bitness. /// ManagedIL = 2, /// /// A managed executable specifically marked as 32-bit. /// Managed32Bit = 3, /// /// A managed executable specifically marked as 64-bit. /// Managed64Bit = 4, /// /// Use the same bitness as the currently running executable. /// SameAsCurrentProcess = 5, /// /// 64-bit native ARM64 executable. /// NativeARM64 = 6, /// /// 64-bit managed ARM64 executable. /// ManagedARM64 = 7 } #pragma warning disable format // region formatting is different in net7.0 and net472, and cannot be fixed for both /// /// This class contains utility functions to encapsulate launching and logging for the Tracker /// public static class FileTracker { #region Static Member Data /// /// The default path to temp, used to create explicitly short and long paths. /// /// /// This must be the base system-wide temp path because we use it to filter out I/O of tools outside of our control. /// Tools running under the tracker may put temp files in the temp base or in a sub-directory of their choosing. /// private static readonly string s_tempPath = FileUtilities.EnsureTrailingSlash(Path.GetTempPath()); // The short path to temp private static readonly string s_tempShortPath = FileUtilities.EnsureTrailingSlash(NativeMethodsShared.GetShortFilePath(s_tempPath).ToUpperInvariant()); // The long path to temp private static readonly string s_tempLongPath = FileUtilities.EnsureTrailingSlash(NativeMethodsShared.GetLongFilePath(s_tempPath).ToUpperInvariant()); // The path to ApplicationData (is equal to %USERPROFILE%\Application Data folder in Windows XP and %USERPROFILE%\AppData\Roaming in Vista and later) private static readonly string s_applicationDataPath = FileUtilities.EnsureTrailingSlash(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData).ToUpperInvariant()); // The path to LocalApplicationData (is equal to %USERPROFILE%\Local Settings\Application Data folder in Windows XP and %USERPROFILE%\AppData\Local in Vista and later). private static readonly string s_localApplicationDataPath = FileUtilities.EnsureTrailingSlash(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).ToUpperInvariant()); // The path to the LocalLow folder. In Vista and later, user application data is organized across %USERPROFILE%\AppData\LocalLow, %USERPROFILE%\AppData\Local (%LOCALAPPDATA%) // and %USERPROFILE%\AppData\Roaming (%APPDATA%). The LocalLow folder is not present in XP. private static readonly string s_localLowApplicationDataPath = FileUtilities.EnsureTrailingSlash(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData\\LocalLow").ToUpperInvariant()); // The path to the common Application Data, which is also used by some programs (e.g. antivirus) that we wish to ignore. // Is equal to C:\Documents and Settings\All Users\Application Data on XP, and C:\ProgramData on Vista+. // But for backward compatibility, the paths "C:\Documents and Settings\All Users\Application Data" and "C:\Users\All Users\Application Data" are still accessible via Junction point on Vista+. // Thus this list is created to store all possible common application data paths to cover more cases as possible. private static readonly List s_commonApplicationDataPaths = InitializeCommonApplicationDataPaths(); // The name of the standalone tracker tool. private const string s_TrackerFilename = "Tracker.exe"; // The name of the assembly that is injected into the executing process. // Detours handles picking between FileTracker{32,64}.dll so only mention one. private const string s_FileTrackerFilename = "FileTracker32.dll"; // The name of the PATH environment variable. private const string pathEnvironmentVariableName = "PATH"; // Static cache of the path separator character in an array for use in String.Split. private static readonly char[] pathSeparatorArray = { Path.PathSeparator }; // Static cache of the path separator character in an array for use in String.Split. private static readonly string pathSeparator = Path.PathSeparator.ToString(); #endregion #region Static Member Initializers private static List InitializeCommonApplicationDataPaths() { List commonApplicationDataPaths = new(); string defaultCommonApplicationDataPath = FileUtilities.EnsureTrailingSlash(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData).ToUpperInvariant()); commonApplicationDataPaths.Add(defaultCommonApplicationDataPath); string defaultRootDirectory = Path.GetPathRoot(defaultCommonApplicationDataPath); string alternativeCommonApplicationDataPath1 = FileUtilities.EnsureTrailingSlash(Path.Combine(defaultRootDirectory, @"Documents and Settings\All Users\Application Data").ToUpperInvariant()); if (!alternativeCommonApplicationDataPath1.Equals(defaultCommonApplicationDataPath, StringComparison.Ordinal)) { commonApplicationDataPaths.Add(alternativeCommonApplicationDataPath1); } string alternativeCommonApplicationDataPath2 = FileUtilities.EnsureTrailingSlash(Path.Combine(defaultRootDirectory, @"Users\All Users\Application Data").ToUpperInvariant()); if (!alternativeCommonApplicationDataPath2.Equals(defaultCommonApplicationDataPath, StringComparison.Ordinal)) { commonApplicationDataPaths.Add(alternativeCommonApplicationDataPath2); } return commonApplicationDataPaths; } #endregion #region Native method wrappers /// /// Stops tracking file accesses. /// public static void EndTrackingContext() => InprocTrackingNativeMethods.EndTrackingContext(); /// /// Resume tracking file accesses in the current tracking context. /// public static void ResumeTracking() => InprocTrackingNativeMethods.ResumeTracking(); /// /// Set the global thread count, and assign that count to the current thread. /// public static void SetThreadCount(int threadCount) => InprocTrackingNativeMethods.SetThreadCount(threadCount); /// /// Starts tracking file accesses. /// /// The directory into which to write the tracking log files /// The name of the task calling this function, used to determine the /// names of the tracking log files public static void StartTrackingContext(string intermediateDirectory, string taskName) => InprocTrackingNativeMethods.StartTrackingContext(intermediateDirectory, taskName); /// /// Starts tracking file accesses, using the rooting marker in the response file provided. To /// automatically generate a response file given a rooting marker, call /// FileTracker.CreateRootingMarkerResponseFile. /// /// The directory into which to write the tracking log files /// The name of the task calling this function, used to determine the /// names of the tracking log files /// The path to the root marker response file. public static void StartTrackingContextWithRoot(string intermediateDirectory, string taskName, string rootMarkerResponseFile) => InprocTrackingNativeMethods.StartTrackingContextWithRoot(intermediateDirectory, taskName, rootMarkerResponseFile); /// /// Stop tracking file accesses and get rid of the current tracking contexts. /// public static void StopTrackingAndCleanup() => InprocTrackingNativeMethods.StopTrackingAndCleanup(); /// /// Temporarily suspend tracking of file accesses in the current tracking context. /// public static void SuspendTracking() => InprocTrackingNativeMethods.SuspendTracking(); /// /// Write tracking logs for all contexts and threads. /// /// The directory into which to write the tracking log files /// The name of the task calling this function, used to determine the /// names of the tracking log files [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "TLogs", Justification = "Has now shipped as public API; plus it's unclear whether 'Tlog' or 'TLog' is the preferred casing")] public static void WriteAllTLogs(string intermediateDirectory, string taskName) => InprocTrackingNativeMethods.WriteAllTLogs(intermediateDirectory, taskName); /// /// Write tracking logs corresponding to the current tracking context. /// /// The directory into which to write the tracking log files /// The name of the task calling this function, used to determine the /// names of the tracking log files [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "TLogs", Justification = "Has now shipped as public API; plus it's unclear whether 'Tlog' or 'TLog' is the preferred casing")] public static void WriteContextTLogs(string intermediateDirectory, string taskName) => InprocTrackingNativeMethods.WriteContextTLogs(intermediateDirectory, taskName); #endregion // Native method wrappers #region Methods /// /// Test to see if the specified file is excluded from tracked dependencies /// /// /// Full path of the file to test /// public static bool FileIsExcludedFromDependencies(string fileName) { // UNDONE: This check means that we cannot incremental build projects // that exist under the following directories on XP: // %USERPROFILE%\Application Data // %USERPROFILE%\Local Settings\Application Data // // and the following directories on Vista: // %USERPROFILE%\AppData\Local // %USERPROFILE%\AppData\LocalLow // %USERPROFILE%\AppData\Roaming // We don't want to be including these as dependencies or outputs: // 1. Files under %USERPROFILE%\Application Data in XP and %USERPROFILE%\AppData\Roaming in Vista and later. // 2. Files under %USERPROFILE%\Local Settings\Application Data in XP and %USERPROFILE%\AppData\Local in Vista and later. // 3. Files under %USERPROFILE%\AppData\LocalLow in Vista and later. // 4. Files that are in the TEMP directory (Since on XP, temp files are not // located under AppData, they would not be compacted out correctly otherwise). // 5. Files under the common ("All Users") Application Data location -- C:\Documents and Settings\All Users\Application Data // on XP and either C:\Users\All Users\Application Data or C:\ProgramData on Vista+ if (FileIsUnderNormalizedPath(fileName, s_applicationDataPath) || FileIsUnderNormalizedPath(fileName, s_localApplicationDataPath) || FileIsUnderNormalizedPath(fileName, s_localLowApplicationDataPath) || FileIsUnderNormalizedPath(fileName, s_tempShortPath) || FileIsUnderNormalizedPath(fileName, s_tempLongPath)) { return true; } // PERF: Avoid LINQ in this path. foreach (string p in s_commonApplicationDataPaths) { if (FileIsUnderNormalizedPath(fileName, p)) { return true; } } return false; } /// /// Test to see if the specified file is under the specified path /// /// /// Full path of the file to test /// /// /// Is the file under this full path? /// public static bool FileIsUnderPath(string fileName, string path) { // Ensure that the path has a trailing slash that we are checking under // By default the paths that we check for most often will have, so this will // return fast and not allocate memory in the process return FileIsUnderNormalizedPath(fileName, FileUtilities.EnsureTrailingSlash(path)); } internal static bool FileIsUnderNormalizedPath(string fileName, string path) { int pathLength = path.Length; Debug.Assert(path[pathLength - 1] == Path.DirectorySeparatorChar); // UNDONE: Get the long file path for the entry // This is an incredibly expensive operation. The tracking log // as written by CL etc. does not contain short paths // fileDirectory = NativeMethods.GetFullLongFilePath(fileDirectory); // quick checks to return early. If our given filename is less than the length of the path, it can't be under it. // Similarly, if the file name doesn't have a path separator // at the index of the last separator in the path, it doesn't match. // Because we have a normalized path below we can check against normal-slash directly. if (fileName.Length < pathLength || fileName[pathLength - 1] != Path.DirectorySeparatorChar) { return false; } // Is the fileName under the filePath? return string.Compare(fileName, 0, path, 0, path.Length, StringComparison.OrdinalIgnoreCase) == 0; } /// /// Construct a rooting marker string from the ITaskItem array of primary sources. /// /// An containing information about the primary source. public static string FormatRootingMarker(ITaskItem source) => FormatRootingMarker([source], null); /// /// Construct a rooting marker string from the ITaskItem array of primary sources. /// /// An containing information about the primary source. /// An containing information about the output. public static string FormatRootingMarker(ITaskItem source, ITaskItem output) => FormatRootingMarker([source], [output]); /// /// Construct a rooting marker string from the ITaskItem array of primary sources. /// /// /// ITaskItem array of primary sources. /// public static string FormatRootingMarker(ITaskItem[] sources) => FormatRootingMarker(sources, null); /// /// Construct a rooting marker string from the ITaskItem array of primary sources. /// /// /// ITaskItem array of primary sources. /// /// ITaskItem array of outputs. public static string FormatRootingMarker(ITaskItem[] sources, ITaskItem[] outputs) { ArgumentNullException.ThrowIfNull(sources); // So we don't have to deal with null checks. outputs ??= Array.Empty(); var rootSources = new List(sources.Length + outputs.Length); foreach (ITaskItem source in sources) { rootSources.Add(FileUtilities.NormalizePath(source.ItemSpec).ToUpperInvariant()); } foreach (ITaskItem output in outputs) { rootSources.Add(FileUtilities.NormalizePath(output.ItemSpec).ToUpperInvariant()); } rootSources.Sort(StringComparer.OrdinalIgnoreCase); return string.Join("|", rootSources); } /// /// Given a set of source files in the form of ITaskItem, creates a temporary response /// file containing the rooting marker that corresponds to those sources. /// /// /// ITaskItem array of primary sources. /// /// The response file path. public static string CreateRootingMarkerResponseFile(ITaskItem[] sources) => CreateRootingMarkerResponseFile(FormatRootingMarker(sources)); /// /// Given a rooting marker, creates a temporary response file with that rooting marker /// in it. /// /// The rooting marker to put in the response file. /// The response file path. public static string CreateRootingMarkerResponseFile(string rootMarker) { string trackerResponseFile = FileUtilities.GetTemporaryFileName(".rsp"); File.WriteAllText(trackerResponseFile, "/r \"" + rootMarker + "\"", Encoding.Unicode); return trackerResponseFile; } /// /// Prepends the path to the appropriate FileTracker assembly to the PATH /// environment variable. Used for inproc tracking, or when the .NET Framework may /// not be on the PATH. /// /// The old value of PATH public static string EnsureFileTrackerOnPath() => EnsureFileTrackerOnPath(null); /// /// Prepends the path to the appropriate FileTracker assembly to the PATH /// environment variable. Used for inproc tracking, or when the .NET Framework may /// not be on the PATH. /// /// The root path for FileTracker.dll. Overrides the toolType if specified. /// The old value of PATH public static string EnsureFileTrackerOnPath(string rootPath) { string oldPath = Environment.GetEnvironmentVariable(pathEnvironmentVariableName); string fileTrackerPath = GetFileTrackerPath(ExecutableType.SameAsCurrentProcess, rootPath); if (!string.IsNullOrEmpty(fileTrackerPath)) { Environment.SetEnvironmentVariable(pathEnvironmentVariableName, Path.GetDirectoryName(fileTrackerPath) + pathSeparator + oldPath); } return oldPath; } /// /// Searches %PATH% for the location of Tracker.exe, and returns the first /// path that matches. /// /// The full path to Tracker.exe, or if a matching path is not found. public static string FindTrackerOnPath() { string[] paths = Environment.GetEnvironmentVariable(pathEnvironmentVariableName).Split(pathSeparatorArray, StringSplitOptions.RemoveEmptyEntries); foreach (string path in paths) { try { string trackerPath = !Path.IsPathRooted(path) ? Path.GetFullPath(path) : path; trackerPath = Path.Combine(trackerPath, s_TrackerFilename); if (FileSystems.Default.FileExists(trackerPath)) { return trackerPath; } } catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e)) { // ignore this path and move on -- it's just bad for some reason. } } // Still haven't found it. return null; } /// /// Determines whether we must track out-of-proc, or whether inproc tracking will work. /// /// The executable type for the tool being tracked /// True if we need to track out-of-proc, false if inproc tracking is OK public static bool ForceOutOfProcTracking(ExecutableType toolType) => ForceOutOfProcTracking(toolType, null, null); /// /// Determines whether we must track out-of-proc, or whether inproc tracking will work. /// /// The executable type for the tool being tracked /// An optional assembly name. /// The name of the cancel event tracker should listen for, or null if there isn't one /// True if we need to track out-of-proc, false if inproc tracking is OK public static bool ForceOutOfProcTracking(ExecutableType toolType, string dllName, string cancelEventName) { bool trackOutOfProc = false; if (cancelEventName != null) { // If we have a cancel event, we must track out-of-proc. trackOutOfProc = true; } else if (dllName != null) { // If we have a DLL name, we need to track out of proc -- inproc tracking just uses // the default FileTracker trackOutOfProc = true; } // toolType is not relevant now that Detours can handle child processes of a different // bitness than the parent. return trackOutOfProc; } /// /// Given the ExecutableType of the tool being wrapped and information that we /// know about our current bitness, figures out and returns the path to the correct /// Tracker.exe. /// /// The of the tool being wrapped public static string GetTrackerPath(ExecutableType toolType) => GetTrackerPath(toolType, null); /// /// Given the ExecutableType of the tool being wrapped and information that we /// know about our current bitness, figures out and returns the path to the correct /// Tracker.exe. /// /// The of the tool being wrapped /// The root path for Tracker.exe. Overrides the toolType if specified. public static string GetTrackerPath(ExecutableType toolType, string rootPath) => GetPath(s_TrackerFilename, toolType, rootPath); /// /// Given the ExecutableType of the tool being wrapped and information that we /// know about our current bitness, figures out and returns the path to the correct /// FileTracker.dll. /// /// The of the tool being wrapped public static string GetFileTrackerPath(ExecutableType toolType) => GetFileTrackerPath(toolType, null); /// /// Given the ExecutableType of the tool being wrapped and information that we /// know about our current bitness, figures out and returns the path to the correct /// FileTracker.dll. /// /// The of the tool being wrapped /// The root path for FileTracker.dll. Overrides the toolType if specified. public static string GetFileTrackerPath(ExecutableType toolType, string rootPath) => GetPath(s_FileTrackerFilename, toolType, rootPath); /// /// Given a filename (only really meant to support either Tracker.exe or FileTracker.dll), returns /// the appropriate path for the appropriate file type. /// /// /// /// The root path for the file. Overrides the toolType if specified. private static string GetPath(string filename, ExecutableType toolType, string rootPath) { string trackerPath; if (!string.IsNullOrEmpty(rootPath)) { trackerPath = Path.Combine(rootPath, filename); if (!FileSystems.Default.FileExists(trackerPath)) { // if an override path was specified, that's it -- we don't want to fall back if the file // is not found there. trackerPath = null; } } else { // Since Detours can handle cross-bitness process launches, the toolType // can be ignored; just return the path corresponding to the current architecture. trackerPath = GetPath(filename, DotNetFrameworkArchitecture.Current); } return trackerPath; } /// /// Given a filename (currently only Tracker.exe and FileTracker.dll are supported), return /// the path to that file. /// /// /// /// private static string GetPath(string filename, DotNetFrameworkArchitecture bitness) { // Make sure that if someone starts passing the wrong thing to this method we don't silently // eat it and do something possibly unexpected. Assumed.True(s_TrackerFilename.Equals(filename, StringComparison.OrdinalIgnoreCase) || s_FileTrackerFilename.Equals(filename, StringComparison.OrdinalIgnoreCase), $"This method should only be passed s_TrackerFilename or s_FileTrackerFilename, but was passed {filename} instead!"); // Look for FileTracker.dll/Tracker.exe in the MSBuild tools directory. They may exist elsewhere on disk, // but other copies aren't guaranteed to be compatible with the latest. var path = ToolLocationHelper.GetPathToBuildToolsFile(filename, ToolLocationHelper.CurrentToolsVersion, bitness); // Due to a Detours limitation, the path to FileTracker32.dll must be // representable in ANSI characters. Look for it first in the global // shared location which is guaranteed to be ANSI. Fall back to // current folder. if (s_FileTrackerFilename.Equals(filename, StringComparison.OrdinalIgnoreCase)) { string progfilesPath = Path.Combine(FrameworkLocationHelper.GenerateProgramFiles32(), "MSBuild", "15.0", "FileTracker", s_FileTrackerFilename); if (FileSystems.Default.FileExists(progfilesPath)) { return progfilesPath; } } return path; } /// /// This method constructs the correct Tracker.exe response file arguments from its parameters /// /// The name of the dll that will do the tracking /// Intermediate directory where tracking logs will be written /// Rooting marker /// The arguments as a string public static string TrackerResponseFileArguments(string dllName, string intermediateDirectory, string rootFiles) => TrackerResponseFileArguments(dllName, intermediateDirectory, rootFiles, null); /// /// This method constructs the correct Tracker.exe response file arguments from its parameters /// /// The name of the dll that will do the tracking /// Intermediate directory where tracking logs will be written /// Rooting marker /// If a cancel event has been created that Tracker should be listening for, its name is passed here /// The arguments as a string public static string TrackerResponseFileArguments(string dllName, string intermediateDirectory, string rootFiles, string cancelEventName) { CommandLineBuilder builder = new CommandLineBuilder(); builder.AppendSwitchIfNotNull("/d ", dllName); if (!string.IsNullOrEmpty(intermediateDirectory)) { intermediateDirectory = FileUtilities.NormalizePath(intermediateDirectory); // If the intermediate directory ends up with a trailing slash, then be rid of it! if (FileUtilities.EndsWithSlash(intermediateDirectory)) { intermediateDirectory = Path.GetDirectoryName(intermediateDirectory); } builder.AppendSwitchIfNotNull("/i ", intermediateDirectory); } builder.AppendSwitchIfNotNull("/r ", rootFiles); builder.AppendSwitchIfNotNull("/b ", cancelEventName); // b for break return builder.ToString() + " "; } /// /// This method constructs the correct Tracker.exe command arguments from its parameters /// /// The command to track /// The command to track's arguments /// The arguments as a string public static string TrackerCommandArguments(string command, string arguments) { CommandLineBuilder builder = new CommandLineBuilder(); builder.AppendSwitch(" /c"); builder.AppendFileNameIfNotNull(command); string fullArguments = builder.ToString(); fullArguments += " " + arguments; return fullArguments; } /// /// This method constructs the correct Tracker.exe arguments from its parameters /// /// The command to track /// The command to track's arguments /// The name of the dll that will do the tracking /// Intermediate directory where tracking logs will be written /// Rooting marker /// The arguments as a string public static string TrackerArguments(string command, string arguments, string dllName, string intermediateDirectory, string rootFiles) => TrackerArguments(command, arguments, dllName, intermediateDirectory, rootFiles, null); /// /// This method constructs the correct Tracker.exe arguments from its parameters /// /// The command to track /// The command to track's arguments /// The name of the dll that will do the tracking /// Intermediate directory where tracking logs will be written /// Rooting marker /// If a cancel event has been created that Tracker should be listening for, its name is passed here /// The arguments as a string public static string TrackerArguments(string command, string arguments, string dllName, string intermediateDirectory, string rootFiles, string cancelEventName) => TrackerResponseFileArguments(dllName, intermediateDirectory, rootFiles, cancelEventName) + TrackerCommandArguments(command, arguments); #region StartProcess methods /// /// Start the process; tracking the command. /// /// The command to track /// The command to track's arguments /// The type of executable the wrapped tool is /// The name of the dll that will do the tracking /// Intermediate directory where tracking logs will be written /// Rooting marker /// If Tracker should be listening on a particular event for cancellation, pass its name here /// Process instance public static Process StartProcess(string command, string arguments, ExecutableType toolType, string dllName, string intermediateDirectory, string rootFiles, string cancelEventName) { dllName ??= GetFileTrackerPath(toolType); string fullArguments = TrackerArguments(command, arguments, dllName, intermediateDirectory, rootFiles, cancelEventName); return Process.Start(GetTrackerPath(toolType), fullArguments); } /// /// Start the process; tracking the command. /// /// The command to track /// The command to track's arguments /// The type of executable the wrapped tool is /// The name of the dll that will do the tracking /// Intermediate directory where tracking logs will be written /// Rooting marker /// Process instance public static Process StartProcess(string command, string arguments, ExecutableType toolType, string dllName, string intermediateDirectory, string rootFiles) => StartProcess(command, arguments, toolType, dllName, intermediateDirectory, rootFiles, null); /// /// Start the process; tracking the command. /// /// The command to track /// The command to track's arguments /// The type of executable the wrapped tool is /// Intermediate directory where tracking logs will be written /// Rooting marker /// Process instance public static Process StartProcess(string command, string arguments, ExecutableType toolType, string intermediateDirectory, string rootFiles) => StartProcess(command, arguments, toolType, null, intermediateDirectory, rootFiles, null); /// /// Start the process; tracking the command. /// /// The command to track /// The command to track's arguments /// The type of executable the wrapped tool is /// Rooting marker /// Process instance public static Process StartProcess(string command, string arguments, ExecutableType toolType, string rootFiles) => StartProcess(command, arguments, toolType, null, null, rootFiles, null); /// /// Start the process; tracking the command. /// /// The command to track /// The command to track's arguments /// The type of executable the wrapped tool is /// Process instance public static Process StartProcess(string command, string arguments, ExecutableType toolType) => StartProcess(command, arguments, toolType, null, null, null, null); #endregion // StartProcess methods /// /// Logs a message of the given importance using the specified resource string. To the specified Log. /// /// This method is not thread-safe. /// The Log to log to. /// The importance level of the message. /// The name of the string resource to load. /// Optional arguments for formatting the loaded string. /// Thrown when messageResourceName is null. internal static void LogMessageFromResources(TaskLoggingHelper Log, MessageImportance importance, string messageResourceName, params object[] messageArgs) { // Only log when we have been passed a TaskLoggingHelper if (Log != null) { ArgumentNullException.ThrowIfNull(messageResourceName); Log.LogMessage(importance, AssemblyResources.GetString(messageResourceName), messageArgs); } } /// /// Logs a message of the given importance using the specified string. /// /// This method is not thread-safe. /// The Log to log to. /// The importance level of the message. /// The message string. /// Optional arguments for formatting the message string. /// Thrown when message is null. internal static void LogMessage(TaskLoggingHelper Log, MessageImportance importance, string message, params object[] messageArgs) { // Only log when we have been passed a TaskLoggingHelper Log?.LogMessage(importance, message, messageArgs); } /// /// Logs a warning using the specified resource string. /// /// The Log to log to. /// The name of the string resource to load. /// Optional arguments for formatting the loaded string. /// Thrown when messageResourceName is null. internal static void LogWarningWithCodeFromResources(TaskLoggingHelper Log, string messageResourceName, params object[] messageArgs) { // Only log when we have been passed a TaskLoggingHelper Log?.LogWarningWithCodeFromResources(messageResourceName, messageArgs); } #endregion } #pragma warning restore format /// /// Dependency filter delegate. Used during TLog saves in order for tasks to selectively remove dependencies from the written /// graph. /// /// The full path to the dependency file about to be written to the compacted TLog /// If the file should actually be written to the TLog (true) or not (false) public delegate bool DependencyFilter(string fullPath); } #endif