diff --git a/src/Tasks/Common/ProcessTaskEnvironmentDriver.cs b/src/Tasks/Common/ProcessTaskEnvironmentDriver.cs deleted file mode 100644 index 8f522decc9b0..000000000000 --- a/src/Tasks/Common/ProcessTaskEnvironmentDriver.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// This is a polyfill for the MultiProcessTaskEnvironmentDriver from MSBuild. -// Adapted for use in the SDK tasks project where NativeMethodsShared is not available. -// See: https://github.com/dotnet/msbuild/blob/main/src/Build/BackEnd/TaskExecutionHost/MultiProcessTaskEnvironmentDriver.cs - -#if NETFRAMEWORK - -#nullable enable - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; - -namespace Microsoft.Build.Framework -{ - /// - /// Default implementation of that directly interacts with the file system - /// and environment variables. Used for multi-process mode and as a test helper. - /// - internal sealed class ProcessTaskEnvironmentDriver : ITaskEnvironmentDriver - { - private AbsolutePath _projectDirectory; - private readonly Dictionary _environmentVariables; - - /// - /// Initializes a new instance with the specified project directory. - /// - public ProcessTaskEnvironmentDriver(string projectDirectory) - { - _projectDirectory = new AbsolutePath(projectDirectory); - - // Seed from the current process environment - _environmentVariables = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables()) - { - if (entry.Key is string key && entry.Value is string value) - { - _environmentVariables[key] = value; - } - } - } - - /// - public AbsolutePath ProjectDirectory - { - get => _projectDirectory; - set => _projectDirectory = value; - } - - /// - public AbsolutePath GetAbsolutePath(string path) - { - if (Path.IsPathRooted(path)) - { - return new AbsolutePath(path); - } - - return new AbsolutePath(path, _projectDirectory); - } - - /// - public string? GetEnvironmentVariable(string name) - { - return _environmentVariables.TryGetValue(name, out var value) ? value : null; - } - - /// - public IReadOnlyDictionary GetEnvironmentVariables() - { - return new Dictionary(_environmentVariables, StringComparer.OrdinalIgnoreCase); - } - - /// - public void SetEnvironmentVariable(string name, string? value) - { - if (value == null) - { - _environmentVariables.Remove(name); - } - else - { - _environmentVariables[name] = value; - } - } - - /// - public void SetEnvironment(IDictionary newEnvironment) - { - _environmentVariables.Clear(); - foreach (var kvp in newEnvironment) - { - _environmentVariables[kvp.Key] = kvp.Value; - } - } - - /// - public ProcessStartInfo GetProcessStartInfo() - { - // No SDK task calls this method. It exists only to satisfy the ITaskEnvironmentDriver - // interface contract. ToolTask subclasses use their own process-start logic instead. - throw new NotImplementedException( - "ProcessTaskEnvironmentDriver.GetProcessStartInfo is not used by SDK tasks."); - } - - /// - public void Dispose() - { - // No resources to clean up in this implementation. - } - } -} - -#endif diff --git a/src/Tasks/Common/TaskEnvironmentDefaults.cs b/src/Tasks/Common/TaskEnvironmentDefaults.cs index 7ef5666a4175..f666cac3163f 100644 --- a/src/Tasks/Common/TaskEnvironmentDefaults.cs +++ b/src/Tasks/Common/TaskEnvironmentDefaults.cs @@ -4,23 +4,24 @@ // Provides a default TaskEnvironment for single-threaded MSBuild execution. // When MSBuild supports IMultiThreadableTask, it sets TaskEnvironment directly. // This fallback ensures tasks work with older MSBuild versions that do not set it. +// +// Delegates to MSBuild's public TaskEnvironment.Fallback API +// (see https://github.com/dotnet/msbuild/pull/13462) so we no longer carry our +// own polyfill driver implementation. #if NETFRAMEWORK -using System; - namespace Microsoft.Build.Framework { internal static class TaskEnvironmentDefaults { /// - /// Creates a default TaskEnvironment backed by the current process environment. - /// Uses Environment.CurrentDirectory as the project directory, which in single-threaded - /// MSBuild is set to the project directory before task execution. + /// Returns the MSBuild-provided fallback TaskEnvironment, which is backed by the + /// current process environment and uses Environment.CurrentDirectory as the project + /// directory. /// - internal static TaskEnvironment Create() => - new TaskEnvironment(new ProcessTaskEnvironmentDriver(Environment.CurrentDirectory)); + internal static TaskEnvironment Create() => TaskEnvironment.Fallback; } } -#endif +#endif \ No newline at end of file diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/ShowPreviewMessage.cs b/src/Tasks/Microsoft.NET.Build.Tasks/ShowPreviewMessage.cs index a83fe3cc3e5a..363934df9156 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/ShowPreviewMessage.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/ShowPreviewMessage.cs @@ -10,23 +10,32 @@ namespace Microsoft.NET.Build.Tasks /// /// Provides a localizable mechanism for logging messages with different levels of importance from the SDK targets. /// + [MSBuildMultiThreadableTask] public class ShowPreviewMessage : TaskBase { + private static readonly object s_previewMessageLock = new(); + protected override void ExecuteCore() { const string previewMessageKey = "Microsoft.NET.Build.Tasks.DisplayPreviewMessageKey"; - object messageDisplayed = - BuildEngine4.GetRegisteredTaskObject(previewMessageKey, RegisteredTaskObjectLifetime.Build); - if (messageDisplayed == null) + if (BuildEngine4.GetRegisteredTaskObject(previewMessageKey, RegisteredTaskObjectLifetime.Build) is not null) + { + return; + } + + lock (s_previewMessageLock) { - Log.LogMessage(MessageImportance.High, Strings.UsingPreviewSdk); + if (BuildEngine4.GetRegisteredTaskObject(previewMessageKey, RegisteredTaskObjectLifetime.Build) is null) + { + Log.LogMessage(MessageImportance.High, Strings.UsingPreviewSdk); - BuildEngine4.RegisterTaskObject( - previewMessageKey, - new object(), - RegisteredTaskObjectLifetime.Build, - true); + BuildEngine4.RegisterTaskObject( + previewMessageKey, + new object(), + RegisteredTaskObjectLifetime.Build, + true); + } } } } diff --git a/test/Microsoft.NET.Build.Tasks.Tests/TaskEnvironmentHelper.cs b/test/Microsoft.NET.Build.Tasks.Tests/TaskEnvironmentHelper.cs index 5702d9f59aa4..12b2b5e3cb82 100644 --- a/test/Microsoft.NET.Build.Tasks.Tests/TaskEnvironmentHelper.cs +++ b/test/Microsoft.NET.Build.Tasks.Tests/TaskEnvironmentHelper.cs @@ -2,16 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // Helper class for creating TaskEnvironment instances in tests. -// NOT gated with #if — always available in the test project. -// Adapted from: https://github.com/dotnet/msbuild/blob/main/src/UnitTests.Shared/TaskEnvironmentHelper.cs - -using System; -using System.Collections; -using System.Collections.Generic; +// Delegates to MSBuild's public TaskEnvironment factory APIs (see +// https://github.com/dotnet/msbuild/pull/13462) instead of constructing a +// reflection-based driver locally. using System.IO; -using System.Linq; -using System.Reflection; using Microsoft.Build.Framework; namespace Microsoft.NET.Build.Tasks.UnitTests @@ -22,142 +17,19 @@ namespace Microsoft.NET.Build.Tasks.UnitTests public static class TaskEnvironmentHelper { /// - /// Creates a TaskEnvironment using the current working directory as the project directory. + /// Creates a TaskEnvironment using the current process environment and CWD. /// public static TaskEnvironment CreateForTest() { - return CreateForTest(Directory.GetCurrentDirectory()); + return TaskEnvironment.Fallback; } /// /// Creates a TaskEnvironment with the specified project directory. - /// Uses reflection to work around internal visibility of ITaskEnvironmentDriver and TaskEnvironment ctor. /// public static TaskEnvironment CreateForTest(string projectDirectory) { - // Get the internal ITaskEnvironmentDriver type from Microsoft.Build.Framework - var driverInterfaceType = typeof(TaskEnvironment).Assembly - .GetType("Microsoft.Build.Framework.ITaskEnvironmentDriver", throwOnError: true)!; - - // Create a DispatchProxy that implements ITaskEnvironmentDriver dynamically. - // DispatchProxy.Create() is called via reflection since TInterface is internal. - var createMethod = typeof(DispatchProxy) - .GetMethods(BindingFlags.Public | BindingFlags.Static) - .First(m => m.Name == nameof(DispatchProxy.Create) && m.GetGenericArguments().Length == 2) - .MakeGenericMethod(driverInterfaceType, typeof(TestDriverProxy)); - - var proxy = createMethod.Invoke(null, null)!; - - // Initialize the proxy with the project directory - ((TestDriverProxy)proxy).Initialize(projectDirectory); - - // Call the internal TaskEnvironment(ITaskEnvironmentDriver) constructor via reflection - var ctor = typeof(TaskEnvironment) - .GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance) - .FirstOrDefault(c => - { - var parameters = c.GetParameters(); - return parameters.Length == 1 && parameters[0].ParameterType == driverInterfaceType; - }); - - if (ctor is null) - { - throw new InvalidOperationException("Could not find TaskEnvironment constructor with ITaskEnvironmentDriver parameter."); - } - - return (TaskEnvironment)ctor.Invoke(new[] { proxy }); - } - } - - /// - /// DispatchProxy-based implementation of the internal ITaskEnvironmentDriver interface. - /// Stores its own project directory independently from the process's CWD, - /// enabling tests to verify tasks resolve paths relative to ProjectDirectory, not CWD. - /// - internal class TestDriverProxy : DispatchProxy - { - private string _projectDirectory = string.Empty; - private Dictionary _environmentVariables = new Dictionary(StringComparer.OrdinalIgnoreCase); - - internal void Initialize(string projectDirectory) - { - _projectDirectory = projectDirectory; - - // Seed from the current process environment - foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables()) - { - if (entry.Key is string key && entry.Value is string value) - _environmentVariables[key] = value; - } - } - - protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) - { - if (targetMethod == null) return null; - - return targetMethod.Name switch - { - "get_ProjectDirectory" => new AbsolutePath(_projectDirectory), - "set_ProjectDirectory" => SetProjectDir(args), - "GetAbsolutePath" => ResolveAbsolutePath((string)args![0]!), - "GetEnvironmentVariable" => DoGetEnvVar(args), - "GetEnvironmentVariables" => GetEnvVars(), - "SetEnvironmentVariable" => DoSetEnvVar(args), - "SetEnvironment" => DoSetEnv(args), - "GetProcessStartInfo" => throw new NotImplementedException( - "GetProcessStartInfo is not used by SDK tasks."), - "Dispose" => null, - _ => throw new NotSupportedException($"Method '{targetMethod.Name}' is not supported by {nameof(TestDriverProxy)}."), - }; + return TaskEnvironment.CreateWithProjectDirectoryAndEnvironment(projectDirectory); } - - private object? SetProjectDir(object?[]? args) - { - _projectDirectory = ((AbsolutePath)args![0]!).Value; - return null; - } - - private AbsolutePath ResolveAbsolutePath(string path) - { - if (Path.IsPathRooted(path)) - return new AbsolutePath(path); - return new AbsolutePath(path, new AbsolutePath(_projectDirectory)); - } - - private object? DoGetEnvVar(object?[]? args) - { - var name = (string)args![0]!; - return _environmentVariables.TryGetValue(name, out var value) ? value : null; - } - - private IReadOnlyDictionary GetEnvVars() - { - return new Dictionary(_environmentVariables, StringComparer.OrdinalIgnoreCase); - } - - private object? DoSetEnvVar(object?[]? args) - { - var name = (string)args![0]!; - var value = (string?)args[1]; - if (value == null) - { - _environmentVariables.Remove(name); - } - else - { - _environmentVariables[name] = value; - } - return null; - } - - private object? DoSetEnv(object?[]? args) - { - var newEnv = (IDictionary)args![0]!; - _environmentVariables.Clear(); - foreach (var kvp in newEnv) - _environmentVariables[kvp.Key] = kvp.Value; - return null; - } - } -} +} \ No newline at end of file