diff --git a/.vsts-dotnet-ci.yml b/.vsts-dotnet-ci.yml index 68d8903b4cc..501b35caca3 100644 --- a/.vsts-dotnet-ci.yml +++ b/.vsts-dotnet-ci.yml @@ -378,7 +378,7 @@ jobs: arguments: --repoRoot $(Build.SourcesDirectory) - task: PublishPipelineArtifact@1 displayName: Publish Code Coverage Results - continueOnError: false + continueOnError: true condition: eq(variables.onlyDocChanged, 0) inputs: targetPath: '$(Build.SourcesDirectory)/artifacts/CoverageResults/merged.coverage' @@ -468,7 +468,7 @@ jobs: arguments: --repoRoot $(Build.SourcesDirectory) - task: PublishPipelineArtifact@1 displayName: Publish Code Coverage Results - continueOnError: false + continueOnError: true condition: eq(variables.onlyDocChanged, 0) inputs: targetPath: '$(Build.SourcesDirectory)/artifacts/CoverageResults/merged.coverage' diff --git a/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs b/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs index 4df21d5d18a..47ae718122e 100644 --- a/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs +++ b/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs @@ -43,7 +43,7 @@ public class AssemblyTaskFactory_Tests /// public AssemblyTaskFactory_Tests() { - SetupTaskFactory(null, false); + SetupTaskFactory(TaskHostParameters.Empty, false); } #region AssemblyTaskFactory @@ -57,7 +57,7 @@ public void NullLoadInfo() Assert.Throws(() => { AssemblyTaskFactory taskFactory = new AssemblyTaskFactory(); - taskFactory.InitializeFactory(null, "TaskToTestFactories", new Dictionary(), string.Empty, null, false, null, ElementLocation.Create("NONE"), String.Empty); + taskFactory.InitializeFactory(null, "TaskToTestFactories", new Dictionary(), string.Empty, TaskHostParameters.Empty, false, null, ElementLocation.Create("NONE"), String.Empty); }); } /// @@ -69,7 +69,7 @@ public void NullTaskName() Assert.Throws(() => { AssemblyTaskFactory taskFactory = new AssemblyTaskFactory(); - taskFactory.InitializeFactory(_loadInfo, null, new Dictionary(), string.Empty, null, false, null, ElementLocation.Create("NONE"), String.Empty); + taskFactory.InitializeFactory(_loadInfo, null, new Dictionary(), string.Empty, TaskHostParameters.Empty, false, null, ElementLocation.Create("NONE"), String.Empty); }); } /// @@ -81,7 +81,7 @@ public void EmptyTaskName() Assert.Throws(() => { AssemblyTaskFactory taskFactory = new AssemblyTaskFactory(); - taskFactory.InitializeFactory(_loadInfo, String.Empty, new Dictionary(), string.Empty, null, false, null, ElementLocation.Create("NONE"), String.Empty); + taskFactory.InitializeFactory(_loadInfo, String.Empty, new Dictionary(), string.Empty, TaskHostParameters.Empty, false, null, ElementLocation.Create("NONE"), String.Empty); }); } /// @@ -93,7 +93,7 @@ public void GoodTaskNameButNotInInfo() Assert.Throws(() => { AssemblyTaskFactory taskFactory = new AssemblyTaskFactory(); - taskFactory.InitializeFactory(_loadInfo, "RandomTask", new Dictionary(), string.Empty, null, false, null, ElementLocation.Create("NONE"), String.Empty); + taskFactory.InitializeFactory(_loadInfo, "RandomTask", new Dictionary(), string.Empty, TaskHostParameters.Empty, false, null, ElementLocation.Create("NONE"), String.Empty); }); } /// @@ -110,6 +110,7 @@ public void CallPublicInitializeFactory() taskFactory.Initialize(String.Empty, new Dictionary(), String.Empty, null); }); } + /// /// Make sure we get an internal error when we call the ITaskFactory2 version of initialize factory. /// This is done because we cannot properly initialize the task factory using the public interface and keep @@ -121,7 +122,7 @@ public void CallPublicInitializeFactory2() Assert.Throws(() => { AssemblyTaskFactory taskFactory = new AssemblyTaskFactory(); - taskFactory.Initialize(String.Empty, null, new Dictionary(), String.Empty, null); + taskFactory.Initialize(String.Empty, TaskHostParameters.Empty, new Dictionary(), String.Empty, null); }); } #endregion @@ -132,7 +133,7 @@ public void CallPublicInitializeFactory2() [Fact] public void CreatableByTaskFactoryGoodName() { - Assert.True(_taskFactory.TaskNameCreatableByFactory("TaskToTestFactories", null, String.Empty, null, ElementLocation.Create(".", 1, 1))); + Assert.True(_taskFactory.TaskNameCreatableByFactory("TaskToTestFactories", TaskHostParameters.Empty, String.Empty, null, ElementLocation.Create(".", 1, 1))); } /// @@ -141,7 +142,7 @@ public void CreatableByTaskFactoryGoodName() [Fact] public void CreatableByTaskFactoryNotInAssembly() { - Assert.False(_taskFactory.TaskNameCreatableByFactory("NotInAssembly", null, String.Empty, null, ElementLocation.Create(".", 1, 1))); + Assert.False(_taskFactory.TaskNameCreatableByFactory("NotInAssembly", TaskHostParameters.Empty, String.Empty, null, ElementLocation.Create(".", 1, 1))); } /// @@ -152,7 +153,7 @@ public void CreatableByTaskFactoryNotInAssemblyEmptyTaskName() { Assert.Throws(() => { - Assert.False(_taskFactory.TaskNameCreatableByFactory(String.Empty, null, String.Empty, null, ElementLocation.Create(".", 1, 1))); + Assert.False(_taskFactory.TaskNameCreatableByFactory(String.Empty, TaskHostParameters.Empty, String.Empty, null, ElementLocation.Create(".", 1, 1))); }); } /// @@ -163,7 +164,7 @@ public void CreatableByTaskFactoryNullTaskName() { Assert.Throws(() => { - Assert.False(_taskFactory.TaskNameCreatableByFactory(null, null, String.Empty, null, ElementLocation.Create(".", 1, 1))); + Assert.False(_taskFactory.TaskNameCreatableByFactory(null, TaskHostParameters.Empty, String.Empty, null, ElementLocation.Create(".", 1, 1))); }); } /// @@ -173,15 +174,10 @@ public void CreatableByTaskFactoryNullTaskName() [Fact] public void CreatableByTaskFactoryMatchingIdentity() { - IDictionary factoryIdentityParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - factoryIdentityParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.currentRuntime); - factoryIdentityParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); - + TaskHostParameters factoryIdentityParameters = new (XMakeAttributes.MSBuildRuntimeValues.currentRuntime, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); SetupTaskFactory(factoryIdentityParameters, false /* don't want task host */); - IDictionary taskIdentityParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskIdentityParameters.Add(XMakeAttributes.runtime, XMakeAttributes.GetCurrentMSBuildRuntime()); - taskIdentityParameters.Add(XMakeAttributes.architecture, XMakeAttributes.GetCurrentMSBuildArchitecture()); + TaskHostParameters taskIdentityParameters = new(XMakeAttributes.GetCurrentMSBuildRuntime(), XMakeAttributes.GetCurrentMSBuildArchitecture()); Assert.True(_taskFactory.TaskNameCreatableByFactory("TaskToTestFactories", taskIdentityParameters, String.Empty, null, ElementLocation.Create(".", 1, 1))); } @@ -193,15 +189,11 @@ public void CreatableByTaskFactoryMatchingIdentity() [Fact] public void CreatableByTaskFactoryMismatchedIdentity() { - IDictionary factoryIdentityParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - factoryIdentityParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr2); - factoryIdentityParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); + TaskHostParameters factoryIdentityParameters = new (XMakeAttributes.MSBuildRuntimeValues.clr2, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); SetupTaskFactory(factoryIdentityParameters, false /* don't want task host */); - IDictionary taskIdentityParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskIdentityParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr4); - taskIdentityParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); + TaskHostParameters taskIdentityParameters = new(XMakeAttributes.MSBuildRuntimeValues.clr4, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); Assert.False(_taskFactory.TaskNameCreatableByFactory("TaskToTestFactories", taskIdentityParameters, String.Empty, null, ElementLocation.Create(".", 1, 1))); } @@ -246,7 +238,7 @@ public void VerifyGoodTaskInstantiation() ITask createdTask = null; try { - createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null, + createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), TaskHostParameters.Empty, #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -275,9 +267,7 @@ public void VerifyMatchingTaskParametersDontLaunchTaskHost1() ITask createdTask = null; try { - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any); + TaskHostParameters taskParameters = new (XMakeAttributes.MSBuildRuntimeValues.any, XMakeAttributes.MSBuildArchitectureValues.any); createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), taskParameters, #if FEATURE_APPDOMAIN @@ -308,9 +298,7 @@ public void VerifyMatchingTaskParametersDontLaunchTaskHost2() ITask createdTask = null; try { - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.GetCurrentMSBuildRuntime()); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.GetCurrentMSBuildArchitecture()); + TaskHostParameters taskParameters = new (XMakeAttributes.GetCurrentMSBuildRuntime(), XMakeAttributes.GetCurrentMSBuildArchitecture()); createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), taskParameters, #if FEATURE_APPDOMAIN @@ -341,13 +329,11 @@ public void VerifyMatchingUsingTaskParametersDontLaunchTaskHost1() ITask createdTask = null; try { - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any); + TaskHostParameters taskParameters = new (XMakeAttributes.MSBuildRuntimeValues.any, XMakeAttributes.MSBuildArchitectureValues.any); SetupTaskFactory(taskParameters, false /* don't want task host */); - createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null, + createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), TaskHostParameters.Empty, #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -376,13 +362,11 @@ public void VerifyMatchingUsingTaskParametersDontLaunchTaskHost2() ITask createdTask = null; try { - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.GetCurrentMSBuildArchitecture()); + TaskHostParameters taskParameters = new (XMakeAttributes.MSBuildRuntimeValues.any, XMakeAttributes.GetCurrentMSBuildArchitecture()); SetupTaskFactory(taskParameters, false /* don't want task host */); - createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null, + createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), TaskHostParameters.Empty, #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -411,13 +395,11 @@ public void VerifyMatchingParametersDontLaunchTaskHost() ITask createdTask = null; try { - IDictionary factoryParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - factoryParameters.Add(XMakeAttributes.runtime, XMakeAttributes.GetCurrentMSBuildRuntime()); + TaskHostParameters factoryParameters = new (XMakeAttributes.GetCurrentMSBuildRuntime()); SetupTaskFactory(factoryParameters, false /* don't want task host */); - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); + TaskHostParameters taskParameters = new (architecture: XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), taskParameters, #if FEATURE_APPDOMAIN @@ -448,13 +430,11 @@ public void VerifyNonmatchingUsingTaskParametersLaunchTaskHost() ITask createdTask = null; try { - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr2); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any); + TaskHostParameters taskParameters = new (XMakeAttributes.MSBuildRuntimeValues.clr2, XMakeAttributes.MSBuildArchitectureValues.any); SetupTaskFactory(taskParameters, false /* don't want task host */); - createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null, + createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), TaskHostParameters.Empty, #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -483,9 +463,7 @@ public void VerifyNonmatchingTaskParametersLaunchTaskHost() ITask createdTask = null; try { - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr2); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any); + TaskHostParameters taskParameters = new(XMakeAttributes.MSBuildRuntimeValues.clr2, XMakeAttributes.MSBuildArchitectureValues.any); createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), taskParameters, #if FEATURE_APPDOMAIN @@ -516,13 +494,11 @@ public void VerifyNonmatchingParametersLaunchTaskHost() ITask createdTask = null; try { - IDictionary factoryParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - factoryParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr2); + TaskHostParameters factoryParameters = new (XMakeAttributes.MSBuildRuntimeValues.clr2); SetupTaskFactory(factoryParameters, false /* don't want task host */); - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any); + TaskHostParameters taskParameters = new(architecture: XMakeAttributes.MSBuildArchitectureValues.any); createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), taskParameters, #if FEATURE_APPDOMAIN @@ -553,9 +529,9 @@ public void VerifyExplicitlyLaunchTaskHost() ITask createdTask = null; try { - SetupTaskFactory(null, true /* want task host */, true); + SetupTaskFactory(TaskHostParameters.Empty, true /* want task host */, true); - createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null, + createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), TaskHostParameters.Empty, #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -584,13 +560,11 @@ public void VerifyExplicitlyLaunchTaskHostEvenIfParametersMatch1() ITask createdTask = null; try { - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any); + TaskHostParameters taskParameters = new (XMakeAttributes.MSBuildRuntimeValues.any, XMakeAttributes.MSBuildArchitectureValues.any); SetupTaskFactory(taskParameters, true /* want task host */, isTaskHostFactory: true); - createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null, + createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), TaskHostParameters.Empty, #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -619,11 +593,9 @@ public void VerifyExplicitlyLaunchTaskHostEvenIfParametersMatch2() ITask createdTask = null; try { - SetupTaskFactory(null, true /* want task host */, true); + SetupTaskFactory(TaskHostParameters.Empty, true /* want task host */, true); - IDictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any); + TaskHostParameters taskParameters = new (XMakeAttributes.MSBuildRuntimeValues.any, XMakeAttributes.MSBuildArchitectureValues.any); createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), taskParameters, #if FEATURE_APPDOMAIN @@ -652,16 +624,14 @@ public void VerifyExplicitlyLaunchTaskHostEvenIfParametersMatch2() public void VerifySameFactoryCanGenerateDifferentTaskInstances() { ITask createdTask = null; - IDictionary factoryParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - factoryParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.any); - factoryParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.any); + TaskHostParameters factoryParameters = new TaskHostParameters(XMakeAttributes.MSBuildRuntimeValues.any, XMakeAttributes.MSBuildArchitectureValues.any); SetupTaskFactory(factoryParameters, explicitlyLaunchTaskHost: false, isTaskHostFactory: false); try { // #1: don't launch task host - createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), null, + createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), TaskHostParameters.Empty, #if FEATURE_APPDOMAIN new AppDomainSetup(), #endif @@ -682,9 +652,7 @@ public void VerifySameFactoryCanGenerateDifferentTaskInstances() try { // #2: launch task host - var taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr2); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); + TaskHostParameters taskParameters = new(XMakeAttributes.MSBuildRuntimeValues.clr2, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture); createdTask = _taskFactory.CreateTaskInstance(ElementLocation.Create("MSBUILD"), null, new MockHost(), taskParameters, #if FEATURE_APPDOMAIN @@ -709,18 +677,19 @@ public void VerifySameFactoryCanGenerateDifferentTaskInstances() /// Abstract out the creation of the new AssemblyTaskFactory with default task, and /// with some basic validation. /// - private void SetupTaskFactory(IDictionary factoryParameters, bool explicitlyLaunchTaskHost = false, bool isTaskHostFactory = false) + private void SetupTaskFactory(TaskHostParameters factoryParameters, bool explicitlyLaunchTaskHost = false, bool isTaskHostFactory = false) { _taskFactory = new AssemblyTaskFactory(); #if FEATURE_ASSEMBLY_LOCATION _loadInfo = AssemblyLoadInfo.Create(null, Assembly.GetAssembly(typeof(TaskToTestFactories)).Location); #else - _loadInfo = AssemblyLoadInfo.Create(typeof(TaskToTestFactories).GetTypeInfo().Assembly.FullName, null); + _loadInfo = explicitlyLaunchTaskHost || isTaskHostFactory + ? AssemblyLoadInfo.Create(assemblyName: null, typeof(TaskToTestFactories).GetTypeInfo().Assembly.Location) + : AssemblyLoadInfo.Create(typeof(TaskToTestFactories).GetTypeInfo().Assembly.FullName, assemblyFile: null); #endif if (explicitlyLaunchTaskHost) { - factoryParameters ??= new Dictionary(); - factoryParameters.Add(Internal.Constants.TaskHostExplicitlyRequested, "true"); + factoryParameters = TaskHostParameters.MergeTaskHostParameters(factoryParameters, new TaskHostParameters(taskHostFactoryExplicitlyRequested: true)); } _loadedType = _taskFactory.InitializeFactory(_loadInfo, "TaskToTestFactories", new Dictionary(), string.Empty, factoryParameters, explicitlyLaunchTaskHost, null, ElementLocation.Create("NONE"), String.Empty); Assert.True(_loadedType.Assembly.Equals(_loadInfo)); // "Expected the AssemblyLoadInfo to be equal" diff --git a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs index b36206e3751..fa3e238ac69 100644 --- a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs @@ -997,8 +997,8 @@ public void TestTaskResolutionFailureWithUsingTask() #endif false, CancellationToken.None); - _host.FindTask(null); - _host.InitializeForBatch(new TaskLoggingContext(_loggingService, tlc.BuildEventContext), _bucket, null, scheduledNodeId: 1); + _host.FindTask(TaskHostParameters.Empty); + _host.InitializeForBatch(new TaskLoggingContext(_loggingService, tlc.BuildEventContext), _bucket, TaskHostParameters.Empty, scheduledNodeId: 1); }); } /// @@ -1026,8 +1026,8 @@ public void TestTaskResolutionFailureWithNoUsingTask() false, CancellationToken.None); - _host.FindTask(null); - _host.InitializeForBatch(new TaskLoggingContext(_loggingService, tlc.BuildEventContext), _bucket, null, scheduledNodeId: 1); + _host.FindTask(TaskHostParameters.Empty); + _host.InitializeForBatch(new TaskLoggingContext(_loggingService, tlc.BuildEventContext), _bucket, TaskHostParameters.Empty, scheduledNodeId: 1); _logger.AssertLogContains("MSB4036"); } @@ -1254,7 +1254,7 @@ private void InitializeHost(bool throwOnExecute) TaskBuilderTestTask.TaskBuilderTestTaskFactory taskFactory = new TaskBuilderTestTask.TaskBuilderTestTaskFactory(); taskFactory.ThrowOnExecute = throwOnExecute; string taskName = "TaskBuilderTestTask"; - (_host as TaskExecutionHost)._UNITTESTONLY_TaskFactoryWrapper = new TaskFactoryWrapper(taskFactory, loadedType, taskName, null); + (_host as TaskExecutionHost)._UNITTESTONLY_TaskFactoryWrapper = new TaskFactoryWrapper(taskFactory, loadedType, taskName, TaskHostParameters.Empty); _host.InitializeForTask( this, tlc, @@ -1289,8 +1289,8 @@ private void InitializeHost(bool throwOnExecute) _bucket = new ItemBucket(FrozenSet.Empty, new Dictionary(), new Lookup(itemsByName, new PropertyDictionary()), 0); _bucket.Initialize(null); - _host.FindTask(null); - _host.InitializeForBatch(talc, _bucket, null, scheduledNodeId: 1); + _host.FindTask(TaskHostParameters.Empty); + _host.InitializeForBatch(talc, _bucket, TaskHostParameters.Empty, scheduledNodeId: 1); _parametersSetOnTask = new Dictionary(StringComparer.OrdinalIgnoreCase); _outputsReadFromTask = new Dictionary(StringComparer.OrdinalIgnoreCase); } diff --git a/src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs index e11032cd36c..831101e51fd 100644 --- a/src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs @@ -4,6 +4,8 @@ using System; using System.Diagnostics; using System.Globalization; +using System.IO; +using System.Threading; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.UnitTests; @@ -23,6 +25,8 @@ namespace Microsoft.Build.Engine.UnitTests.BackEnd /// public sealed class TaskHostFactory_Tests { + private static string AssemblyLocation { get; } = Path.Combine(Path.GetDirectoryName(typeof(TaskHostFactory_Tests).Assembly.Location) ?? AppContext.BaseDirectory, "Microsoft.Build.Engine.UnitTests.dll"); + private ITestOutputHelper _output; public TaskHostFactory_Tests(ITestOutputHelper testOutputHelper) @@ -39,7 +43,7 @@ public TaskHostFactory_Tests(ITestOutputHelper testOutputHelper) /// Whether to set MSBUILDFORCEALLTASKSOUTOFPROC environment variable [Theory] [InlineData(true, false)] - [InlineData(false, true, Skip = "floating failure, it requires separate investigation")] + [InlineData(false, true)] [InlineData(true, true)] public void TaskNodesDieAfterBuild(bool taskHostFactorySpecified, bool envVariableSpecified) { @@ -48,7 +52,7 @@ public void TaskNodesDieAfterBuild(bool taskHostFactorySpecified, bool envVariab string taskFactory = taskHostFactorySpecified ? "TaskHostFactory" : "AssemblyTaskFactory"; string pidTaskProject = $@" - + @@ -93,11 +97,44 @@ public void TaskNodesDieAfterBuild(bool taskHostFactorySpecified, bool envVariab } else { - // This is the sidecar TaskHost case - it should persist after build is done. So we need to clean up and kill it ourselves. Process taskHostNode = Process.GetProcessById(pid); + + // This is the sidecar TaskHost case - it should persist after build is done. So we need to clean up and kill it ourselves. + // Wait for process to be responsive. The standard 3 secs can be not enough for the child process to start, let's try several times. + int attempts = 0; + while (attempts < 5) + { + try + { + if (taskHostNode.HasExited) + { + Assert.Fail($"TaskHost exited during startup with code: {taskHostNode.ExitCode}"); + } + + // Check if process has loaded its main module + if (taskHostNode.Modules.Count > 0) + { + break; + } + } + catch + { + // Process not ready yet + } + + Thread.Sleep(1000); + attempts++; + taskHostNode.Refresh(); + } + + // Now wait to ensure it stays alive bool processExited = taskHostNode.WaitForExit(3000); - processExited.ShouldBeFalse(); + processExited.ShouldBeFalse( + processExited + ? $"TaskHost should remain alive after build. TaskHost exited with code: {taskHostNode.ExitCode}" + : "TaskHost should remain alive after build for task host case."); + try { taskHostNode.Kill(); @@ -121,17 +158,17 @@ public void TransientAndSidecarNodeCanCoexist() { string pidTaskProject = $@" - - - - - - - - - - - + + + + + + + + + + + "; TransientTestFile project = env.CreateFile("testProject.csproj", pidTaskProject); diff --git a/src/Build.UnitTests/BackEnd/TaskRegistry_Tests.cs b/src/Build.UnitTests/BackEnd/TaskRegistry_Tests.cs index 1e870839811..6c4b7461b8a 100644 --- a/src/Build.UnitTests/BackEnd/TaskRegistry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskRegistry_Tests.cs @@ -114,7 +114,7 @@ public void RegisterTaskSimple() foreach (ProjectUsingTaskElement taskElement in elementList) { - List registrationRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(taskElement.TaskName, null)]; + List registrationRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(taskElement.TaskName, TaskHostParameters.Empty)]; Assert.NotNull(registrationRecords); // "Task registrationrecord not found in TaskRegistry.TaskRegistrations!" Assert.Single(registrationRecords); // "Expected only one record registered under this TaskName!" @@ -154,7 +154,7 @@ public void RegisterMultipleTasksWithDifferentNames() foreach (ProjectUsingTaskElement taskElement in elementList) { - List registrationRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(taskElement.TaskName, null)]; + List registrationRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(taskElement.TaskName, TaskHostParameters.Empty)]; Assert.NotNull(registrationRecords); // "Task registrationrecord not found in TaskRegistry.TaskRegistrations!" Assert.Single(registrationRecords); // "Expected only one record registered under this TaskName!" @@ -198,7 +198,7 @@ public void RegisterMultipleTasksSomeWithSameName() Assert.Equal(2, registry.TaskRegistrations.Count); // "Expected only two buckets since two of three tasks have the same name!" // Now let's look at the bucket with only one task - List singletonBucket = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(elementList[1].TaskName, null)]; + List singletonBucket = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(elementList[1].TaskName, TaskHostParameters.Empty)]; Assert.NotNull(singletonBucket); // "Record not found in TaskRegistry.TaskRegistrations!" Assert.Single(singletonBucket); // "Expected only Record registered under this TaskName!" AssemblyLoadInfo singletonAssemblyLoadInfo = singletonBucket[0].TaskFactoryAssemblyLoadInfo; @@ -207,7 +207,7 @@ public void RegisterMultipleTasksSomeWithSameName() Assert.Equal(singletonAssemblyLoadInfo, AssemblyLoadInfo.Create(assemblyName, assemblyFile)); // "Task record was not properly registered by TaskRegistry.RegisterTask!" // Now let's look at the bucket with two tasks - List duplicateBucket = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(elementList[0].TaskName, null)]; + List duplicateBucket = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(elementList[0].TaskName, TaskHostParameters.Empty)]; Assert.NotNull(duplicateBucket); // "Records not found in TaskRegistry.TaskRegistrations!" Assert.Equal(2, duplicateBucket.Count); // "Expected two Records registered under this TaskName!" @@ -261,7 +261,7 @@ public void RegisterMultipleTasksWithDifferentNamesFromSameAssembly() foreach (ProjectUsingTaskElement taskElement in elementList) { - List registrationRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(taskElement.TaskName, null)]; + List registrationRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(taskElement.TaskName, TaskHostParameters.Empty)]; Assert.NotNull(registrationRecords); // "Task registrationrecord not found in TaskRegistry.TaskRegistrations!" Assert.Single(registrationRecords); // "Expected only one record registered under this TaskName!" @@ -554,51 +554,6 @@ public void RetrieveFromCacheMatchingExactParameters() shouldBeRetrievedFromCache: true); } - /// - /// Validate task retrieval and exact cache retrieval when attempting to load - /// a task with parameters beyond just runtime and architecture. Hint: it shouldn't - /// ever work, since we don't currently have a way to create a using task with - /// parameters other than those two. - /// - [Fact] - public void RetrieveFromCacheMatchingExactParameters_AdditionalParameters() - { - Assert.NotNull(_testTaskLocation); // "Need a test task to run this test" - - List elementList = new List(); - ProjectRootElement project = ProjectRootElement.Create(); - - ProjectUsingTaskElement element = project.AddUsingTask(TestTaskName, _testTaskLocation, null); - element.Runtime = "CLR4"; - element.Architecture = "x86"; - elementList.Add(element); - - TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - - // Runtime and architecture match the using task exactly, but since there is an additional parameter, it still - // doesn't match when doing exact matching. - Dictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr4); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.x86); - taskParameters.Add("Foo", "Bar"); - - RetrieveAndValidateRegisteredTaskRecord( - registry, - true /* exact match */, - taskParameters, - shouldBeRetrieved: false, - shouldBeRetrievedFromCache: false); - - // However, it should still match itself -- so if we try again, we should get the "no match" - // back from the cache this time. - RetrieveAndValidateRegisteredTaskRecord( - registry, - true /* exact match */, - taskParameters, - shouldBeRetrieved: false, - shouldBeRetrievedFromCache: true); - } - [Theory] [InlineData("x64", "true", "x86", "", "x64")] // x64 wins [InlineData("x64", "false", "x86", "true", "x86")] // x86 wins @@ -1032,70 +987,6 @@ public void RetrieveFromCacheFuzzyMatchingParameters_ExactMatches() shouldBeRetrievedFromCache: true); } - /// - /// Validate task retrieval and exact cache retrieval when attempting to load - /// a task with parameters beyond just runtime and architecture. Hint: it shouldn't - /// ever work, since we don't currently have a way to create a using task with - /// parameters other than those two. - /// - [Fact] - public void RetrieveFromCacheFuzzyMatchingParameters_AdditionalParameters() - { - Assert.NotNull(_testTaskLocation); // "Need a test task to run this test" - - List elementList = new List(); - ProjectRootElement project = ProjectRootElement.Create(); - - ProjectUsingTaskElement element = project.AddUsingTask(TestTaskName, _testTaskLocation, null); - element.Runtime = "CLR4"; - element.Architecture = "x86"; - elementList.Add(element); - - TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - - // Runtime and architecture match, so even though we have the extra parameter, it should still match - Dictionary taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr4); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.x86); - taskParameters.Add("Foo", "Bar"); - - RetrieveAndValidateRegisteredTaskRecord( - registry, - false /* fuzzy match */, - taskParameters, - shouldBeRetrieved: true, - shouldBeRetrievedFromCache: false, - expectedRuntime: XMakeAttributes.MSBuildRuntimeValues.clr4, - expectedArchitecture: XMakeAttributes.MSBuildArchitectureValues.x86); - - // And if we try again, we should get it from the cache this time. - RetrieveAndValidateRegisteredTaskRecord( - registry, - false /* fuzzy match */, - taskParameters, - shouldBeRetrieved: true, - shouldBeRetrievedFromCache: true, - expectedRuntime: XMakeAttributes.MSBuildRuntimeValues.clr4, - expectedArchitecture: XMakeAttributes.MSBuildArchitectureValues.x86); - - taskParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - taskParameters.Add(XMakeAttributes.runtime, XMakeAttributes.MSBuildRuntimeValues.clr4); - taskParameters.Add(XMakeAttributes.architecture, XMakeAttributes.MSBuildArchitectureValues.x86); - taskParameters.Add("Baz", "Qux"); - - // Even with a different value to the additional parameter, because it's a fuzzy equals and because all - // our equivalence check looks for is runtime and architecture, it still successfully retrieves the - // existing record from the cache. - RetrieveAndValidateRegisteredTaskRecord( - registry, - false /* fuzzy match */, - taskParameters, - shouldBeRetrieved: true, - shouldBeRetrievedFromCache: true, - expectedRuntime: XMakeAttributes.MSBuildRuntimeValues.clr4, - expectedArchitecture: XMakeAttributes.MSBuildArchitectureValues.x86); - } - #endregion /// @@ -1140,7 +1031,7 @@ public void AllUsingTaskAttributesAreExpanded() expandedAssemblyFile = String.IsNullOrEmpty(expandedAssemblyFile) ? null : expandedAssemblyFile; expandedTaskFactory = String.IsNullOrEmpty(expandedTaskFactory) ? "AssemblyTaskFactory" : expandedTaskFactory; - List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(expandedtaskName, null)]; + List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(expandedtaskName, TaskHostParameters.Empty)]; Assert.NotNull(registeredTaskRecords); // "Task to be found in TaskRegistry.TaskRegistrations!" Assert.Single(registeredTaskRecords); // "Expected only one task registered under this TaskName!" @@ -1195,7 +1086,7 @@ public void TaskRegisteredOnlyIfConditionIsTrue() expandedAssemblyName = String.IsNullOrEmpty(expandedAssemblyName) ? null : expandedAssemblyName; expandedAssemblyFile = String.IsNullOrEmpty(expandedAssemblyFile) ? null : expandedAssemblyFile; - List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(expandedtaskName, null)]; + List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity(expandedtaskName, TaskHostParameters.Empty)]; Assert.NotNull(registeredTaskRecords); // "Task to be found in TaskRegistry.TaskRegistrations!" Assert.Single(registeredTaskRecords); // "Expected only one task registered under this TaskName!" @@ -1224,7 +1115,7 @@ public void NoChildrenElements() IDictionary> registeredTasks = registry.TaskRegistrations; ProjectUsingTaskElement taskElement = elementList[0]; - List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Hello", null)]; + List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Hello", TaskHostParameters.Empty)]; Assert.NotNull(registeredTaskRecords); // "Task to be found in TaskRegistry.TaskRegistrations!" Assert.Single(registeredTaskRecords); // "Expected only one task registered under this TaskName!" Assert.Empty(registeredTaskRecords[0].ParameterGroupAndTaskBody.UsingTaskParameters); @@ -1244,7 +1135,7 @@ public void TaskFactoryWithNullTaskTypeLogsError() TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - InvalidProjectFileException exception = Should.Throw(() => registry.GetRegisteredTask("Task1", "none", null, false, new TargetLoggingContext(_loggingService, new BuildEventContext(1, 1, BuildEventContext.InvalidProjectContextId, 1)), ElementLocation.Create("none", 1, 2), false)); + InvalidProjectFileException exception = Should.Throw(() => registry.GetRegisteredTask("Task1", "none", TaskHostParameters.Empty, false, new TargetLoggingContext(_loggingService, new BuildEventContext(1, 1, BuildEventContext.InvalidProjectContextId, 1)), ElementLocation.Create("none", 1, 2), false)); exception.ErrorCode.ShouldBe("MSB4175"); @@ -1276,7 +1167,7 @@ public void EmptyParameterGroup() IDictionary> registeredTasks = registry.TaskRegistrations; ProjectUsingTaskElement taskElement = elementList[0]; - List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)]; + List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)]; Assert.NotNull(registeredTaskRecords); // "Task to be found in TaskRegistry.TaskRegistrations!" Assert.Single(registeredTaskRecords); // "Expected only one task registered under this TaskName!" TaskRegistry.RegisteredTaskRecord.ParameterGroupAndTaskElementRecord inlineTaskRecord = registeredTaskRecords[0].ParameterGroupAndTaskBody; @@ -1312,7 +1203,7 @@ public void MultipleGoodParameters() IDictionary> registeredTasks = registry.TaskRegistrations; ProjectUsingTaskElement taskElement = elementList[0]; - List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)]; + List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)]; Assert.NotNull(registeredTaskRecords); // "Task to be found in TaskRegistry.TaskRegistrations!" Assert.Single(registeredTaskRecords); // "Expected only one task registered under this TaskName!" @@ -1348,7 +1239,7 @@ public void EmptyTypeOnParameter() List elementList = CreateParameterElementWithAttributes(output, required, type); TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"].PropertyType.Equals(typeof(String))); + Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"].PropertyType.Equals(typeof(String))); } /// @@ -1363,7 +1254,7 @@ public void NullTypeOnParameter() List elementList = CreateParameterElementWithAttributes(output, required, type); TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"].PropertyType.Equals(typeof(String))); + Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"].PropertyType.Equals(typeof(String))); } /// @@ -1573,7 +1464,7 @@ public void EmptyOutput() List elementList = CreateParameterElementWithAttributes(output, required, type); TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - Assert.False(((TaskPropertyInfo)registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"]).Output); + Assert.False(((TaskPropertyInfo)registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"]).Output); } /// @@ -1588,7 +1479,7 @@ public void NullOutput() List elementList = CreateParameterElementWithAttributes(output, required, type); TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - Assert.False(((TaskPropertyInfo)registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"]).Output); + Assert.False(((TaskPropertyInfo)registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"]).Output); } /// @@ -1620,7 +1511,7 @@ public void EmptyRequired() List elementList = CreateParameterElementWithAttributes(output, required, type); TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - Assert.False(((TaskPropertyInfo)registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"]).Required); + Assert.False(((TaskPropertyInfo)registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"]).Required); } /// @@ -1635,7 +1526,7 @@ public void NullRequired() List elementList = CreateParameterElementWithAttributes(output, required, type); TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - Assert.False(((TaskPropertyInfo)registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"]).Required); + Assert.False(((TaskPropertyInfo)registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"]).Required); } /// @@ -1685,7 +1576,7 @@ public void ExpandedGoodParameters() IDictionary> registeredTasks = registry.TaskRegistrations; ProjectUsingTaskElement taskElement = elementList[0]; - List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)]; + List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)]; Assert.NotNull(registeredTaskRecords); // "Task to be found in TaskRegistry.TaskRegistrations!" Assert.Single(registeredTaskRecords); // "Expected only one task registered under this TaskName!" @@ -1734,7 +1625,7 @@ public void ExpandedPropertyEvaluate() TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)]; + List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)]; Assert.Single(registeredTaskRecords); // "Expected only one task registered under this TaskName!" TaskRegistry.RegisteredTaskRecord.ParameterGroupAndTaskElementRecord inlineTaskRecord = registeredTaskRecords[0].ParameterGroupAndTaskBody; @@ -1758,7 +1649,7 @@ public void ExpandedItemEvaluate() TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)]; + List registeredTaskRecords = registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)]; Assert.Single(registeredTaskRecords); // "Expected only one task registered under this TaskName!" TaskRegistry.RegisteredTaskRecord.ParameterGroupAndTaskElementRecord inlineTaskRecord = registeredTaskRecords[0].ParameterGroupAndTaskBody; @@ -1777,7 +1668,7 @@ public void FalseEvaluateWithBody() TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); // Make sure when evaluate is false the string passed in is not expanded - Assert.False(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated.Equals(body)); + Assert.False(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated.Equals(body)); } /// @@ -1795,7 +1686,7 @@ public void EvaluateWithBody() TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); // Make sure when evaluate is false the string passed in is not expanded - Assert.False(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated.Equals(expandedBody)); + Assert.False(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated.Equals(expandedBody)); } /// @@ -1821,7 +1712,7 @@ public void FalseEvaluate() string evaluate = bool.FalseString; List elementList = CreateTaskBodyElementWithAttributes(evaluate, ""); TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - Assert.False(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated); + Assert.False(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated); } /// @@ -1833,7 +1724,7 @@ public void EmptyEvaluate() string evaluate = ""; List elementList = CreateTaskBodyElementWithAttributes(evaluate, ""); TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated); + Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated); } /// @@ -1845,7 +1736,7 @@ public void NullEvaluate() string evaluate = null; List elementList = CreateTaskBodyElementWithAttributes(evaluate, ""); TaskRegistry registry = CreateTaskRegistryAndRegisterTasks(elementList); - Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated); + Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.TaskBodyEvaluated); } #endregion @@ -2001,7 +1892,7 @@ public void TaskRegistryCanSerializeViaTranslator(List private void RetrieveAndValidateRegisteredTaskRecord( TaskRegistry registry, bool exactMatchRequired, - Dictionary taskParameters, + TaskHostParameters taskParameters, bool shouldBeRetrieved, bool shouldBeRetrievedFromCache, string expectedRuntime, @@ -2016,12 +1907,12 @@ private void RetrieveAndValidateRegisteredTaskRecord( if (expectedRuntime != null) { - Assert.Equal(expectedRuntime, record.TaskFactoryParameters[XMakeAttributes.runtime]); + Assert.Equal(expectedRuntime, record.TaskFactoryParameters.Runtime); } if (expectedArchitecture != null) { - Assert.Equal(expectedArchitecture, record.TaskFactoryParameters[XMakeAttributes.architecture]); + Assert.Equal(expectedArchitecture, record.TaskFactoryParameters.Architecture); } } else @@ -2041,50 +1932,24 @@ private void RetrieveAndValidateRegisteredTaskRecord( /// values as its factory parameters. /// private void RetrieveAndValidateRegisteredTaskRecord( - TaskRegistry registry, - bool exactMatchRequired, - string runtime, - string architecture, - bool shouldBeRetrieved, - bool shouldBeRetrievedFromCache, - string expectedRuntime, - string expectedArchitecture) - { - Dictionary parameters = null; + TaskRegistry registry, + bool exactMatchRequired, + string runtime, + string architecture, + bool shouldBeRetrieved, + bool shouldBeRetrievedFromCache, + string expectedRuntime, + string expectedArchitecture) + { + TaskHostParameters parameters = TaskHostParameters.Empty; if (runtime != null || architecture != null) { - parameters = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - {XMakeAttributes.runtime, runtime ?? XMakeAttributes.MSBuildRuntimeValues.any}, - {XMakeAttributes.architecture, architecture ?? XMakeAttributes.MSBuildArchitectureValues.any} - }; + parameters = new(runtime ?? XMakeAttributes.MSBuildRuntimeValues.any, architecture ?? XMakeAttributes.MSBuildArchitectureValues.any); } RetrieveAndValidateRegisteredTaskRecord(registry, exactMatchRequired, parameters, shouldBeRetrieved, shouldBeRetrievedFromCache, expectedRuntime, expectedArchitecture); } - /// - /// With the given task registry, retrieve a copy of the test task with the given runtime and - /// architecture and verify: - /// - that it was retrieved (or not) as expected - /// - that it was retrieved from the cache (or not) as expected - /// - private void RetrieveAndValidateRegisteredTaskRecord(TaskRegistry registry, bool exactMatchRequired, Dictionary taskParameters, bool shouldBeRetrieved, bool shouldBeRetrievedFromCache) - { - // if we're requiring an exact match, we can cheat and figure out what the expected runtime / architecture should be. - // if not, then if the user didn't pass us an expected runtime, we can't really check it, so just pass - // null (which will be treated as "don't validate"). - string expectedRuntime = null; - string expectedArchitecture = null; - if (exactMatchRequired) - { - taskParameters.TryGetValue(XMakeAttributes.runtime, out expectedRuntime); - taskParameters.TryGetValue(XMakeAttributes.architecture, out expectedArchitecture); - } - - RetrieveAndValidateRegisteredTaskRecord(registry, exactMatchRequired, taskParameters, shouldBeRetrieved, shouldBeRetrievedFromCache, expectedRuntime, expectedArchitecture); - } - /// /// With the given task registry, retrieve a copy of the test task with the given runtime and /// architecture and verify: @@ -2125,7 +1990,7 @@ private void VerifyTypeParameter(string output, string required, string type) true /* case-insensitive */); } - Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", null)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"].PropertyType.Equals(paramType)); + Assert.True(registry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("Name", TaskHostParameters.Empty)][0].ParameterGroupAndTaskBody.UsingTaskParameters["ParameterWithAllAttributesHardCoded"].PropertyType.Equals(paramType)); } /// diff --git a/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs b/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs index 115f418e3c3..23d5fc1aa63 100644 --- a/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs +++ b/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs @@ -47,25 +47,25 @@ public void OverrideTasksAreFoundInOverridePath() foreach (string expectedRegisteredTask in expectedRegisteredTasks) { - Assert.True(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(expectedRegisteredTask, null)), + Assert.True(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(expectedRegisteredTask, TaskHostParameters.Empty)), String.Format("Expected task '{0}' registered!", expectedRegisteredTask)); } foreach (string expectedRegisteredTask in expectedOverrideTasks) { - Assert.True(taskoverrideRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(expectedRegisteredTask, null)), + Assert.True(taskoverrideRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(expectedRegisteredTask, TaskHostParameters.Empty)), String.Format("Expected task '{0}' registered!", expectedRegisteredTask)); } foreach (string unexpectedRegisteredTask in unexpectedRegisteredTasks) { - Assert.False(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(unexpectedRegisteredTask, null)), + Assert.False(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(unexpectedRegisteredTask, TaskHostParameters.Empty)), String.Format("Unexpected task '{0}' registered!", unexpectedRegisteredTask)); } foreach (string unexpectedRegisteredTask in unexpectedOverrideRegisteredTasks) { - Assert.False(taskoverrideRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(unexpectedRegisteredTask, null)), + Assert.False(taskoverrideRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(unexpectedRegisteredTask, TaskHostParameters.Empty)), String.Format("Unexpected task '{0}' registered!", unexpectedRegisteredTask)); } } @@ -172,12 +172,12 @@ public void DefaultTasksAreFoundInToolsPath() foreach (string expectedRegisteredTask in expectedRegisteredTasks) { - Assert.True(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(expectedRegisteredTask, null)), + Assert.True(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(expectedRegisteredTask, TaskHostParameters.Empty)), String.Format("Expected task '{0}' registered!", expectedRegisteredTask)); } foreach (string unexpectedRegisteredTask in unexpectedRegisteredTasks) { - Assert.False(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(unexpectedRegisteredTask, null)), + Assert.False(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(unexpectedRegisteredTask, TaskHostParameters.Empty)), String.Format("Unexpected task '{0}' registered!", unexpectedRegisteredTask)); } } @@ -202,7 +202,7 @@ public void WarningLoggedIfNoDefaultTasksFound() Assert.Equal(1, mockLogger.WarningCount); // "Expected 1 warning logged!" foreach (string unexpectedRegisteredTask in unexpectedRegisteredTasks) { - Assert.False(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(unexpectedRegisteredTask, null)), + Assert.False(taskRegistry.TaskRegistrations.ContainsKey(new TaskRegistry.RegisteredTaskIdentity(unexpectedRegisteredTask, TaskHostParameters.Empty)), String.Format("Unexpected task '{0}' registered!", unexpectedRegisteredTask)); } } diff --git a/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs b/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs index 49fc0f9bfad..fd8ba8841d0 100644 --- a/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs +++ b/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs @@ -70,10 +70,10 @@ public void GetTaskRegistrations() ProjectInstance project = new Project(projectRootElementFromString.Project).CreateProjectInstance(); project.TaskRegistry.TaskRegistrations.Count.ShouldBe(3); - project.TaskRegistry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("t0", null)][0].TaskFactoryAssemblyLoadInfo.AssemblyFile.ShouldBe(Path.Combine(Directory.GetCurrentDirectory(), "af0")); - project.TaskRegistry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("t1", null)][0].TaskFactoryAssemblyLoadInfo.AssemblyFile.ShouldBe(Path.Combine(Directory.GetCurrentDirectory(), "af1a")); - project.TaskRegistry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("t1", null)][1].TaskFactoryAssemblyLoadInfo.AssemblyName.ShouldBe("an1"); - project.TaskRegistry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("t2", null)][0].TaskFactoryAssemblyLoadInfo.AssemblyName.ShouldBe("an2"); + project.TaskRegistry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("t0", TaskHostParameters.Empty)][0].TaskFactoryAssemblyLoadInfo.AssemblyFile.ShouldBe(Path.Combine(Directory.GetCurrentDirectory(), "af0")); + project.TaskRegistry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("t1", TaskHostParameters.Empty)][0].TaskFactoryAssemblyLoadInfo.AssemblyFile.ShouldBe(Path.Combine(Directory.GetCurrentDirectory(), "af1a")); + project.TaskRegistry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("t1", TaskHostParameters.Empty)][1].TaskFactoryAssemblyLoadInfo.AssemblyName.ShouldBe("an1"); + project.TaskRegistry.TaskRegistrations[new TaskRegistry.RegisteredTaskIdentity("t2", TaskHostParameters.Empty)][0].TaskFactoryAssemblyLoadInfo.AssemblyName.ShouldBe("an2"); } finally { diff --git a/src/Build.UnitTests/NetTaskHost_E2E_Tests.cs b/src/Build.UnitTests/NetTaskHost_E2E_Tests.cs index 195fcae8edc..5c17f584c8d 100644 --- a/src/Build.UnitTests/NetTaskHost_E2E_Tests.cs +++ b/src/Build.UnitTests/NetTaskHost_E2E_Tests.cs @@ -153,7 +153,7 @@ public void NetTaskWithImplicitHostParamsTest() successTestTask.ShouldBeTrue(); testTaskOutput.ShouldContain($"The task is executed in process: dotnet"); testTaskOutput.ShouldContain($"Process path: {dotnetPath}", customMessage: testTaskOutput); - testTaskOutput.ShouldContain("/nodereuse:False"); + testTaskOutput.ShouldContain("/nodereuse:True"); } } } diff --git a/src/Build.UnitTests/TestComparers/TaskRegistryComparers.cs b/src/Build.UnitTests/TestComparers/TaskRegistryComparers.cs index 31360a91580..6cadd556476 100644 --- a/src/Build.UnitTests/TestComparers/TaskRegistryComparers.cs +++ b/src/Build.UnitTests/TestComparers/TaskRegistryComparers.cs @@ -50,14 +50,14 @@ public bool Equals(TaskRegistry.RegisteredTaskRecord x, TaskRegistry.RegisteredT Assert.Equal(x.TaskFactoryAttributeName, y.TaskFactoryAttributeName); Assert.Equal(x.ParameterGroupAndTaskBody, y.ParameterGroupAndTaskBody, new ParamterGroupAndTaskBodyComparer()); - Helpers.AssertDictionariesEqual( - x.TaskFactoryParameters, - y.TaskFactoryParameters, - (xp, yp) => - { - Assert.Equal(xp.Key, yp.Key); - Assert.Equal(xp.Value, yp.Value); - }); + // Assert TaskFactoryParameters equality + var xParams = x.TaskFactoryParameters; + var yParams = y.TaskFactoryParameters; + Assert.Equal(xParams.Runtime, yParams.Runtime); + Assert.Equal(xParams.Architecture, yParams.Architecture); + Assert.Equal(xParams.DotnetHostPath, yParams.DotnetHostPath); + Assert.Equal(xParams.MSBuildAssemblyPath, yParams.MSBuildAssemblyPath); + Assert.Equal(xParams.TaskHostFactoryExplicitlyRequested, yParams.TaskHostFactoryExplicitlyRequested); return true; } diff --git a/src/Build/BackEnd/Client/MSBuildClient.cs b/src/Build/BackEnd/Client/MSBuildClient.cs index d5c4766c2d8..9bd05788271 100644 --- a/src/Build/BackEnd/Client/MSBuildClient.cs +++ b/src/Build/BackEnd/Client/MSBuildClient.cs @@ -534,10 +534,10 @@ private ServerNodeBuildCommand GetServerNodeBuildCommand() partialBuildTelemetry); } - private ServerNodeHandshake GetHandshake() - { - return new ServerNodeHandshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture())); - } + private ServerNodeHandshake GetHandshake() => new(CommunicationsUtilities.GetHandshakeOptions( + taskHost: false, + taskHostParameters: TaskHostParameters.Empty, + architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture())); /// /// Handle cancellation. diff --git a/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs b/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs index 2a7f57ece4e..437d741d41b 100644 --- a/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs +++ b/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs @@ -1,6 +1,7 @@ // 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.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; @@ -35,6 +36,7 @@ protected override Handshake GetHandshake() { HandshakeOptions handshakeOptions = CommunicationsUtilities.GetHandshakeOptions( taskHost: false, + taskHostParameters: TaskHostParameters.Empty, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture(), nodeReuse: _enableReuse, lowPriority: LowPriority); diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs index dbafe43db2a..ef893070292 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs @@ -70,7 +70,7 @@ public int AvailableNodes internal static Handshake GetHandshake(bool enableNodeReuse, bool enableLowPriority) { CommunicationsUtilities.Trace("MSBUILDNODEHANDSHAKESALT=\"{0}\", msbuildDirectory=\"{1}\", enableNodeReuse={2}, enableLowPriority={3}", Traits.MSBuildNodeHandshakeSalt, BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32, enableNodeReuse, enableLowPriority); - return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture(), nodeReuse: enableNodeReuse, lowPriority: enableLowPriority)); + return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, taskHostParameters: TaskHostParameters.Empty, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture(), nodeReuse: enableNodeReuse, lowPriority: enableLowPriority)); } /// @@ -98,7 +98,7 @@ public IList CreateNodes(int nextNodeId, INodePacketFactory factory, F CommunicationsUtilities.Trace("Starting to acquire {1} new or existing node(s) to establish nodes from ID {0} to {2}...", nextNodeId, numberOfNodesToCreate, nextNodeId + numberOfNodesToCreate - 1); - Handshake hostHandshake = new(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture(), nodeReuse: ComponentHost.BuildParameters.EnableNodeReuse, lowPriority: ComponentHost.BuildParameters.LowPriority)); + Handshake hostHandshake = new(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, taskHostParameters: TaskHostParameters.Empty, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture(), nodeReuse: ComponentHost.BuildParameters.EnableNodeReuse, lowPriority: ComponentHost.BuildParameters.LowPriority)); IList nodeContexts = GetNodes(null, commandLineArgs, nextNodeId, factory, hostHandshake, NodeContextCreated, NodeContextTerminated, numberOfNodesToCreate); if (nodeContexts.Count > 0) diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs index b93889a94b5..344a24663ad 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using Microsoft.Build.Exceptions; +using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; @@ -450,23 +451,20 @@ internal static string GetMSBuildExecutablePathForNonNETRuntimes(HandshakeOption /// - RuntimeHostPath: The path to the dotnet executable that will host the .NET runtime /// - MSBuildAssemblyPath: The full path to MSBuild.dll that will be loaded by the dotnet host. /// - internal static (string RuntimeHostPath, string MSBuildAssemblyPath) GetMSBuildLocationForNETRuntime(HandshakeOptions hostContext, Dictionary taskHostParameters) + internal static (string RuntimeHostPath, string MSBuildAssemblyPath) GetMSBuildLocationForNETRuntime(HandshakeOptions hostContext, TaskHostParameters taskHostParameters) { ErrorUtilities.VerifyThrowInternalErrorUnreachable(Handshake.IsHandshakeOptionEnabled(hostContext, HandshakeOptions.TaskHost)); - taskHostParameters.TryGetValue(Constants.DotnetHostPath, out string runtimeHostPath); - var msbuildAssemblyPath = GetMSBuildAssemblyPath(taskHostParameters); - - return (runtimeHostPath, msbuildAssemblyPath); + return (taskHostParameters.DotnetHostPath, GetMSBuildAssemblyPath(taskHostParameters)); } - private static string GetMSBuildAssemblyPath(Dictionary taskHostParameters) + private static string GetMSBuildAssemblyPath(TaskHostParameters taskHostParameters) { - if (taskHostParameters.TryGetValue(Constants.MSBuildAssemblyPath, out string msbuildAssemblyPath)) + if (taskHostParameters.MSBuildAssemblyPath != null) { - ValidateNetHostSdkVersion(msbuildAssemblyPath); + ValidateNetHostSdkVersion(taskHostParameters.MSBuildAssemblyPath); - return msbuildAssemblyPath; + return taskHostParameters.MSBuildAssemblyPath; } throw new InvalidProjectFileException(ResourceUtilities.GetResourceString("NETHostTaskLoad_Failed")); @@ -572,7 +570,7 @@ internal bool AcquireAndSetUpHost( INodePacketFactory factory, INodePacketHandler handler, TaskHostConfiguration configuration, - Dictionary taskHostParameters) + TaskHostParameters taskHostParameters) { bool nodeCreationSucceeded; if (!_nodeContexts.ContainsKey(taskHostNodeId)) @@ -613,7 +611,7 @@ internal void DisconnectFromHost(int nodeId) /// /// Instantiates a new MSBuild or MSBuildTaskHost process acting as a child node. /// - internal bool CreateNode(HandshakeOptions hostContext, int taskHostNodeId, INodePacketFactory factory, INodePacketHandler handler, TaskHostConfiguration configuration, Dictionary taskHostParameters) + internal bool CreateNode(HandshakeOptions hostContext, int taskHostNodeId, INodePacketFactory factory, INodePacketHandler handler, TaskHostConfiguration configuration, TaskHostParameters taskHostParameters) { ErrorUtilities.VerifyThrowArgumentNull(factory); ErrorUtilities.VerifyThrow(!_nodeIdToPacketFactory.ContainsKey(taskHostNodeId), "We should not already have a factory for this context! Did we forget to call DisconnectFromHost somewhere?"); diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index 2d2958b557d..12890a9124b 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -421,7 +421,7 @@ private async ValueTask ExecuteBucket(TaskHost taskHost, ItemBuc if (howToExecuteTask == TaskExecutionMode.ExecuteTaskAndGatherOutputs) { // We need to find the task before logging the task started event so that the using task statement comes before the task started event - IDictionary taskIdentityParameters = GatherTaskIdentityParameters(bucket.Expander); + TaskHostParameters taskIdentityParameters = GatherTaskIdentityParameters(bucket.Expander); (TaskRequirements? requirements, TaskFactoryWrapper taskFactoryWrapper) = _taskExecutionHost.FindTask(taskIdentityParameters); string taskAssemblyLocation = taskFactoryWrapper?.TaskFactoryLoadedType?.Path; @@ -527,29 +527,24 @@ private async ValueTask ExecuteBucket(TaskHost taskHost, ItemBuc /// /// Returns the set of parameters that can contribute to a task's identity, and their values for this particular task. /// - private IDictionary GatherTaskIdentityParameters(Expander expander) + private TaskHostParameters GatherTaskIdentityParameters(Expander expander) { ErrorUtilities.VerifyThrowInternalNull(_taskNode, "taskNode"); // taskNode should never be null when we're calling this method. string msbuildArchitecture = expander.ExpandIntoStringAndUnescape(_taskNode.MSBuildArchitecture ?? String.Empty, ExpanderOptions.ExpandAll, _taskNode.MSBuildArchitectureLocation ?? ElementLocation.EmptyLocation); string msbuildRuntime = expander.ExpandIntoStringAndUnescape(_taskNode.MSBuildRuntime ?? String.Empty, ExpanderOptions.ExpandAll, _taskNode.MSBuildRuntimeLocation ?? ElementLocation.EmptyLocation); - IDictionary taskIdentityParameters = null; - // only bother to create a task identity parameter set if we're putting anything in there -- otherwise, // a null set will be treated as equivalent to all parameters being "don't care". if (msbuildRuntime != string.Empty || msbuildArchitecture != string.Empty) { - taskIdentityParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - msbuildArchitecture = msbuildArchitecture == string.Empty ? XMakeAttributes.MSBuildArchitectureValues.any : msbuildArchitecture.Trim(); msbuildRuntime = msbuildRuntime == string.Empty ? XMakeAttributes.MSBuildRuntimeValues.any : msbuildRuntime.Trim(); - taskIdentityParameters.Add(XMakeAttributes.runtime, msbuildRuntime); - taskIdentityParameters.Add(XMakeAttributes.architecture, msbuildArchitecture); + return new TaskHostParameters(msbuildRuntime, msbuildArchitecture); } - return taskIdentityParameters; + return TaskHostParameters.Empty; } #if FEATURE_APARTMENT_STATE @@ -561,7 +556,7 @@ private IDictionary GatherTaskIdentityParameters(Expander [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is caught and rethrown in the correct thread.")] - private WorkUnitResult ExecuteTaskInSTAThread(ItemBucket bucket, TaskLoggingContext taskLoggingContext, IDictionary taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask) + private WorkUnitResult ExecuteTaskInSTAThread(ItemBucket bucket, TaskLoggingContext taskLoggingContext, TaskHostParameters taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask) { WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null); Thread staThread = null; @@ -656,7 +651,7 @@ private void ExecuteIntrinsicTask(ItemBucket bucket) /// /// Initializes and executes the task. /// - private async Task InitializeAndExecuteTask(TaskLoggingContext taskLoggingContext, ItemBucket bucket, IDictionary taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask) + private async Task InitializeAndExecuteTask(TaskLoggingContext taskLoggingContext, ItemBucket bucket, TaskHostParameters taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask) { if (!_taskExecutionHost.InitializeForBatch(taskLoggingContext, bucket, taskIdentityParameters, _buildRequestEntry.Request.ScheduledNodeId)) { diff --git a/src/Build/BackEnd/Node/OutOfProcServerNode.cs b/src/Build/BackEnd/Node/OutOfProcServerNode.cs index 8b562b1d433..e5b1e76f412 100644 --- a/src/Build/BackEnd/Node/OutOfProcServerNode.cs +++ b/src/Build/BackEnd/Node/OutOfProcServerNode.cs @@ -99,7 +99,7 @@ public OutOfProcServerNode(BuildCallback buildFunction) public NodeEngineShutdownReason Run(out Exception? shutdownException) { ServerNodeHandshake handshake = new( - CommunicationsUtilities.GetHandshakeOptions(taskHost: false, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture())); + CommunicationsUtilities.GetHandshakeOptions(taskHost: false, taskHostParameters: TaskHostParameters.Empty, architectureFlagToSet: XMakeAttributes.GetCurrentMSBuildArchitecture())); _serverBusyMutexName = GetBusyServerMutexName(handshake); diff --git a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs index 14c3b5534dd..9adb34867c7 100644 --- a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs +++ b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs @@ -271,7 +271,7 @@ public void InitializeForTask(IBuildEngine2 buildEngine, TargetLoggingContext lo /// Ask the task host to find its task in the registry and get it ready for initializing the batch /// /// The task requirements and task factory wrapper if the task is found, (null, null) otherwise. - public (TaskRequirements? requirements, TaskFactoryWrapper taskFactoryWrapper) FindTask(IDictionary taskIdentityParameters) + public (TaskRequirements? requirements, TaskFactoryWrapper taskFactoryWrapper) FindTask(TaskHostParameters taskIdentityParameters) { _taskFactoryWrapper ??= FindTaskInRegistry(taskIdentityParameters); @@ -302,7 +302,7 @@ public void InitializeForTask(IBuildEngine2 buildEngine, TargetLoggingContext lo /// /// Initialize to run a specific batch of the current task. /// - public bool InitializeForBatch(TaskLoggingContext loggingContext, ItemBucket batchBucket, IDictionary taskIdentityParameters, int scheduledNodeId) + public bool InitializeForBatch(TaskLoggingContext loggingContext, ItemBucket batchBucket, TaskHostParameters taskIdentityParameters, int scheduledNodeId) { ErrorUtilities.VerifyThrowArgumentNull(loggingContext); @@ -886,7 +886,7 @@ private string[] GetValueOutputs(TaskPropertyInfo parameter) /// If the set of task identity parameters are defined, only tasks that match that identity are chosen. /// /// The Type of the task, or null if it was not found. - private TaskFactoryWrapper FindTaskInRegistry(IDictionary taskIdentityParameters) + private TaskFactoryWrapper FindTaskInRegistry(TaskHostParameters taskIdentityParameters) { if (!_intrinsicTasks.TryGetValue(_taskName, out TaskFactoryWrapper returnClass)) { @@ -897,11 +897,11 @@ private TaskFactoryWrapper FindTaskInRegistry(IDictionary taskId if (returnClass == null) { - returnClass = _projectInstance.TaskRegistry.GetRegisteredTask(_taskName, null, null, true /* exact match */, _targetLoggingContext, _taskLocation, _buildComponentHost?.BuildParameters?.MultiThreaded ?? false); + returnClass = _projectInstance.TaskRegistry.GetRegisteredTask(_taskName, null, TaskHostParameters.Empty, true /* exact match */, _targetLoggingContext, _taskLocation, _buildComponentHost?.BuildParameters?.MultiThreaded ?? false); if (returnClass == null) { - returnClass = _projectInstance.TaskRegistry.GetRegisteredTask(_taskName, null, null, false /* fuzzy match */, _targetLoggingContext, _taskLocation, _buildComponentHost?.BuildParameters?.MultiThreaded ?? false); + returnClass = _projectInstance.TaskRegistry.GetRegisteredTask(_taskName, null, TaskHostParameters.Empty, false /* fuzzy match */, _targetLoggingContext, _taskLocation, _buildComponentHost?.BuildParameters?.MultiThreaded ?? false); if (returnClass == null) { @@ -915,26 +915,14 @@ private TaskFactoryWrapper FindTaskInRegistry(IDictionary taskId } } - string usingTaskRuntime = null; - string usingTaskArchitecture = null; - - if (returnClass.FactoryIdentityParameters != null) - { - returnClass.FactoryIdentityParameters.TryGetValue(XMakeAttributes.runtime, out usingTaskRuntime); - returnClass.FactoryIdentityParameters.TryGetValue(XMakeAttributes.architecture, out usingTaskArchitecture); - } - - taskIdentityParameters.TryGetValue(XMakeAttributes.runtime, out string taskRuntime); - taskIdentityParameters.TryGetValue(XMakeAttributes.architecture, out string taskArchitecture); - _targetLoggingContext.LogError( new BuildEventFileInfo(_taskLocation), "TaskExistsButHasMismatchedIdentityError", _taskName, - usingTaskRuntime ?? XMakeAttributes.MSBuildRuntimeValues.any, - usingTaskArchitecture ?? XMakeAttributes.MSBuildArchitectureValues.any, - taskRuntime ?? XMakeAttributes.MSBuildRuntimeValues.any, - taskArchitecture ?? XMakeAttributes.MSBuildArchitectureValues.any); + returnClass.FactoryIdentityParameters.Runtime ?? XMakeAttributes.MSBuildRuntimeValues.any, + returnClass.FactoryIdentityParameters.Architecture ?? XMakeAttributes.MSBuildArchitectureValues.any, + taskIdentityParameters.Runtime ?? XMakeAttributes.MSBuildRuntimeValues.any, + taskIdentityParameters.Architecture ?? XMakeAttributes.MSBuildArchitectureValues.any); // if we've logged this error, even though we've found something, we want to act like we didn't. return null; @@ -945,13 +933,13 @@ private TaskFactoryWrapper FindTaskInRegistry(IDictionary taskId if (String.Equals(returnClass.TaskFactory.TaskType.FullName, "Microsoft.Build.Tasks.MSBuild", StringComparison.OrdinalIgnoreCase)) { Assembly taskExecutionHostAssembly = typeof(TaskExecutionHost).GetTypeInfo().Assembly; - returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(MSBuild)), new LoadedType(typeof(MSBuild), AssemblyLoadInfo.Create(taskExecutionHostAssembly.FullName, null), taskExecutionHostAssembly, typeof(ITaskItem)), _taskName, null); + returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(MSBuild)), new LoadedType(typeof(MSBuild), AssemblyLoadInfo.Create(taskExecutionHostAssembly.FullName, null), taskExecutionHostAssembly, typeof(ITaskItem)), _taskName, TaskHostParameters.Empty); _intrinsicTasks[_taskName] = returnClass; } else if (String.Equals(returnClass.TaskFactory.TaskType.FullName, "Microsoft.Build.Tasks.CallTarget", StringComparison.OrdinalIgnoreCase)) { Assembly taskExecutionHostAssembly = typeof(TaskExecutionHost).GetTypeInfo().Assembly; - returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(CallTarget)), new LoadedType(typeof(CallTarget), AssemblyLoadInfo.Create(taskExecutionHostAssembly.FullName, null), taskExecutionHostAssembly, typeof(ITaskItem)), _taskName, null); + returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(CallTarget)), new LoadedType(typeof(CallTarget), AssemblyLoadInfo.Create(taskExecutionHostAssembly.FullName, null), taskExecutionHostAssembly, typeof(ITaskItem)), _taskName, TaskHostParameters.Empty); _intrinsicTasks[_taskName] = returnClass; } } @@ -962,7 +950,7 @@ private TaskFactoryWrapper FindTaskInRegistry(IDictionary taskId /// /// Instantiates the task. /// - private ITask InstantiateTask(int scheduledNodeId, IDictionary taskIdentityParameters) + private ITask InstantiateTask(int scheduledNodeId, TaskHostParameters taskIdentityParameters) { ITask task = null; @@ -1006,12 +994,21 @@ private ITask InstantiateTask(int scheduledNodeId, IDictionary t task = CreateTaskHostTaskForOutOfProcFactory(taskIdentityParameters, taskFactoryEngineContext, outOfProcTaskFactory, scheduledNodeId); isTaskHost = true; } + + // Normal in-process execution for custom task factories else { - // Normal in-process execution for custom task factories - task = _taskFactoryWrapper.TaskFactory is ITaskFactory2 taskFactory2 ? - taskFactory2.CreateTask(taskFactoryEngineContext, taskIdentityParameters) : - _taskFactoryWrapper.TaskFactory.CreateTask(taskFactoryEngineContext); + // ITaskFactory2 is here for compat reasons + if (_taskFactoryWrapper.TaskFactory is ITaskFactory2 taskFactory2) + { + task = taskFactory2.CreateTask(taskFactoryEngineContext, taskIdentityParameters.ToDictionary()); + } + else + { + task = _taskFactoryWrapper.TaskFactory is ITaskFactory3 taskFactory3 + ? taskFactory3.CreateTask(taskFactoryEngineContext, taskIdentityParameters) + : _taskFactoryWrapper.TaskFactory.CreateTask(taskFactoryEngineContext); + } } // Track telemetry for non-AssemblyTaskFactory task factories @@ -1743,16 +1740,24 @@ private void DisplayCancelWaitMessage() /// Node for which the task host should be called /// A TaskHostTask that will execute the inner task out of process, or null if task creation fails. private ITask CreateTaskHostTaskForOutOfProcFactory( - IDictionary taskIdentityParameters, + TaskHostParameters taskIdentityParameters, TaskFactoryEngineContext taskFactoryEngineContext, IOutOfProcTaskFactory outOfProcTaskFactory, int scheduledNodeId) { ITask innerTask; - innerTask = _taskFactoryWrapper.TaskFactory is ITaskFactory2 taskFactory2 ? - taskFactory2.CreateTask(taskFactoryEngineContext, taskIdentityParameters) : - _taskFactoryWrapper.TaskFactory.CreateTask(taskFactoryEngineContext); + // ITaskFactory2 is used for compatibility reasons + if (_taskFactoryWrapper.TaskFactory is ITaskFactory2 taskFactory2) + { + innerTask = taskFactory2.CreateTask(taskFactoryEngineContext, taskIdentityParameters.ToDictionary()); + } + else + { + innerTask = _taskFactoryWrapper.TaskFactory is ITaskFactory3 taskFactory3 + ? taskFactory3.CreateTask(taskFactoryEngineContext, taskIdentityParameters) + : _taskFactoryWrapper.TaskFactory.CreateTask(taskFactoryEngineContext); + } if (innerTask == null) { @@ -1778,19 +1783,12 @@ private ITask CreateTaskHostTaskForOutOfProcFactory( typeof(ITaskItem)); // Default task host parameters for out-of-process execution for inline tasks - Dictionary taskHostParameters = new Dictionary - { - [XMakeAttributes.runtime] = XMakeAttributes.GetCurrentMSBuildRuntime(), - [XMakeAttributes.architecture] = XMakeAttributes.GetCurrentMSBuildArchitecture() - }; + TaskHostParameters taskHostParameters = new(XMakeAttributes.GetCurrentMSBuildRuntime(), XMakeAttributes.GetCurrentMSBuildArchitecture()); // Merge with any existing task identity parameters - if (taskIdentityParameters?.Count > 0) + if (!taskIdentityParameters.IsEmpty) { - foreach (var kvp in taskIdentityParameters.Where(kvp => !taskHostParameters.ContainsKey(kvp.Key))) - { - taskHostParameters[kvp.Key] = kvp.Value; - } + taskHostParameters = TaskHostParameters.MergeTaskHostParameters(taskHostParameters, taskIdentityParameters); } // Clean up the original task since we're going to wrap it @@ -1802,7 +1800,7 @@ private ITask CreateTaskHostTaskForOutOfProcFactory( _buildComponentHost, taskHostParameters, taskLoadedType, - true, + useSidecarTaskHost: false, #if FEATURE_APPDOMAIN AppDomainSetup, #endif diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index 3ad05875fa0..c464f1b2a05 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -502,11 +502,7 @@ internal static bool DoesTaskHostExist(string runtime, string architecture) runtime = XMakeAttributes.GetExplicitMSBuildRuntime(runtime); architecture = XMakeAttributes.GetExplicitMSBuildArchitecture(architecture); - Dictionary parameters = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { XMakeAttributes.runtime, runtime }, - { XMakeAttributes.architecture, architecture }, - }; + TaskHostParameters parameters = new(runtime, architecture); HandshakeOptions desiredContext = CommunicationsUtilities.GetHandshakeOptions(taskHost: true, taskHostParameters: parameters); diff --git a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs index 1b960fbd1c1..df9404eb74a 100644 --- a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs +++ b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs @@ -30,7 +30,7 @@ namespace Microsoft.Build.BackEnd /// /// The assembly task factory is used to wrap and construct tasks which are from .net assemblies. /// - internal class AssemblyTaskFactory : ITaskFactory2 + internal class AssemblyTaskFactory : ITaskFactory3 { #region Data @@ -57,17 +57,9 @@ internal class AssemblyTaskFactory : ITaskFactory2 #endif /// - /// the set of parameters owned by this particular task host + /// Parameters owned by this particular task host. /// - private IDictionary _factoryIdentityParameters; - - /// - /// Tracks whether, in the UsingTask invocation, we were specifically asked to use - /// the task host. If so, that overrides all other concerns, and we will launch - /// the task host even if the requested runtime / architecture match that of the - /// current MSBuild process. - /// - private bool _taskHostFactoryExplicitlyRequested; + private TaskHostParameters _factoryIdentityParameters; /// /// Need to store away the taskloggingcontext used by CreateTaskInstance so that @@ -75,13 +67,6 @@ internal class AssemblyTaskFactory : ITaskFactory2 /// private TaskLoggingContext _taskLoggingContext; - /// - /// If yes, then this is a task that will for any reason be run in a task host. - /// This might be due to the task not supporting the new single threaded interface - /// or due to the project requesting it to be run out of process. - /// - private bool _isTaskHostFactory; - #endregion /// @@ -258,36 +243,29 @@ public void CleanupTask(ITask task) #region Internal Members /// - /// Initialize the factory from the task registry + /// Initialize the factory from the task registry. /// internal LoadedType InitializeFactory( - AssemblyLoadInfo loadInfo, - string taskName, - IDictionary taskParameters, - string taskElementContents, - IDictionary taskFactoryIdentityParameters, - bool taskHostExplicitlyRequested, - TargetLoggingContext targetLoggingContext, - ElementLocation elementLocation, - string taskProjectFile) + AssemblyLoadInfo loadInfo, + string taskName, + IDictionary taskParameters, + string taskElementContents, + TaskHostParameters taskFactoryIdentityParameters, + bool taskHostExplicitlyRequested, + TargetLoggingContext targetLoggingContext, + ElementLocation elementLocation, + string taskProjectFile) { ErrorUtilities.VerifyThrowArgumentNull(loadInfo); VerifyThrowIdentityParametersValid(taskFactoryIdentityParameters, elementLocation, taskName, "Runtime", "Architecture"); bool taskHostParamsMatchCurrentProc = true; - if (taskFactoryIdentityParameters != null) + if (!taskFactoryIdentityParameters.IsEmpty) { taskHostParamsMatchCurrentProc = TaskHostParametersMatchCurrentProcess(taskFactoryIdentityParameters); - _factoryIdentityParameters = new Dictionary(taskFactoryIdentityParameters, StringComparer.OrdinalIgnoreCase); + _factoryIdentityParameters = taskFactoryIdentityParameters; } - _taskHostFactoryExplicitlyRequested = taskHostExplicitlyRequested; - - _isTaskHostFactory = (taskFactoryIdentityParameters != null - && taskFactoryIdentityParameters.TryGetValue(Constants.TaskHostExplicitlyRequested, out string isTaskHostFactory) - && isTaskHostFactory.Equals("true", StringComparison.OrdinalIgnoreCase)) - || !taskHostParamsMatchCurrentProc; - try { ErrorUtilities.VerifyThrowArgumentLength(taskName); @@ -296,7 +274,7 @@ internal LoadedType InitializeFactory( string assemblyName = loadInfo.AssemblyName ?? Path.GetFileName(loadInfo.AssemblyFile); using var assemblyLoadsTracker = AssemblyLoadsTracker.StartTracking(targetLoggingContext, AssemblyLoadingContext.TaskRun, assemblyName); - _loadedType = _typeLoader.Load(taskName, loadInfo, _taskHostFactoryExplicitlyRequested, taskHostParamsMatchCurrentProc); + _loadedType = _typeLoader.Load(taskName, loadInfo, taskHostExplicitlyRequested, taskHostParamsMatchCurrentProc); ProjectErrorUtilities.VerifyThrowInvalidProject(_loadedType != null, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, String.Empty); } catch (TargetInvocationException e) @@ -338,7 +316,7 @@ internal ITask CreateTaskInstance( ElementLocation taskLocation, TaskLoggingContext taskLoggingContext, IBuildComponentHost buildComponentHost, - IDictionary taskIdentityParameters, + TaskHostParameters taskIdentityParameters, #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif @@ -346,27 +324,22 @@ internal ITask CreateTaskInstance( int scheduledNodeId, Func getProperty) { - bool useTaskFactory = false; - Dictionary mergedParameters = null; + // If the type was loaded via MetadataLoadContext, we MUST use TaskFactory since it didn't load any task assemblies in memory. + bool useTaskFactory = _loadedType.LoadedViaMetadataLoadContext; + + TaskHostParameters mergedParameters = new(); _taskLoggingContext = taskLoggingContext; // Optimization for the common (vanilla AssemblyTaskFactory) case -- only calculate // the task factory parameters if we have any to calculate; otherwise even if we // still launch the task factory, it will be with parameters corresponding to the // current process. - if ((_factoryIdentityParameters?.Count > 0) || (taskIdentityParameters?.Count > 0)) + if (!_factoryIdentityParameters.IsEmpty || !taskIdentityParameters.IsEmpty) { VerifyThrowIdentityParametersValid(taskIdentityParameters, taskLocation, _taskName, "MSBuildRuntime", "MSBuildArchitecture"); mergedParameters = MergeTaskFactoryParameterSets(_factoryIdentityParameters, taskIdentityParameters); - useTaskFactory = _taskHostFactoryExplicitlyRequested || !TaskHostParametersMatchCurrentProcess(mergedParameters); - } - else - { - // if we don't have any task host parameters specified on either the using task or the - // task invocation, then we will run in-proc UNLESS "TaskHostFactory" is explicitly specified - // as the task factory. - useTaskFactory = _taskHostFactoryExplicitlyRequested; + useTaskFactory = _loadedType.LoadedViaMetadataLoadContext || !TaskHostParametersMatchCurrentProcess(mergedParameters); } // Multi-threaded mode routing: Determine if non-thread-safe tasks need TaskHost isolation. @@ -386,30 +359,21 @@ internal ITask CreateTaskInstance( { ErrorUtilities.VerifyThrowInternalNull(buildComponentHost); - mergedParameters ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + string runtime = mergedParameters.Runtime ?? XMakeAttributes.GetCurrentMSBuildRuntime(); + string architecture = mergedParameters.Architecture ?? XMakeAttributes.GetCurrentMSBuildArchitecture(); - if (!mergedParameters.ContainsKey(XMakeAttributes.runtime)) - { - mergedParameters[XMakeAttributes.runtime] = XMakeAttributes.GetCurrentMSBuildRuntime(); - } - - if (!mergedParameters.ContainsKey(XMakeAttributes.architecture)) - { - mergedParameters[XMakeAttributes.architecture] = XMakeAttributes.GetCurrentMSBuildArchitecture(); - } + (mergedParameters, bool isNetRuntime) = AddNetHostParamsIfNeeded(runtime, architecture, getProperty); - if (mergedParameters[XMakeAttributes.runtime].Equals(XMakeAttributes.MSBuildRuntimeValues.net)) - { - AddNetHostParams(ref mergedParameters, getProperty); - } + bool useSidecarTaskHost = !(_factoryIdentityParameters.TaskHostFactoryExplicitlyRequested ?? false) + || isNetRuntime; - TaskHostTask task = new TaskHostTask( + TaskHostTask task = new( taskLocation, taskLoggingContext, buildComponentHost, mergedParameters, _loadedType, - taskHostFactoryExplicitlyRequested: _isTaskHostFactory, + useSidecarTaskHost: useSidecarTaskHost, #if FEATURE_APPDOMAIN appDomainSetup, #endif @@ -467,7 +431,7 @@ internal ITask CreateTaskInstance( /// Is the given task name able to be created by the task factory. In the case of an assembly task factory /// this question is answered by checking the assembly wrapped by the task factory to see if it exists. /// - internal bool TaskNameCreatableByFactory(string taskName, IDictionary taskIdentityParameters, string taskProjectFile, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation) + internal bool TaskNameCreatableByFactory(string taskName, TaskHostParameters taskIdentityParameters, string taskProjectFile, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation) { if (!TaskIdentityParametersMatchFactory(_factoryIdentityParameters, taskIdentityParameters)) { @@ -519,45 +483,40 @@ internal bool TaskNameCreatableByFactory(string taskName, IDictionary /// Validates the given set of parameters, logging the appropriate errors as necessary. /// - private static void VerifyThrowIdentityParametersValid(IDictionary identityParameters, IElementLocation errorLocation, string taskName, string runtimeName, string architectureName) + private static void VerifyThrowIdentityParametersValid(TaskHostParameters identityParameters, IElementLocation errorLocation, string taskName, string runtimeName, string architectureName) { // validate the task factory parameters - if (identityParameters?.Count > 0) + if (identityParameters.Runtime != null) { - string runtime; - if (identityParameters.TryGetValue(XMakeAttributes.runtime, out runtime)) + if (!XMakeAttributes.IsValidMSBuildRuntimeValue(identityParameters.Runtime)) { - if (!XMakeAttributes.IsValidMSBuildRuntimeValue(runtime)) - { - ProjectErrorUtilities.ThrowInvalidProject( - errorLocation, - "TaskLoadFailureInvalidTaskHostFactoryParameter", - taskName, - runtime, - runtimeName, - XMakeAttributes.MSBuildRuntimeValues.clr2, - XMakeAttributes.MSBuildRuntimeValues.clr4, - XMakeAttributes.MSBuildRuntimeValues.currentRuntime, - XMakeAttributes.MSBuildRuntimeValues.any); - } + ProjectErrorUtilities.ThrowInvalidProject( + errorLocation, + "TaskLoadFailureInvalidTaskHostFactoryParameter", + taskName, + identityParameters.Runtime, + runtimeName, + XMakeAttributes.MSBuildRuntimeValues.clr2, + XMakeAttributes.MSBuildRuntimeValues.clr4, + XMakeAttributes.MSBuildRuntimeValues.currentRuntime, + XMakeAttributes.MSBuildRuntimeValues.any); } + } - string architecture; - if (identityParameters.TryGetValue(XMakeAttributes.architecture, out architecture)) + if (identityParameters.Architecture != null) + { + if (!XMakeAttributes.IsValidMSBuildArchitectureValue(identityParameters.Architecture)) { - if (!XMakeAttributes.IsValidMSBuildArchitectureValue(architecture)) - { - ProjectErrorUtilities.ThrowInvalidProject( - errorLocation, - "TaskLoadFailureInvalidTaskHostFactoryParameter", - taskName, - architecture, - architectureName, - XMakeAttributes.MSBuildArchitectureValues.x86, - XMakeAttributes.MSBuildArchitectureValues.x64, - XMakeAttributes.MSBuildArchitectureValues.currentArchitecture, - XMakeAttributes.MSBuildArchitectureValues.any); - } + ProjectErrorUtilities.ThrowInvalidProject( + errorLocation, + "TaskLoadFailureInvalidTaskHostFactoryParameter", + taskName, + identityParameters.Architecture, + architectureName, + XMakeAttributes.MSBuildArchitectureValues.x86, + XMakeAttributes.MSBuildArchitectureValues.x64, + XMakeAttributes.MSBuildArchitectureValues.currentArchitecture, + XMakeAttributes.MSBuildArchitectureValues.any); } } } @@ -566,27 +525,17 @@ private static void VerifyThrowIdentityParametersValid(IDictionary - private static bool TaskIdentityParametersMatchFactory(IDictionary factoryIdentityParameters, IDictionary taskIdentityParameters) + private static bool TaskIdentityParametersMatchFactory(TaskHostParameters factoryIdentityParameters, TaskHostParameters taskIdentityParameters) { - if (taskIdentityParameters == null || taskIdentityParameters.Count == 0 || factoryIdentityParameters == null || factoryIdentityParameters.Count == 0) + if (taskIdentityParameters.IsEmpty || factoryIdentityParameters.IsEmpty) { // either the task or the using task doesn't care about anything, in which case we match by default. return true; } - string taskArchitecture; - string taskRuntime; - taskIdentityParameters.TryGetValue(XMakeAttributes.runtime, out taskRuntime); - string usingTaskRuntime; - factoryIdentityParameters.TryGetValue(XMakeAttributes.runtime, out usingTaskRuntime); - - if (XMakeAttributes.RuntimeValuesMatch(taskRuntime, usingTaskRuntime)) + if (XMakeAttributes.RuntimeValuesMatch(taskIdentityParameters.Runtime, factoryIdentityParameters.Runtime)) { - taskIdentityParameters.TryGetValue(XMakeAttributes.architecture, out taskArchitecture); - string usingTaskArchitecture; - factoryIdentityParameters.TryGetValue(XMakeAttributes.architecture, out usingTaskArchitecture); - - if (XMakeAttributes.ArchitectureValuesMatch(taskArchitecture, usingTaskArchitecture)) + if (XMakeAttributes.ArchitectureValuesMatch(taskIdentityParameters.Architecture, factoryIdentityParameters.Architecture)) { // both match return true; @@ -601,106 +550,108 @@ private static bool TaskIdentityParametersMatchFactory(IDictionary - private static Dictionary MergeTaskFactoryParameterSets( - IDictionary factoryIdentityParameters, - IDictionary taskIdentityParameters) + private static TaskHostParameters MergeTaskFactoryParameterSets( + TaskHostParameters factoryIdentityParameters, + TaskHostParameters taskIdentityParameters) { - Dictionary mergedParameters = null; - if (factoryIdentityParameters == null || factoryIdentityParameters.Count == 0) + // If one is empty, just use the other + if (factoryIdentityParameters.IsEmpty) { - mergedParameters = new Dictionary(taskIdentityParameters, StringComparer.OrdinalIgnoreCase); + return taskIdentityParameters.IsEmpty + ? TaskHostParameters.Empty + : new TaskHostParameters( + runtime: XMakeAttributes.GetExplicitMSBuildRuntime(taskIdentityParameters.Runtime), + architecture: XMakeAttributes.GetExplicitMSBuildArchitecture(taskIdentityParameters.Architecture)); } - else if (taskIdentityParameters == null || taskIdentityParameters.Count == 0) + + if (taskIdentityParameters.IsEmpty) { - mergedParameters = new Dictionary(factoryIdentityParameters, StringComparer.OrdinalIgnoreCase); + return new TaskHostParameters( + runtime: XMakeAttributes.GetExplicitMSBuildRuntime(factoryIdentityParameters.Runtime), + architecture: XMakeAttributes.GetExplicitMSBuildArchitecture(factoryIdentityParameters.Architecture)); } - string mergedRuntime; - string mergedArchitecture; - if (mergedParameters != null) + // Both have values - need to merge them + if (!XMakeAttributes.TryMergeRuntimeValues(taskIdentityParameters.Runtime, factoryIdentityParameters.Runtime, out var mergedRuntime)) { - mergedParameters.TryGetValue(XMakeAttributes.runtime, out mergedRuntime); - mergedParameters.TryGetValue(XMakeAttributes.architecture, out mergedArchitecture); - - mergedParameters[XMakeAttributes.runtime] = XMakeAttributes.GetExplicitMSBuildRuntime(mergedRuntime); - mergedParameters[XMakeAttributes.architecture] = XMakeAttributes.GetExplicitMSBuildArchitecture(mergedArchitecture); + ErrorUtilities.ThrowInternalError("How did we get two runtime values that were unmergeable? " + + $"TaskIdentity Runtime: {taskIdentityParameters.Runtime}, FactoryIdentity Runtime: {factoryIdentityParameters.Runtime}."); } - else - { - mergedParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - - taskIdentityParameters.TryGetValue(XMakeAttributes.runtime, out string taskRuntime); - factoryIdentityParameters.TryGetValue(XMakeAttributes.runtime, out string usingTaskRuntime); - if (!XMakeAttributes.TryMergeRuntimeValues(taskRuntime, usingTaskRuntime, out mergedRuntime)) - { - ErrorUtilities.ThrowInternalError("How did we get two runtime values that were unmergeable?"); - } - else - { - mergedParameters.Add(XMakeAttributes.runtime, mergedRuntime); - } - - taskIdentityParameters.TryGetValue(XMakeAttributes.architecture, out string taskArchitecture); - factoryIdentityParameters.TryGetValue(XMakeAttributes.architecture, out string usingTaskArchitecture); - - if (!XMakeAttributes.TryMergeArchitectureValues(taskArchitecture, usingTaskArchitecture, out mergedArchitecture)) - { - ErrorUtilities.ThrowInternalError("How did we get two runtime values that were unmergeable?"); - } - else - { - mergedParameters.Add(XMakeAttributes.architecture, mergedArchitecture); - } + if (!XMakeAttributes.TryMergeArchitectureValues(taskIdentityParameters.Architecture, factoryIdentityParameters.Architecture, out var mergedArchitecture)) + { + ErrorUtilities.ThrowInternalError("How did we get two architecture values that were unmergeable? " + + $"TaskIdentity Architecture: {taskIdentityParameters.Architecture}, FactoryIdentity Architecture: {factoryIdentityParameters.Architecture}."); } - return mergedParameters; + return new TaskHostParameters( + runtime: mergedRuntime, + architecture: mergedArchitecture); } /// /// Adds the properties necessary for NET task host instantiation. /// - private static void AddNetHostParams(ref Dictionary taskParams, Func getProperty) + /// + /// Adds the properties necessary for .NET task host instantiation if the runtime is .NET. + /// Returns a new TaskHostParameters with .NET host parameters added, or the original if not needed. + /// + private static (TaskHostParameters TaskHostParams, bool isNetRuntime) AddNetHostParamsIfNeeded( + string runtime, + string architecture, + Func getProperty) { - string hostPath = getProperty(Constants.DotnetHostPathEnvVarName)?.EvaluatedValue; - string msBuildAssemblyDirectoryPath = Path.GetDirectoryName(getProperty(Constants.RuntimeIdentifierGraphPath)?.EvaluatedValue) ?? string.Empty; - if (!string.IsNullOrEmpty(hostPath) && !string.IsNullOrEmpty(msBuildAssemblyDirectoryPath)) - { - taskParams.Add(Constants.DotnetHostPath, hostPath); - taskParams.Add(Constants.MSBuildAssemblyPath, msBuildAssemblyDirectoryPath); - } + // Only add .NET host parameters if runtime is .NET + if (!runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.net, StringComparison.OrdinalIgnoreCase)) + { + return (new TaskHostParameters(runtime, architecture), isNetRuntime: false); + } + + string dotnetHostPath = getProperty(Constants.DotnetHostPathEnvVarName)?.EvaluatedValue; + string ridGraphPath = getProperty(Constants.RuntimeIdentifierGraphPath)?.EvaluatedValue; + string msBuildAssemblyPath = !string.IsNullOrEmpty(ridGraphPath) + ? Path.GetDirectoryName(ridGraphPath) ?? string.Empty + : string.Empty; + + // Only create new parameters if we have valid .NET host paths + return string.IsNullOrEmpty(dotnetHostPath) || string.IsNullOrEmpty(msBuildAssemblyPath) + ? (new TaskHostParameters(runtime, architecture), isNetRuntime: false) + : (new TaskHostParameters( + runtime: runtime, + architecture: architecture, + dotnetHostPath: dotnetHostPath, + msBuildAssemblyPath: msBuildAssemblyPath), + isNetRuntime: true); } /// /// Returns true if the provided set of task host parameters matches the current process, /// and false otherwise. /// - private static bool TaskHostParametersMatchCurrentProcess(IDictionary mergedParameters) + private static bool TaskHostParametersMatchCurrentProcess(TaskHostParameters mergedParameters) { - if (mergedParameters == null || mergedParameters.Count == 0) + if (mergedParameters.IsEmpty) { // We don't care, so they match by default. return true; } - string runtime; - if (mergedParameters.TryGetValue(XMakeAttributes.runtime, out runtime)) + if (mergedParameters.Runtime != null) { string currentRuntime = XMakeAttributes.GetExplicitMSBuildRuntime(XMakeAttributes.MSBuildRuntimeValues.currentRuntime); - if (!currentRuntime.Equals(XMakeAttributes.GetExplicitMSBuildRuntime(runtime), StringComparison.OrdinalIgnoreCase)) + if (!currentRuntime.Equals(XMakeAttributes.GetExplicitMSBuildRuntime(mergedParameters.Runtime), StringComparison.OrdinalIgnoreCase)) { // runtime doesn't match return false; } } - string architecture; - if (mergedParameters.TryGetValue(XMakeAttributes.architecture, out architecture)) + if (mergedParameters.Architecture != null) { string currentArchitecture = XMakeAttributes.GetCurrentMSBuildArchitecture(); - if (!currentArchitecture.Equals(XMakeAttributes.GetExplicitMSBuildArchitecture(architecture), StringComparison.OrdinalIgnoreCase)) + if (!currentArchitecture.Equals(XMakeAttributes.GetExplicitMSBuildArchitecture(mergedParameters.Architecture), StringComparison.OrdinalIgnoreCase)) { // architecture doesn't match return false; @@ -756,6 +707,54 @@ private void ErrorLoggingDelegate(string taskLocation, int taskLine, int taskCol _taskLoggingContext.LogError(new BuildEventFileInfo(taskLocation, taskLine, taskColumn), message, messageArgs); } + /// + /// Initializes this factory for instantiating tasks with a particular inline task block and a set of UsingTask parameters. + /// + /// Name of the task. + /// Special parameters that the task factory can use to modify how it executes tasks, + /// such as Runtime and Architecture. The key is the name of the parameter and the value is the parameter's value. This + /// is the set of parameters that was set on the UsingTask using e.g. the UsingTask Runtime and Architecture parameters. + /// The parameter group. + /// The task body. + /// The task factory logging host. + /// A value indicating whether initialization was successful. + /// + /// MSBuild engine will call this to initialize the factory. This should initialize the factory enough so that the + /// factory can be asked whether or not task names can be created by the factory. If a task factory implements ITaskFactory2, + /// this Initialize method will be called in place of ITaskFactory.Initialize. + /// + /// The taskFactoryLoggingHost will log messages in the context of the target where the task is first used. + /// + /// + public bool Initialize(string taskName, TaskHostParameters factoryIdentityParameters, IDictionary parameterGroup, string taskBody, IBuildEngine taskFactoryLoggingHost) + { + ErrorUtilities.ThrowInternalError("Use internal call to properly initialize the assembly task factory"); + return false; + } + + /// + /// Create an instance of the task to be used. + /// + /// + /// The task factory logging host will log messages in the context of the task. + /// + /// + /// Special parameters that the task factory can use to modify how it executes tasks, such as Runtime and Architecture. + /// The key is the name of the parameter and the value is the parameter's value. This is the set of parameters that was + /// set to the task invocation itself, via e.g. the special MSBuildRuntime and MSBuildArchitecture parameters. + /// + /// + /// If a task factory implements ITaskFactory2, MSBuild will call this method instead of ITaskFactory.CreateTask. + /// + /// + /// The generated task, or null if the task failed to be created. + /// + public ITask CreateTask(IBuildEngine taskFactoryLoggingHost, TaskHostParameters taskIdentityParameters) + { + ErrorUtilities.ThrowInternalError("Use internal call to properly create a task instance from the assembly task factory"); + return null; + } + #endregion } } diff --git a/src/Build/Instance/TaskFactories/TaskHostTask.cs b/src/Build/Instance/TaskFactories/TaskHostTask.cs index c51f2f78436..3d76fba1e1e 100644 --- a/src/Build/Instance/TaskFactories/TaskHostTask.cs +++ b/src/Build/Instance/TaskFactories/TaskHostTask.cs @@ -78,7 +78,7 @@ internal class TaskHostTask : IGeneratedTask, ICancelableTask, INodePacketFactor /// /// The set of parameters used to decide which host to launch. /// - private Dictionary _taskHostParameters; + private TaskHostParameters _taskHostParameters; /// /// The type of the task that we are wrapping. @@ -141,10 +141,13 @@ internal class TaskHostTask : IGeneratedTask, ICancelableTask, INodePacketFactor private bool _taskExecutionSucceeded = false; /// - /// This separates the cause where we force all tasks to run in a task host via environment variables and TaskHostFactory - /// The difference is that TaskHostFactory requires the TaskHost to be transient i.e. to expire after build. + /// If true TaskHostFactory expects the TaskHost not will NOT expire after build (until it timeouts or is killed). + /// This is relevant for the next cases: + /// 1) TaskHostFactory is NOT explicitly requested (we always disable node reuse due to the transient nature of task host factory hosts). + /// 2) Runtime="NET" is specified in UsingTask. + /// 3) Environment variable MSBUILDFORCEALLTASKSOUTOFPROC is set. /// - private bool _taskHostFactoryExplicitlyRequested = false; + private bool _useSidecarTaskHost = false; /// /// Constructor. @@ -153,9 +156,9 @@ public TaskHostTask( IElementLocation taskLocation, TaskLoggingContext taskLoggingContext, IBuildComponentHost buildComponentHost, - Dictionary taskHostParameters, + TaskHostParameters taskHostParameters, LoadedType taskType, - bool taskHostFactoryExplicitlyRequested, + bool useSidecarTaskHost, #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif @@ -173,7 +176,7 @@ public TaskHostTask( _appDomainSetup = appDomainSetup; #endif _taskHostParameters = taskHostParameters; - _taskHostFactoryExplicitlyRequested = taskHostFactoryExplicitlyRequested; + _useSidecarTaskHost = useSidecarTaskHost; _packetFactory = new NodePacketFactory(); @@ -280,11 +283,13 @@ public void Cancel() /// public bool Execute() { - // log that we are about to spawn the task host - string runtime = _taskHostParameters[XMakeAttributes.runtime]; - string architecture = _taskHostParameters[XMakeAttributes.architecture]; - - _taskLoggingContext.LogComment(MessageImportance.Low, "ExecutingTaskInTaskHost", _taskType.Type.Name, _taskType.Assembly.AssemblyLocation, runtime, architecture); + _taskLoggingContext.LogComment( + MessageImportance.Low, + "ExecutingTaskInTaskHost", + _taskType.Type.Name, + _taskType.Assembly.AssemblyLocation, + _taskHostParameters.Runtime, + _taskHostParameters.Architecture); // set up the node lock (_taskHostLock) @@ -331,8 +336,7 @@ public bool Execute() taskHost: true, // Determine if we should use node reuse based on build parameters or user preferences (comes from UsingTask element). - // If the user explicitly requested the task host factory, then we always disable node reuse due to the transient nature of task host factory hosts. - nodeReuse: _buildComponentHost.BuildParameters.EnableNodeReuse && !_taskHostFactoryExplicitlyRequested, + nodeReuse: _buildComponentHost.BuildParameters.EnableNodeReuse && _useSidecarTaskHost, taskHostParameters: _taskHostParameters); _taskHostNodeId = GenerateTaskHostNodeId(_scheduledNodeId, _requiredContext); @@ -372,16 +376,16 @@ public bool Execute() } else { - LogErrorUnableToCreateTaskHost(_requiredContext, runtime, architecture, null); + LogErrorUnableToCreateTaskHost(_requiredContext, _taskHostParameters.Runtime, _taskHostParameters.Architecture, null); } } catch (BuildAbortedException) { - LogErrorUnableToCreateTaskHost(_requiredContext, runtime, architecture, null); + LogErrorUnableToCreateTaskHost(_requiredContext, _taskHostParameters.Runtime, _taskHostParameters.Architecture, null); } catch (NodeFailedToLaunchException e) { - LogErrorUnableToCreateTaskHost(_requiredContext, runtime, architecture, e); + LogErrorUnableToCreateTaskHost(_requiredContext, _taskHostParameters.Runtime, _taskHostParameters.Architecture, e); } return _taskExecutionSucceeded; diff --git a/src/Build/Instance/TaskFactoryWrapper.cs b/src/Build/Instance/TaskFactoryWrapper.cs index 63719b5b6ba..f2d24afd836 100644 --- a/src/Build/Instance/TaskFactoryWrapper.cs +++ b/src/Build/Instance/TaskFactoryWrapper.cs @@ -68,10 +68,9 @@ public PropertyData( private string _taskName; /// - /// The set of special parameters that, along with the name, contribute to the identity of - /// this factory. + /// The set of special parameters that, along with the name, contribute to the identity of this factory. /// - private IDictionary _factoryIdentityParameters; + private TaskHostParameters _factoryIdentityParameters; /// /// An execution statistics holder. @@ -89,7 +88,7 @@ internal TaskFactoryWrapper( ITaskFactory taskFactory, LoadedType taskFactoryLoadInfo, string taskName, - IDictionary factoryIdentityParameters, + TaskHostParameters factoryIdentityParameters, TaskRegistry.RegisteredTaskRecord.Stats? statistics = null) { ErrorUtilities.VerifyThrowArgumentNull(taskFactory); @@ -167,13 +166,7 @@ public string Name /// The set of task identity parameters that were set on /// this particular factory's UsingTask statement. /// - public IDictionary FactoryIdentityParameters - { - get - { - return _factoryIdentityParameters; - } - } + public TaskHostParameters FactoryIdentityParameters => _factoryIdentityParameters; #endregion diff --git a/src/Build/Instance/TaskRegistry.cs b/src/Build/Instance/TaskRegistry.cs index b0af558f603..c27d08c0cd6 100644 --- a/src/Build/Instance/TaskRegistry.cs +++ b/src/Build/Instance/TaskRegistry.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; @@ -16,7 +15,6 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; -using Microsoft.Build.Internal; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Microsoft.NET.StringTools; @@ -420,17 +418,16 @@ private static void RegisterTasksFromUsingTaskElement parameterGroupAndTaskElementRecord.ExpandUsingTask(projectUsingTaskXml, expander, expanderOptions); } - Dictionary taskFactoryParameters = null; + TaskHostParameters taskFactoryParameters = TaskHostParameters.Empty; string runtime = expander.ExpandIntoStringLeaveEscaped(projectUsingTaskXml.Runtime, expanderOptions, projectUsingTaskXml.RuntimeLocation); string architecture = expander.ExpandIntoStringLeaveEscaped(projectUsingTaskXml.Architecture, expanderOptions, projectUsingTaskXml.ArchitectureLocation); string overrideUsingTask = expander.ExpandIntoStringLeaveEscaped(projectUsingTaskXml.Override, expanderOptions, projectUsingTaskXml.OverrideLocation); - if ((runtime != String.Empty) || (architecture != String.Empty)) + if ((runtime != string.Empty) || (architecture != string.Empty)) { - taskFactoryParameters = CreateTaskFactoryParametersDictionary(); - - taskFactoryParameters.Add(XMakeAttributes.runtime, runtime == String.Empty ? XMakeAttributes.MSBuildRuntimeValues.any : runtime); - taskFactoryParameters.Add(XMakeAttributes.architecture, architecture == String.Empty ? XMakeAttributes.MSBuildArchitectureValues.any : architecture); + taskFactoryParameters = new TaskHostParameters( + runtime == string.Empty ? XMakeAttributes.MSBuildRuntimeValues.any : runtime, + architecture == string.Empty ? XMakeAttributes.MSBuildArchitectureValues.any : architecture); } taskRegistry.RegisterTask( @@ -444,13 +441,6 @@ private static void RegisterTasksFromUsingTaskElement ConversionUtilities.ValidBooleanTrue(overrideUsingTask)); } - private static Dictionary CreateTaskFactoryParametersDictionary(int? initialCount = null) - { - return initialCount == null - ? new Dictionary(StringComparer.OrdinalIgnoreCase) - : new Dictionary(initialCount.Value, StringComparer.OrdinalIgnoreCase); - } - /// /// Given a task name, this method retrieves the task class. If the task has been requested before, it will be found in /// the class cache; otherwise, <UsingTask> declarations will be used to search the appropriate assemblies. @@ -458,7 +448,7 @@ private static Dictionary CreateTaskFactoryParametersDictionary( internal TaskFactoryWrapper GetRegisteredTask( string taskName, string taskProjectFile, - IDictionary taskIdentityParameters, + TaskHostParameters taskIdentityParameters, bool exactMatchRequired, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation, @@ -516,7 +506,7 @@ internal TaskFactoryWrapper GetRegisteredTask( internal RegisteredTaskRecord GetTaskRegistrationRecord( string taskName, string taskProjectFile, - IDictionary taskIdentityParameters, + TaskHostParameters taskIdentityParameters, bool exactMatchRequired, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation, @@ -525,7 +515,7 @@ internal RegisteredTaskRecord GetTaskRegistrationRecord( { RegisteredTaskRecord taskRecord = null; retrievedFromCache = false; - RegisteredTaskIdentity taskIdentity = new RegisteredTaskIdentity(taskName, taskIdentityParameters); + RegisteredTaskIdentity taskIdentity = new(taskName, taskIdentityParameters); // Project-level override tasks are keyed by task name (unqualified). // Because Foo.Bar and Baz.Bar are both valid, they are stored @@ -685,7 +675,7 @@ private void RegisterTask( string taskName, AssemblyLoadInfo assemblyLoadInfo, string taskFactory, - Dictionary taskFactoryParameters, + TaskHostParameters taskFactoryParameters, RegisteredTaskRecord.ParameterGroupAndTaskElementRecord inlineTaskRecord, LoggingContext loggingContext, ProjectUsingTaskElement projectUsingTaskInXml, @@ -767,7 +757,7 @@ private RegisteredTaskRecord GetMatchingRegistration( string taskName, IEnumerable taskRecords, string taskProjectFile, - IDictionary taskIdentityParameters, + TaskHostParameters taskIdentityParameters, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation, bool isMultiThreadedBuild) @@ -791,34 +781,15 @@ private RegisteredTaskRecord GetMatchingRegistration( internal class RegisteredTaskIdentity : ITranslatable { private string _name; - private IDictionary _taskIdentityParameters; + private TaskHostParameters _taskIdentityParameters; /// - /// Constructor + /// Constructor. /// - internal RegisteredTaskIdentity(string name, IDictionary taskIdentityParameters) + internal RegisteredTaskIdentity(string name, TaskHostParameters taskIdentityParameters) { _name = name; - - // The ReadOnlyDictionary is a *wrapper*, the Dictionary is the copy. - _taskIdentityParameters = taskIdentityParameters == null ? null : new ReadOnlyDictionary(CreateTaskIdentityParametersDictionary(taskIdentityParameters)); - } - - private static IDictionary CreateTaskIdentityParametersDictionary(IDictionary initialState = null, int? initialCount = null) - { - ErrorUtilities.VerifyThrow(initialState == null || initialCount == null, "at most one can be non-null"); - - if (initialState != null) - { - return new Dictionary(initialState, StringComparer.OrdinalIgnoreCase); - } - - if (initialCount != null) - { - return new Dictionary(initialCount.Value, StringComparer.OrdinalIgnoreCase); - } - - return new Dictionary(StringComparer.OrdinalIgnoreCase); + _taskIdentityParameters = taskIdentityParameters.IsEmpty ? TaskHostParameters.Empty : taskIdentityParameters; } public RegisteredTaskIdentity() @@ -834,12 +805,9 @@ public string Name } /// - /// The identity parameters + /// The identity parameters. /// - public IDictionary TaskIdentityParameters - { - get { return _taskIdentityParameters; } - } + public TaskHostParameters TaskIdentityParameters => _taskIdentityParameters; /// /// Comparer used to figure out whether two RegisteredTaskIdentities are equal or not. @@ -893,14 +861,9 @@ public static RegisteredTaskIdentityComparer Fuzzy /// public static bool IsPartialMatch(RegisteredTaskIdentity x, RegisteredTaskIdentity y) { - if (TypeLoader.IsPartialTypeNameMatch(x.Name, y.Name)) - { - return IdentityParametersMatch(x.TaskIdentityParameters, y.TaskIdentityParameters, false /* fuzzy match */); - } - else - { - return false; - } + return TypeLoader.IsPartialTypeNameMatch(x.Name, y.Name) + ? IdentityParametersMatch(x.TaskIdentityParameters, y.TaskIdentityParameters, false /* fuzzy match */) + : false; } /// @@ -944,20 +907,11 @@ public int GetHashCode(RegisteredTaskIdentity obj) // Since equality for the exact comparer depends on the exact values of the parameters, // we need our hash code to depend on them as well. However, for fuzzy matches, we just // need the ultimate meaning of the parameters to be the same. - string runtime = null; - string architecture = null; - - if (obj.TaskIdentityParameters != null) - { - obj.TaskIdentityParameters.TryGetValue(XMakeAttributes.runtime, out runtime); - obj.TaskIdentityParameters.TryGetValue(XMakeAttributes.architecture, out architecture); - } - int paramHash; if (_exactMatchRequired) { - int runtimeHash = runtime == null ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(runtime); - int architectureHash = architecture == null ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(architecture); + int runtimeHash = obj.TaskIdentityParameters.Runtime == null ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(obj.TaskIdentityParameters.Runtime); + int architectureHash = obj.TaskIdentityParameters.Architecture == null ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(obj.TaskIdentityParameters.Architecture); paramHash = runtimeHash ^ architectureHash; } @@ -976,65 +930,42 @@ public int GetHashCode(RegisteredTaskIdentity obj) } /// - /// Returns true if the two dictionaries representing sets of task identity parameters match; false otherwise. + /// Returns true if the two TaskHostParameters match; false otherwise. /// Internal so that RegisteredTaskRecord can use this function in its determination of whether the task factory /// supports a certain task identity. /// - private static bool IdentityParametersMatch(IDictionary x, IDictionary y, bool exactMatchRequired) + private static bool IdentityParametersMatch(TaskHostParameters x, TaskHostParameters y, bool exactMatchRequired) { - if (x == null && y == null) + // Both empty - match + if (x.IsEmpty && y.IsEmpty) { return true; } if (exactMatchRequired) { - if (x == null || y == null) + // For exact match, one empty means no match + if (x.IsEmpty || y.IsEmpty) { return false; } - if (x.Count != y.Count) - { - return false; - } - - // make sure that each parameter value matches as well - foreach (KeyValuePair param in x) - { - string value; - if (y.TryGetValue(param.Key, out value)) - { - if (!String.Equals(param.Value, value, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - else - { - return false; - } - } + // For exact match, all properties must be equal (case-insensitive) + return string.Equals(x.Runtime, y.Runtime, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.Architecture, y.Architecture, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.DotnetHostPath, y.DotnetHostPath, StringComparison.OrdinalIgnoreCase) && + string.Equals(x.MSBuildAssemblyPath, y.MSBuildAssemblyPath, StringComparison.OrdinalIgnoreCase) && + x.TaskHostFactoryExplicitlyRequested == y.TaskHostFactoryExplicitlyRequested; } else { - // we need to fuzzy-match the parameters as well - string runtimeX = null; - string runtimeY = null; - string architectureX = null; - string architectureY = null; + // Fuzzy match: null is treated as "don't care" + // Only check runtime and architecture for fuzzy matching - if (x != null) - { - x.TryGetValue(XMakeAttributes.runtime, out runtimeX); - x.TryGetValue(XMakeAttributes.architecture, out architectureX); - } - - if (y != null) - { - y.TryGetValue(XMakeAttributes.runtime, out runtimeY); - y.TryGetValue(XMakeAttributes.architecture, out architectureY); - } + string runtimeX = x.Runtime; + string runtimeY = y.Runtime; + string architectureX = x.Architecture; + string architectureY = y.Architecture; // null is OK -- it's treated as a "don't care" if (!XMakeAttributes.RuntimeValuesMatch(runtimeX, runtimeY)) @@ -1048,7 +979,7 @@ private static bool IdentityParametersMatch(IDictionary x, IDict } } - // if we didn't return before now, all parameters were found and matched. + // if we didn't return before now, all parameters matched return true; } } @@ -1056,14 +987,7 @@ private static bool IdentityParametersMatch(IDictionary x, IDict public void Translate(ITranslator translator) { translator.Translate(ref _name); - - IDictionary taskIdentityParameters = _taskIdentityParameters; - translator.TranslateDictionary(ref taskIdentityParameters, count => CreateTaskIdentityParametersDictionary(null, count)); - - if (translator.Mode == TranslationDirection.ReadFromStream && taskIdentityParameters != null) - { - _taskIdentityParameters = new ReadOnlyDictionary(taskIdentityParameters); - } + translator.Translate(ref _taskIdentityParameters); } } @@ -1157,9 +1081,9 @@ internal class RegisteredTaskRecord : ITranslatable private Dictionary _taskNamesCreatableByFactory; /// - /// Set of parameters that can be used by the task factory specifically. + /// Parameters that can be used by the task factory specifically. /// - private Dictionary _taskFactoryParameters; + private TaskHostParameters _taskFactoryParameters; /// /// Encapsulates the parameters and the body of the task element for the inline task. @@ -1186,7 +1110,7 @@ internal class Stats() { public short ExecutedCount { get; private set; } = 0; public long TotalMemoryConsumption { get; private set; } = 0; - private readonly Stopwatch _executedSw = new Stopwatch(); + private readonly Stopwatch _executedSw = new Stopwatch(); private long _memoryConsumptionOnStart; public TimeSpan ExecutedTime => _executedSw.Elapsed; @@ -1228,7 +1152,7 @@ internal RegisteredTaskRecord( string registeredName, AssemblyLoadInfo assemblyLoadInfo, string taskFactory, - Dictionary taskFactoryParameters, + TaskHostParameters taskFactoryParameters, ParameterGroupAndTaskElementRecord inlineTask, int registrationOrderId, string containingFileFullPath) @@ -1241,11 +1165,13 @@ internal RegisteredTaskRecord( _parameterGroupAndTaskBody = inlineTask; _registrationOrderId = registrationOrderId; - if (String.IsNullOrEmpty(taskFactory)) + if (string.IsNullOrEmpty(taskFactory)) { - if (taskFactoryParameters != null) + if (!taskFactoryParameters.IsEmpty) { - ErrorUtilities.VerifyThrow(taskFactoryParameters.Count == 2, "if the parameter dictionary is non-null, it should contain both parameters when we get here!"); + ErrorUtilities.VerifyThrow( + taskFactoryParameters.Runtime != null && taskFactoryParameters.Architecture != null, + "if the parameters are non-null, it should contain both Runtime and Architecture when we get here!"); } _taskFactory = AssemblyTaskFactory; @@ -1324,13 +1250,12 @@ internal string TaskFactoryAttributeName } /// - /// Gets the set of parameters for the task factory + /// Gets the set of parameters for the task factory. /// - internal IDictionary TaskFactoryParameters + internal TaskHostParameters TaskFactoryParameters { [DebuggerStepThrough] - get - { return _taskFactoryParameters; } + get => _taskFactoryParameters; } /// @@ -1360,7 +1285,7 @@ internal ParameterGroupAndTaskElementRecord ParameterGroupAndTaskBody /// loads an external file and uses that to generate the tasks. /// /// true if the task can be created by the factory, false if it cannot be created - internal bool CanTaskBeCreatedByFactory(string taskName, string taskProjectFile, IDictionary taskIdentityParameters, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation, bool isMultiThreadedBuild) + internal bool CanTaskBeCreatedByFactory(string taskName, string taskProjectFile, TaskHostParameters taskIdentityParameters, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation, bool isMultiThreadedBuild) { // First check (fast path - no locking) if (_taskNamesCreatableByFactory == null) @@ -1468,7 +1393,7 @@ internal bool CanTaskBeCreatedByFactory(string taskName, string taskProjectFile, /// Given a Registered task record and a task name. Check create an instance of the task factory using the record. /// If the factory is a assembly task factory see if the assemblyFile has the correct task inside of it. /// - internal TaskFactoryWrapper GetTaskFactoryFromRegistrationRecord(string taskName, string taskProjectFile, IDictionary taskIdentityParameters, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation, bool isMultiThreadedBuild) + internal TaskFactoryWrapper GetTaskFactoryFromRegistrationRecord(string taskName, string taskProjectFile, TaskHostParameters taskIdentityParameters, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation, bool isMultiThreadedBuild) { if (CanTaskBeCreatedByFactory(taskName, taskProjectFile, taskIdentityParameters, targetLoggingContext, elementLocation, isMultiThreadedBuild)) { @@ -1494,11 +1419,10 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo bool isAssemblyTaskFactory = String.Equals(TaskFactoryAttributeName, AssemblyTaskFactory, StringComparison.OrdinalIgnoreCase); bool isTaskHostFactory = String.Equals(TaskFactoryAttributeName, TaskHostFactory, StringComparison.OrdinalIgnoreCase); - _taskFactoryParameters ??= []; if (isTaskHostFactory) { - TaskFactoryParameters.Add(Constants.TaskHostExplicitlyRequested, isTaskHostFactory.ToString()); + _taskFactoryParameters = TaskHostParameters.MergeTaskHostParameters(_taskFactoryParameters, new TaskHostParameters(taskHostFactoryExplicitlyRequested: true)); } if (isAssemblyTaskFactory || isTaskHostFactory) @@ -1506,7 +1430,6 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo // If ForceAllTasksOutOfProc is true, we will force all tasks to run in the MSBuild task host // "EXCEPT a small well-known set of tasks that are known to depend on IBuildEngine callbacks // as forcing those out of proc would be just setting them up for known failure" - bool launchTaskHost = isTaskHostFactory || ( @@ -1591,10 +1514,21 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo bool initialized = false; try { - ITaskFactory2 factory2 = factory as ITaskFactory2; - if (factory2 != null) + // for backward compatibility with public interface + if (factory is ITaskFactory2 factory2) { - initialized = factory2.Initialize(RegisteredName, TaskFactoryParameters, ParameterGroupAndTaskBody.UsingTaskParameters, ParameterGroupAndTaskBody.InlineTaskXmlBody, taskFactoryLoggingHost); + var taskFactoryParams = new Dictionary(3) + { + { nameof(TaskHostParameters.Runtime), TaskFactoryParameters.Runtime }, + { nameof(TaskHostParameters.Architecture), TaskFactoryParameters.Architecture }, + { nameof(TaskHostParameters.TaskHostFactoryExplicitlyRequested), TaskFactoryParameters.TaskHostFactoryExplicitlyRequested.ToString() }, + }; + + initialized = factory2.Initialize(RegisteredName, taskFactoryParams, ParameterGroupAndTaskBody.UsingTaskParameters, ParameterGroupAndTaskBody.InlineTaskXmlBody, taskFactoryLoggingHost); + } + else if (factory is ITaskFactory3 factory3) + { + initialized = factory3.Initialize(RegisteredName, TaskFactoryParameters, ParameterGroupAndTaskBody.UsingTaskParameters, ParameterGroupAndTaskBody.InlineTaskXmlBody, taskFactoryLoggingHost); } else { @@ -1602,8 +1536,8 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo // TaskFactoryParameters will always be null unless specifically created to have runtime and architecture parameters. // In case TaskHostFactory is explicitly requested, we will now have a parameter for that. - bool containsArchOrRuntimeParam = TaskFactoryParameters?.TryGetValue(XMakeAttributes.runtime, out _) == true - || TaskFactoryParameters?.TryGetValue(XMakeAttributes.architecture, out _) == true; + bool containsArchOrRuntimeParam = TaskFactoryParameters.Runtime != null + || TaskFactoryParameters.Architecture != null; if (initialized && containsArchOrRuntimeParam) { @@ -1946,14 +1880,7 @@ public void Translate(ITranslator translator) translator.Translate(ref _parameterGroupAndTaskBody); translator.Translate(ref _registrationOrderId); translator.Translate(ref _definingFileFullPath); - - IDictionary localParameters = _taskFactoryParameters; - translator.TranslateDictionary(ref localParameters, count => CreateTaskFactoryParametersDictionary(count)); - - if (translator.Mode == TranslationDirection.ReadFromStream && localParameters != null) - { - _taskFactoryParameters = (Dictionary)localParameters; - } + translator.Translate(ref _taskFactoryParameters); } internal static RegisteredTaskRecord FactoryForDeserialization(ITranslator translator) diff --git a/src/Framework/BinaryTranslator.cs b/src/Framework/BinaryTranslator.cs index f86c5de8f22..3760eec83e3 100644 --- a/src/Framework/BinaryTranslator.cs +++ b/src/Framework/BinaryTranslator.cs @@ -192,6 +192,37 @@ public void Translate(ref int value) /// public void Translate(ref uint unsignedInteger) => unsignedInteger = _reader.ReadUInt32(); + /// + /// Translates a TaskHostParameters. + /// + /// The TaskHostParameters to be translated. + public void Translate(ref TaskHostParameters value) + { + string runtime = null; + string architecture = null; + string dotnetHostPath = null; + string msBuildAssemblyPath = null; + bool? isTaskHostFactory = null; + + Translate(ref runtime); + Translate(ref architecture); + Translate(ref dotnetHostPath); + Translate(ref msBuildAssemblyPath); + + bool hasTaskHostFactory = _reader.ReadBoolean(); + if (hasTaskHostFactory) + { + isTaskHostFactory = _reader.ReadBoolean(); + } + + value = new TaskHostParameters( + runtime: runtime, + architecture: architecture, + dotnetHostPath: dotnetHostPath, + msBuildAssemblyPath: msBuildAssemblyPath, + taskHostFactoryExplicitlyRequested: isTaskHostFactory); + } + /// /// Translates an array. /// @@ -1089,6 +1120,30 @@ public void Translate(ref double value) _writer.Write(value); } + /// + /// Translates a TaskHostParameters. + /// + /// The TaskHostParameters to be translated. + public void Translate(ref TaskHostParameters value) + { + string runtime = value.Runtime; + string architecture = value.Architecture; + string dotnetHostPath = value.DotnetHostPath; + string msBuildAssemblyPath = value.MSBuildAssemblyPath; + + Translate(ref runtime); + Translate(ref architecture); + Translate(ref dotnetHostPath); + Translate(ref msBuildAssemblyPath); + + bool hasTaskHostFactory = value.TaskHostFactoryExplicitlyRequested.HasValue; + _writer.Write(hasTaskHostFactory); + if (hasTaskHostFactory) + { + _writer.Write(value.TaskHostFactoryExplicitlyRequested.Value); + } + } + /// /// Translates a string. /// diff --git a/src/Framework/ITaskFactory3.cs b/src/Framework/ITaskFactory3.cs new file mode 100644 index 00000000000..591e739faf7 --- /dev/null +++ b/src/Framework/ITaskFactory3.cs @@ -0,0 +1,75 @@ +// 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; + +#nullable disable + +namespace Microsoft.Build.Framework +{ + /// + /// Interface that a task factory Instance should implement if it wants to be able to + /// use new UsingTask parameters such as Runtime and Architecture. + /// + /// + /// + /// Before implementing a custom task factory, consider whether your scenario can be addressed by MSBuild's built-in + /// task factories (such as AssemblyTaskFactory for tasks in compiled assemblies, TaskHostFactory for inline tasks, + /// or RoslynCodeTaskFactory for C# code tasks). Custom task factories add complexity and maintenance burden, and + /// most scenarios can be handled by the existing MSBuild infrastructure. + /// + /// + /// If you have an existing custom task factory implementation, we recommend evaluating whether migrating to + /// MSBuild's built-in task factories would be feasible for your use case. + /// + /// + public interface ITaskFactory3 : ITaskFactory2 + { + /// + /// Initializes this factory for instantiating tasks with a particular inline task block and a set of UsingTask parameters. MSBuild + /// provides an implementation of this interface, TaskHostFactory, that uses "Runtime", with values "CLR2", "CLR4", "CurrentRuntime", + /// and "*" (Any); and "Architecture", with values "x86", "x64", "CurrentArchitecture", and "*" (Any). An implementer of ITaskFactory2 + /// can choose to use these pre-defined Runtime and Architecture values, or can specify new values for these parameters. + /// + /// Name of the task. + /// Special parameters that the task factory can use to modify how it executes tasks, + /// such as Runtime and Architecture. The key is the name of the parameter and the value is the parameter's value. This + /// is the set of parameters that was set on the UsingTask using e.g. the UsingTask Runtime and Architecture parameters. + /// The parameter group. + /// The task body. + /// The task factory logging host. + /// A value indicating whether initialization was successful. + /// + /// MSBuild engine will call this to initialize the factory. This should initialize the factory enough so that the + /// factory can be asked whether or not task names can be created by the factory. If a task factory implements ITaskFactory2, + /// this Initialize method will be called in place of ITaskFactory.Initialize. + /// + /// The taskFactoryLoggingHost will log messages in the context of the target where the task is first used. + /// + /// + bool Initialize(string taskName, TaskHostParameters factoryIdentityParameters, IDictionary parameterGroup, string taskBody, IBuildEngine taskFactoryLoggingHost); + + /// + /// Create an instance of the task to be used, with an optional set of "special" parameters set on the individual task invocation using + /// the MSBuildRuntime and MSBuildArchitecture default task parameters. MSBuild provides an implementation of this interface, + /// TaskHostFactory, that uses "MSBuildRuntime", with values "CLR2", "CLR4", "CurrentRuntime", and "*" (Any); and "MSBuildArchitecture", + /// with values "x86", "x64", "CurrentArchitecture", and "*" (Any). An implementer of ITaskFactory2 can choose to use these pre-defined + /// MSBuildRuntime and MSBuildArchitecture values, or can specify new values for these parameters. + /// + /// + /// The task factory logging host will log messages in the context of the task. + /// + /// + /// Special parameters that the task factory can use to modify how it executes tasks, such as Runtime and Architecture. + /// The key is the name of the parameter and the value is the parameter's value. This is the set of parameters that was + /// set to the task invocation itself, via e.g. the special MSBuildRuntime and MSBuildArchitecture parameters. + /// + /// + /// If a task factory implements ITaskFactory2, MSBuild will call this method instead of ITaskFactory.CreateTask. + /// + /// + /// The generated task, or null if the task failed to be created. + /// + ITask CreateTask(IBuildEngine taskFactoryLoggingHost, TaskHostParameters taskIdentityParameters); + } +} diff --git a/src/Framework/ITranslator.cs b/src/Framework/ITranslator.cs index de450dccf7a..1e063ca1bd9 100644 --- a/src/Framework/ITranslator.cs +++ b/src/Framework/ITranslator.cs @@ -343,6 +343,12 @@ void TranslateArray(ref T[] array) /// The comparer used to instantiate the dictionary. void TranslateDictionary(ref Dictionary dictionary, IEqualityComparer comparer); + /// + /// Translates a TaskHostParameters. + /// + /// The TaskHostParameters to translate. + void Translate(ref TaskHostParameters value); + /// /// Translates a dictionary of { string, string } adding additional entries. /// diff --git a/src/Framework/TaskHostParameters.cs b/src/Framework/TaskHostParameters.cs new file mode 100644 index 00000000000..6f308b924dd --- /dev/null +++ b/src/Framework/TaskHostParameters.cs @@ -0,0 +1,125 @@ +// 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.Framework +{ + /// + /// A readonly struct that represents task host parameters used to determine which host process to launch. + /// + public readonly struct TaskHostParameters + { + /// + /// A static empty instance to avoid allocations when default parameters are needed. + /// + public static readonly TaskHostParameters Empty = new(); + + private readonly string? _runtime; + private readonly string? _architecture; + private readonly string? _dotnetHostPath; + private readonly string? _msBuildAssemblyPath; + private readonly bool? _taskHostFactoryExplicitlyRequested; + + /// + /// Initializes a new instance of the TaskHostParameters struct with the specified parameters. + /// + /// The target runtime identifier (e.g., "net8.0", "net472"). + /// The target architecture (e.g., "x64", "x86", "arm64"). + /// The path to the dotnet host executable. + /// The path to the MSBuild assembly. + /// Defines if Task Host Factory was explicitly requested. + internal TaskHostParameters( + string? runtime = null, + string? architecture = null, + string? dotnetHostPath = null, + string? msBuildAssemblyPath = null, + bool? taskHostFactoryExplicitlyRequested = null) + { + _runtime = runtime; + _architecture = architecture; + _dotnetHostPath = dotnetHostPath; + _msBuildAssemblyPath = msBuildAssemblyPath; + _taskHostFactoryExplicitlyRequested = taskHostFactoryExplicitlyRequested; + } + + /// + /// Gets the target runtime identifier (e.g., "net8.0", "net472"). + /// + /// The runtime identifier, or null if not specified. + public string? Runtime => _runtime; + + /// + /// Gets the target architecture (e.g., "x64", "x86", "arm64"). + /// + /// The architecture identifier, or an empty string if not specified. + public string? Architecture => _architecture; + + /// + /// Gets the path to the dotnet host executable. + /// + /// The dotnet host path, or null if not specified. + public string? DotnetHostPath => _dotnetHostPath; + + /// + /// Gets the path to the MSBuild assembly. + /// + /// The MSBuild assembly path, or null if not specified. + public string? MSBuildAssemblyPath => _msBuildAssemblyPath; + + /// + /// Gets if Task Host Factory was requested explicitly (by specifying TaskHost="TaskHostFactory" in UsingTask element). + /// + public bool? TaskHostFactoryExplicitlyRequested => _taskHostFactoryExplicitlyRequested; + + /// + /// Gets a value indicating whether returns true if parameters were unset. + /// + internal bool IsEmpty => Equals(Empty); + + /// + /// Merges two TaskHostParameters instances, with the second parameter values taking precedence when both are specified. + /// + /// The base parameters. + /// The override parameters that take precedence. + /// A new TaskHostParameters with merged values. + internal static TaskHostParameters MergeTaskHostParameters(TaskHostParameters baseParameters, TaskHostParameters overrideParameters) + { + // If both are empty, return empty + if (baseParameters.IsEmpty && overrideParameters.IsEmpty) + { + return Empty; + } + + // If override is empty, return base + if (overrideParameters.IsEmpty) + { + return baseParameters; + } + + // If base is empty, return override + if (baseParameters.IsEmpty) + { + return overrideParameters; + } + + // Merge: override values take precedence, fall back to base values + return new TaskHostParameters( + runtime: overrideParameters.Runtime ?? baseParameters.Runtime, + architecture: overrideParameters.Architecture ?? baseParameters.Architecture, + dotnetHostPath: overrideParameters.DotnetHostPath ?? baseParameters.DotnetHostPath, + msBuildAssemblyPath: overrideParameters.MSBuildAssemblyPath ?? baseParameters.MSBuildAssemblyPath, + taskHostFactoryExplicitlyRequested: overrideParameters.TaskHostFactoryExplicitlyRequested ?? baseParameters.TaskHostFactoryExplicitlyRequested); + } + + /// + /// The method was added to sustain compatibility with ITaskFactory2 factoryIdentityParameters parameters dictionary. + /// + internal Dictionary ToDictionary() => new(3) + { + { nameof(Runtime), Runtime ?? string.Empty }, + { nameof(Architecture), Architecture ?? string.Empty }, + { nameof(TaskHostFactoryExplicitlyRequested), TaskHostFactoryExplicitlyRequested?.ToString() ?? string.Empty }, + }; + } +} diff --git a/src/Framework/Telemetry/MSBuildActivitySource.cs b/src/Framework/Telemetry/MSBuildActivitySource.cs index 33668c0926f..7d73f87062f 100644 --- a/src/Framework/Telemetry/MSBuildActivitySource.cs +++ b/src/Framework/Telemetry/MSBuildActivitySource.cs @@ -29,6 +29,7 @@ public MSBuildActivitySource(string name, double sampleRate) ? _source.StartActivity($"{TelemetryConstants.EventPrefix}{name}", ActivityKind.Internal, parentId: Activity.Current.ParentId) : _source.StartActivity($"{TelemetryConstants.EventPrefix}{name}"); activity?.WithTag(new("SampleRate", _sampleRate, false)); + return activity; } } diff --git a/src/MSBuild/NodeEndpointOutOfProcTaskHost.cs b/src/MSBuild/NodeEndpointOutOfProcTaskHost.cs index 5190268d274..cc08771ceb9 100644 --- a/src/MSBuild/NodeEndpointOutOfProcTaskHost.cs +++ b/src/MSBuild/NodeEndpointOutOfProcTaskHost.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; using Microsoft.Build.Internal; #nullable disable @@ -31,9 +32,7 @@ internal NodeEndpointOutOfProcTaskHost(bool nodeReuse) /// /// Returns the host handshake for this node endpoint /// - protected override Handshake GetHandshake() - { - return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: true, nodeReuse: _nodeReuse)); - } + protected override Handshake GetHandshake() => + new(CommunicationsUtilities.GetHandshakeOptions(taskHost: true, taskHostParameters: TaskHostParameters.Empty, nodeReuse: _nodeReuse)); } } diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index 5d921f04016..373e1aaa334 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -229,6 +229,7 @@ + diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs index 6c90cde97d6..530b23b164b 100644 --- a/src/Shared/CommunicationsUtilities.cs +++ b/src/Shared/CommunicationsUtilities.cs @@ -846,10 +846,10 @@ internal static async ValueTask ReadAsync(Stream stream, byte[] buffer, int /// internal static HandshakeOptions GetHandshakeOptions( bool taskHost, + TaskHostParameters taskHostParameters, string architectureFlagToSet = null, bool nodeReuse = false, - bool lowPriority = false, - IDictionary taskHostParameters = null) + bool lowPriority = false) { HandshakeOptions context = taskHost ? HandshakeOptions.TaskHost : HandshakeOptions.None; @@ -859,25 +859,25 @@ internal static HandshakeOptions GetHandshakeOptions( if (taskHost) { // No parameters given, default to current - if (taskHostParameters == null) + if (taskHostParameters.IsEmpty) { clrVersion = typeof(bool).GetTypeInfo().Assembly.GetName().Version.Major; architectureFlagToSet = XMakeAttributes.GetCurrentMSBuildArchitecture(); } else // Figure out flags based on parameters given { - ErrorUtilities.VerifyThrow(taskHostParameters.TryGetValue(XMakeAttributes.runtime, out string runtimeVersion), "Should always have an explicit runtime when we call this method."); - ErrorUtilities.VerifyThrow(taskHostParameters.TryGetValue(XMakeAttributes.architecture, out string architecture), "Should always have an explicit architecture when we call this method."); + ErrorUtilities.VerifyThrow(taskHostParameters.Runtime != null, "Should always have an explicit runtime when we call this method."); + ErrorUtilities.VerifyThrow(taskHostParameters.Architecture != null, "Should always have an explicit architecture when we call this method."); - if (runtimeVersion.Equals(XMakeAttributes.MSBuildRuntimeValues.clr2, StringComparison.OrdinalIgnoreCase)) + if (taskHostParameters.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.clr2, StringComparison.OrdinalIgnoreCase)) { clrVersion = 2; } - else if (runtimeVersion.Equals(XMakeAttributes.MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase)) + else if (taskHostParameters.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase)) { clrVersion = 4; } - else if (runtimeVersion.Equals(XMakeAttributes.MSBuildRuntimeValues.net, StringComparison.OrdinalIgnoreCase)) + else if (taskHostParameters.Runtime.Equals(XMakeAttributes.MSBuildRuntimeValues.net, StringComparison.OrdinalIgnoreCase)) { clrVersion = 5; } @@ -886,7 +886,7 @@ internal static HandshakeOptions GetHandshakeOptions( ErrorUtilities.ThrowInternalErrorUnreachable(); } - architectureFlagToSet = architecture; + architectureFlagToSet = taskHostParameters.Architecture; } } diff --git a/src/Shared/LoadedType.cs b/src/Shared/LoadedType.cs index 27405b89265..f3957c89bc9 100644 --- a/src/Shared/LoadedType.cs +++ b/src/Shared/LoadedType.cs @@ -36,12 +36,13 @@ internal LoadedType(Type type, AssemblyLoadInfo assemblyLoadInfo, Assembly loade HasSTAThreadAttribute = CheckForHardcodedSTARequirement(); LoadedAssemblyName = loadedAssembly.GetName(); - + LoadedViaMetadataLoadContext = loadedViaMetadataLoadContext; + // For inline tasks loaded from bytes, Assembly.Location is empty, so use the original path - Path = string.IsNullOrEmpty(loadedAssembly.Location) - ? assemblyLoadInfo.AssemblyLocation + Path = string.IsNullOrEmpty(loadedAssembly.Location) + ? assemblyLoadInfo.AssemblyLocation : loadedAssembly.Location; - + LoadedAssembly = loadedAssembly; #if !NET35 @@ -166,6 +167,11 @@ internal LoadedType(Type type, AssemblyLoadInfo assemblyLoadInfo, Assembly loade /// public bool IsMarshalByRef { get; } + /// + /// Gets whether this type was loaded by using MetadataLoadContext. + /// + public bool LoadedViaMetadataLoadContext { get; } + /// /// Determines if the task has a hardcoded requirement for STA thread usage. /// diff --git a/src/Shared/XMakeAttributes.cs b/src/Shared/XMakeAttributes.cs index a188590d6fa..47e15477b90 100644 --- a/src/Shared/XMakeAttributes.cs +++ b/src/Shared/XMakeAttributes.cs @@ -149,18 +149,12 @@ internal static bool IsNonBatchingTargetAttribute(string attribute) /// /// Returns true if the given string is a valid member of the MSBuildRuntimeValues set /// - internal static bool IsValidMSBuildRuntimeValue(string runtime) - { - return runtime == null || ValidMSBuildRuntimeValues.Contains(runtime); - } + internal static bool IsValidMSBuildRuntimeValue(string runtime) => runtime == null || ValidMSBuildRuntimeValues.Contains(runtime); /// /// Returns true if the given string is a valid member of the MSBuildArchitectureValues set /// - internal static bool IsValidMSBuildArchitectureValue(string architecture) - { - return architecture == null || ValidMSBuildArchitectureValues.Contains(architecture); - } + internal static bool IsValidMSBuildArchitectureValue(string architecture) => architecture == null || ValidMSBuildArchitectureValues.Contains(architecture); /// /// Compares two members of MSBuildRuntimeValues, returning true if they count as a match, and false otherwise.