diff --git a/NuGet.config b/NuGet.config
index 4e9907ea835..d7187b4d64a 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -8,6 +8,7 @@
+
diff --git a/eng/Signing.props b/eng/Signing.props
index 66347f3d9d1..3e8e6dbf5cc 100644
--- a/eng/Signing.props
+++ b/eng/Signing.props
@@ -1,9 +1,11 @@
+
+
-
+
true
-
\ No newline at end of file
+
diff --git a/eng/dependabot/Packages.props b/eng/dependabot/Packages.props
index 4a6756eff0a..59fdddfc56a 100644
--- a/eng/dependabot/Packages.props
+++ b/eng/dependabot/Packages.props
@@ -19,6 +19,9 @@
+
+
+
diff --git a/src/Build.UnitTests/BackEnd/TaskHostTaskComplete_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHostTaskComplete_Tests.cs
index 5c6bf006b2c..9fba141c3a3 100644
--- a/src/Build.UnitTests/BackEnd/TaskHostTaskComplete_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/TaskHostTaskComplete_Tests.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Framework;
+using Microsoft.Build.Framework.FileAccess;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
using Xunit;
@@ -25,21 +26,67 @@ public class TaskHostTaskComplete_Tests
[Fact]
public void TestConstructors()
{
- TaskHostTaskComplete complete = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.Success), null);
- TaskHostTaskComplete complete2 = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure), null);
- TaskHostTaskComplete complete3 = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringInitialization, new ArgumentOutOfRangeException()), null);
- TaskHostTaskComplete complete4 = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringExecution, new ArgumentNullException()), null);
+#if FEATURE_REPORTFILEACCESSES
+ var fileAccessData = new List()
+ {
+ new FileAccessData(
+ ReportedFileOperation.CreateFile,
+ RequestedAccess.Read,
+ 0,
+ 0,
+ DesiredAccess.GENERIC_READ,
+ FlagsAndAttributes.FILE_ATTRIBUTE_NORMAL,
+ "foo",
+ null,
+ true),
+ };
+#endif
+
+ _ = new TaskHostTaskComplete(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.Success),
+#if FEATURE_REPORTFILEACCESSES
+ fileAccessData,
+#endif
+ null);
+ _ = new TaskHostTaskComplete(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.Failure),
+#if FEATURE_REPORTFILEACCESSES
+ fileAccessData,
+#endif
+ null);
+ _ = new TaskHostTaskComplete(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringInitialization,
+ new ArgumentOutOfRangeException()),
+#if FEATURE_REPORTFILEACCESSES
+ fileAccessData,
+#endif
+ null);
+ _ = new TaskHostTaskComplete(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedDuringExecution, new ArgumentNullException()),
+#if FEATURE_REPORTFILEACCESSES
+ fileAccessData,
+#endif
+ null);
IDictionary parameters = new Dictionary();
- TaskHostTaskComplete complete5 = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters), null);
+ _ = new TaskHostTaskComplete(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters),
+#if FEATURE_REPORTFILEACCESSES
+ null,
+#endif
+ null);
IDictionary parameters2 = new Dictionary();
parameters2.Add("Text", "Hello!");
parameters2.Add("MyBoolValue", true);
parameters2.Add("MyITaskItem", new TaskItem("ABC"));
parameters2.Add("ItemArray", new ITaskItem[] { new TaskItem("DEF"), new TaskItem("GHI"), new TaskItem("JKL") });
-
- TaskHostTaskComplete complete6 = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters2), null);
+ _ = new TaskHostTaskComplete(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters2),
+#if FEATURE_REPORTFILEACCESSES
+ null,
+#endif
+ null);
}
///
@@ -60,7 +107,12 @@ public void TestInvalidConstructors()
[Fact]
public void TestTranslationWithNullDictionary()
{
- TaskHostTaskComplete complete = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.Success), null);
+ TaskHostTaskComplete complete = new(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.Success),
+#if FEATURE_REPORTFILEACCESSES
+ null,
+#endif
+ null);
((ITranslatable)complete).Translate(TranslationHelpers.GetWriteTranslator());
INodePacket packet = TaskHostTaskComplete.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
@@ -78,7 +130,12 @@ public void TestTranslationWithNullDictionary()
[Fact]
public void TestTranslationWithEmptyDictionary()
{
- TaskHostTaskComplete complete = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, new Dictionary()), null);
+ TaskHostTaskComplete complete = new(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, new Dictionary()),
+#if FEATURE_REPORTFILEACCESSES
+ null,
+#endif
+ null);
((ITranslatable)complete).Translate(TranslationHelpers.GetWriteTranslator());
INodePacket packet = TaskHostTaskComplete.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
@@ -99,7 +156,12 @@ public void TestTranslationWithValueTypesInDictionary()
IDictionary parameters = new Dictionary();
parameters.Add("Text", "Foo");
parameters.Add("BoolValue", false);
- TaskHostTaskComplete complete = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters), null);
+ TaskHostTaskComplete complete = new(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters),
+#if FEATURE_REPORTFILEACCESSES
+ null,
+#endif
+ null);
((ITranslatable)complete).Translate(TranslationHelpers.GetWriteTranslator());
INodePacket packet = TaskHostTaskComplete.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
@@ -121,7 +183,12 @@ public void TestTranslationWithITaskItemInDictionary()
{
IDictionary parameters = new Dictionary();
parameters.Add("TaskItemValue", new TaskItem("Foo"));
- TaskHostTaskComplete complete = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters), null);
+ TaskHostTaskComplete complete = new(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters),
+#if FEATURE_REPORTFILEACCESSES
+ null,
+#endif
+ null);
((ITranslatable)complete).Translate(TranslationHelpers.GetWriteTranslator());
INodePacket packet = TaskHostTaskComplete.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
@@ -142,7 +209,12 @@ public void TestTranslationWithITaskItemArrayInDictionary()
{
IDictionary parameters = new Dictionary();
parameters.Add("TaskItemArrayValue", new ITaskItem[] { new TaskItem("Foo"), new TaskItem("Baz") });
- TaskHostTaskComplete complete = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters), null);
+ TaskHostTaskComplete complete = new(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.Success, parameters),
+#if FEATURE_REPORTFILEACCESSES
+ null,
+#endif
+ null);
((ITranslatable)complete).Translate(TranslationHelpers.GetWriteTranslator());
INodePacket packet = TaskHostTaskComplete.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
@@ -168,7 +240,12 @@ private void AssertInvalidConstructorThrows(Type expectedExceptionType, TaskComp
try
{
- TaskHostTaskComplete complete = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(taskResult, taskOutputParameters, taskException, taskExceptionMessage, taskExceptionMessageArgs), buildProcessEnvironment);
+ TaskHostTaskComplete complete = new(
+ new OutOfProcTaskHostTaskResult(taskResult, taskOutputParameters, taskException, taskExceptionMessage, taskExceptionMessageArgs),
+#if FEATURE_REPORTFILEACCESSES
+ null,
+#endif
+ buildProcessEnvironment);
}
catch (Exception e)
{
diff --git a/src/Build/AssemblyInfo.cs b/src/Build/AssemblyInfo.cs
index 40a6964c0e5..86a5a8073d2 100644
--- a/src/Build/AssemblyInfo.cs
+++ b/src/Build/AssemblyInfo.cs
@@ -32,3 +32,6 @@
[assembly: ComVisible(false)]
[assembly: CLSCompliant(true)]
+
+[assembly: Dependency("BuildXL.Utilities.Core", LoadHint.Sometimes)]
+[assembly: Dependency("BuildXL.Processes", LoadHint.Sometimes)]
diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs
index 4c8bda783c3..f9db0075c8c 100644
--- a/src/Build/BackEnd/BuildManager/BuildManager.cs
+++ b/src/Build/BackEnd/BuildManager/BuildManager.cs
@@ -13,6 +13,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
@@ -25,6 +26,7 @@
using Microsoft.Build.Exceptions;
using Microsoft.Build.Experimental;
using Microsoft.Build.Experimental.ProjectCache;
+using Microsoft.Build.FileAccesses;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Telemetry;
using Microsoft.Build.Graph;
@@ -558,6 +560,13 @@ public void BeginBuild(BuildParameters parameters)
_buildParameters.OutputResultsCacheFile = FileUtilities.NormalizePath("msbuild-cache");
}
+#if FEATURE_REPORTFILEACCESSES
+ if (_buildParameters.ReportFileAccesses)
+ {
+ EnableDetouredNodeLauncher();
+ }
+#endif
+
// Initialize components.
_nodeManager = ((IBuildComponentHost)this).GetComponent(BuildComponentType.NodeManager) as INodeManager;
@@ -572,9 +581,17 @@ public void BeginBuild(BuildParameters parameters)
InitializeCaches();
+#if FEATURE_REPORTFILEACCESSES
+ var fileAccessManager = ((IBuildComponentHost)this).GetComponent(BuildComponentType.FileAccessManager) as IFileAccessManager;
+#endif
+
_projectCacheService = new ProjectCacheService(
this,
loggingService,
+#if FEATURE_REPORTFILEACCESSES
+ fileAccessManager,
+#endif
+ _configCache,
_buildParameters.ProjectCacheDescriptor);
_taskHostNodeManager = ((IBuildComponentHost)this).GetComponent(BuildComponentType.TaskHostNodeManager) as INodeManager;
@@ -584,7 +601,9 @@ public void BeginBuild(BuildParameters parameters)
_nodeManager.RegisterPacketHandler(NodePacketType.BuildRequestConfiguration, BuildRequestConfiguration.FactoryForDeserialization, this);
_nodeManager.RegisterPacketHandler(NodePacketType.BuildRequestConfigurationResponse, BuildRequestConfigurationResponse.FactoryForDeserialization, this);
_nodeManager.RegisterPacketHandler(NodePacketType.BuildResult, BuildResult.FactoryForDeserialization, this);
+ _nodeManager.RegisterPacketHandler(NodePacketType.FileAccessReport, FileAccessReport.FactoryForDeserialization, this);
_nodeManager.RegisterPacketHandler(NodePacketType.NodeShutdown, NodeShutdown.FactoryForDeserialization, this);
+ _nodeManager.RegisterPacketHandler(NodePacketType.ProcessReport, ProcessReport.FactoryForDeserialization, this);
_nodeManager.RegisterPacketHandler(NodePacketType.ResolveSdkRequest, SdkResolverRequest.FactoryForDeserialization, SdkResolverService as INodePacketHandler);
_nodeManager.RegisterPacketHandler(NodePacketType.ResourceRequest, ResourceRequest.FactoryForDeserialization, this);
@@ -699,6 +718,26 @@ void InitializeCaches()
}
}
+#if FEATURE_REPORTFILEACCESSES
+ ///
+ /// Configure the build to use I/O tracking for nodes.
+ ///
+ ///
+ /// Must be a separate non-inlinable method to avoid loading the BuildXL assembly when not opted in.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void EnableDetouredNodeLauncher()
+ {
+ // To properly report file access, we need to disable the in-proc node which won't be detoured.
+ _buildParameters.DisableInProcNode = true;
+
+ // Node reuse must be disabled as future builds will not be able to listen to events raised by detours.
+ _buildParameters.EnableNodeReuse = false;
+
+ _componentFactories.ReplaceFactory(BuildComponentType.NodeLauncher, DetouredNodeLauncher.CreateComponent);
+ }
+#endif
+
private static void AttachDebugger()
{
if (Debugger.IsAttached)
@@ -1564,6 +1603,16 @@ private void ProcessPacket(int node, INodePacket packet)
HandleNodeShutdown(node, shutdownPacket);
break;
+ case NodePacketType.FileAccessReport:
+ FileAccessReport fileAccessReport = ExpectPacketType(packet, NodePacketType.FileAccessReport);
+ HandleFileAccessReport(node, fileAccessReport);
+ break;
+
+ case NodePacketType.ProcessReport:
+ ProcessReport processReport = ExpectPacketType(packet, NodePacketType.ProcessReport);
+ HandleProcessReport(node, processReport);
+ break;
+
default:
ErrorUtilities.ThrowInternalError("Unexpected packet received by BuildManager: {0}", packet.Type);
break;
@@ -2371,6 +2420,39 @@ private void HandleResult(int node, BuildResult result)
configuration.ProjectTargets ??= result.ProjectTargets;
}
+ // Only report results to the project cache services if it's the result for a build submission.
+ // Note that graph builds create a submission for each node in the graph, so each node in the graph will be
+ // handled here. This intentionally mirrors the behavior for cache requests, as it doesn't make sense to
+ // report for projects which aren't going to be requested. Ideally, *any* request could be handled, but that
+ // would require moving the cache service interactions to the Scheduler.
+ if (_buildSubmissions.TryGetValue(result.SubmissionId, out BuildSubmission buildSubmission))
+ {
+ // The result may be associated with the build submission due to it being the submission which
+ // caused the build, but not the actual request which was originally used with the build submission.
+ // ie. it may be a dependency of the "root-level" project which is associated with this submission, which
+ // isn't what we're looking for. Ensure only the actual submission's request is considered.
+ if (buildSubmission.BuildRequest != null
+ && buildSubmission.BuildRequest.ConfigurationId == configuration.ConfigurationId
+ && _projectCacheService.ShouldUseCache(configuration))
+ {
+ BuildEventContext buildEventContext = _projectStartedEvents.TryGetValue(result.SubmissionId, out BuildEventArgs buildEventArgs)
+ ? buildEventArgs.BuildEventContext
+ : new BuildEventContext(result.SubmissionId, node, configuration.Project?.EvaluationId ?? BuildEventContext.InvalidEvaluationId, configuration.ConfigurationId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidTaskId);
+ try
+ {
+ _projectCacheService.HandleBuildResultAsync(configuration, result, buildEventContext, _executionCancellationTokenSource.Token).Wait();
+ }
+ catch (AggregateException ex) when (ex.InnerExceptions.All(inner => inner is OperationCanceledException))
+ {
+ // The build is being cancelled. Swallow any exceptions related specifically to cancellation.
+ }
+ catch (OperationCanceledException)
+ {
+ // The build is being cancelled. Swallow any exceptions related specifically to cancellation.
+ }
+ }
+ }
+
IEnumerable response = _scheduler.ReportResult(node, result);
PerformSchedulingActions(response);
}
@@ -2437,6 +2519,36 @@ private void HandleNodeShutdown(int node, NodeShutdown shutdownPacket)
CheckForActiveNodesAndCleanUpSubmissions();
}
+ ///
+ /// Report the received to the file access manager.
+ ///
+ /// The id of the node from which the was received.
+ /// The file access report.
+ private void HandleFileAccessReport(int nodeId, FileAccessReport fileAccessReport)
+ {
+#if FEATURE_REPORTFILEACCESSES
+ if (_buildParameters.ReportFileAccesses)
+ {
+ ((FileAccessManager)((IBuildComponentHost)this).GetComponent(BuildComponentType.FileAccessManager)).ReportFileAccess(fileAccessReport.FileAccessData, nodeId);
+ }
+#endif
+ }
+
+ ///
+ /// Report the received to the file access manager.
+ ///
+ /// The id of the node from which the was received.
+ /// The process data report.
+ private void HandleProcessReport(int nodeId, ProcessReport processReport)
+ {
+#if FEATURE_REPORTFILEACCESSES
+ if (_buildParameters.ReportFileAccesses)
+ {
+ ((FileAccessManager)((IBuildComponentHost)this).GetComponent(BuildComponentType.FileAccessManager)).ReportProcess(processReport.ProcessData, nodeId);
+ }
+#endif
+ }
+
///
/// If there are no more active nodes, cleans up any remaining submissions.
///
diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs
index 8d7a8268648..582532e5795 100644
--- a/src/Build/BackEnd/BuildManager/BuildParameters.cs
+++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs
@@ -220,6 +220,8 @@ public class BuildParameters : ITranslatable
private string _outputResultsCacheFile;
+ private bool _reportFileAccesses;
+
///
/// Constructor for those who intend to set all properties themselves.
///
@@ -303,6 +305,7 @@ internal BuildParameters(BuildParameters other, bool resetEnvironment = false)
_projectIsolationMode = other.ProjectIsolationMode;
_inputResultsCacheFiles = other._inputResultsCacheFiles;
_outputResultsCacheFile = other._outputResultsCacheFile;
+ _reportFileAccesses = other._reportFileAccesses;
DiscardBuildResults = other.DiscardBuildResults;
LowPriority = other.LowPriority;
Question = other.Question;
@@ -801,6 +804,17 @@ public string OutputResultsCacheFile
set => _outputResultsCacheFile = value;
}
+#if FEATURE_REPORTFILEACCESSES
+ ///
+ /// Gets or sets a value indicating whether file accesses should be reported to any configured project cache plugins.
+ ///
+ public bool ReportFileAccesses
+ {
+ get => _reportFileAccesses;
+ set => _reportFileAccesses = value;
+ }
+#endif
+
///
/// Determines whether MSBuild will save the results of builds after EndBuild to speed up future builds.
///
@@ -885,6 +899,7 @@ void ITranslatable.Translate(ITranslator translator)
translator.Translate(ref _interactive);
translator.Translate(ref _question);
translator.TranslateEnum(ref _projectIsolationMode, (int)_projectIsolationMode);
+ translator.Translate(ref _reportFileAccesses);
// ProjectRootElementCache is not transmitted.
// ResetCaches is not transmitted.
diff --git a/src/Build/BackEnd/Client/MSBuildClient.cs b/src/Build/BackEnd/Client/MSBuildClient.cs
index 693912475d0..e0782c3fbf3 100644
--- a/src/Build/BackEnd/Client/MSBuildClient.cs
+++ b/src/Build/BackEnd/Client/MSBuildClient.cs
@@ -473,7 +473,7 @@ private bool TryLaunchServer()
};
NodeLauncher nodeLauncher = new NodeLauncher();
CommunicationsUtilities.Trace("Starting Server...");
- Process msbuildProcess = nodeLauncher.Start(_msbuildLocation, string.Join(" ", msBuildServerOptions));
+ Process msbuildProcess = nodeLauncher.Start(_msbuildLocation, string.Join(" ", msBuildServerOptions), nodeId: 0);
CommunicationsUtilities.Trace("Server started with PID: {0}", msbuildProcess?.Id);
}
catch (Exception ex)
diff --git a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs
index 5ea3ee2bde1..c4d543c87da 100644
--- a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs
+++ b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using Microsoft.Build.BackEnd.Components.Caching;
using Microsoft.Build.BackEnd.SdkResolution;
+using Microsoft.Build.FileAccesses;
using Microsoft.Build.Shared;
#nullable disable
@@ -61,6 +62,7 @@ public void RegisterDefaultFactories()
_componentEntriesByType[BuildComponentType.NodeManager] = new BuildComponentEntry(BuildComponentType.NodeManager, NodeManager.CreateComponent, CreationPattern.Singleton);
_componentEntriesByType[BuildComponentType.TaskHostNodeManager] = new BuildComponentEntry(BuildComponentType.TaskHostNodeManager, TaskHostNodeManager.CreateComponent, CreationPattern.Singleton);
+ _componentEntriesByType[BuildComponentType.NodeLauncher] = new BuildComponentEntry(BuildComponentType.NodeLauncher, NodeLauncher.CreateComponent, CreationPattern.Singleton);
_componentEntriesByType[BuildComponentType.InProcNodeProvider] = new BuildComponentEntry(BuildComponentType.InProcNodeProvider, NodeProviderInProc.CreateComponent, CreationPattern.Singleton);
_componentEntriesByType[BuildComponentType.OutOfProcNodeProvider] = new BuildComponentEntry(BuildComponentType.OutOfProcNodeProvider, NodeProviderOutOfProc.CreateComponent, CreationPattern.Singleton);
_componentEntriesByType[BuildComponentType.OutOfProcTaskHostNodeProvider] = new BuildComponentEntry(BuildComponentType.OutOfProcTaskHostNodeProvider, NodeProviderOutOfProcTaskHost.CreateComponent, CreationPattern.Singleton);
@@ -80,6 +82,10 @@ public void RegisterDefaultFactories()
// SDK resolution
_componentEntriesByType[BuildComponentType.SdkResolverService] = new BuildComponentEntry(BuildComponentType.SdkResolverService, MainNodeSdkResolverService.CreateComponent, CreationPattern.Singleton);
+
+#if FEATURE_REPORTFILEACCESSES
+ _componentEntriesByType[BuildComponentType.FileAccessManager] = new BuildComponentEntry(BuildComponentType.FileAccessManager, FileAccessManager.CreateComponent, CreationPattern.Singleton);
+#endif
}
///
diff --git a/src/Build/BackEnd/Components/Communications/DetouredNodeLauncher.cs b/src/Build/BackEnd/Components/Communications/DetouredNodeLauncher.cs
new file mode 100644
index 00000000000..d34bf7c9eec
--- /dev/null
+++ b/src/Build/BackEnd/Components/Communications/DetouredNodeLauncher.cs
@@ -0,0 +1,191 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if FEATURE_REPORTFILEACCESSES
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using BuildXL.Processes;
+using BuildXL.Utilities.Core;
+using Microsoft.Build.Exceptions;
+using Microsoft.Build.FileAccesses;
+using Microsoft.Build.Internal;
+using Microsoft.Build.Shared;
+using Microsoft.Build.Shared.FileSystem;
+
+#nullable disable
+
+namespace Microsoft.Build.BackEnd
+{
+ internal sealed class DetouredNodeLauncher : INodeLauncher, IBuildComponent
+ {
+ private readonly List _sandboxedProcesses = new();
+
+ private readonly BuildParameters.IBuildParameters _environmentVariables = CreateEnvironmentVariables();
+
+ private IFileAccessManager _fileAccessManager;
+
+ public static IBuildComponent CreateComponent(BuildComponentType type)
+ {
+ ErrorUtilities.VerifyThrowArgumentOutOfRange(type == BuildComponentType.NodeLauncher, nameof(type));
+ return new DetouredNodeLauncher();
+ }
+
+ public void InitializeComponent(IBuildComponentHost host)
+ {
+ _fileAccessManager = (IFileAccessManager)host.GetComponent(BuildComponentType.FileAccessManager);
+ }
+
+ public void ShutdownComponent()
+ {
+ _fileAccessManager = null;
+
+ foreach (ISandboxedProcess sandboxedProcess in _sandboxedProcesses)
+ {
+ sandboxedProcess.Dispose();
+ }
+
+ _sandboxedProcesses.Clear();
+ }
+
+ ///
+ /// Creates a new MSBuild process
+ ///
+ public Process Start(string msbuildLocation, string commandLineArgs, int nodeId)
+ {
+ // Should always have been set already.
+ ErrorUtilities.VerifyThrowInternalLength(msbuildLocation, nameof(msbuildLocation));
+
+ ErrorUtilities.VerifyThrowInternalNull(_fileAccessManager, nameof(_fileAccessManager));
+
+ if (!FileSystems.Default.FileExists(msbuildLocation))
+ {
+ throw new BuildAbortedException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("CouldNotFindMSBuildExe", msbuildLocation));
+ }
+
+ // Repeat the executable name as the first token of the command line because the command line
+ // parser logic expects it and will otherwise skip the first argument
+ commandLineArgs = $"\"{msbuildLocation}\" {commandLineArgs}";
+
+ CommunicationsUtilities.Trace("Launching node from {0}", msbuildLocation);
+
+ string exeName = msbuildLocation;
+
+#if RUNTIME_TYPE_NETCORE
+ // Run the child process with the same host as the currently-running process.
+ exeName = CurrentHost.GetCurrentHost();
+#endif
+
+ var eventListener = new DetoursEventListener(_fileAccessManager, nodeId);
+ eventListener.SetMessageHandlingFlags(MessageHandlingFlags.DebugMessageNotify | MessageHandlingFlags.FileAccessNotify | MessageHandlingFlags.ProcessDataNotify | MessageHandlingFlags.ProcessDetoursStatusNotify);
+
+ var info = new SandboxedProcessInfo(
+ fileStorage: null, // Don't write stdout/stderr to files
+ fileName: exeName,
+ disableConHostSharing: false,
+ detoursEventListener: eventListener,
+ createJobObjectForCurrentProcess: false)
+ {
+ SandboxKind = SandboxKind.Default,
+ PipDescription = "MSBuild",
+ PipSemiStableHash = 0,
+ Arguments = commandLineArgs,
+ EnvironmentVariables = _environmentVariables,
+ MaxLengthInMemory = 0, // Don't buffer any output
+ };
+
+ // FileAccessManifest.AddScope is used to define the list of files which the running process is allowed to access and what kinds of file accesses are allowed
+ // Tracker internally uses AbsolutePath.Invalid to represent the root, just like Unix '/' root.
+ // this code allows all types of accesses for all files
+ info.FileAccessManifest.AddScope(
+ AbsolutePath.Invalid,
+ FileAccessPolicy.MaskNothing,
+ FileAccessPolicy.AllowAll | FileAccessPolicy.ReportAccess);
+
+ // Support shared compilation
+ info.FileAccessManifest.ChildProcessesToBreakawayFromSandbox = new string[] { NativeMethodsShared.IsWindows ? "VBCSCompiler.exe" : "VBCSCompiler" };
+ info.FileAccessManifest.MonitorChildProcesses = true;
+ info.FileAccessManifest.IgnoreReparsePoints = true;
+ info.FileAccessManifest.UseExtraThreadToDrainNtClose = false;
+ info.FileAccessManifest.UseLargeNtClosePreallocatedList = true;
+ info.FileAccessManifest.LogProcessData = true;
+
+ // needed for logging process arguments when a new process is invoked; see DetoursEventListener.cs
+ info.FileAccessManifest.ReportProcessArgs = true;
+
+ // By default, BuildXL sets the timestamp of all input files to January 1, 1970
+ // This breaks some tools like Robocopy which will not copy a file to the destination if the file exists at the destination and has a timestamp that is more recent than the source file
+ info.FileAccessManifest.NormalizeReadTimestamps = false;
+
+ // If a process exits but its child processes survive, Tracker waits 30 seconds by default to wait for this process to exit.
+ // This slows down C++ builds in which mspdbsrv.exe doesn't exit when it's parent exits. Set this time to 0.
+ info.NestedProcessTerminationTimeout = TimeSpan.Zero;
+
+ ISandboxedProcess sp = SandboxedProcessFactory.StartAsync(info, forceSandboxing: false).GetAwaiter().GetResult();
+ lock (_sandboxedProcesses)
+ {
+ _sandboxedProcesses.Add(sp);
+ }
+
+ CommunicationsUtilities.Trace("Successfully launched {1} node with PID {0}", sp.ProcessId, exeName);
+ return Process.GetProcessById(sp.ProcessId);
+ }
+
+ private static BuildParameters.IBuildParameters CreateEnvironmentVariables()
+ {
+ var envVars = new Dictionary();
+ foreach (DictionaryEntry baseVar in Environment.GetEnvironmentVariables())
+ {
+ envVars.Add((string)baseVar.Key, (string)baseVar.Value);
+ }
+
+ return BuildParameters.GetFactory().PopulateFromDictionary(envVars);
+ }
+
+ private sealed class DetoursEventListener : IDetoursEventListener
+ {
+ private readonly IFileAccessManager _fileAccessManager;
+ private readonly int _nodeId;
+
+ public DetoursEventListener(IFileAccessManager fileAccessManager, int nodeId)
+ {
+ _fileAccessManager = fileAccessManager;
+ _nodeId = nodeId;
+ }
+
+ public override void HandleDebugMessage(DebugData debugData)
+ {
+ }
+
+ public override void HandleFileAccess(FileAccessData fileAccessData) => _fileAccessManager.ReportFileAccess(
+ new Framework.FileAccess.FileAccessData(
+ (Framework.FileAccess.ReportedFileOperation)fileAccessData.Operation,
+ (Framework.FileAccess.RequestedAccess)fileAccessData.RequestedAccess,
+ fileAccessData.ProcessId,
+ fileAccessData.Error,
+ (Framework.FileAccess.DesiredAccess)fileAccessData.DesiredAccess,
+ (Framework.FileAccess.FlagsAndAttributes)fileAccessData.FlagsAndAttributes,
+ fileAccessData.Path,
+ fileAccessData.ProcessArgs,
+ fileAccessData.IsAnAugmentedFileAccess),
+ _nodeId);
+
+ public override void HandleProcessData(ProcessData processData) => _fileAccessManager.ReportProcess(
+ new Framework.FileAccess.ProcessData(
+ processData.ProcessName,
+ processData.ProcessId,
+ processData.ParentProcessId,
+ processData.CreationDateTime,
+ processData.ExitDateTime,
+ processData.ExitCode),
+ _nodeId);
+
+ public override void HandleProcessDetouringStatus(ProcessDetouringStatusData data)
+ {
+ }
+ }
+ }
+}
+#endif
diff --git a/src/Build/BackEnd/Components/Communications/INodeLauncher.cs b/src/Build/BackEnd/Components/Communications/INodeLauncher.cs
new file mode 100644
index 00000000000..c409c856c0b
--- /dev/null
+++ b/src/Build/BackEnd/Components/Communications/INodeLauncher.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace Microsoft.Build.BackEnd
+{
+ internal interface INodeLauncher
+ {
+ Process Start(string msbuildLocation, string commandLineArgs, int nodeId);
+ }
+}
diff --git a/src/Build/BackEnd/Components/Communications/NodeLauncher.cs b/src/Build/BackEnd/Components/Communications/NodeLauncher.cs
index 611c4ca68c9..ddec6d79279 100644
--- a/src/Build/BackEnd/Components/Communications/NodeLauncher.cs
+++ b/src/Build/BackEnd/Components/Communications/NodeLauncher.cs
@@ -16,12 +16,26 @@
namespace Microsoft.Build.BackEnd
{
- internal class NodeLauncher
+ internal sealed class NodeLauncher : INodeLauncher, IBuildComponent
{
+ public static IBuildComponent CreateComponent(BuildComponentType type)
+ {
+ ErrorUtilities.VerifyThrowArgumentOutOfRange(type == BuildComponentType.NodeLauncher, nameof(type));
+ return new NodeLauncher();
+ }
+
+ public void InitializeComponent(IBuildComponentHost host)
+ {
+ }
+
+ public void ShutdownComponent()
+ {
+ }
+
///
/// Creates a new MSBuild process
///
- public Process Start(string msbuildLocation, string commandLineArgs)
+ public Process Start(string msbuildLocation, string commandLineArgs, int nodeId)
{
// Disable MSBuild server for a child process.
// In case of starting msbuild server it prevents an infinite recurson. In case of starting msbuild node we also do not want this variable to be set.
@@ -181,7 +195,7 @@ private Process StartInternal(string msbuildLocation, string commandLineArgs)
}
}
- private Process DisableMSBuildServer(Func func)
+ private static Process DisableMSBuildServer(Func func)
{
string useMSBuildServerEnvVarValue = Environment.GetEnvironmentVariable(Traits.UseMSBuildServerEnvVarName);
try
diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
index 58105527c16..273e6990f13 100644
--- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
+++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
@@ -332,8 +332,8 @@ bool StartNewNode(int nodeId)
}
#endif
// Create the node process
- NodeLauncher nodeLauncher = new NodeLauncher();
- Process msbuildProcess = nodeLauncher.Start(msbuildLocation, commandLineArgs);
+ INodeLauncher nodeLauncher = (INodeLauncher)_componentHost.GetComponent(BuildComponentType.NodeLauncher);
+ Process msbuildProcess = nodeLauncher.Start(msbuildLocation, commandLineArgs, nodeId);
_processesToIgnore.TryAdd(GetProcessesToIgnoreKey(hostHandshake, msbuildProcess.Id), default);
// Note, when running under IMAGEFILEEXECUTIONOPTIONS registry key to debug, the process ID
diff --git a/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs b/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs
new file mode 100644
index 00000000000..efbe32a0f64
--- /dev/null
+++ b/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs
@@ -0,0 +1,188 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if FEATURE_REPORTFILEACCESSES
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Runtime.Versioning;
+using System.Threading;
+using Microsoft.Build.BackEnd;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework.FileAccess;
+using Microsoft.Build.Shared;
+
+namespace Microsoft.Build.FileAccesses
+{
+ internal sealed class FileAccessManager : IFileAccessManager, IBuildComponent
+ {
+ private record Handlers(Action FileAccessHander, Action ProcessHandler);
+
+ // In order to synchronize between the node communication and the file access reporting, a special file access
+ // is used to mark when the file accesses should be considered complete. Only after both this special file access is seen
+ // and the build result is reported can plugins be notified about project completion.
+ // NOTE! This is currently Windows-specific and will need to change once this feature is opened up to more scenarios.
+ private static readonly string FileAccessCompletionPrefix = BuildParameters.StartupDirectory[0] + @":\{MSBuildFileAccessCompletion}\";
+
+ private IScheduler? _scheduler;
+ private IConfigCache? _configCache;
+
+ private object _handlersWriteLock = new object();
+ private Handlers[] _handlers = Array.Empty();
+ private string? _tempDirectory;
+
+ // Keyed on global request id
+ private readonly ConcurrentDictionary _fileAccessCompletionWaitHandles = new();
+
+ public static IBuildComponent CreateComponent(BuildComponentType type)
+ {
+ ErrorUtilities.VerifyThrowArgumentOutOfRange(type == BuildComponentType.FileAccessManager, nameof(type));
+ return new FileAccessManager();
+ }
+
+ public void InitializeComponent(IBuildComponentHost host)
+ {
+ _scheduler = host.GetComponent(BuildComponentType.Scheduler) as IScheduler;
+ _configCache = host.GetComponent(BuildComponentType.ConfigCache) as IConfigCache;
+ _tempDirectory = FileUtilities.EnsureNoTrailingSlash(FileUtilities.TempFileDirectory);
+ }
+
+ public void ShutdownComponent()
+ {
+ _scheduler = null;
+ _configCache = null;
+ _tempDirectory = null;
+ _fileAccessCompletionWaitHandles.Clear();
+ }
+
+ public void ReportFileAccess(FileAccessData fileAccessData, int nodeId)
+ {
+ string fileAccessPath = fileAccessData.Path;
+
+ // Intercept and avoid forwarding the file access completion
+ if (fileAccessPath.StartsWith(FileAccessCompletionPrefix, StringComparison.Ordinal))
+ {
+ // Parse out the global request id. Note, this must match what NotifyFileAccessCompletion does.
+ int globalRequestId = int.Parse(fileAccessPath.Substring(FileAccessCompletionPrefix.Length));
+
+ ManualResetEventSlim handle = _fileAccessCompletionWaitHandles.GetOrAdd(globalRequestId, static _ => new ManualResetEventSlim());
+ handle.Set();
+ }
+ else if (_tempDirectory != null && fileAccessPath.StartsWith(_tempDirectory))
+ {
+ // Ignore MSBuild's temp directory as these are related to internal MSBuild functionality and not always directly related to the execution of the project itself,
+ // so should not be exposed to handlers. Note that this is not %TEMP% but instead a subdir under %TEMP% which is only expected to be used by MSBuild.
+ return;
+ }
+ else
+ {
+ // Forward the file access to handlers.
+ BuildRequest? buildRequest = GetBuildRequest(nodeId);
+ if (buildRequest != null)
+ {
+ Handlers[] localHandlers = _handlers;
+ foreach (Handlers handlers in localHandlers)
+ {
+ handlers.FileAccessHander.Invoke(buildRequest, fileAccessData);
+ }
+ }
+ }
+ }
+
+ public void ReportProcess(ProcessData processData, int nodeId)
+ {
+ BuildRequest? buildRequest = GetBuildRequest(nodeId);
+ if (buildRequest != null)
+ {
+ Handlers[] localHandlers = _handlers;
+ foreach (Handlers handlers in localHandlers)
+ {
+ handlers.ProcessHandler.Invoke(buildRequest, processData);
+ }
+ }
+ }
+
+ public HandlerRegistration RegisterHandlers(Action fileAccessHandler, Action processHandler)
+ {
+ lock (_handlersWriteLock)
+ {
+ Handlers[] newHandlers = new Handlers[_handlers.Length + 1];
+ _handlers.CopyTo(newHandlers, 0);
+
+ Handlers addedHandlers = new(fileAccessHandler, processHandler);
+ newHandlers[_handlers.Length] = addedHandlers;
+
+ _handlers = newHandlers;
+
+ return new HandlerRegistration(() => UnregisterHandlers(addedHandlers));
+ }
+ }
+
+ private void UnregisterHandlers(Handlers handlersToRemove)
+ {
+ lock (_handlersWriteLock)
+ {
+ Handlers[] newHandlers = new Handlers[_handlers.Length - 1];
+ int newHandlersIdx = 0;
+ for (int handlersIdx = 0; handlersIdx < _handlers.Length; handlersIdx++)
+ {
+ if (_handlers[handlersIdx] != handlersToRemove)
+ {
+ newHandlers[newHandlersIdx] = _handlers[handlersIdx];
+ newHandlersIdx++;
+ }
+ }
+
+ _handlers = newHandlers;
+ }
+ }
+
+ // The [SupportedOSPlatform] attribute is a safeguard to ensure that the comment on FileAccessCompletionPrefix regarding being Windows-only gets addressed.
+ // [SupportedOSPlatform] doesn't apply to fields, so using it here as a reasonable proxy.
+ [SupportedOSPlatform("windows")]
+ public static void NotifyFileAccessCompletion(int globalRequestId)
+ {
+ // Make a dummy file access to use as a notification that the file accesses should be completed for a project.
+ string filePath = FileAccessCompletionPrefix + globalRequestId.ToString();
+ _ = File.Exists(filePath);
+ }
+
+ public void WaitForFileAccessReportCompletion(int globalRequestId, CancellationToken cancellationToken)
+ {
+ ManualResetEventSlim handle = _fileAccessCompletionWaitHandles.GetOrAdd(globalRequestId, static _ => new ManualResetEventSlim());
+ if (!handle.IsSet)
+ {
+ handle.Wait(cancellationToken);
+ }
+
+ // Try to keep the collection clean. A request should not need to be completed twice.
+ _fileAccessCompletionWaitHandles.TryRemove(globalRequestId, out _);
+ }
+
+ private BuildRequest? GetBuildRequest(int nodeId)
+ {
+ ErrorUtilities.VerifyThrow(
+ _scheduler != null && _configCache != null,
+ "Component has not been initialized");
+
+ // Note: If the node isn't executing anything it may be accessing binaries required to run, eg. the MSBuild binaries
+ return _scheduler!.GetExecutingRequestByNode(nodeId);
+ }
+
+ internal readonly struct HandlerRegistration : IDisposable
+ {
+ private readonly Action _unregisterAction;
+
+ public HandlerRegistration(Action unregisterAction)
+ {
+ _unregisterAction = unregisterAction;
+ }
+
+ public void Dispose()
+ {
+ _unregisterAction();
+ }
+ }
+ }
+}
+#endif
diff --git a/src/Build/BackEnd/Components/FileAccesses/FileAccessReport.cs b/src/Build/BackEnd/Components/FileAccesses/FileAccessReport.cs
new file mode 100644
index 00000000000..f69b6fd1580
--- /dev/null
+++ b/src/Build/BackEnd/Components/FileAccesses/FileAccessReport.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Build.BackEnd;
+using Microsoft.Build.Framework.FileAccess;
+
+namespace Microsoft.Build.FileAccesses
+{
+ internal sealed class FileAccessReport : INodePacket
+ {
+ private FileAccessData _fileAccessData;
+
+ internal FileAccessReport(FileAccessData fileAccessData) => _fileAccessData = fileAccessData;
+
+ private FileAccessReport(ITranslator translator) => Translate(translator);
+
+ ///
+ public NodePacketType Type => NodePacketType.FileAccessReport;
+
+ ///
+ public void Translate(ITranslator translator) => translator.Translate(ref _fileAccessData);
+
+ internal FileAccessData FileAccessData => _fileAccessData;
+
+ internal static INodePacket FactoryForDeserialization(ITranslator translator) => new FileAccessReport(translator);
+ }
+}
diff --git a/src/Build/BackEnd/Components/FileAccesses/IFileAccessManager.cs b/src/Build/BackEnd/Components/FileAccesses/IFileAccessManager.cs
new file mode 100644
index 00000000000..3dd724afef3
--- /dev/null
+++ b/src/Build/BackEnd/Components/FileAccesses/IFileAccessManager.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if FEATURE_REPORTFILEACCESSES
+using System;
+using System.Threading;
+using Microsoft.Build.BackEnd;
+using Microsoft.Build.Framework.FileAccess;
+
+namespace Microsoft.Build.FileAccesses
+{
+ internal interface IFileAccessManager
+ {
+ void ReportFileAccess(FileAccessData fileAccessData, int nodeId);
+
+ void ReportProcess(ProcessData processData, int nodeId);
+
+ // Note: The return type of FileAccessManager.HandlerRegistration is exposed directly instead of IDisposable to avoid boxing.
+ FileAccessManager.HandlerRegistration RegisterHandlers(
+ Action fileAccessHandler,
+ Action processHandler);
+
+ void WaitForFileAccessReportCompletion(int globalRequestId, CancellationToken cancellationToken);
+ }
+}
+#endif
diff --git a/src/Build/BackEnd/Components/FileAccesses/OutOfProcNodeFileAccessManager.cs b/src/Build/BackEnd/Components/FileAccesses/OutOfProcNodeFileAccessManager.cs
new file mode 100644
index 00000000000..80255059350
--- /dev/null
+++ b/src/Build/BackEnd/Components/FileAccesses/OutOfProcNodeFileAccessManager.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if FEATURE_REPORTFILEACCESSES
+using System;
+using System.Threading;
+using Microsoft.Build.BackEnd;
+using Microsoft.Build.Framework.FileAccess;
+using Microsoft.Build.Shared;
+
+namespace Microsoft.Build.FileAccesses
+{
+ ///
+ /// Reports file accesses and process data to the in-proc node.
+ ///
+ internal sealed class OutOfProcNodeFileAccessManager : IFileAccessManager, IBuildComponent
+ {
+ ///
+ /// The to report file accesses and process
+ /// data to the in-proc node.
+ ///
+ private readonly Action _sendPacket;
+
+ private OutOfProcNodeFileAccessManager(Action sendPacket) => _sendPacket = sendPacket;
+
+ public static IBuildComponent CreateComponent(BuildComponentType type, Action sendPacket)
+ {
+ ErrorUtilities.VerifyThrowArgumentOutOfRange(type == BuildComponentType.FileAccessManager, nameof(type));
+ return new OutOfProcNodeFileAccessManager(sendPacket);
+ }
+
+ public void InitializeComponent(IBuildComponentHost host)
+ {
+ }
+
+ public void ShutdownComponent()
+ {
+ }
+
+ ///
+ /// Reports a file access to the in-proc node.
+ ///
+ /// The file access to report to the in-proc node.
+ /// The id of the reporting out-of-proc node.
+ public void ReportFileAccess(FileAccessData fileAccessData, int nodeId) => _sendPacket(new FileAccessReport(fileAccessData));
+
+ ///
+ /// Reports process data to the in-proc node.
+ ///
+ /// The process data to report to the in-proc node.
+ /// The id of the reporting out-of-proc node.
+ public void ReportProcess(ProcessData processData, int nodeId) => _sendPacket(new ProcessReport(processData));
+
+ public FileAccessManager.HandlerRegistration RegisterHandlers(
+ Action fileAccessHandler,
+ Action processHandler) =>
+ throw new NotImplementedException("This method should not be called in OOP nodes.");
+
+ public void WaitForFileAccessReportCompletion(int globalRequestId, CancellationToken cancellationToken) =>
+ throw new NotImplementedException("This method should not be called in OOP nodes.");
+ }
+}
+#endif
diff --git a/src/Build/BackEnd/Components/FileAccesses/ProcessReport.cs b/src/Build/BackEnd/Components/FileAccesses/ProcessReport.cs
new file mode 100644
index 00000000000..89bf533ed86
--- /dev/null
+++ b/src/Build/BackEnd/Components/FileAccesses/ProcessReport.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Build.BackEnd;
+using Microsoft.Build.Framework.FileAccess;
+
+namespace Microsoft.Build.FileAccesses
+{
+ internal sealed class ProcessReport : INodePacket
+ {
+ private ProcessData _processData;
+
+ internal ProcessReport(ProcessData processData) => _processData = processData;
+
+ private ProcessReport(ITranslator translator) => Translate(translator);
+
+ ///
+ public NodePacketType Type => NodePacketType.ProcessReport;
+
+ internal ProcessData ProcessData => _processData;
+
+ internal static INodePacket FactoryForDeserialization(ITranslator translator) => new ProcessReport(translator);
+
+ ///
+ public void Translate(ITranslator translator) => translator.Translate(ref _processData);
+ }
+}
diff --git a/src/Build/BackEnd/Components/IBuildComponentHost.cs b/src/Build/BackEnd/Components/IBuildComponentHost.cs
index bcbc7eac430..5ae9d947906 100644
--- a/src/Build/BackEnd/Components/IBuildComponentHost.cs
+++ b/src/Build/BackEnd/Components/IBuildComponentHost.cs
@@ -130,6 +130,18 @@ internal enum BuildComponentType
/// The SDK resolution service.
///
SdkResolverService,
+
+#if FEATURE_REPORTFILEACCESSES
+ ///
+ /// The component which is the sink for file access reports and forwards reports to other components.
+ ///
+ FileAccessManager,
+#endif
+
+ ///
+ /// The component which launches new MSBuild nodes.
+ ///
+ NodeLauncher,
}
///
diff --git a/src/Build/BackEnd/Components/ProjectCache/FileAccessContext.cs b/src/Build/BackEnd/Components/ProjectCache/FileAccessContext.cs
new file mode 100644
index 00000000000..5ff62cfb514
--- /dev/null
+++ b/src/Build/BackEnd/Components/ProjectCache/FileAccessContext.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Build.Experimental.ProjectCache
+{
+ public readonly struct FileAccessContext
+ {
+ public FileAccessContext(
+ string projectFullPath,
+ IReadOnlyDictionary globalProperties,
+ IReadOnlyList targets)
+ {
+ ProjectFullPath = projectFullPath;
+ GlobalProperties = globalProperties;
+ Targets = targets;
+ }
+
+ public string ProjectFullPath { get; }
+
+ public IReadOnlyDictionary GlobalProperties { get; }
+
+ public IReadOnlyList Targets { get; }
+ }
+}
diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCachePluginBase.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCachePluginBase.cs
index 2bf479c6055..eb55d482ba0 100644
--- a/src/Build/BackEnd/Components/ProjectCache/ProjectCachePluginBase.cs
+++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCachePluginBase.cs
@@ -1,9 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Execution;
+using Microsoft.Build.Framework.FileAccess;
namespace Microsoft.Build.Experimental.ProjectCache
{
@@ -39,5 +41,32 @@ public abstract Task GetCacheResultAsync(
/// Errors are checked via .
///
public abstract Task EndBuildAsync(PluginLoggerBase logger, CancellationToken cancellationToken);
+
+ ///
+ /// Called for each file access from an MSBuild node or one of its children.
+ ///
+ [CLSCompliant(false)]
+ public virtual void HandleFileAccess(FileAccessContext fileAccessContext, FileAccessData fileAccessData)
+ {
+ }
+
+ ///
+ /// Called for each new child process created by an MSBuild node or one of its children.
+ ///
+ [CLSCompliant(false)]
+ public virtual void HandleProcess(FileAccessContext fileAccessContext, ProcessData processData)
+ {
+ }
+
+ ///
+ /// Called when a build request finishes execution. This provides an opportunity for the plugin to take action on the
+ /// aggregated file access reports from .
+ /// Errors are checked via .
+ ///
+ public virtual Task HandleProjectFinishedAsync(
+ FileAccessContext fileAccessContext,
+ BuildResult buildResult,
+ PluginLoggerBase logger,
+ CancellationToken cancellationToken) => Task.CompletedTask;
}
}
diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs
index e34e70cca44..0960e200e33 100644
--- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs
+++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs
@@ -16,6 +16,7 @@
using Microsoft.Build.Construction;
using Microsoft.Build.Eventing;
using Microsoft.Build.Execution;
+using Microsoft.Build.FileAccesses;
using Microsoft.Build.FileSystem;
using Microsoft.Build.Framework;
using Microsoft.Build.Graph;
@@ -33,17 +34,31 @@ internal sealed class ProjectCacheService : IAsyncDisposable
private static HashSet s_projectSpecificPropertyNames = new(StringComparer.OrdinalIgnoreCase) { "TargetFramework", "Configuration", "Platform", "TargetPlatform", "OutputType" };
private readonly BuildManager _buildManager;
+ private readonly IBuildComponentHost _componentHost;
private readonly ILoggingService _loggingService;
+#if FEATURE_REPORTFILEACCESSES
+ private readonly IFileAccessManager _fileAccessManager;
+#endif
+ private readonly IConfigCache _configCache;
private readonly ProjectCacheDescriptor? _globalProjectCacheDescriptor;
private readonly ConcurrentDictionary>> _projectCachePlugins = new(ProjectCacheDescriptorEqualityComparer.Instance);
+ // Helps to avoid excessive allocation since BuildRequestConfiguration doesn't expose global properties in a way the plugins can consume (PropertyDictionary vs IReadOnlyDictionary).
+ private readonly ConcurrentDictionary> _globalPropertiesPerConfiguration = new();
+
private bool _isVsScenario;
private bool _isDisposed;
- private record struct ProjectCachePlugin(string Name, ProjectCachePluginBase? Instance, ExceptionDispatchInfo? InitializationException = null);
+ private record struct ProjectCachePlugin(
+ string Name,
+ ProjectCachePluginBase? Instance,
+#if FEATURE_REPORTFILEACCESSES
+ FileAccessManager.HandlerRegistration? HandlerRegistration,
+#endif
+ ExceptionDispatchInfo? InitializationException = null);
///
/// An instanatiable version of MSBuildFileSystemBase not overriding any methods,
@@ -61,10 +76,19 @@ private DefaultMSBuildFileSystem()
public ProjectCacheService(
BuildManager buildManager,
ILoggingService loggingService,
+#if FEATURE_REPORTFILEACCESSES
+ IFileAccessManager fileAccessManager,
+#endif
+ IConfigCache configCache,
ProjectCacheDescriptor? globalProjectCacheDescriptor)
{
_buildManager = buildManager;
+ _componentHost = buildManager;
_loggingService = loggingService;
+#if FEATURE_REPORTFILEACCESSES
+ _fileAccessManager = fileAccessManager;
+#endif
+ _configCache = configCache;
_globalProjectCacheDescriptor = globalProjectCacheDescriptor;
}
@@ -187,7 +211,13 @@ private async Task CreateAndInitializePluginAsync(
}
catch (Exception e)
{
- return new ProjectCachePlugin(pluginTypeName, Instance: null, ExceptionDispatchInfo.Capture(e));
+ return new ProjectCachePlugin(
+ pluginTypeName,
+ Instance: null,
+#if FEATURE_REPORTFILEACCESSES
+ HandlerRegistration: null,
+#endif
+ ExceptionDispatchInfo.Capture(e));
}
finally
{
@@ -218,11 +248,43 @@ await pluginInstance.BeginBuildAsync(
ProjectCacheException.ThrowForErrorLoggedInsideTheProjectCache("ProjectCacheInitializationFailed");
}
- return new ProjectCachePlugin(pluginTypeName, pluginInstance);
+#if FEATURE_REPORTFILEACCESSES
+ FileAccessManager.HandlerRegistration? handlerRegistration = null;
+ if (_componentHost.BuildParameters.ReportFileAccesses)
+ {
+ handlerRegistration = _fileAccessManager.RegisterHandlers(
+ (buildRequest, fileAccessData) =>
+ {
+ // TODO: Filter out projects which do not configure this plugin
+ FileAccessContext fileAccessContext = GetFileAccessContext(buildRequest);
+ pluginInstance.HandleFileAccess(fileAccessContext, fileAccessData);
+ },
+ (buildRequest, processData) =>
+ {
+ // TODO: Filter out projects which do not configure this plugin
+ FileAccessContext fileAccessContext = GetFileAccessContext(buildRequest);
+ pluginInstance.HandleProcess(fileAccessContext, processData);
+ });
+ }
+#endif
+
+ return new ProjectCachePlugin(
+ pluginTypeName,
+ pluginInstance,
+#if FEATURE_REPORTFILEACCESSES
+ handlerRegistration,
+#endif
+ InitializationException: null);
}
catch (Exception e)
{
- return new ProjectCachePlugin(pluginTypeName, Instance: null, ExceptionDispatchInfo.Capture(e));
+ return new ProjectCachePlugin(
+ pluginTypeName,
+ Instance: null,
+#if FEATURE_REPORTFILEACCESSES
+ HandlerRegistration: null,
+#endif
+ ExceptionDispatchInfo.Capture(e));
}
finally
{
@@ -230,6 +292,27 @@ await pluginInstance.BeginBuildAsync(
}
}
+ private FileAccessContext GetFileAccessContext(BuildRequest buildRequest)
+ {
+ BuildRequestConfiguration configuration = _configCache[buildRequest.ConfigurationId];
+ IReadOnlyDictionary globalProperties = GetGlobalProperties(configuration);
+ return new FileAccessContext(configuration.ProjectFullPath, globalProperties, buildRequest.Targets);
+ }
+
+ private IReadOnlyDictionary GetGlobalProperties(BuildRequestConfiguration configuration)
+ => _globalPropertiesPerConfiguration.GetOrAdd(
+ configuration,
+ static configuration =>
+ {
+ Dictionary globalProperties = new(configuration.GlobalProperties.Count, StringComparer.OrdinalIgnoreCase);
+ foreach (ProjectPropertyInstance property in configuration.GlobalProperties)
+ {
+ globalProperties.Add(property.Name, property.EvaluatedValue);
+ }
+
+ return globalProperties;
+ });
+
private static ProjectCachePluginBase GetPluginInstanceFromType(Type pluginType)
{
try
@@ -306,6 +389,12 @@ public bool ShouldUseCache(BuildRequestConfiguration buildRequestConfiguration)
return false;
}
+ // We need to retrieve the configuration if it's already loaded in order to access the Project property below.
+ if (buildRequestConfiguration.IsCached)
+ {
+ buildRequestConfiguration.RetrieveFromCache();
+ }
+
// Check if there are any project cache items defined in the project
return GetProjectCacheDescriptors(buildRequestConfiguration.Project).Any();
}
@@ -587,6 +676,98 @@ static IReadOnlyCollection GenerateGraphEntryPointsFromS
}
}
+ public async Task HandleBuildResultAsync(
+ BuildRequestConfiguration requestConfiguration,
+ BuildResult buildResult,
+ BuildEventContext buildEventContext,
+ CancellationToken cancellationToken)
+ {
+ ErrorUtilities.VerifyThrowInternalNull(requestConfiguration.Project, nameof(requestConfiguration.Project));
+
+ if (_projectCachePlugins.IsEmpty)
+ {
+ return;
+ }
+
+ // We need to retrieve the configuration if it's already loaded in order to access the Project property below.
+ if (requestConfiguration.IsCached)
+ {
+ requestConfiguration.RetrieveFromCache();
+ }
+
+ // Filter to plugins which apply to the project, if any
+ List projectCacheDescriptors = GetProjectCacheDescriptors(requestConfiguration.Project).ToList();
+ if (projectCacheDescriptors.Count == 0)
+ {
+ return;
+ }
+
+#if FEATURE_REPORTFILEACCESSES
+ if (_componentHost.BuildParameters.ReportFileAccesses)
+ {
+ _fileAccessManager.WaitForFileAccessReportCompletion(buildResult.GlobalRequestId, cancellationToken);
+ }
+#endif
+
+ IReadOnlyDictionary globalProperties = GetGlobalProperties(requestConfiguration);
+
+ List targets = buildResult.ResultsByTarget.Keys.ToList();
+ string? targetNames = string.Join(", ", targets);
+
+ FileAccessContext fileAccessContext = new(requestConfiguration.ProjectFullPath, globalProperties, targets);
+
+ var buildEventFileInfo = new BuildEventFileInfo(requestConfiguration.ProjectFullPath);
+ var pluginLogger = new LoggingServiceToPluginLoggerAdapter(
+ _loggingService,
+ buildEventContext,
+ buildEventFileInfo);
+
+ Task[] tasks = new Task[projectCacheDescriptors.Count];
+ int idx = 0;
+ foreach (ProjectCacheDescriptor projectCacheDescriptor in projectCacheDescriptors)
+ {
+ tasks[idx++] = Task.Run(
+ async () =>
+ {
+ if (!_projectCachePlugins.TryGetValue(projectCacheDescriptor, out Lazy>? pluginLazyTask))
+ {
+ // The plugin might not be in the collection if it was never initialized, which can happen if there are multiple plugins
+ // and the first one(s) always handles the cache request so the subsequent one(s) never get lazy initialized.
+ return;
+ }
+
+ ProjectCachePlugin plugin = await pluginLazyTask.Value;
+
+ // Rethrow any initialization exception.
+ plugin.InitializationException?.Throw();
+
+ ErrorUtilities.VerifyThrow(plugin.Instance != null, "Plugin '{0}' instance is null", plugin.Name);
+
+ MSBuildEventSource.Log.ProjectCacheHandleBuildResultStart(plugin.Name, fileAccessContext.ProjectFullPath, targetNames);
+ try
+ {
+ await plugin.Instance!.HandleProjectFinishedAsync(fileAccessContext, buildResult, pluginLogger, cancellationToken);
+ }
+ catch (Exception e) when (e is not ProjectCacheException)
+ {
+ HandlePluginException(e, nameof(ProjectCachePluginBase.HandleProjectFinishedAsync));
+ }
+ finally
+ {
+ MSBuildEventSource.Log.ProjectCacheHandleBuildResultStop(plugin.Name, fileAccessContext.ProjectFullPath, targetNames);
+ }
+ },
+ cancellationToken);
+ }
+
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+
+ if (pluginLogger.HasLoggedErrors)
+ {
+ ProjectCacheException.ThrowForErrorLoggedInsideTheProjectCache("ProjectCacheHandleBuildResultFailed", fileAccessContext.ProjectFullPath);
+ }
+ }
+
public async ValueTask DisposeAsync()
{
if (_isDisposed)
@@ -624,6 +805,13 @@ public async ValueTask DisposeAsync()
return;
}
+#if FEATURE_REPORTFILEACCESSES
+ if (plugin.HandlerRegistration.HasValue)
+ {
+ plugin.HandlerRegistration.Value.Dispose();
+ }
+#endif
+
MSBuildEventSource.Log.ProjectCacheEndBuildStart(plugin.Name);
try
{
diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
index 939370ab515..0d67e69b661 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
@@ -6,22 +6,24 @@
using System.Collections.Generic;
using System.Globalization;
#if FEATURE_APPDOMAIN
-using System.Runtime.Remoting.Lifetime;
using System.Runtime.Remoting;
+using System.Runtime.Remoting.Lifetime;
#endif
+using System.Diagnostics;
+using System.Reflection;
using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Build.BackEnd.Components.Caching;
+using Microsoft.Build.Collections;
+using Microsoft.Build.Eventing;
+using Microsoft.Build.Execution;
+using Microsoft.Build.FileAccesses;
using Microsoft.Build.Framework;
+using Microsoft.Build.Framework.FileAccess;
using Microsoft.Build.Shared;
-using Microsoft.Build.Execution;
-using System.Diagnostics;
-using Microsoft.Build.Collections;
using ElementLocation = Microsoft.Build.Construction.ElementLocation;
using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem;
using TaskLoggingContext = Microsoft.Build.BackEnd.Logging.TaskLoggingContext;
-using System.Threading.Tasks;
-using Microsoft.Build.BackEnd.Components.Caching;
-using System.Reflection;
-using Microsoft.Build.Eventing;
#nullable disable
@@ -343,6 +345,14 @@ public BuildEngineResult BuildProjectFilesInParallel(string[] projectFileNames,
///
public void Yield()
{
+#if FEATURE_REPORTFILEACCESSES
+ // If file accesses are being reported we should not yield as file access will be attributed to the wrong project.
+ if (_host.BuildParameters.ReportFileAccesses)
+ {
+ return;
+ }
+#endif
+
lock (_callbackMonitor)
{
IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback;
@@ -364,6 +374,14 @@ public void Reacquire()
// to release explicitly granted cores when reacquiring the node may lead to deadlocks.
ReleaseAllCores();
+#if FEATURE_REPORTFILEACCESSES
+ // If file accesses are being reported yielding is a no-op so reacquire should be too.
+ if (_host.BuildParameters.ReportFileAccesses)
+ {
+ return;
+ }
+#endif
+
lock (_callbackMonitor)
{
IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback;
@@ -377,7 +395,7 @@ public void Reacquire()
}
}
- #endregion
+#endregion
#region IBuildEngine Members
@@ -920,11 +938,23 @@ public override bool LogsMessagesOfImportance(MessageImportance importance)
///
public override bool IsTaskInputLoggingEnabled => _taskHost._host.BuildParameters.LogTaskInputs;
+
+ ///
+ public override void ReportFileAccess(FileAccessData fileAccessData)
+ {
+#if FEATURE_REPORTFILEACCESSES
+ IBuildComponentHost buildComponentHost = _taskHost._host;
+ if (buildComponentHost.BuildParameters.ReportFileAccesses)
+ {
+ ((IFileAccessManager)buildComponentHost.GetComponent(BuildComponentType.FileAccessManager)).ReportFileAccess(fileAccessData, buildComponentHost.BuildParameters.NodeId);
+ }
+#endif
+ }
}
public EngineServices EngineServices { get; }
- #endregion
+#endregion
///
/// Called by the internal MSBuild task.
diff --git a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs
index d66e50d62c1..84e22a9c67c 100644
--- a/src/Build/BackEnd/Components/Scheduler/IScheduler.cs
+++ b/src/Build/BackEnd/Components/Scheduler/IScheduler.cs
@@ -33,6 +33,11 @@ internal interface IScheduler : IBuildComponent
/// A positive configuration id if one exists in the plan, 0 otherwise.
int GetConfigurationIdFromPlan(string configurationPath);
+ ///
+ /// Retrieves the request executing on a node.
+ ///
+ BuildRequest GetExecutingRequestByNode(int nodeId);
+
///
/// Reports to the scheduler that a request is blocked.
///
diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs
index 53bf46ec2f3..6208aa883e4 100644
--- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs
+++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs
@@ -283,6 +283,20 @@ public int GetConfigurationIdFromPlan(string configPath)
return _schedulingPlan.GetConfigIdForPath(configPath);
}
+ ///
+ /// Retrieves the request executing on a node.
+ ///
+ public BuildRequest GetExecutingRequestByNode(int nodeId)
+ {
+ if (!_schedulingData.IsNodeWorking(nodeId))
+ {
+ return null;
+ }
+
+ SchedulableRequest request = _schedulingData.GetExecutingRequestByNode(nodeId);
+ return request.BuildRequest;
+ }
+
///
/// Reports that the specified request has become blocked and cannot proceed.
///
diff --git a/src/Build/BackEnd/Node/OutOfProcNode.cs b/src/Build/BackEnd/Node/OutOfProcNode.cs
index 619c476a1f6..c5d8282d5bb 100644
--- a/src/Build/BackEnd/Node/OutOfProcNode.cs
+++ b/src/Build/BackEnd/Node/OutOfProcNode.cs
@@ -14,6 +14,7 @@
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.BackEnd.SdkResolution;
using Microsoft.Build.Evaluation;
+using Microsoft.Build.FileAccesses;
using Microsoft.Build.Framework;
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
@@ -153,11 +154,15 @@ public OutOfProcNode()
// Create a factory for the out-of-proc SDK resolver service which can pass our SendPacket delegate to be used for sending packets to the main node
OutOfProcNodeSdkResolverServiceFactory sdkResolverServiceFactory = new OutOfProcNodeSdkResolverServiceFactory(SendPacket);
-
((IBuildComponentHost)this).RegisterFactory(BuildComponentType.SdkResolverService, sdkResolverServiceFactory.CreateInstance);
-
_sdkResolverService = (this as IBuildComponentHost).GetComponent(BuildComponentType.SdkResolverService) as ISdkResolverService;
+#if FEATURE_REPORTFILEACCESSES
+ ((IBuildComponentHost)this).RegisterFactory(
+ BuildComponentType.FileAccessManager,
+ (componentType) => OutOfProcNodeFileAccessManager.CreateComponent(componentType, SendPacket));
+#endif
+
if (s_projectRootElementCacheBase == null)
{
s_projectRootElementCacheBase = new ProjectRootElementCache(true /* automatically reload any changes from disk */);
@@ -369,6 +374,13 @@ private void OnRequestComplete(BuildRequest request, BuildResult result)
{
_nodeEndpoint.SendData(result);
}
+
+#if FEATURE_REPORTFILEACCESSES
+ if (_buildParameters.ReportFileAccesses)
+ {
+ FileAccessManager.NotifyFileAccessCompletion(result.GlobalRequestId);
+ }
+#endif
}
///
diff --git a/src/Build/Collections/RetrievableEntryHashSet/HashSet.cs b/src/Build/Collections/RetrievableEntryHashSet/HashSet.cs
index 8380f9cd3b9..fb954982e34 100644
--- a/src/Build/Collections/RetrievableEntryHashSet/HashSet.cs
+++ b/src/Build/Collections/RetrievableEntryHashSet/HashSet.cs
@@ -32,6 +32,11 @@
#nullable disable
+// The BuildXL package causes an indirect dependency on the RuntimeContracts package, which adds an analyzer which forbids the use of System.Diagnostics.Contract.
+// So effectively if your dependencies use RuntimeContracts, it attempts to force itself on your as well.
+// See: https://github.com/SergeyTeplyakov/RuntimeContracts/issues/12
+#pragma warning disable RA001 // Do not use System.Diagnostics.Contract class.
+
namespace Microsoft.Build.Collections
{
///
diff --git a/src/Build/Instance/TaskFactories/TaskHostTask.cs b/src/Build/Instance/TaskFactories/TaskHostTask.cs
index 0b874696321..4fb2fe61f8d 100644
--- a/src/Build/Instance/TaskFactories/TaskHostTask.cs
+++ b/src/Build/Instance/TaskFactories/TaskHostTask.cs
@@ -9,7 +9,9 @@
using System.Threading;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Exceptions;
+using Microsoft.Build.FileAccesses;
using Microsoft.Build.Framework;
+using Microsoft.Build.Framework.FileAccess;
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
@@ -433,6 +435,17 @@ private void HandlePacket(INodePacket packet, out bool taskFinished)
///
private void HandleTaskHostTaskComplete(TaskHostTaskComplete taskHostTaskComplete)
{
+#if FEATURE_REPORTFILEACCESSES
+ if (taskHostTaskComplete.FileAccessData.Count > 0)
+ {
+ IFileAccessManager fileAccessManager = ((IFileAccessManager)_buildComponentHost.GetComponent(BuildComponentType.FileAccessManager));
+ foreach (FileAccessData fileAccessData in taskHostTaskComplete.FileAccessData)
+ {
+ fileAccessManager.ReportFileAccess(fileAccessData, _buildComponentHost.BuildParameters.NodeId);
+ }
+ }
+#endif
+
// If it crashed, or if it failed, it didn't succeed.
_taskExecutionSucceeded = taskHostTaskComplete.TaskResult == TaskCompleteType.Success ? true : false;
diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj
index 39a953f2948..9a5b1175b0e 100644
--- a/src/Build/Microsoft.Build.csproj
+++ b/src/Build/Microsoft.Build.csproj
@@ -1,4 +1,4 @@
-
+
@@ -32,12 +32,15 @@
+
+
+
@@ -51,7 +54,6 @@
-
@@ -152,8 +154,14 @@
+
+
+
+
+
+
@@ -361,6 +369,7 @@
+
diff --git a/src/Build/Microsoft.Build.pkgdef b/src/Build/Microsoft.Build.pkgdef
index 74546cfe4fe..5ade779232c 100644
--- a/src/Build/Microsoft.Build.pkgdef
+++ b/src/Build/Microsoft.Build.pkgdef
@@ -5,3 +5,27 @@
"culture"="neutral"
"oldVersion"="0.0.0.0-99.9.9.9"
"newVersion"="15.1.0.0"
+
+[$RootKey$\RuntimeConfiguration\dependentAssembly\bindingRedirection\{F74A7C60-AC4A-4EC4-A8DB-1FE89FDB53CD}]
+"name"="BuildXL.Processes"
+"codeBase"="$BaseInstallDir$\MSBuild\Current\Bin\BuildXL.Processes.dll"
+"publicKeyToken"="6212d9137135ce5d"
+"culture"="neutral"
+"oldVersion"="0.0.0.0-1.0.0.0"
+"newVersion"="1.0.0.0"
+
+[$RootKey$\RuntimeConfiguration\dependentAssembly\bindingRedirection\{A038F286-A634-460D-9964-75465129EEF2}]
+"name"="BuildXL.Utilities.Core"
+"codeBase"="$BaseInstallDir$\MSBuild\Current\Bin\BuildXL.Utilities.Core.dll"
+"publicKeyToken"="6212d9137135ce5d"
+"culture"="neutral"
+"oldVersion"="0.0.0.0-1.0.0.0"
+"newVersion"="1.0.0.0"
+
+[$RootKey$\RuntimeConfiguration\dependentAssembly\bindingRedirection\{0EE5D593-1F73-4FA2-98D7-B347DFD50186}]
+"name"="BuildXL.Native"
+"codeBase"="$BaseInstallDir$\MSBuild\Current\Bin\BuildXL.Native.dll"
+"publicKeyToken"="6212d9137135ce5d"
+"culture"="neutral"
+"oldVersion"="0.0.0.0-1.0.0.0"
+"newVersion"="1.0.0.0"
diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx
index 3a046f4d554..77ba4bf1928 100644
--- a/src/Build/Resources/Strings.resx
+++ b/src/Build/Resources/Strings.resx
@@ -1906,6 +1906,9 @@ Utilization: {0} Average Utilization: {1:###.0}
MSB4268: The project cache failed to shut down properly.
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
MSB4270: No project cache plugins found in assembly "{0}". Expected one.
diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf
index 0fb219fdd76..58322b4d71c 100644
--- a/src/Build/Resources/xlf/Strings.cs.xlf
+++ b/src/Build/Resources/xlf/Strings.cs.xlf
@@ -264,6 +264,11 @@
MSB4273: Mezipaměť projektu vyvolala neošetřenou výjimku z metody {0}.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ Přístup do mezipaměti projektu pro „{0}“ (výchozí cíle).
diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf
index 34f2984b55a..9df8c02b4e4 100644
--- a/src/Build/Resources/xlf/Strings.de.xlf
+++ b/src/Build/Resources/xlf/Strings.de.xlf
@@ -264,6 +264,11 @@
MSB4273: Der Projektcache hat über die Methode {0} eine unbehandelte Ausnahme ausgelöst.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ Projektcachetreffer für „{0}“ (Standardziele).
diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf
index ad3fa5bce70..6f6a8accdd3 100644
--- a/src/Build/Resources/xlf/Strings.es.xlf
+++ b/src/Build/Resources/xlf/Strings.es.xlf
@@ -264,6 +264,11 @@
MSB4273: la caché del proyecto inició una excepción no controlada desde el método {0}.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ Acierto de caché de proyecto para "{0}" (destinos predeterminados).
diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf
index 62958ed7308..fb485ad66d5 100644
--- a/src/Build/Resources/xlf/Strings.fr.xlf
+++ b/src/Build/Resources/xlf/Strings.fr.xlf
@@ -264,6 +264,11 @@
MSB4273: le cache de projet a levé une exception non gérée à partir de la méthode {0}.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ Le cache de projet a été atteint pour « {0} » (cibles par défaut).
diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf
index b78569055ff..90e465e9b9c 100644
--- a/src/Build/Resources/xlf/Strings.it.xlf
+++ b/src/Build/Resources/xlf/Strings.it.xlf
@@ -264,6 +264,11 @@
MSB4273: la cache del progetto ha generato un'eccezione non gestita dal metodo {0}.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ Riscontro nella cache del progetto per "{0}" (destinazioni predefinite).
diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf
index 6b1cdb3452c..b5bd9c4b976 100644
--- a/src/Build/Resources/xlf/Strings.ja.xlf
+++ b/src/Build/Resources/xlf/Strings.ja.xlf
@@ -264,6 +264,11 @@
MSB4273: プロジェクト キャッシュが {0} メソッドで処理されていない例外が返されました。
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ "{0}" のプロジェクト キャッシュ ヒット (既定のターゲット)。
diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf
index 76064b46602..0119bf3cf53 100644
--- a/src/Build/Resources/xlf/Strings.ko.xlf
+++ b/src/Build/Resources/xlf/Strings.ko.xlf
@@ -264,6 +264,11 @@
MSB4273: 프로젝트 캐시는 {0} 메서드에서 처리되지 않은 예외를 발생시켰습니다.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ "{0}"(기본 대상)에 대한 프로젝트 캐시 적중입니다.
diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf
index a4ea3f9b1d4..0a42102a49e 100644
--- a/src/Build/Resources/xlf/Strings.pl.xlf
+++ b/src/Build/Resources/xlf/Strings.pl.xlf
@@ -264,6 +264,11 @@
MSB4273: pamięć podręczna projektu zgłosiła nieobsługiwany wyjątek z metody {0}.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ Trafienie pamięci podręcznej projektu dla „{0}” (domyślne elementy docelowe).
diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf
index 6ca0dd891e9..68b22a9abcb 100644
--- a/src/Build/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf
@@ -264,6 +264,11 @@
MSB4273: O cache do projeto lançou uma exceção sem tratamento do método {0}.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ Acerto de cache do projeto para "{0}" (destinos padrão).
diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf
index d54d3bca5a4..5c11764c3ca 100644
--- a/src/Build/Resources/xlf/Strings.ru.xlf
+++ b/src/Build/Resources/xlf/Strings.ru.xlf
@@ -264,6 +264,11 @@
MSB4273: в кэше проектов возникло необработанное исключение из метода {0}.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ Попадание в кэше проекта для "{0}" (целевые объекты по умолчанию).
diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf
index 2d211d40f45..199be842787 100644
--- a/src/Build/Resources/xlf/Strings.tr.xlf
+++ b/src/Build/Resources/xlf/Strings.tr.xlf
@@ -264,6 +264,11 @@
MSB4273: Proje önbelleği {0} yönteminden yakalanamayan özel durum oluşturdu.
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ "{0}" (varsayılan hedefler) için proje önbelleği isabeti.
diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
index 1d2c6843628..78cef9bde28 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
@@ -264,6 +264,11 @@
MSB4273: 项目缓存从 {0} 方法引发了未经处理的异常。
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ 项目缓存命中 "{0}" (默认目标)。
diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
index c073146888d..da21593b766 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
@@ -264,6 +264,11 @@
MSB4273: 專案快取從 {0} 方法擲回未處理的例外狀況。
+
+
+ MSB4269: The project cache failed while handling a build result for the following project: {0}.
+
+ "{0}" 的專案快取命中 (預設目標)。
diff --git a/src/Directory.BeforeCommon.targets b/src/Directory.BeforeCommon.targets
index d136a2607c9..0352fbf056b 100644
--- a/src/Directory.BeforeCommon.targets
+++ b/src/Directory.BeforeCommon.targets
@@ -117,4 +117,9 @@
true
+
+ $(DefineConstants);FEATURE_REPORTFILEACCESSES
+ true
+
+
diff --git a/src/Framework/BinaryTranslator.cs b/src/Framework/BinaryTranslator.cs
index 995cfebfbc7..207390427d6 100644
--- a/src/Framework/BinaryTranslator.cs
+++ b/src/Framework/BinaryTranslator.cs
@@ -9,6 +9,9 @@
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.BuildException;
+#if !CLR2COMPATIBILITY
+using Microsoft.Build.Framework.FileAccess;
+#endif
#nullable disable
@@ -172,6 +175,9 @@ public void Translate(ref int value)
value = _reader.ReadInt32();
}
+ ///
+ public void Translate(ref uint unsignedInteger) => unsignedInteger = _reader.ReadUInt32();
+
///
/// Translates an array.
///
@@ -421,6 +427,81 @@ public void Translate(ref BuildEventContext value)
_reader.ReadInt32());
}
+ ///
+ public void Translate(ref FileAccessData fileAccessData)
+ {
+ ReportedFileOperation reportedFileOperation = default;
+ RequestedAccess requestedAccess = default;
+ uint processId = default;
+ uint error = default;
+ DesiredAccess desiredAccess = default;
+ FlagsAndAttributes flagsAndAttributes = default;
+ string path = default;
+ string processArgs = default;
+ bool isAnAugmentedFileAccess = default;
+ TranslateEnum(ref reportedFileOperation, (int)reportedFileOperation);
+ TranslateEnum(ref requestedAccess, (int)requestedAccess);
+ Translate(ref processId);
+ Translate(ref error);
+ TranslateEnum(ref desiredAccess, (int)desiredAccess);
+ TranslateEnum(ref flagsAndAttributes, (int)flagsAndAttributes);
+ Translate(ref path);
+ Translate(ref processArgs);
+ Translate(ref isAnAugmentedFileAccess);
+ fileAccessData = new FileAccessData(
+ reportedFileOperation,
+ requestedAccess,
+ processId,
+ error,
+ desiredAccess,
+ flagsAndAttributes,
+ path,
+ processArgs,
+ isAnAugmentedFileAccess);
+ }
+
+ ///
+ public void Translate(ref List fileAccessDataList)
+ {
+ if (!TranslateNullable(fileAccessDataList))
+ {
+ return;
+ }
+
+ int count = default;
+ Translate(ref count);
+ fileAccessDataList = new List(count);
+ for (int i = 0; i < count; i++)
+ {
+ FileAccessData fileAccessData = default;
+ Translate(ref fileAccessData);
+ fileAccessDataList.Add(fileAccessData);
+ }
+ }
+
+ ///
+ public void Translate(ref ProcessData processData)
+ {
+ string processName = default;
+ uint processId = default;
+ uint parentProcessId = default;
+ DateTime creationDateTime = default;
+ DateTime exitDateTime = default;
+ uint exitCode = default;
+ Translate(ref processName);
+ Translate(ref processId);
+ Translate(ref parentProcessId);
+ Translate(ref creationDateTime);
+ Translate(ref exitDateTime);
+ Translate(ref exitCode);
+ processData = new ProcessData(
+ processName,
+ processId,
+ parentProcessId,
+ creationDateTime,
+ exitDateTime,
+ exitCode);
+ }
#endif
///
@@ -884,6 +965,9 @@ public void Translate(ref int value)
_writer.Write(value);
}
+ ///
+ public void Translate(ref uint unsignedInteger) => _writer.Write(unsignedInteger);
+
///
/// Translates an array.
///
@@ -1109,6 +1193,58 @@ public void Translate(ref BuildEventContext value)
_writer.Write(value.TaskId);
}
+ ///
+ public void Translate(ref FileAccessData fileAccessData)
+ {
+ ReportedFileOperation reportedFileOperation = fileAccessData.Operation;
+ RequestedAccess requestedAccess = fileAccessData.RequestedAccess;
+ uint processId = fileAccessData.ProcessId;
+ uint error = fileAccessData.Error;
+ DesiredAccess desiredAccess = fileAccessData.DesiredAccess;
+ FlagsAndAttributes flagsAndAttributes = fileAccessData.FlagsAndAttributes;
+ string path = fileAccessData.Path;
+ string processArgs = fileAccessData.ProcessArgs;
+ bool isAnAugmentedFileAccess = fileAccessData.IsAnAugmentedFileAccess;
+ TranslateEnum(ref reportedFileOperation, (int)reportedFileOperation);
+ TranslateEnum(ref requestedAccess, (int)requestedAccess);
+ Translate(ref processId);
+ Translate(ref error);
+ TranslateEnum(ref desiredAccess, (int)desiredAccess);
+ TranslateEnum(ref flagsAndAttributes, (int)flagsAndAttributes);
+ Translate(ref path);
+ Translate(ref processArgs);
+ Translate(ref isAnAugmentedFileAccess);
+ }
+
+ ///
+ public void Translate(ref List fileAccessDataList)
+ {
+ if (!TranslateNullable(fileAccessDataList))
+ {
+ return;
+ }
+
+ int count = fileAccessDataList.Count;
+ Translate(ref count);
+ fileAccessDataList.ForEach(fileAccessData => Translate(ref fileAccessData));
+ }
+
+ ///
+ public void Translate(ref ProcessData processData)
+ {
+ string processName = processData.ProcessName;
+ uint processId = processData.ProcessId;
+ uint parentProcessId = processData.ParentProcessId;
+ DateTime creationDateTime = processData.CreationDateTime;
+ DateTime exitDateTime = processData.ExitDateTime;
+ uint exitCode = processData.ExitCode;
+ Translate(ref processName);
+ Translate(ref processId);
+ Translate(ref parentProcessId);
+ Translate(ref creationDateTime);
+ Translate(ref exitDateTime);
+ Translate(ref exitCode);
+ }
#endif
///
diff --git a/src/Framework/EngineServices.cs b/src/Framework/EngineServices.cs
index 271bc7d33c7..853663d4e5b 100644
--- a/src/Framework/EngineServices.cs
+++ b/src/Framework/EngineServices.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using Microsoft.Build.Framework.FileAccess;
namespace Microsoft.Build.Framework
{
@@ -21,10 +22,18 @@ public abstract class EngineServices
public const int Version1 = 1;
///
- /// An explicit version of this class. Must be incremented whenever new members are added. Derived classes should override
- /// the property to return the version actually being implemented.
+ /// Includes .
///
- public virtual int Version => Version1; // Not updated since we have not shipped 17.0 yet. This comment is meant to bypass RequiredVersionBumps check in build.ps1 for PR #470646. If the changes in the file are cosmetic, change PR# in this comment to silence the build error on CI build.
+ public const int Version2 = 2;
+
+ ///
+ /// Gets an explicit version of this class.
+ ///
+ ///
+ /// Must be incremented whenever new members are added. Derived classes should override
+ /// the property to return the version actually being implemented.
+ ///
+ public virtual int Version => Version2;
///
/// Returns if the given message importance is not guaranteed to be ignored by registered loggers.
@@ -45,5 +54,12 @@ public abstract class EngineServices
/// This is a performance optimization allowing tasks to skip expensive double-logging.
///
public virtual bool IsTaskInputLoggingEnabled => throw new NotImplementedException();
+
+ ///
+ /// Reports a file access from a task.
+ ///
+ /// The file access to report.
+ [CLSCompliant(false)]
+ public virtual void ReportFileAccess(FileAccessData fileAccessData) => throw new NotImplementedException();
}
}
diff --git a/src/Framework/FileAccess/DesiredAccess.cs b/src/Framework/FileAccess/DesiredAccess.cs
new file mode 100644
index 00000000000..056c74d6f19
--- /dev/null
+++ b/src/Framework/FileAccess/DesiredAccess.cs
@@ -0,0 +1,112 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Build.Framework.FileAccess
+{
+ /*
+ * Implementation note: This is a copy of BuildXL.Processes.DesiredAccess.
+ * The purpose of the copy is because this is part of the public MSBuild API and it's not desirable to
+ * expose BuildXL types directly.
+ */
+
+ ///
+ /// The requested access to the file or device.
+ ///
+ ///
+ /// See https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants for a full list of values.
+ ///
+ [Flags]
+ [CLSCompliant(false)]
+ public enum DesiredAccess : uint
+ {
+ ///
+ /// For a directory, the right to list the contents of the directory.
+ ///
+ FILE_LIST_DIRECTORY = 0x00000001,
+
+ ///
+ /// For a directory, the right to create a file in the directory.
+ ///
+ FILE_ADD_FILE = 0x00000002,
+
+ ///
+ /// For a directory, the right to create a subdirectory.
+ ///
+ FILE_ADD_SUBDIRECTORY = 0x00000004,
+
+ ///
+ /// The right to read extended file attributes.
+ ///
+ FILE_READ_EA = 0x00000008,
+
+ ///
+ /// Right to delete an object.
+ ///
+ DELETE = 0x00010000,
+
+ ///
+ /// Right to wait on a handle.
+ ///
+ SYNCHRONIZE = 0x00100000,
+
+ ///
+ /// For a file object, the right to append data to the file. (For local files, write operations will not overwrite existing
+ /// data if this flag is specified without .) For a directory object, the right to create a subdirectory
+ /// ().
+ ///
+ FILE_APPEND_DATA = 0x00000004,
+
+ ///
+ /// The right to write extended file attributes.
+ ///
+ FILE_WRITE_EA = 0x00000010,
+
+ ///
+ /// For a native code file, the right to execute the file. This access right given to scripts may cause the script to be executable, depending on the script interpreter.
+ ///
+ FILE_EXECUTE = 0x00000020,
+
+ ///
+ /// For a directory, the right to delete a directory and all the files it contains, including read-only files.
+ ///
+ FILE_DELETE_CHILD = 0x00000040,
+
+ ///
+ /// The right to read file attributes.
+ ///
+ FILE_READ_ATTRIBUTES = 0x00000080,
+
+ ///
+ /// The right to write file attributes.
+ ///
+ FILE_WRITE_ATTRIBUTES = 0x00000100,
+
+ ///
+ /// For a file object, the right to write data to the file. For a directory object, the right to create a file in the
+ /// directory ().
+ ///
+ FILE_WRITE_DATA = 0x00000002,
+
+ ///
+ /// All possible access rights.
+ ///
+ GENERIC_ALL = 0x10000000,
+
+ ///
+ /// Execute access.
+ ///
+ GENERIC_EXECUTE = 0x20000000,
+
+ ///
+ /// Write access.
+ ///
+ GENERIC_WRITE = 0x40000000,
+
+ ///
+ /// Read access.
+ ///
+ GENERIC_READ = 0x80000000,
+ }
+}
diff --git a/src/Framework/FileAccess/FileAccessData.cs b/src/Framework/FileAccess/FileAccessData.cs
new file mode 100644
index 00000000000..2f95ce9d471
--- /dev/null
+++ b/src/Framework/FileAccess/FileAccessData.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Build.Framework.FileAccess
+{
+ ///
+ /// File access data.
+ ///
+ /// The operation that performed the file access.
+ /// The requested access.
+ /// The process id.
+ /// The error code of the operation.
+ /// The desired access.
+ /// The file flags and attributes.
+ /// The path being accessed.
+ /// The process arguments.
+ /// Whether the file access is augmented.
+ [CLSCompliant(false)]
+ public readonly record struct FileAccessData(
+ ReportedFileOperation Operation,
+ RequestedAccess RequestedAccess,
+ uint ProcessId,
+ uint Error,
+ DesiredAccess DesiredAccess,
+ FlagsAndAttributes FlagsAndAttributes,
+ string Path,
+ string? ProcessArgs,
+ bool IsAnAugmentedFileAccess);
+}
diff --git a/src/Framework/FileAccess/FlagsAndAttributes.cs b/src/Framework/FileAccess/FlagsAndAttributes.cs
new file mode 100644
index 00000000000..8b01f48106f
--- /dev/null
+++ b/src/Framework/FileAccess/FlagsAndAttributes.cs
@@ -0,0 +1,185 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Build.Framework.FileAccess
+{
+ /*
+ * Implementation note: This is a copy of BuildXL.Processes.FlagsAndAttributes.
+ * The purpose of the copy is because this is part of the public MSBuild API and it's not desirable to
+ * expose BuildXL types directly.
+ */
+
+ ///
+ /// The file or device attributes and flags.
+ ///
+ [Flags]
+ [CLSCompliant(false)]
+ public enum FlagsAndAttributes : uint
+ {
+ ///
+ /// The file is read only. Applications can read the file but cannot write to or delete it.
+ ///
+ FILE_ATTRIBUTE_READONLY = 0x00000001,
+
+ ///
+ /// The file is hidden. Do not include it in an ordinary directory listing.
+ ///
+ FILE_ATTRIBUTE_HIDDEN = 0x00000002,
+
+ ///
+ /// The file is part of or used exclusively by an operating system.
+ ///
+ FILE_ATTRIBUTE_SYSTEM = 0x00000004,
+
+ ///
+ /// The path is a directory.
+ ///
+ FILE_ATTRIBUTE_DIRECTORY = 0x00000010,
+
+ ///
+ /// The file should be archived. Applications use this attribute to mark files for backup or removal.
+ ///
+ FILE_ATTRIBUTE_ARCHIVE = 0x00000020,
+
+ ///
+ /// The file does not have other attributes set. This attribute is valid only if used alone.
+ ///
+ FILE_ATTRIBUTE_NORMAL = 0x00000080,
+
+ ///
+ /// The file is being used for temporary storage.
+ ///
+ ///
+ /// For more information, see the Caching Behavior section of this topic.
+ ///
+ FILE_ATTRIBUTE_TEMPORARY = 0x00000100,
+
+ ///
+ /// The data of a file is not immediately available. This attribute indicates that file data is physically moved to offline
+ /// storage. This attribute is used by Remote Storage, the hierarchical storage management software. Applications should
+ /// not arbitrarily change this attribute.
+ ///
+ FILE_ATTRIBUTE_OFFLINE = 0x00001000,
+
+ ///
+ /// The file or directory is encrypted. For a file, this means that all data in the file is encrypted. For a directory,
+ /// this means that encryption is the default for newly created files and subdirectories. For more information, see File
+ /// Encryption.
+ ///
+ ///
+ /// This flag has no effect if is also specified.
+ /// This flag is not supported on Home, Home Premium, Starter, or ARM editions of Windows.
+ ///
+ FILE_ATTRIBUTE_ENCRYPED = 0x00004000,
+
+ ///
+ /// The file data is requested, but it should continue to be located in remote storage. It should not be transported back
+ /// to local storage. This flag is for use by remote storage systems.
+ ///
+ FILE_FLAG_OPEN_NO_RECALL = 0x00100000,
+
+ ///
+ /// Normal reparse point processing will not occur; CreateFile will attempt to open the reparse point. When a file is
+ /// opened, a file handle is returned, whether or not the filter that controls the reparse point is operational.
+ ///
+ ///
+ /// This flag cannot be used with the CREATE_ALWAYS flag.
+ /// If the file is not a reparse point, then this flag is ignored.
+ /// For more information, see the Remarks section.
+ ///
+ FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000,
+
+ ///
+ /// The file or device is being opened with session awareness. If this flag is not specified, then per-session devices
+ /// (such as a redirected USB device) cannot be opened by processes running in session 0. This flag has no effect for
+ /// callers not in session 0. This flag is supported only on server editions of Windows.
+ ///
+ ///
+ /// Windows Server 2008 R2, Windows Server 2008, and Windows Server 2003: This flag is not supported before Windows Server
+ /// 2012.
+ ///
+ FILE_FLAG_SESSION_AWARE = 0x00800000,
+
+ ///
+ /// Access will occur according to POSIX rules. This includes allowing multiple files with names, differing only in case,
+ /// for file systems that support that naming. Use care when using this option, because files created with this flag may
+ /// not be accessible by applications that are written for MS-DOS or 16-bit Windows.
+ ///
+ FILE_FLAG_POSIX_SEMANTICS = 0x01000000,
+
+ ///
+ /// The file is being opened or created for a backup or restore operation. The system ensures that the calling process
+ /// overrides file security checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges. For more
+ /// information, see Changing Privileges in a Token.
+ ///
+ ///
+ /// You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of
+ /// a file handle. For more information, see the Remarks section.
+ ///
+ FILE_FLAG_BACKUP_SEMANTICS = 0x02000000,
+
+ ///
+ /// The file is to be deleted immediately after all of its handles are closed, which includes the specified handle and any
+ /// other open or duplicated handles.
+ ///
+ ///
+ /// If there are existing open handles to a file, the call fails unless they were all opened with the FILE_SHARE_DELETE
+ /// share mode.
+ /// Subsequent open requests for the file fail, unless the FILE_SHARE_DELETE share mode is specified.
+ ///
+ FILE_FLAG_DELETE_ON_CLOSE = 0x04000000,
+
+ ///
+ /// Access is intended to be sequential from beginning to end. The system can use this as a hint to optimize file caching.
+ ///
+ ///
+ /// This flag should not be used if read-behind (that is, reverse scans) will be used.
+ /// This flag has no effect if the file system does not support cached I/O and .
+ /// For more information, see the Caching Behavior section of this topic.
+ ///
+ FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000,
+
+ ///
+ /// Access is intended to be random. The system can use this as a hint to optimize file caching.
+ ///
+ ///
+ /// This flag has no effect if the file system does not support cached I/O and .
+ /// For more information, see the Caching Behavior section of this topic.
+ ///
+ FILE_FLAG_RANDOM_ACCESS = 0x10000000,
+
+ ///
+ /// The file or device is being opened with no system caching for data reads and writes. This flag does not affect hard
+ /// disk caching or memory mapped files.
+ ///
+ ///
+ /// There are strict requirements for successfully working with files opened with CreateFile using this
+ /// flag; for details, see File Buffering.
+ ///
+ FILE_FLAG_NO_BUFFERING = 0x20000000,
+
+ ///
+ /// The file or device is being opened or created for asynchronous I/O.
+ ///
+ ///
+ /// When subsequent I/O operations are completed on this handle, the event specified in the OVERLAPPED structure will be
+ /// set to the signaled state.
+ /// If this flag is specified, the file can be used for simultaneous read and write operations.
+ /// If this flag is not specified, then I/O operations are serialized, even if the calls to the read and write functions
+ /// specify an OVERLAPPED structure.
+ /// For information about considerations when using a file handle created with this flag, see the Synchronous and
+ /// Asynchronous I/O Handles section of this topic.
+ ///
+ FILE_FLAG_OVERLAPPED = 0x40000000,
+
+ ///
+ /// Write operations will not go through any intermediate cache; they will go directly to disk.
+ ///
+ ///
+ /// For additional information, see the Caching Behavior section of this topic.
+ ///
+ FILE_FLAG_WRITE_THROUGH = 0x80000000,
+ }
+}
diff --git a/src/Framework/FileAccess/ProcessData.cs b/src/Framework/FileAccess/ProcessData.cs
new file mode 100644
index 00000000000..aa4c8ab873c
--- /dev/null
+++ b/src/Framework/FileAccess/ProcessData.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Build.Framework.FileAccess
+{
+ ///
+ /// Process data.
+ ///
+ /// The process name.
+ /// The process id.
+ /// The parent process id.
+ /// The creation date time.
+ /// The exit date time.
+ /// The exit code.
+ [CLSCompliant(false)]
+ public readonly record struct ProcessData(
+ string ProcessName,
+ uint ProcessId,
+ uint ParentProcessId,
+ DateTime CreationDateTime,
+ DateTime ExitDateTime,
+ uint ExitCode);
+}
diff --git a/src/Framework/FileAccess/ReportedFileOperation.cs b/src/Framework/FileAccess/ReportedFileOperation.cs
new file mode 100644
index 00000000000..22e90c56cea
--- /dev/null
+++ b/src/Framework/FileAccess/ReportedFileOperation.cs
@@ -0,0 +1,263 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.Build.Framework.FileAccess
+{
+ /*
+ * Implementation note: This is a copy of BuildXL.Processes.ReportedFileOperation.
+ * The purpose of the copy is because this is part of the public MSBuild API and it's not desirable to
+ * expose BuildXL types directly.
+ */
+
+ ///
+ /// Which operation resulted in a reported file access.
+ ///
+ public enum ReportedFileOperation : byte
+ {
+ ///
+ /// Unknown operation.
+ ///
+ Unknown = 0,
+
+ ///
+ /// CreateFile.
+ ///
+ CreateFile,
+
+ ///
+ /// CreateProcess.
+ ///
+ CreateProcess,
+
+ ///
+ /// GetFileAttributes.
+ ///
+ GetFileAttributes,
+
+ ///
+ /// GetFileAttributesEx.
+ ///
+ GetFileAttributesEx,
+
+ ///
+ /// Process forked.
+ ///
+ Process,
+
+ ///
+ /// FindFirstFileEx.
+ ///
+ ///
+ /// FindFirstFile also indicates this op, since we implement it in terms of FindFirstFileEx.
+ ///
+ FindFirstFileEx,
+
+ ///
+ /// FindNextFile.
+ ///
+ FindNextFile,
+
+ ///
+ /// CreateDirectory.
+ ///
+ CreateDirectory,
+
+ ///
+ /// DeleteFile.
+ ///
+ DeleteFile,
+
+ ///
+ /// MoveFile (source; read and deleted).
+ ///
+ MoveFileSource,
+
+ ///
+ /// MoveFile (destination; written).
+ ///
+ MoveFileDestination,
+
+ ///
+ /// SetFileInformationByHandleSource (source; read and deleted).
+ ///
+ SetFileInformationByHandleSource,
+
+ ///
+ /// SetFileInformationByHandleDest (destination; written).
+ ///
+ SetFileInformationByHandleDest,
+
+ ///
+ /// ZwSetRenameInformationFileSource (source; read and deleted).
+ ///
+ ZwSetRenameInformationFileSource,
+
+ ///
+ /// ZwSetRenameInformationFileDest (destination; written).
+ ///
+ ZwSetRenameInformationFileDest,
+
+ ///
+ /// ZwSetLinkInformationFileDest.
+ ///
+ ZwSetLinkInformationFile,
+
+ ///
+ /// ZwSetDispositionInformationFile (delete-on-close; deleted).
+ ///
+ ZwSetDispositionInformationFile,
+
+ ///
+ /// ZwSetModeInformationFile (delete-on-close; deleted).
+ ///
+ ZwSetModeInformationFile,
+
+ ///
+ /// ZwSetFileNameInformationFile (source; read and written).
+ ///
+ ZwSetFileNameInformationFileSource,
+
+ ///
+ /// ZwSetFileNameInformationFile (destination; written).
+ ///
+ ZwSetFileNameInformationFileDest,
+
+ ///
+ /// CopyFile (source; read).
+ ///
+ CopyFileSource,
+
+ ///
+ /// CopyFile (destination; written).
+ ///
+ CopyFileDestination,
+
+ ///
+ /// CreateHardLink (source; read).
+ ///
+ CreateHardLinkSource,
+
+ ///
+ /// CreateHardLink (destination; written).
+ ///
+ CreateHardLinkDestination,
+
+ ///
+ /// RemoveDirectory.
+ ///
+ RemoveDirectory,
+
+ ///
+ /// RemoveDirectory (source; written).
+ ///
+ RemoveDirectorySource,
+
+ ///
+ /// NtQueryDirectoryFile.
+ ///
+ NtQueryDirectoryFile,
+
+ ///
+ /// ZwQueryDirectoryFile.
+ ///
+ ZwQueryDirectoryFile,
+
+ ///
+ /// NtCreateFile.
+ ///
+ NtCreateFile,
+
+ ///
+ /// ZwCreateFile.
+ ///
+ ZwCreateFile,
+
+ ///
+ /// ZwOpenFile.
+ ///
+ ZwOpenFile,
+
+ ///
+ /// This is a quasi operation. We issue this
+ /// report when Detours is changing file open
+ /// request with Read/Write access to Read access only.
+ ///
+ ChangedReadWriteToReadAccess,
+
+ ///
+ /// This is a quasi operation. The sandbox issues this only when FileAccessPolicy.OverrideAllowWriteForExistingFiles is set, representing
+ /// that an allow for write check was performed for a given path for the first time (in the scope of a process, another process in the same process
+ /// tree may also report this for the same path).
+ ///
+ FirstAllowWriteCheckInProcess,
+
+ ///
+ /// This operation used to indicate to the engine by the Linux sandbox that a process being executed statically links libc
+ /// and may have missing file observations.
+ ///
+ StaticallyLinkedProcess,
+
+ ///
+ /// Access of reparse point target.
+ ///
+ ReparsePointTarget,
+
+ ///
+ /// Access of reparse point target, cached by Detours.
+ ///
+ ReparsePointTargetCached,
+
+ ///
+ /// Access checks for source of CreateSymbolicLink API.
+ ///
+ CreateSymbolicLinkSource,
+
+ ///
+ /// Access check for MoveFileWithgProgress source target.
+ ///
+ MoveFileWithProgressSource,
+
+ ///
+ /// Access check for MoveFileWithProgress dest target.
+ ///
+ MoveFileWithProgressDest,
+
+ ///
+ /// Multiple operations lumped into one.
+ ///
+ MultipleOperations,
+
+ ///
+ /// Process exited.
+ ///
+ ProcessExit,
+
+ #region Operation Names Reported by BuildXLSandbox (macOS sandbox implementation)
+ MacLookup,
+ MacReadlink,
+ MacVNodeCreate,
+ KAuthMoveSource,
+ KAuthMoveDest,
+ KAuthCreateHardlinkSource,
+ KAuthCreateHardlinkDest,
+ KAuthCopySource,
+ KAuthCopyDest,
+ KAuthDeleteDir,
+ KAuthDeleteFile,
+ KAuthOpenDir,
+ KAuthReadFile,
+ KAuthCreateDir,
+ KAuthWriteFile,
+ KAuthClose,
+ KAuthCloseModified,
+ KAuthGetAttributes,
+ KAuthVNodeExecute,
+ KAuthVNodeWrite,
+ KAuthVNodeRead,
+ KAuthVNodeProbe,
+ MacVNodeWrite,
+ MacVNodeCloneSource,
+ MacVNodeCloneDest,
+ #endregion
+ }
+}
diff --git a/src/Framework/FileAccess/RequestedAccess.cs b/src/Framework/FileAccess/RequestedAccess.cs
new file mode 100644
index 00000000000..b522571460f
--- /dev/null
+++ b/src/Framework/FileAccess/RequestedAccess.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Build.Framework.FileAccess
+{
+ /*
+ * Implementation note: This is a copy of BuildXL.Processes.RequestedAccess.
+ * The purpose of the copy is because this is part of the public MSBuild API and it's not desirable to
+ * expose BuildXL types directly.
+ */
+
+ ///
+ /// Level of access requested by a reported file operation.
+ ///
+ [Flags]
+ public enum RequestedAccess : byte
+ {
+ ///
+ /// No access requested.
+ ///
+ None = 0,
+
+ ///
+ /// Read access requested.
+ ///
+ Read = 1,
+
+ ///
+ /// Write access requested.
+ ///
+ Write = 2,
+
+ ///
+ /// Metadata-only probe access requested (e.g. ).
+ ///
+ Probe = 4,
+
+ ///
+ /// Directory enumeration access requested (on the directory itself; immediate children will be enumerated).
+ ///
+ Enumerate = 8,
+
+ ///
+ /// Metadata-only probe access requested; probed as part of a directory enumeration (e.g. ).
+ ///
+ EnumerationProbe = 16,
+
+ ///
+ /// Both read and write access requested.
+ ///
+ ReadWrite = Read | Write,
+
+ ///
+ /// All defined access levels requested.
+ ///
+ All = Read | Write | Probe | Enumerate | EnumerationProbe,
+ }
+}
diff --git a/src/Framework/ITranslator.cs b/src/Framework/ITranslator.cs
index 930cc45f6b2..77f1c76f647 100644
--- a/src/Framework/ITranslator.cs
+++ b/src/Framework/ITranslator.cs
@@ -6,6 +6,9 @@
using System.Globalization;
using System.IO;
using Microsoft.Build.Framework;
+#if !CLR2COMPATIBILITY
+using Microsoft.Build.Framework.FileAccess;
+#endif
#nullable disable
@@ -134,6 +137,12 @@ BinaryWriter Writer
/// The value to be translated.
void Translate(ref int value);
+ ///
+ /// Translates an unsigned integer.
+ ///
+ /// The unsigned integer to translate.
+ void Translate(ref uint unsignedInteger);
+
///
/// Translates an array.
///
@@ -234,6 +243,23 @@ BinaryWriter Writer
/// The context to be translated.
void Translate(ref BuildEventContext value);
+ ///
+ /// Translates .
+ ///
+ /// The to translate.
+ void Translate(ref FileAccessData fileAccessData);
+
+ ///
+ /// Translates .
+ ///
+ /// The file accesses to translate.
+ void Translate(ref List fileAccessDataList);
+
+ ///
+ /// Translates .
+ ///
+ /// The to translate.
+ void Translate(ref ProcessData processData);
#endif
///
diff --git a/src/Framework/MSBuildEventSource.cs b/src/Framework/MSBuildEventSource.cs
index 29e9e67abfb..a5dbe84a3ee 100644
--- a/src/Framework/MSBuildEventSource.cs
+++ b/src/Framework/MSBuildEventSource.cs
@@ -661,6 +661,18 @@ public void MSBuildServerBuildStop(string commandLine, int countOfConsoleMessage
{
WriteEvent(90, commandLine, countOfConsoleMessages, sumSizeOfConsoleMessages, clientExitType, serverExitType);
}
+
+ [Event(91, Keywords = Keywords.All)]
+ public void ProjectCacheHandleBuildResultStart(string pluginTypeName, string projectPath, string targets)
+ {
+ WriteEvent(91, pluginTypeName, projectPath, targets);
+ }
+
+ [Event(92, Keywords = Keywords.All)]
+ public void ProjectCacheHandleBuildResultStop(string pluginTypeName, string projectPath, string targets)
+ {
+ WriteEvent(92, pluginTypeName, projectPath, targets);
+ }
#endregion
}
}
diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs
index e31b42aa0e4..d30a8ec85f5 100644
--- a/src/Framework/NativeMethods.cs
+++ b/src/Framework/NativeMethods.cs
@@ -1307,7 +1307,7 @@ internal static int GetParentProcessId(int processId)
// using (var r = FileUtilities.OpenRead("/proc/" + processId + "/stat"))
// and could be again when FileUtilities moves to Framework
- using var fileStream = new FileStream("/proc/" + processId + "/stat", FileMode.Open, FileAccess.Read);
+ using var fileStream = new FileStream("/proc/" + processId + "/stat", FileMode.Open, System.IO.FileAccess.Read);
using StreamReader r = new(fileStream);
line = r.ReadLine();
diff --git a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs
index d151bc71199..a3f744978d0 100644
--- a/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs
+++ b/src/MSBuild.UnitTests/CommandLineSwitches_Tests.cs
@@ -1131,6 +1131,9 @@ public void InvalidToolsVersionErrors()
outputResultsCache: null,
saveProjectResult: false,
ref buildResult,
+#if FEATURE_REPORTFILEACCESSES
+ reportFileAccesses: false,
+#endif
commandLine: null);
}
finally
diff --git a/src/MSBuild/CommandLineSwitches.cs b/src/MSBuild/CommandLineSwitches.cs
index d5d2b08a104..847da8ba276 100644
--- a/src/MSBuild/CommandLineSwitches.cs
+++ b/src/MSBuild/CommandLineSwitches.cs
@@ -106,6 +106,9 @@ internal enum ParameterizedSwitch
GraphBuild,
InputResultsCaches,
OutputResultsCache,
+#if FEATURE_REPORTFILEACCESSES
+ ReportFileAccesses,
+#endif
LowPriority,
Question,
DetailedSummary,
@@ -268,6 +271,9 @@ internal ParameterizedSwitchInfo(
new ParameterizedSwitchInfo( new string[] { "graphbuild", "graph" }, ParameterizedSwitch.GraphBuild, null, true, null, true, false),
new ParameterizedSwitchInfo( new string[] { "inputResultsCaches", "irc" }, ParameterizedSwitch.InputResultsCaches, null, true, null, true, true),
new ParameterizedSwitchInfo( new string[] { "outputResultsCache", "orc" }, ParameterizedSwitch.OutputResultsCache, "DuplicateOutputResultsCache", false, null, true, true),
+#if FEATURE_REPORTFILEACCESSES
+ new ParameterizedSwitchInfo( new string[] { "reportfileaccesses" }, ParameterizedSwitch.ReportFileAccesses, null, false, null, true, false),
+#endif
new ParameterizedSwitchInfo( new string[] { "lowpriority", "low" }, ParameterizedSwitch.LowPriority, null, false, null, true, false),
new ParameterizedSwitchInfo( new string[] { "question", "q" }, ParameterizedSwitch.Question, null, false, null, true, false),
new ParameterizedSwitchInfo( new string[] { "detailedsummary", "ds" }, ParameterizedSwitch.DetailedSummary, null, false, null, true, false),
diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj
index 3ca22b8e510..994aaf60d86 100644
--- a/src/MSBuild/MSBuild.csproj
+++ b/src/MSBuild/MSBuild.csproj
@@ -218,6 +218,7 @@
+
diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs
index 1a0c3871a2d..64ab30c8d6d 100644
--- a/src/MSBuild/OutOfProcTaskHostNode.cs
+++ b/src/MSBuild/OutOfProcTaskHostNode.cs
@@ -5,14 +5,16 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
-using System.IO;
using System.Globalization;
-using System.Threading;
+using System.IO;
using System.Reflection;
-
+using System.Threading;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
+#if !CLR2COMPATIBILITY
+using Microsoft.Build.Framework.FileAccess;
+#endif
using Microsoft.Build.Internal;
using Microsoft.Build.Shared;
#if FEATURE_APPDOMAIN
@@ -165,6 +167,13 @@ internal class OutOfProcTaskHostNode :
private RegisteredTaskObjectCacheBase _registeredTaskObjectCache;
#endif
+#if FEATURE_REPORTFILEACCESSES
+ ///
+ /// The file accesses reported by the most recently completed task.
+ ///
+ private List _fileAccessData = new List();
+#endif
+
///
/// Constructor.
///
@@ -531,11 +540,19 @@ public override bool IsTaskInputLoggingEnabled
return _taskHost._currentConfiguration.IsTaskInputLoggingEnabled;
}
}
+
+ ///
+ public override void ReportFileAccess(FileAccessData fileAccessData)
+ {
+#if FEATURE_REPORTFILEACCESSES
+ _taskHost._fileAccessData.Add(fileAccessData);
+#endif
+ }
}
public EngineServices EngineServices { get; }
- #endregion
+#endregion
#endif
@@ -936,8 +953,11 @@ private void RunTask(object state)
lock (_taskCompleteLock)
{
_taskCompletePacket = new TaskHostTaskComplete(
- taskResult,
- currentEnvironment);
+ taskResult,
+#if FEATURE_REPORTFILEACCESSES
+ _fileAccessData,
+#endif
+ currentEnvironment);
}
#if FEATURE_APPDOMAIN
@@ -956,11 +976,20 @@ private void RunTask(object state)
lock (_taskCompleteLock)
{
// Create a minimal taskCompletePacket to carry the exception so that the TaskHostTask does not hang while waiting
- _taskCompletePacket = new TaskHostTaskComplete(new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedAfterExecution, e), null);
+ _taskCompletePacket = new TaskHostTaskComplete(
+ new OutOfProcTaskHostTaskResult(TaskCompleteType.CrashedAfterExecution, e),
+#if FEATURE_REPORTFILEACCESSES
+ _fileAccessData,
+#endif
+ null);
}
}
finally
{
+#if FEATURE_REPORTFILEACCESSES
+ _fileAccessData = new List();
+#endif
+
// Call CleanupTask to unload any domains and other necessary cleanup in the taskWrapper
_taskWrapper.CleanupTask();
diff --git a/src/MSBuild/Resources/Strings.resx b/src/MSBuild/Resources/Strings.resx
index 328cb2dcdd4..a753a09c5b6 100644
--- a/src/MSBuild/Resources/Strings.resx
+++ b/src/MSBuild/Resources/Strings.resx
@@ -865,6 +865,18 @@
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
MSBUILD : Configuration error MSB1043: The application could not start. {0}
@@ -1336,6 +1348,15 @@
MSBUILD : error MSB1049: The {0} parameter must be specified{StrBegin="MSBUILD : error MSB1049: "}
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
+
+
diff --git a/src/MSBuild/Resources/xlf/Strings.cs.xlf b/src/MSBuild/Resources/xlf/Strings.cs.xlf
index f2b85eea526..6570154d5b3 100644
--- a/src/MSBuild/Resources/xlf/Strings.cs.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.cs.xlf
@@ -97,6 +97,24 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
@@ -117,6 +135,16 @@
UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
This error is shown when a user specifies a value for the lowPriority parameter that is not equivalent to Boolean.TrueString or Boolean.FalseString.
LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.de.xlf b/src/MSBuild/Resources/xlf/Strings.de.xlf
index f29f73bd6ed..44c3b64f74e 100644
--- a/src/MSBuild/Resources/xlf/Strings.de.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.de.xlf
@@ -96,6 +96,24 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
@@ -116,6 +134,16 @@
UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
This error is shown when a user specifies a value for the lowPriority parameter that is not equivalent to Boolean.TrueString or Boolean.FalseString.
LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.es.xlf b/src/MSBuild/Resources/xlf/Strings.es.xlf
index 65032455f70..2a6689e3a55 100644
--- a/src/MSBuild/Resources/xlf/Strings.es.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.es.xlf
@@ -96,6 +96,24 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
@@ -116,6 +134,16 @@
UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
This error is shown when a user specifies a value for the lowPriority parameter that is not equivalent to Boolean.TrueString or Boolean.FalseString.
LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.fr.xlf b/src/MSBuild/Resources/xlf/Strings.fr.xlf
index 80312d77ab5..afb7ae87d96 100644
--- a/src/MSBuild/Resources/xlf/Strings.fr.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.fr.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.it.xlf b/src/MSBuild/Resources/xlf/Strings.it.xlf
index 9a391602b01..78b7e8a8f38 100644
--- a/src/MSBuild/Resources/xlf/Strings.it.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.it.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.ja.xlf b/src/MSBuild/Resources/xlf/Strings.ja.xlf
index 35aa68724fe..fce26fa54cd 100644
--- a/src/MSBuild/Resources/xlf/Strings.ja.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.ja.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.ko.xlf b/src/MSBuild/Resources/xlf/Strings.ko.xlf
index 47d973d6a67..c5cf18ccd43 100644
--- a/src/MSBuild/Resources/xlf/Strings.ko.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.ko.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.pl.xlf b/src/MSBuild/Resources/xlf/Strings.pl.xlf
index 9d9bfb601d7..c1750d20b72 100644
--- a/src/MSBuild/Resources/xlf/Strings.pl.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.pl.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf b/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf
index 4c60b3fbda2..009de7ec650 100644
--- a/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.ru.xlf b/src/MSBuild/Resources/xlf/Strings.ru.xlf
index e94db7008c0..2be3e0d40f6 100644
--- a/src/MSBuild/Resources/xlf/Strings.ru.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.ru.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.tr.xlf b/src/MSBuild/Resources/xlf/Strings.tr.xlf
index f2ee8ee03a4..60f3e963be5 100644
--- a/src/MSBuild/Resources/xlf/Strings.tr.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.tr.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf
index d1f839fc7b7..07cef143521 100644
--- a/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf
index 4e6f1da310b..526d7e26345 100644
--- a/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf
@@ -96,6 +96,34 @@
LOCALIZATION: "MSBuild" should not be localized.
LOCALIZATION: "-question" and "-q" should not be localized.
LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ -reportFileAccesses[:True|False]
+ Causes MSBuild to report file accesses to any configured
+ project cache plugins.
+
+ This flag is experimental and may not work as intended.
+
+
+ LOCALIZATION: "-reportFileAccesses" should not be localized.
+ LOCALIZATION: None of the lines should be longer than a standard width console window, eg 80 chars.
+
+
+
+
+ MSBUILD : error MSB1063: Report file accesses value is not valid. {0}
+
+ {StrBegin="MSBUILD : error MSB1063: "}
+ UE: This message does not need in-line parameters because the exception takes care of displaying the invalid arg.
+ This error is shown when a user specifies a value that is not equivalent to Boolean.TrueString or Boolean.FalseString.
+ LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:" should not be localized.
diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs
index d57971461d4..dacfef278e2 100644
--- a/src/MSBuild/XMake.cs
+++ b/src/MSBuild/XMake.cs
@@ -713,6 +713,9 @@ public static ExitType Execute(
string[] getItem = Array.Empty();
string[] getTargetResult = Array.Empty();
BuildResult result = null;
+#if FEATURE_REPORTFILEACCESSES
+ bool reportFileAccesses = false;
+#endif
GatherAllSwitches(commandLine, out var switchesFromAutoResponseFile, out var switchesNotFromAutoResponseFile, out _);
bool buildCanBeInvoked = ProcessCommandLineSwitches(
@@ -747,6 +750,9 @@ public static ExitType Execute(
ref graphBuildOptions,
ref inputResultsCaches,
ref outputResultsCache,
+#if FEATURE_REPORTFILEACCESSES
+ ref reportFileAccesses,
+#endif
ref lowPriority,
ref question,
ref getProperty,
@@ -846,6 +852,9 @@ public static ExitType Execute(
outputResultsCache,
saveProjectResult: outputPropertiesItemsOrTargetResults,
ref result,
+#if FEATURE_REPORTFILEACCESSES
+ reportFileAccesses,
+#endif
commandLine))
{
exitType = ExitType.BuildError;
@@ -1232,6 +1241,9 @@ internal static bool BuildProject(
string outputResultsCache,
bool saveProjectResult,
ref BuildResult result,
+#if FEATURE_REPORTFILEACCESSES
+ bool reportFileAccesses,
+#endif
#if FEATURE_GET_COMMANDLINE
string commandLine)
#else
@@ -1423,6 +1435,9 @@ internal static bool BuildProject(
parameters.InputResultsCacheFiles = inputResultsCaches;
parameters.OutputResultsCacheFile = outputResultsCache;
parameters.Question = question;
+#if FEATURE_REPORTFILEACCESSES
+ parameters.ReportFileAccesses = reportFileAccesses;
+#endif
// Propagate the profiler flag into the project load settings so the evaluator
// can pick it up
@@ -2383,6 +2398,9 @@ private static bool ProcessCommandLineSwitches(
ref GraphBuildOptions graphBuild,
ref string[] inputResultsCaches,
ref string outputResultsCache,
+#if FEATURE_REPORTFILEACCESSES
+ ref bool reportFileAccesses,
+#endif
ref bool lowPriority,
ref bool question,
ref string[] getProperty,
@@ -2446,6 +2464,13 @@ private static bool ProcessCommandLineSwitches(
// leave priority where it was.
catch (Win32Exception) { }
+#if FEATURE_REPORTFILEACCESSES
+ if (commandLineSwitches.IsParameterizedSwitchSet(CommandLineSwitches.ParameterizedSwitch.ReportFileAccesses))
+ {
+ reportFileAccesses = ProcessBooleanSwitch(commandLineSwitches[CommandLineSwitches.ParameterizedSwitch.ReportFileAccesses], defaultValue: true, resourceName: "");
+ }
+#endif
+
// if help switch is set (regardless of switch errors), show the help message and ignore the other switches
if (commandLineSwitches[CommandLineSwitches.ParameterlessSwitch.Help])
{
@@ -2508,6 +2533,9 @@ private static bool ProcessCommandLineSwitches(
ref graphBuild,
ref inputResultsCaches,
ref outputResultsCache,
+#if FEATURE_REPORTFILEACCESSES
+ ref reportFileAccesses,
+#endif
ref lowPriority,
ref question,
ref getProperty,
@@ -4426,6 +4454,9 @@ private static void ShowHelpMessage()
Console.WriteLine(AssemblyResources.GetString("HelpMessage_InputCachesFiles"));
Console.WriteLine(AssemblyResources.GetString("HelpMessage_OutputCacheFile"));
Console.WriteLine(AssemblyResources.GetString("HelpMessage_36_GraphBuildSwitch"));
+#if FEATURE_REPORTFILEACCESSES
+ Console.WriteLine(AssemblyResources.GetString("HelpMessage_42_ReportFileAccessesSwitch"));
+#endif
Console.WriteLine(AssemblyResources.GetString("HelpMessage_39_LowPrioritySwitch"));
Console.WriteLine(AssemblyResources.GetString("HelpMessage_41_QuestionSwitch"));
Console.WriteLine(AssemblyResources.GetString("HelpMessage_7_ResponseFile"));
diff --git a/src/MSBuild/app.amd64.config b/src/MSBuild/app.amd64.config
index 91009eec184..1feab29307d 100644
--- a/src/MSBuild/app.amd64.config
+++ b/src/MSBuild/app.amd64.config
@@ -54,6 +54,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -69,6 +81,10 @@
+
+
+
+
@@ -121,6 +137,11 @@
+
+
+
+
+
diff --git a/src/Package/MSBuild.VSSetup/files.swr b/src/Package/MSBuild.VSSetup/files.swr
index 3780b51c588..06d5e138401 100644
--- a/src/Package/MSBuild.VSSetup/files.swr
+++ b/src/Package/MSBuild.VSSetup/files.swr
@@ -36,13 +36,18 @@ folder InstallDir:\MSBuild\Current\Bin
file source=$(X86BinPath)Microsoft.IO.Redist.dll vs.file.ngenApplications="[installDir]\Common7\IDE\vsn.exe" vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=1
file source=$(X86BinPath)MSBuild.exe vs.file.ngenArchitecture=x86 vs.file.ngenPriority=1
file source=$(X86BinPath)MSBuild.exe.config
- file source=$(TaskHostBinPath)MSBuildTaskHost.exe
+ file source=$(TaskHostBinPath)MSBuildTaskHost.exe
file source=$(TaskHostBinPath)MSBuildTaskHost.exe.config
+ file source=$(X86BinPath)BuildXL.Native.dll
+ file source=$(X86BinPath)BuildXL.Processes.dll
+ file source=$(X86BinPath)BuildXL.Utilities.Core.dll
+ file source=$(X86BinPath)RuntimeContracts.dll
file source=$(X86BinPath)System.Buffers.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=1
file source=$(X86BinPath)System.Memory.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=1
file source=$(X86BinPath)System.Reflection.Metadata.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=1
file source=$(X86BinPath)System.Reflection.MetadataLoadContext.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=1
file source=$(X86BinPath)System.Text.Json.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=1
+ file source=$(X86BinPath)System.Threading.Channels.dll
file source=$(X86BinPath)Microsoft.Bcl.AsyncInterfaces.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=1
file source=$(X86BinPath)System.Text.Encodings.Web.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=1
file source=$(X86BinPath)System.Threading.Tasks.Extensions.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=1
@@ -313,6 +318,13 @@ folder InstallDir:\MSBuild\Current\Bin\amd64\zh-Hant
file source=$(X64BinPath)zh-Hant\MSBuild.resources.dll
file source=$(TaskHostX64BinPath)zh-Hant\MSBuildTaskHost.resources.dll
+folder InstallDir:\MSBuild\Current\Bin\x86
+ file source=$(X86BinPath)x86\DetoursServices.dll
+
+folder InstallDir:\MSBuild\Current\Bin\x64
+ file source=$(X86BinPath)x64\DetoursServices.dll
+ file source=$(X86BinPath)x64\BuildXLNatives.dll
+
folder InstallDir:\Common7\IDE\CommonExtensions\MSBuild
file source=$(SourceDir)Package\MSBuild.VSSetup\MSBuild.clientenabledpkg
file source=$(SourceDir)Framework\Microsoft.Build.Framework.pkgdef
diff --git a/src/Shared/INodePacket.cs b/src/Shared/INodePacket.cs
index cb89889c3ac..52d335944af 100644
--- a/src/Shared/INodePacket.cs
+++ b/src/Shared/INodePacket.cs
@@ -190,6 +190,16 @@ internal enum NodePacketType : byte
///
ResourceResponse,
+ ///
+ /// Message sent from a node reporting a file access.
+ ///
+ FileAccessReport,
+
+ ///
+ /// Message sent from a node reporting process data.
+ ///
+ ProcessReport,
+
///
/// Command in form of MSBuild command line for server node - MSBuild Server.
/// Keep this enum value constant intact as this is part of contract with dotnet CLI
diff --git a/src/Shared/TaskHostTaskComplete.cs b/src/Shared/TaskHostTaskComplete.cs
index faad07dd31e..599b5bfe9db 100644
--- a/src/Shared/TaskHostTaskComplete.cs
+++ b/src/Shared/TaskHostTaskComplete.cs
@@ -4,6 +4,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+#if !CLR2COMPATIBILITY
+using Microsoft.Build.Framework.FileAccess;
+#endif
using Microsoft.Build.Shared;
#nullable disable
@@ -49,6 +52,10 @@ internal enum TaskCompleteType
///
internal class TaskHostTaskComplete : INodePacket
{
+#if FEATURE_REPORTFILEACCESSES
+ private List _fileAccessData;
+#endif
+
///
/// Result of the task's execution.
///
@@ -82,12 +89,21 @@ internal class TaskHostTaskComplete : INodePacket
///
private Dictionary _buildProcessEnvironment = null;
+
+#pragma warning disable CS1572 // XML comment has a param tag, but there is no parameter by that name. Justification: xmldoc doesn't seem to interact well with #ifdef of params.
///
- /// Constructor
+ /// Initializes a new instance of the class.
///
- /// Result of the task's execution.
+ /// The result of the task's execution.
+ /// The file accesses reported by the task.
/// The build process environment as it was at the end of the task's execution.
- public TaskHostTaskComplete(OutOfProcTaskHostTaskResult result, IDictionary buildProcessEnvironment)
+#pragma warning restore CS1572 // XML comment has a param tag, but there is no parameter by that name
+ public TaskHostTaskComplete(
+ OutOfProcTaskHostTaskResult result,
+#if FEATURE_REPORTFILEACCESSES
+ List fileAccessData,
+#endif
+ IDictionary buildProcessEnvironment)
{
ErrorUtilities.VerifyThrowInternalNull(result, nameof(result));
@@ -95,6 +111,9 @@ public TaskHostTaskComplete(OutOfProcTaskHostTaskResult result, IDictionary
+ /// Gets the file accesses reported by the task.
+ ///
+ public List FileAccessData
+ {
+ [DebuggerStepThrough]
+ get => _fileAccessData;
+ }
+#endif
+
///
/// Translates the packet to/from binary form.
///
@@ -213,6 +243,9 @@ public void Translate(ITranslator translator)
translator.Translate(ref _taskExceptionMessageArgs);
translator.TranslateDictionary(ref _taskOutputParameters, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization);
translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase);
+#if FEATURE_REPORTFILEACCESSES
+ translator.Translate(ref _fileAccessData);
+#endif
}
///