-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathFileTracker.cs
More file actions
804 lines (677 loc) · 40.7 KB
/
Copy pathFileTracker.cs
File metadata and controls
804 lines (677 loc) · 40.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
// 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
{
/// <summary>
/// Enumeration to express the type of executable being wrapped by Tracker.exe.
/// </summary>
public enum ExecutableType
{
/// <summary>
/// 32-bit native executable.
/// </summary>
Native32Bit = 0,
/// <summary>
/// 64-bit native executable.
/// </summary>
Native64Bit = 1,
/// <summary>
/// A managed executable without a specified bitness.
/// </summary>
ManagedIL = 2,
/// <summary>
/// A managed executable specifically marked as 32-bit.
/// </summary>
Managed32Bit = 3,
/// <summary>
/// A managed executable specifically marked as 64-bit.
/// </summary>
Managed64Bit = 4,
/// <summary>
/// Use the same bitness as the currently running executable.
/// </summary>
SameAsCurrentProcess = 5,
/// <summary>
/// 64-bit native ARM64 executable.
/// </summary>
NativeARM64 = 6,
/// <summary>
/// 64-bit managed ARM64 executable.
/// </summary>
ManagedARM64 = 7
}
#pragma warning disable format // region formatting is different in net7.0 and net472, and cannot be fixed for both
/// <summary>
/// This class contains utility functions to encapsulate launching and logging for the Tracker
/// </summary>
public static class FileTracker
{
#region Static Member Data
/// <summary>
/// The default path to temp, used to create explicitly short and long paths.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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<string> 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<string> InitializeCommonApplicationDataPaths()
{
List<string> 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
/// <summary>
/// Stops tracking file accesses.
/// </summary>
public static void EndTrackingContext() => InprocTrackingNativeMethods.EndTrackingContext();
/// <summary>
/// Resume tracking file accesses in the current tracking context.
/// </summary>
public static void ResumeTracking() => InprocTrackingNativeMethods.ResumeTracking();
/// <summary>
/// Set the global thread count, and assign that count to the current thread.
/// </summary>
public static void SetThreadCount(int threadCount) => InprocTrackingNativeMethods.SetThreadCount(threadCount);
/// <summary>
/// Starts tracking file accesses.
/// </summary>
/// <param name="intermediateDirectory">The directory into which to write the tracking log files</param>
/// <param name="taskName">The name of the task calling this function, used to determine the
/// names of the tracking log files</param>
public static void StartTrackingContext(string intermediateDirectory, string taskName) => InprocTrackingNativeMethods.StartTrackingContext(intermediateDirectory, taskName);
/// <summary>
/// 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.
/// </summary>
/// <param name="intermediateDirectory">The directory into which to write the tracking log files</param>
/// <param name="taskName">The name of the task calling this function, used to determine the
/// names of the tracking log files</param>
/// <param name="rootMarkerResponseFile">The path to the root marker response file.</param>
public static void StartTrackingContextWithRoot(string intermediateDirectory, string taskName, string rootMarkerResponseFile)
=> InprocTrackingNativeMethods.StartTrackingContextWithRoot(intermediateDirectory, taskName, rootMarkerResponseFile);
/// <summary>
/// Stop tracking file accesses and get rid of the current tracking contexts.
/// </summary>
public static void StopTrackingAndCleanup() => InprocTrackingNativeMethods.StopTrackingAndCleanup();
/// <summary>
/// Temporarily suspend tracking of file accesses in the current tracking context.
/// </summary>
public static void SuspendTracking() => InprocTrackingNativeMethods.SuspendTracking();
/// <summary>
/// Write tracking logs for all contexts and threads.
/// </summary>
/// <param name="intermediateDirectory">The directory into which to write the tracking log files</param>
/// <param name="taskName">The name of the task calling this function, used to determine the
/// names of the tracking log files</param>
[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);
/// <summary>
/// Write tracking logs corresponding to the current tracking context.
/// </summary>
/// <param name="intermediateDirectory">The directory into which to write the tracking log files</param>
/// <param name="taskName">The name of the task calling this function, used to determine the
/// names of the tracking log files</param>
[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
/// <summary>
/// Test to see if the specified file is excluded from tracked dependencies
/// </summary>
/// <param name="fileName">
/// Full path of the file to test
/// </param>
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;
}
/// <summary>
/// Test to see if the specified file is under the specified path
/// </summary>
/// <param name="fileName">
/// Full path of the file to test
/// </param>
/// <param name="path">
/// Is the file under this full path?
/// </param>
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;
}
/// <summary>
/// Construct a rooting marker string from the ITaskItem array of primary sources.
/// </summary>
/// <param name="source">An <see cref="ITaskItem"/> containing information about the primary source.</param>
public static string FormatRootingMarker(ITaskItem source) => FormatRootingMarker([source], null);
/// <summary>
/// Construct a rooting marker string from the ITaskItem array of primary sources.
/// </summary>
/// <param name="source">An <see cref="ITaskItem"/> containing information about the primary source.</param>
/// <param name="output">An <see cref="ITaskItem"/> containing information about the output.</param>
public static string FormatRootingMarker(ITaskItem source, ITaskItem output) => FormatRootingMarker([source], [output]);
/// <summary>
/// Construct a rooting marker string from the ITaskItem array of primary sources.
/// </summary>
/// <param name="sources">
/// ITaskItem array of primary sources.
/// </param>
public static string FormatRootingMarker(ITaskItem[] sources) => FormatRootingMarker(sources, null);
/// <summary>
/// Construct a rooting marker string from the ITaskItem array of primary sources.
/// </summary>
/// <param name="sources">
/// ITaskItem array of primary sources.
/// </param>
/// <param name="outputs">ITaskItem array of outputs.</param>
public static string FormatRootingMarker(ITaskItem[] sources, ITaskItem[] outputs)
{
ArgumentNullException.ThrowIfNull(sources);
// So we don't have to deal with null checks.
outputs ??= Array.Empty<ITaskItem>();
var rootSources = new List<string>(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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="sources">
/// ITaskItem array of primary sources.
/// </param>
/// <returns>The response file path.</returns>
public static string CreateRootingMarkerResponseFile(ITaskItem[] sources) => CreateRootingMarkerResponseFile(FormatRootingMarker(sources));
/// <summary>
/// Given a rooting marker, creates a temporary response file with that rooting marker
/// in it.
/// </summary>
/// <param name="rootMarker">The rooting marker to put in the response file.</param>
/// <returns>The response file path.</returns>
public static string CreateRootingMarkerResponseFile(string rootMarker)
{
string trackerResponseFile = FileUtilities.GetTemporaryFileName(".rsp");
File.WriteAllText(trackerResponseFile, "/r \"" + rootMarker + "\"", Encoding.Unicode);
return trackerResponseFile;
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The old value of PATH</returns>
public static string EnsureFileTrackerOnPath() => EnsureFileTrackerOnPath(null);
/// <summary>
/// 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.
/// </summary>
/// <param name="rootPath">The root path for FileTracker.dll. Overrides the toolType if specified.</param>
/// <returns>The old value of PATH</returns>
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;
}
/// <summary>
/// Searches %PATH% for the location of Tracker.exe, and returns the first
/// path that matches.
/// </summary>
/// <returns>The full path to Tracker.exe, or <see langword="null" /> if a matching path is not found.</returns>
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;
}
/// <summary>
/// Determines whether we must track out-of-proc, or whether inproc tracking will work.
/// </summary>
/// <param name="toolType">The executable type for the tool being tracked</param>
/// <returns>True if we need to track out-of-proc, false if inproc tracking is OK</returns>
public static bool ForceOutOfProcTracking(ExecutableType toolType) => ForceOutOfProcTracking(toolType, null, null);
/// <summary>
/// Determines whether we must track out-of-proc, or whether inproc tracking will work.
/// </summary>
/// <param name="toolType">The executable type for the tool being tracked</param>
/// <param name="dllName">An optional assembly name.</param>
/// <param name="cancelEventName">The name of the cancel event tracker should listen for, or null if there isn't one</param>
/// <returns>True if we need to track out-of-proc, false if inproc tracking is OK</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="toolType">The <see cref="ExecutableType"/> of the tool being wrapped</param>
public static string GetTrackerPath(ExecutableType toolType) => GetTrackerPath(toolType, null);
/// <summary>
/// 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.
/// </summary>
/// <param name="toolType">The <see cref="ExecutableType"/> of the tool being wrapped</param>
/// <param name="rootPath">The root path for Tracker.exe. Overrides the toolType if specified.</param>
public static string GetTrackerPath(ExecutableType toolType, string rootPath) => GetPath(s_TrackerFilename, toolType, rootPath);
/// <summary>
/// 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.
/// </summary>
/// <param name="toolType">The <see cref="ExecutableType"/> of the tool being wrapped</param>
public static string GetFileTrackerPath(ExecutableType toolType) => GetFileTrackerPath(toolType, null);
/// <summary>
/// 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.
/// </summary>
/// <param name="toolType">The <see cref="ExecutableType"/> of the tool being wrapped</param>
/// <param name="rootPath">The root path for FileTracker.dll. Overrides the toolType if specified.</param>
public static string GetFileTrackerPath(ExecutableType toolType, string rootPath) => GetPath(s_FileTrackerFilename, toolType, rootPath);
/// <summary>
/// Given a filename (only really meant to support either Tracker.exe or FileTracker.dll), returns
/// the appropriate path for the appropriate file type.
/// </summary>
/// <param name="filename"></param>
/// <param name="toolType"></param>
/// <param name="rootPath">The root path for the file. Overrides the toolType if specified.</param>
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;
}
/// <summary>
/// Given a filename (currently only Tracker.exe and FileTracker.dll are supported), return
/// the path to that file.
/// </summary>
/// <param name="filename"></param>
/// <param name="bitness"></param>
/// <returns></returns>
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;
}
/// <summary>
/// This method constructs the correct Tracker.exe response file arguments from its parameters
/// </summary>
/// <param name="dllName">The name of the dll that will do the tracking</param>
/// <param name="intermediateDirectory">Intermediate directory where tracking logs will be written</param>
/// <param name="rootFiles">Rooting marker</param>
/// <returns>The arguments as a string</returns>
public static string TrackerResponseFileArguments(string dllName, string intermediateDirectory, string rootFiles)
=> TrackerResponseFileArguments(dllName, intermediateDirectory, rootFiles, null);
/// <summary>
/// This method constructs the correct Tracker.exe response file arguments from its parameters
/// </summary>
/// <param name="dllName">The name of the dll that will do the tracking</param>
/// <param name="intermediateDirectory">Intermediate directory where tracking logs will be written</param>
/// <param name="rootFiles">Rooting marker</param>
/// <param name="cancelEventName">If a cancel event has been created that Tracker should be listening for, its name is passed here</param>
/// <returns>The arguments as a string</returns>
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() + " ";
}
/// <summary>
/// This method constructs the correct Tracker.exe command arguments from its parameters
/// </summary>
/// <param name="command">The command to track</param>
/// <param name="arguments">The command to track's arguments</param>
/// <returns>The arguments as a string</returns>
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;
}
/// <summary>
/// This method constructs the correct Tracker.exe arguments from its parameters
/// </summary>
/// <param name="command">The command to track</param>
/// <param name="arguments">The command to track's arguments</param>
/// <param name="dllName">The name of the dll that will do the tracking</param>
/// <param name="intermediateDirectory">Intermediate directory where tracking logs will be written</param>
/// <param name="rootFiles">Rooting marker</param>
/// <returns>The arguments as a string</returns>
public static string TrackerArguments(string command, string arguments, string dllName, string intermediateDirectory, string rootFiles)
=> TrackerArguments(command, arguments, dllName, intermediateDirectory, rootFiles, null);
/// <summary>
/// This method constructs the correct Tracker.exe arguments from its parameters
/// </summary>
/// <param name="command">The command to track</param>
/// <param name="arguments">The command to track's arguments</param>
/// <param name="dllName">The name of the dll that will do the tracking</param>
/// <param name="intermediateDirectory">Intermediate directory where tracking logs will be written</param>
/// <param name="rootFiles">Rooting marker</param>
/// <param name="cancelEventName">If a cancel event has been created that Tracker should be listening for, its name is passed here</param>
/// <returns>The arguments as a string</returns>
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
/// <summary>
/// Start the process; tracking the command.
/// </summary>
/// <param name="command">The command to track</param>
/// <param name="arguments">The command to track's arguments</param>
/// <param name="toolType">The type of executable the wrapped tool is</param>
/// <param name="dllName">The name of the dll that will do the tracking</param>
/// <param name="intermediateDirectory">Intermediate directory where tracking logs will be written</param>
/// <param name="rootFiles">Rooting marker</param>
/// <param name="cancelEventName">If Tracker should be listening on a particular event for cancellation, pass its name here</param>
/// <returns>Process instance</returns>
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);
}
/// <summary>
/// Start the process; tracking the command.
/// </summary>
/// <param name="command">The command to track</param>
/// <param name="arguments">The command to track's arguments</param>
/// <param name="toolType">The type of executable the wrapped tool is</param>
/// <param name="dllName">The name of the dll that will do the tracking</param>
/// <param name="intermediateDirectory">Intermediate directory where tracking logs will be written</param>
/// <param name="rootFiles">Rooting marker</param>
/// <returns>Process instance</returns>
public static Process StartProcess(string command, string arguments, ExecutableType toolType, string dllName, string intermediateDirectory, string rootFiles)
=> StartProcess(command, arguments, toolType, dllName, intermediateDirectory, rootFiles, null);
/// <summary>
/// Start the process; tracking the command.
/// </summary>
/// <param name="command">The command to track</param>
/// <param name="arguments">The command to track's arguments</param>
/// <param name="toolType">The type of executable the wrapped tool is</param>
/// <param name="intermediateDirectory">Intermediate directory where tracking logs will be written</param>
/// <param name="rootFiles">Rooting marker</param>
/// <returns>Process instance</returns>
public static Process StartProcess(string command, string arguments, ExecutableType toolType, string intermediateDirectory, string rootFiles)
=> StartProcess(command, arguments, toolType, null, intermediateDirectory, rootFiles, null);
/// <summary>
/// Start the process; tracking the command.
/// </summary>
/// <param name="command">The command to track</param>
/// <param name="arguments">The command to track's arguments</param>
/// <param name="toolType">The type of executable the wrapped tool is</param>
/// <param name="rootFiles">Rooting marker</param>
/// <returns>Process instance</returns>
public static Process StartProcess(string command, string arguments, ExecutableType toolType, string rootFiles)
=> StartProcess(command, arguments, toolType, null, null, rootFiles, null);
/// <summary>
/// Start the process; tracking the command.
/// </summary>
/// <param name="command">The command to track</param>
/// <param name="arguments">The command to track's arguments</param>
/// <param name="toolType">The type of executable the wrapped tool is</param>
/// <returns>Process instance</returns>
public static Process StartProcess(string command, string arguments, ExecutableType toolType)
=> StartProcess(command, arguments, toolType, null, null, null, null);
#endregion // StartProcess methods
/// <summary>
/// Logs a message of the given importance using the specified resource string. To the specified Log.
/// </summary>
/// <remarks>This method is not thread-safe.</remarks>
/// <param name="Log">The Log to log to.</param>
/// <param name="importance">The importance level of the message.</param>
/// <param name="messageResourceName">The name of the string resource to load.</param>
/// <param name="messageArgs">Optional arguments for formatting the loaded string.</param>
/// <exception cref="ArgumentNullException">Thrown when <c>messageResourceName</c> is null.</exception>
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);
}
}
/// <summary>
/// Logs a message of the given importance using the specified string.
/// </summary>
/// <remarks>This method is not thread-safe.</remarks>
/// <param name="Log">The Log to log to.</param>
/// <param name="importance">The importance level of the message.</param>
/// <param name="message">The message string.</param>
/// <param name="messageArgs">Optional arguments for formatting the message string.</param>
/// <exception cref="ArgumentNullException">Thrown when <c>message</c> is null.</exception>
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);
}
/// <summary>
/// Logs a warning using the specified resource string.
/// </summary>
/// <param name="Log">The Log to log to.</param>
/// <param name="messageResourceName">The name of the string resource to load.</param>
/// <param name="messageArgs">Optional arguments for formatting the loaded string.</param>
/// <exception cref="ArgumentNullException">Thrown when <c>messageResourceName</c> is null.</exception>
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
/// <summary>
/// Dependency filter delegate. Used during TLog saves in order for tasks to selectively remove dependencies from the written
/// graph.
/// </summary>
/// <param name="fullPath">The full path to the dependency file about to be written to the compacted TLog</param>
/// <returns>If the file should actually be written to the TLog (true) or not (false)</returns>
public delegate bool DependencyFilter(string fullPath);
}
#endif