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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). 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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). 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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). 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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). 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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). 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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). "{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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). "{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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). 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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). 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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). Попадание в кэше проекта для "{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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). "{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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). 项目缓存命中 "{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}. + MSB4269: The project cache failed while handling a build result for the following project: {0}. + + Project cache hit for "{0}" (default targets). "{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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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. + + -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} + 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 } ///